Commit fb4e6801445b
tarball1 file changed+129-105
@@ -1037,12 +1037,10 @@ impl EventCacheStore for SqliteEventCacheStore {
10371037
10381038
let this = self.clone();10391039
1040
- let result = with_immediate_transaction(self, move |txn| -> Result<_> {1041
- register_linked_chunk_owner(1042
- txn,1043
- &hashed_linked_chunk_id,1044
- &hashed_room_id,1045
- )?;1040
+ let result = self.read().await?.with_transaction(move |txn| -> Result<_> {1041
+ if linked_chunk_is_stale(txn, &hashed_linked_chunk_id, &hashed_room_id)? {1042
+ return Ok(Default::default());1043
+ }10461044
10471045
let mut items = Vec::new();10481046
@@ -1084,51 +1082,55 @@ impl EventCacheStore for SqliteEventCacheStore {
10841082
let linked_chunk_id = linked_chunk_id.to_owned();10851083
let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.room_id());10861084
1087
- with_immediate_transaction(self, move |txn| -> Result<_> {1088
- register_linked_chunk_owner(txn, &hashed_linked_chunk_id, &hashed_room_id)?;1089
-1090
- // We want to collect the metadata about each chunk (id, next, previous), and1091
- // for event chunks, the number of events in it. For gaps, the1092
- // number of events is 0, by convention.1093
- //1094
- // We've tried different strategies over time:1095
- // - use a `LEFT JOIN` + `COUNT`, which was extremely inefficient because it1096
- // caused a full table traversal for each chunk, including for gaps which1097
- // don't have any events. This happened in1098
- // https://github.com/matrix-org/matrix-rust-sdk/pull/5225.1099
- // - use a `CASE` statement on the chunk's type: if it's an event chunk, run an1100
- // additional `SELECT` query. It was an immense improvement, but still caused1101
- // one select query per event chunk. This happened in1102
- // https://github.com/matrix-org/matrix-rust-sdk/pull/5411.1103
- //1104
- // The current solution is to run two queries:1105
- // - one to get each chunk and its number of events, by doing a single `SELECT`1106
- // query over the `event_chunks` table, grouping by chunk ids. This gives us a1107
- // list of `(chunk_id, num_events)` pairs, which can be transformed into a1108
- // hashmap.1109
- // - one to get each chunk's metadata (id, previous, next, type) from the1110
- // database with a `SELECT`, and then use the hashmap to get the number of1111
- // events.1112
- //1113
- // This strategy minimizes the number of queries to the database, and keeps them1114
- // super simple, while doing a bit more processing here, which is much faster.1115
-1116
- let num_events_by_chunk_ids = txn1117
- .prepare(1118
- r#"1085
+ self.read()1086
+ .await?1087
+ .with_transaction(move |txn| -> Result<_> {1088
+ if linked_chunk_is_stale(txn, &hashed_linked_chunk_id, &hashed_room_id)? {1089
+ return Ok(Default::default());1090
+ }1091
+1092
+ // We want to collect the metadata about each chunk (id, next, previous), and1093
+ // for event chunks, the number of events in it. For gaps, the1094
+ // number of events is 0, by convention.1095
+ //1096
+ // We've tried different strategies over time:1097
+ // - use a `LEFT JOIN` + `COUNT`, which was extremely inefficient because it1098
+ // caused a full table traversal for each chunk, including for gaps which1099
+ // don't have any events. This happened in1100
+ // https://github.com/matrix-org/matrix-rust-sdk/pull/5225.1101
+ // - use a `CASE` statement on the chunk's type: if it's an event chunk, run an1102
+ // additional `SELECT` query. It was an immense improvement, but still caused1103
+ // one select query per event chunk. This happened in1104
+ // https://github.com/matrix-org/matrix-rust-sdk/pull/5411.1105
+ //1106
+ // The current solution is to run two queries:1107
+ // - one to get each chunk and its number of events, by doing a single `SELECT`1108
+ // query over the `event_chunks` table, grouping by chunk ids. This gives us a1109
+ // list of `(chunk_id, num_events)` pairs, which can be transformed into a1110
+ // hashmap.1111
+ // - one to get each chunk's metadata (id, previous, next, type) from the1112
+ // database with a `SELECT`, and then use the hashmap to get the number of1113
+ // events.1114
+ //1115
+ // This strategy minimizes the number of queries to the database, and keeps them1116
+ // super simple, while doing a bit more processing here, which is much faster.1117
+1118
+ let num_events_by_chunk_ids = txn1119
+ .prepare(1120
+ r#"11191121
SELECT ec.chunk_id, COUNT(ec.event_id)11201122
FROM event_chunks as ec11211123
WHERE ec.linked_chunk_id = ?11221124
GROUP BY ec.chunk_id11231125
"#,1124
- )?1125
- .query_map((&hashed_linked_chunk_id,), |row| {1126
- Ok((row.get::<_, u64>(0)?, row.get::<_, usize>(1)?))1127
- })?1128
- .collect::<Result<HashMap<_, _>, _>>()?;1126
+ )?1127
+ .query_map((&hashed_linked_chunk_id,), |row| {1128
+ Ok((row.get::<_, u64>(0)?, row.get::<_, usize>(1)?))1129
+ })?1130
+ .collect::<Result<HashMap<_, _>, _>>()?;11291131
1130
- txn.prepare(1131
- r#"1132
+ txn.prepare(1133
+ r#"11321134
SELECT11331135
lc.id,11341136
lc.previous,@@ -1137,39 +1139,39 @@ impl EventCacheStore for SqliteEventCacheStore {
11371139
FROM linked_chunks as lc11381140
WHERE lc.linked_chunk_id = ?11391141
ORDER BY lc.id"#,1140
- )?1141
- .query_map((&hashed_linked_chunk_id,), |row| {1142
- Ok((1143
- row.get::<_, u64>(0)?,1144
- row.get::<_, Option<u64>>(1)?,1145
- row.get::<_, Option<u64>>(2)?,1146
- row.get::<_, String>(3)?,1147
- ))1148
- })?1149
- .map(|data| -> Result<_> {1150
- let (id, previous, next, chunk_type) = data?;1151
-1152
- // Note: since a gap has 0 events, an alternative could be to *not* retrieve1153
- // the chunk type, and just let the hashmap lookup fail for gaps. However,1154
- // benchmarking shows that this is slightly slower than matching the chunk1155
- // type (around 1%, so in the realm of noise), so we keep the explicit1156
- // check instead.1157
- let num_items = if chunk_type == CHUNK_TYPE_GAP_TYPE_STRING {1158
- 01159
- } else {1160
- num_events_by_chunk_ids.get(&id).copied().unwrap_or(0)1161
- };1142
+ )?1143
+ .query_map((&hashed_linked_chunk_id,), |row| {1144
+ Ok((1145
+ row.get::<_, u64>(0)?,1146
+ row.get::<_, Option<u64>>(1)?,1147
+ row.get::<_, Option<u64>>(2)?,1148
+ row.get::<_, String>(3)?,1149
+ ))1150
+ })?1151
+ .map(|data| -> Result<_> {1152
+ let (id, previous, next, chunk_type) = data?;11621153
1163
- Ok(ChunkMetadata {1164
- identifier: ChunkIdentifier::new(id),1165
- previous: previous.map(ChunkIdentifier::new),1166
- next: next.map(ChunkIdentifier::new),1167
- num_items,1154
+ // Note: since a gap has 0 events, an alternative could be to *not* retrieve1155
+ // the chunk type, and just let the hashmap lookup fail for gaps. However,1156
+ // benchmarking shows that this is slightly slower than matching the chunk1157
+ // type (around 1%, so in the realm of noise), so we keep the explicit1158
+ // check instead.1159
+ let num_items = if chunk_type == CHUNK_TYPE_GAP_TYPE_STRING {1160
+ 01161
+ } else {1162
+ num_events_by_chunk_ids.get(&id).copied().unwrap_or(0)1163
+ };1164
+1165
+ Ok(ChunkMetadata {1166
+ identifier: ChunkIdentifier::new(id),1167
+ previous: previous.map(ChunkIdentifier::new),1168
+ next: next.map(ChunkIdentifier::new),1169
+ num_items,1170
+ })11681171
})1172
+ .collect::<Result<Vec<_>, _>>()11691173
})1170
- .collect::<Result<Vec<_>, _>>()1171
- })1172
- .await1174
+ .await11731175
}11741176
11751177
#[instrument(skip(self))]@@ -1186,12 +1188,10 @@ impl EventCacheStore for SqliteEventCacheStore {
11861188
11871189
let this = self.clone();11881190
1189
- with_immediate_transaction(self, move |txn| -> Result<_> {1190
- register_linked_chunk_owner(1191
- txn,1192
- &hashed_linked_chunk_id,1193
- &hashed_room_id,1194
- )?;1191
+ self.read().await?.with_transaction(move |txn| -> Result<_> {1192
+ if linked_chunk_is_stale(txn, &hashed_linked_chunk_id, &hashed_room_id)? {1193
+ return Ok((None, ChunkIdentifierGenerator::new_from_scratch()));1194
+ }11951195
11961196
// Find the latest chunk identifier to generate a `ChunkIdentifierGenerator`, and count the number of chunks.11971197
let (observed_max_identifier, number_of_chunks) = txn@@ -1287,12 +1287,10 @@ impl EventCacheStore for SqliteEventCacheStore {
12871287
12881288
let this = self.clone();12891289
1290
- with_immediate_transaction(self, move |txn| -> Result<_> {1291
- register_linked_chunk_owner(1292
- txn,1293
- &hashed_linked_chunk_id,1294
- &hashed_room_id,1295
- )?;1290
+ self.read().await?.with_transaction(move |txn| -> Result<_> {1291
+ if linked_chunk_is_stale(txn, &hashed_linked_chunk_id, &hashed_room_id)? {1292
+ return Ok(Default::default());1293
+ }12961294
12971295
// Find the chunk before the chunk identified by `before_chunk_identifier`.12981296
let Some((chunk_identifier, previous_chunk, next_chunk, chunk_type)) = txn@@ -1369,12 +1367,10 @@ impl EventCacheStore for SqliteEventCacheStore {
13691367
let linked_chunk_id = linked_chunk_id.to_owned();13701368
let hashed_room_id = self.encode_key(keys::LINKED_CHUNKS, linked_chunk_id.room_id());13711369
1372
- with_immediate_transaction(self, move |txn| -> Result<_> {1373
- register_linked_chunk_owner(1374
- txn,1375
- &hashed_linked_chunk_id,1376
- &hashed_room_id,1377
- )?;1370
+ self.read().await?.with_transaction(move |txn| -> Result<_> {1371
+ if linked_chunk_is_stale(txn, &hashed_linked_chunk_id, &hashed_room_id)? {1372
+ return Ok(Default::default());1373
+ }13781374
13791375
txn.chunk_large_query_over(events, None, move |txn, events| {13801376
let query = format!(@@ -1485,19 +1481,23 @@ impl EventCacheStore for SqliteEventCacheStore {
14851481
let filters = filters.map(ToOwned::to_owned);14861482
let store = self.clone();14871483
1488
- with_immediate_transaction(self, move |txn| -> Result<_> {1489
- register_linked_chunk_owner(txn, &hashed_linked_chunk_id, &hashed_room_id)?;1484
+ self.read()1485
+ .await?1486
+ .with_transaction(move |txn| -> Result<_> {1487
+ if linked_chunk_is_stale(txn, &hashed_linked_chunk_id, &hashed_room_id)? {1488
+ return Ok(Default::default());1489
+ }14901490
1491
- find_event_relations_transaction(1492
- store,1493
- hashed_room_id,1494
- hashed_linked_chunk_id,1495
- event_id,1496
- filters,1497
- txn,1498
- )1499
- })1500
- .await1491
+ find_event_relations_transaction(1492
+ store,1493
+ hashed_room_id,1494
+ hashed_linked_chunk_id,1495
+ event_id,1496
+ filters,1497
+ txn,1498
+ )1499
+ })1500
+ .await15011501
}15021502
15031503
#[instrument(skip(self))]@@ -1791,6 +1791,30 @@ fn insert_chunk(
17911791
Ok(())17921792
}17931793
1794
+/// Rows of a linked chunk that no write registered before its room was cleared1795
+/// are deleted by the next write, so reads must already treat them as gone.1796
+fn linked_chunk_is_stale(1797
+ txn: &Transaction<'_>,1798
+ linked_chunk_id: &Key,1799
+ room_id: &Key,1800
+) -> rusqlite::Result<bool> {1801
+ let registered = txn.query_row(1802
+ "SELECT EXISTS(SELECT 1 FROM linked_chunk_rooms WHERE linked_chunk_id = ?)",1803
+ [linked_chunk_id],1804
+ |row| row.get::<_, bool>(0),1805
+ )?;1806
+1807
+ if registered {1808
+ return Ok(false);1809
+ }1810
+1811
+ txn.query_row(1812
+ "SELECT EXISTS(SELECT 1 FROM cleared_rooms WHERE room_id = ?)",1813
+ [room_id],1814
+ |row| row.get::<_, bool>(0),1815
+ )1816
+}1817
+17941818
fn register_linked_chunk_owner(17951819
txn: &Transaction<'_>,17961820
linked_chunk_id: &Key,