Skip to content

Commit 1da1de1

Browse files
authored
Improve performance of LevelDbDocumentOverlayCache::RemoveOverlaysForBatchId() (#9386)
1 parent d92eb14 commit 1da1de1

11 files changed

+716
-83
lines changed

Firestore/core/src/local/document_overlay_cache.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@
2525
#include "Firestore/core/src/model/mutation.h"
2626
#include "Firestore/core/src/model/mutation/overlay.h"
2727
#include "Firestore/core/src/model/resource_path.h"
28+
#include "absl/strings/string_view.h"
2829
#include "absl/types/optional.h"
2930

3031
namespace firebase {
3132
namespace firestore {
3233
namespace local {
3334

35+
class DocumentOverlayCacheTestHelper;
36+
3437
/**
3538
* Provides methods to read and write document overlays.
3639
*
@@ -101,6 +104,13 @@ class DocumentOverlayCache {
101104
const std::string& collection_group,
102105
int since_batch_id,
103106
std::size_t count) const = 0;
107+
108+
private:
109+
friend class DocumentOverlayCacheTestHelper;
110+
111+
// Returns the total number of overlays in the database.
112+
// This method exists for unit testing only.
113+
virtual int GetOverlayCount() const = 0;
104114
};
105115

106116
} // namespace local

Firestore/core/src/local/leveldb_document_overlay_cache.cc

Lines changed: 100 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -53,24 +53,24 @@ LevelDbDocumentOverlayCache::LevelDbDocumentOverlayCache(
5353
}
5454

5555
absl::optional<Overlay> LevelDbDocumentOverlayCache::GetOverlay(
56-
const DocumentKey& key) const {
57-
const std::string leveldb_key_prefix =
58-
LevelDbDocumentOverlayKey::KeyPrefix(user_id_, key);
56+
const DocumentKey& document_key) const {
57+
const std::string key_prefix =
58+
LevelDbDocumentOverlayKey::KeyPrefix(user_id_, document_key);
5959

6060
auto it = db_->current_transaction()->NewIterator();
61-
it->Seek(leveldb_key_prefix);
61+
it->Seek(key_prefix);
6262

63-
if (!(it->Valid() && absl::StartsWith(it->key(), leveldb_key_prefix))) {
63+
if (!(it->Valid() && absl::StartsWith(it->key(), key_prefix))) {
6464
return absl::nullopt;
6565
}
6666

67-
LevelDbDocumentOverlayKey decoded_key;
68-
HARD_ASSERT(decoded_key.Decode(it->key()));
69-
if (decoded_key.document_key() != key) {
67+
LevelDbDocumentOverlayKey key;
68+
HARD_ASSERT(key.Decode(it->key()));
69+
if (key.document_key() != document_key) {
7070
return absl::nullopt;
7171
}
7272

73-
return ParseOverlay(decoded_key, it->value());
73+
return ParseOverlay(key, it->value());
7474
}
7575

7676
void LevelDbDocumentOverlayCache::SaveOverlays(
@@ -81,16 +81,8 @@ void LevelDbDocumentOverlayCache::SaveOverlays(
8181
}
8282

8383
void LevelDbDocumentOverlayCache::RemoveOverlaysForBatchId(int batch_id) {
84-
// TODO(dconeybe) Implement an index so that this query can be performed
85-
// without requiring a full table scan.
86-
87-
ForEachOverlay([&](absl::string_view encoded_key,
88-
const LevelDbDocumentOverlayKey& decoded_key,
89-
absl::string_view) {
90-
if (decoded_key.largest_batch_id() == batch_id) {
91-
db_->current_transaction()->Delete(encoded_key);
92-
}
93-
});
84+
ForEachKeyWithLargestBatchId(
85+
batch_id, [&](LevelDbDocumentOverlayKey&& key) { DeleteOverlay(key); });
9486
}
9587

9688
DocumentOverlayCache::OverlayByDocumentKeyMap
@@ -103,20 +95,18 @@ LevelDbDocumentOverlayCache::GetOverlays(const ResourcePath& collection,
10395

10496
const size_t immediate_children_path_length{collection.size() + 1};
10597

106-
ForEachOverlay([&](absl::string_view,
107-
const LevelDbDocumentOverlayKey& decoded_key,
98+
ForEachOverlay([&](LevelDbDocumentOverlayKey&& key,
10899
absl::string_view encoded_mutation) {
109-
const DocumentKey key = decoded_key.document_key();
110-
if (!collection.IsPrefixOf(key.path())) {
100+
if (!collection.IsPrefixOf(key.document_key().path())) {
111101
return;
112102
}
113103
// Documents from sub-collections
114-
if (key.path().size() != immediate_children_path_length) {
104+
if (key.document_key().path().size() != immediate_children_path_length) {
115105
return;
116106
}
117107

118-
if (decoded_key.largest_batch_id() > since_batch_id) {
119-
result[key] = ParseOverlay(decoded_key, encoded_mutation);
108+
if (key.largest_batch_id() > since_batch_id) {
109+
result[key.document_key()] = ParseOverlay(key, encoded_mutation);
120110
}
121111
});
122112

@@ -135,17 +125,16 @@ LevelDbDocumentOverlayCache::GetOverlays(const std::string& collection_group,
135125
// by largest_batch_id, the loop below can iterate over it ordered by
136126
// largest_batch_id.
137127
std::map<int, std::unordered_set<Overlay, OverlayHash>> overlays_by_batch_id;
138-
ForEachOverlay([&](absl::string_view,
139-
const LevelDbDocumentOverlayKey& decoded_key,
140-
absl::string_view encoded_mutation) {
141-
if (decoded_key.largest_batch_id() <= since_batch_id) {
142-
return;
143-
}
144-
if (decoded_key.document_key().HasCollectionId(collection_group)) {
145-
overlays_by_batch_id[decoded_key.largest_batch_id()].emplace(
146-
ParseOverlay(decoded_key, encoded_mutation));
147-
}
148-
});
128+
ForEachOverlay(
129+
[&](LevelDbDocumentOverlayKey&& key, absl::string_view encoded_mutation) {
130+
if (key.largest_batch_id() <= since_batch_id) {
131+
return;
132+
}
133+
if (key.document_key().HasCollectionId(collection_group)) {
134+
overlays_by_batch_id[key.largest_batch_id()].emplace(
135+
ParseOverlay(key, encoded_mutation));
136+
}
137+
});
149138

150139
// Trim down the overlays loaded above to respect the given `count`, and
151140
// return them.
@@ -157,8 +146,8 @@ LevelDbDocumentOverlayCache::GetOverlays(const std::string& collection_group,
157146
OverlayByDocumentKeyMap result;
158147
for (auto& overlays_by_batch_id_entry : overlays_by_batch_id) {
159148
for (auto& overlay : overlays_by_batch_id_entry.second) {
160-
DocumentKey key = overlay.key();
161-
result[key] = std::move(overlay);
149+
DocumentKey document_key = overlay.key();
150+
result[document_key] = std::move(overlay);
162151
}
163152
if (result.size() >= count) {
164153
break;
@@ -168,6 +157,27 @@ LevelDbDocumentOverlayCache::GetOverlays(const std::string& collection_group,
168157
return result;
169158
}
170159

160+
int LevelDbDocumentOverlayCache::GetOverlayCount() const {
161+
return CountEntriesWithKeyPrefix(
162+
LevelDbDocumentOverlayKey::KeyPrefix(user_id_));
163+
}
164+
165+
int LevelDbDocumentOverlayCache::GetLargestBatchIdIndexEntryCount() const {
166+
return CountEntriesWithKeyPrefix(
167+
LevelDbDocumentOverlayLargestBatchIdIndexKey::KeyPrefix(user_id_));
168+
}
169+
170+
int LevelDbDocumentOverlayCache::CountEntriesWithKeyPrefix(
171+
const std::string& key_prefix) const {
172+
int count = 0;
173+
auto it = db_->current_transaction()->NewIterator();
174+
for (it->Seek(key_prefix);
175+
it->Valid() && absl::StartsWith(it->key(), key_prefix); it->Next()) {
176+
++count;
177+
}
178+
return count;
179+
}
180+
171181
Overlay LevelDbDocumentOverlayCache::ParseOverlay(
172182
const LevelDbDocumentOverlayKey& key,
173183
absl::string_view encoded_mutation) const {
@@ -181,42 +191,70 @@ Overlay LevelDbDocumentOverlayCache::ParseOverlay(
181191
}
182192

183193
void LevelDbDocumentOverlayCache::SaveOverlay(int largest_batch_id,
184-
const DocumentKey& key,
194+
const DocumentKey& document_key,
185195
const Mutation& mutation) {
186-
DeleteOverlay(key);
187-
const std::string leveldb_key =
188-
LevelDbDocumentOverlayKey::Key(user_id_, key, largest_batch_id);
189-
auto serialized_mutation = serializer_->EncodeMutation(mutation);
190-
db_->current_transaction()->Put(leveldb_key, serialized_mutation);
196+
// Remove the existing overlay and any index entries pointing to it.
197+
DeleteOverlay(document_key);
198+
199+
const LevelDbDocumentOverlayKey key(user_id_, document_key, largest_batch_id);
200+
201+
// Add the overlay to the database and index entries pointing to it.
202+
auto* transaction = db_->current_transaction();
203+
transaction->Put(key.Encode(), serializer_->EncodeMutation(mutation));
204+
transaction->Put(LevelDbDocumentOverlayLargestBatchIdIndexKey::Key(key), "");
191205
}
192206

193-
void LevelDbDocumentOverlayCache::DeleteOverlay(const model::DocumentKey& key) {
194-
const std::string leveldb_key_prefix =
195-
LevelDbDocumentOverlayKey::KeyPrefix(user_id_, key);
207+
void LevelDbDocumentOverlayCache::DeleteOverlay(
208+
const model::DocumentKey& document_key) {
209+
const std::string key_prefix =
210+
LevelDbDocumentOverlayKey::KeyPrefix(user_id_, document_key);
196211
auto it = db_->current_transaction()->NewIterator();
197-
for (it->Seek(leveldb_key_prefix);
198-
it->Valid() && absl::StartsWith(it->key(), leveldb_key_prefix);
199-
it->Next()) {
200-
LevelDbDocumentOverlayKey decoded_key;
201-
HARD_ASSERT(decoded_key.Decode(it->key()));
202-
if (decoded_key.document_key() == key) {
203-
db_->current_transaction()->Delete(it->key());
204-
}
212+
it->Seek(key_prefix);
213+
214+
if (!(it->Valid() && absl::StartsWith(it->key(), key_prefix))) {
215+
return;
205216
}
217+
218+
LevelDbDocumentOverlayKey key;
219+
HARD_ASSERT(key.Decode(it->key()));
220+
if (key.document_key() == document_key) {
221+
DeleteOverlay(key);
222+
}
223+
}
224+
225+
void LevelDbDocumentOverlayCache::DeleteOverlay(
226+
const LevelDbDocumentOverlayKey& key) {
227+
auto* transaction = db_->current_transaction();
228+
transaction->Delete(key.Encode());
229+
transaction->Delete(LevelDbDocumentOverlayLargestBatchIdIndexKey::Key(key));
206230
}
207231

208232
void LevelDbDocumentOverlayCache::ForEachOverlay(
209-
std::function<void(absl::string_view encoded_key,
210-
const LevelDbDocumentOverlayKey& decoded_key,
211-
absl::string_view encoded_mutation)> callback) const {
233+
std::function<void((LevelDbDocumentOverlayKey && key,
234+
absl::string_view encoded_mutation))> callback) const {
212235
auto it = db_->current_transaction()->NewIterator();
213236
const std::string user_key = LevelDbDocumentOverlayKey::KeyPrefix(user_id_);
214237

215238
for (it->Seek(user_key); it->Valid() && absl::StartsWith(it->key(), user_key);
216239
it->Next()) {
217-
LevelDbDocumentOverlayKey decoded_key;
218-
HARD_ASSERT(decoded_key.Decode(it->key()));
219-
callback(it->key(), decoded_key, it->value());
240+
LevelDbDocumentOverlayKey key;
241+
HARD_ASSERT(key.Decode(it->key()));
242+
callback(std::move(key), it->value());
243+
}
244+
}
245+
246+
void LevelDbDocumentOverlayCache::ForEachKeyWithLargestBatchId(
247+
int largest_batch_id,
248+
std::function<void(LevelDbDocumentOverlayKey&& key)> callback) const {
249+
const std::string key_prefix =
250+
LevelDbDocumentOverlayLargestBatchIdIndexKey::KeyPrefix(user_id_,
251+
largest_batch_id);
252+
auto it = db_->current_transaction()->NewIterator();
253+
for (it->Seek(key_prefix);
254+
it->Valid() && absl::StartsWith(it->key(), key_prefix); it->Next()) {
255+
LevelDbDocumentOverlayLargestBatchIdIndexKey key;
256+
HARD_ASSERT(key.Decode(it->key()));
257+
callback(std::move(key).ToLevelDbDocumentOverlayKey());
220258
}
221259
}
222260

Firestore/core/src/local/leveldb_document_overlay_cache.h

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class User;
3333

3434
namespace local {
3535

36+
class LevelDbDocumentOverlayCacheTestHelper;
3637
class LevelDbDocumentOverlayKey;
3738
class LevelDbPersistence;
3839
class LocalSerializer;
@@ -52,7 +53,7 @@ class LevelDbDocumentOverlayCache final : public DocumentOverlayCache {
5253
delete;
5354

5455
absl::optional<model::mutation::Overlay> GetOverlay(
55-
const model::DocumentKey& key) const override;
56+
const model::DocumentKey&) const override;
5657

5758
void SaveOverlays(int largest_batch_id,
5859
const MutationByDocumentKeyMap& overlays) override;
@@ -67,21 +68,35 @@ class LevelDbDocumentOverlayCache final : public DocumentOverlayCache {
6768
std::size_t count) const override;
6869

6970
private:
71+
friend class LevelDbDocumentOverlayCacheTestHelper;
72+
73+
// Returns the number of index entries for the largest batch ID.
74+
// This method exists for unit testing only.
75+
int GetLargestBatchIdIndexEntryCount() const;
76+
77+
int GetOverlayCount() const override;
78+
int CountEntriesWithKeyPrefix(const std::string& key_prefix) const;
79+
7080
model::mutation::Overlay ParseOverlay(
7181
const LevelDbDocumentOverlayKey& key,
7282
absl::string_view encoded_mutation) const;
7383

7484
void SaveOverlay(int largest_batch_id,
75-
const model::DocumentKey& key,
85+
const model::DocumentKey& document_key,
7686
const model::Mutation& mutation);
7787

78-
void DeleteOverlay(const model::DocumentKey& key);
88+
void DeleteOverlay(const model::DocumentKey&);
89+
90+
void DeleteOverlay(const LevelDbDocumentOverlayKey&);
7991

8092
void ForEachOverlay(
81-
std::function<void(absl::string_view encoded_key,
82-
const LevelDbDocumentOverlayKey& decoded_key,
93+
std::function<void(LevelDbDocumentOverlayKey&&,
8394
absl::string_view encoded_mutation)>) const;
8495

96+
void ForEachKeyWithLargestBatchId(
97+
int largest_batch_id,
98+
std::function<void(LevelDbDocumentOverlayKey&&)>) const;
99+
85100
// The LevelDbDocumentOverlayCache instance is owned by LevelDbPersistence.
86101
LevelDbPersistence* db_;
87102

Firestore/core/src/local/leveldb_key.cc

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const char* kIndexConfigurationTable = "index_configuration";
5454
const char* kIndexStateTable = "index_state";
5555
const char* kIndexEntriesTable = "index_entries";
5656
const char* kDocumentOverlaysTable = "document_overlays";
57+
const char* kDocumentOverlaysLargestBatchIdIndexTable =
58+
"document_overlays_largest_batch_id_index";
5759

5860
/**
5961
* Labels for the components of keys. These serve to make keys self-describing.
@@ -1304,6 +1306,53 @@ bool LevelDbDocumentOverlayKey::Decode(absl::string_view key) {
13041306
return reader.ok();
13051307
}
13061308

1309+
std::string LevelDbDocumentOverlayLargestBatchIdIndexKey::KeyPrefix() {
1310+
Writer writer;
1311+
writer.WriteTableName(kDocumentOverlaysLargestBatchIdIndexTable);
1312+
return writer.result();
1313+
}
1314+
1315+
std::string LevelDbDocumentOverlayLargestBatchIdIndexKey::KeyPrefix(
1316+
absl::string_view user_id) {
1317+
Writer writer;
1318+
writer.WriteTableName(kDocumentOverlaysLargestBatchIdIndexTable);
1319+
writer.WriteUserId(user_id);
1320+
return writer.result();
1321+
}
1322+
1323+
std::string LevelDbDocumentOverlayLargestBatchIdIndexKey::KeyPrefix(
1324+
absl::string_view user_id, model::BatchId largest_batch_id) {
1325+
Writer writer;
1326+
writer.WriteTableName(kDocumentOverlaysLargestBatchIdIndexTable);
1327+
writer.WriteUserId(user_id);
1328+
writer.WriteBatchId(largest_batch_id);
1329+
return writer.result();
1330+
}
1331+
1332+
std::string LevelDbDocumentOverlayLargestBatchIdIndexKey::Key(
1333+
absl::string_view user_id,
1334+
model::BatchId largest_batch_id,
1335+
const model::DocumentKey& document_key) {
1336+
Writer writer;
1337+
writer.WriteTableName(kDocumentOverlaysLargestBatchIdIndexTable);
1338+
writer.WriteUserId(user_id);
1339+
writer.WriteBatchId(largest_batch_id);
1340+
writer.WriteResourcePath(document_key.path());
1341+
writer.WriteTerminator();
1342+
return writer.result();
1343+
}
1344+
1345+
bool LevelDbDocumentOverlayLargestBatchIdIndexKey::Decode(
1346+
absl::string_view key) {
1347+
Reader reader{key};
1348+
reader.ReadTableNameMatching(kDocumentOverlaysLargestBatchIdIndexTable);
1349+
user_id_ = reader.ReadUserId();
1350+
largest_batch_id_ = reader.ReadBatchId();
1351+
document_key_ = reader.ReadDocumentKey();
1352+
reader.ReadTerminator();
1353+
return reader.ok();
1354+
}
1355+
13071356
} // namespace local
13081357
} // namespace firestore
13091358
} // namespace firebase

0 commit comments

Comments
 (0)