Skip to content

Commit f321ad8

Browse files
authored
Decode documents in a background thread (#4857)
* Add Index-free benchmarks * Add BackgroundQueue Port of firebase/firebase-android-sdk#559 * Decode documents in background threads to improve performance Run on (12 X 2900 MHz CPU s) CPU Caches: L1 Data 32K (x6) L1 Instruction 32K (x6) L2 Unified 262K (x6) L3 Unified 12582K (x1) Load Average: 2.24, 2.18, 1.96 ---------------------------------------------------------------------------------- Benchmark Time CPU Iterations Improvement ---------------------------------------------------------------------------------- BM_QueryMatching/0/1 132 us 12.7 us 53524 21.4% BM_QueryMatching/1/1 150 us 18.1 us 37305 16.2% BM_QueryMatching/1/10 371 us 27.6 us 10000 48.5% BM_QueryMatching/10/10 429 us 64.3 us 11349 41.0% BM_QueryMatching/1/100 2573 us 29.0 us 1000 51.7% (max) BM_QueryMatching/100/100 3123 us 416 us 1000 45.5% BM_QueryMatching/1/1000 26785 us 33.5 us 100 50.5% BM_QueryMatching/10/1000 28389 us 74.5 us 100 48.0% BM_QueryMatching/100/1000 27883 us 470 us 100 49.4% BM_QueryMatching/1000/1000 32189 us 34.3 us 100 44.8% BM_QueryAll/1 151 us 15.2 us 45385 -49.5% (min) BM_QueryAll/10 444 us 60.7 us 12829 32.5% BM_QueryAll/100 3175 us 414 us 1000 45.1% BM_QueryAll/1000 30222 us 18.8 us 100 49.7% Mean 35.3% * Insert into a vector to reduce contention Run on (12 X 2900 MHz CPU s) CPU Caches: L1 Data 32K (x6) L1 Instruction 32K (x6) L2 Unified 262K (x6) L3 Unified 12582K (x1) Load Average: 1.43, 2.10, 1.98 ---------------------------------------------------------------------------------- Benchmark Time CPU Iterations Improvement ---------------------------------------------------------------------------------- BM_QueryMatching/0/1 127 us 12.2 us 56131 3.8% BM_QueryMatching/1/1 142 us 16.6 us 39200 5.3% BM_QueryMatching/1/10 369 us 26.6 us 10000 0.5% BM_QueryMatching/10/10 425 us 59.2 us 12724 0.9% BM_QueryMatching/1/100 2194 us 28.9 us 10000 14.7% BM_QueryMatching/100/100 2765 us 405 us 1000 11.5% BM_QueryMatching/1/1000 21095 us 32.8 us 1000 21.2% BM_QueryMatching/10/1000 20797 us 66.4 us 1000 26.7% (max) BM_QueryMatching/100/1000 21915 us 408 us 1000 21.4% BM_QueryMatching/1000/1000 23700 us 30.9 us 1000 26.4% BM_QueryAll/1 136 us 14.3 us 44799 9.9% BM_QueryAll/10 443 us 57.3 us 10000 0.2% (min) BM_QueryAll/100 2607 us 389 us 1000 17.9% BM_QueryAll/1000 23512 us 19.4 us 1000 22.2% Mean 13.1%
1 parent 90235b5 commit f321ad8

17 files changed

+281
-36
lines changed

Firestore/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Unreleased
2+
- [changed] Improved performance of queries with large result sets.
23

34
# v1.10.2
45
- [changed] Internal improvements.

Firestore/Example/Benchmarks/remote_document_cache_benchmark.mm

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
return doc;
6262
}
6363

64-
FIRQuerySnapshot* GetDocuments(FIRQuery* query) {
64+
FIRQuerySnapshot* GetDocumentsFromCache(FIRQuery* query) {
6565
__block FIRQuerySnapshot* result;
6666
dispatch_semaphore_t done = dispatch_semaphore_create(0);
6767
[query getDocumentsWithSource:FIRFirestoreSourceCache
@@ -74,6 +74,19 @@
7474
return result;
7575
}
7676

77+
FIRQuerySnapshot* GetDocumentsFromServer(FIRQuery* query) {
78+
__block FIRQuerySnapshot* result;
79+
dispatch_semaphore_t done = dispatch_semaphore_create(0);
80+
[query getDocumentsWithSource:FIRFirestoreSourceServer
81+
completion:^(FIRQuerySnapshot* snap, NSError* error) {
82+
HARD_ASSERT(error == nil, "Failed: %s", MakeString([error description]));
83+
result = snap;
84+
dispatch_semaphore_signal(done);
85+
}];
86+
dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
87+
return result;
88+
}
89+
7790
void WaitForPendingWrites(FIRFirestore* db) {
7891
dispatch_semaphore_t done = dispatch_semaphore_create(0);
7992
[db waitForPendingWritesWithCompletion:^(NSError* error) {
@@ -100,17 +113,51 @@ void Shutdown(FIRFirestore* db) {
100113
dispatch_semaphore_wait(done, DISPATCH_TIME_FOREVER);
101114
}
102115

116+
void BM_QueryIndexFree(benchmark::State& state) {
117+
int64_t matching_docs = state.range(0);
118+
int64_t total_docs = state.range(1);
119+
120+
FIRFirestore* db = OpenFirestore();
121+
auto collection = [db collectionWithPath:MakeNSString("docs-" + CreateAutoId())];
122+
WriteDocs(collection, matching_docs, /*match=*/true);
123+
WriteDocs(collection, total_docs - matching_docs, /*match=*/false);
124+
125+
FIRQuery* query = [collection queryWhereField:@"match" isEqualTo:@YES];
126+
127+
// Query the server to force the target tables to be updated.
128+
GetDocumentsFromServer(query);
129+
130+
for (auto _ : state) {
131+
auto docs = GetDocumentsFromCache(query);
132+
(void)docs;
133+
}
134+
135+
Shutdown(db);
136+
}
137+
BENCHMARK(BM_QueryIndexFree)
138+
->Unit(benchmark::kMicrosecond)
139+
->Args({0, 1})
140+
->Args({1, 1})
141+
->Args({1, 10})
142+
->Args({10, 10})
143+
->Args({1, 100})
144+
->Args({100, 100})
145+
->Args({1, 1000})
146+
->Args({10, 1000})
147+
->Args({100, 1000})
148+
->Args({1000, 1000});
149+
103150
void BM_QueryMatching(benchmark::State& state) {
104151
int64_t matching_docs = state.range(0);
105152
int64_t total_docs = state.range(1);
106153

107154
FIRFirestore* db = OpenFirestore();
108155
auto collection = [db collectionWithPath:MakeNSString("docs-" + CreateAutoId())];
109-
WriteDocs(collection, matching_docs, /* match= */ true);
110-
WriteDocs(collection, total_docs - matching_docs, /* match= */ false);
156+
WriteDocs(collection, matching_docs, /*match=*/true);
157+
WriteDocs(collection, total_docs - matching_docs, /*match=*/false);
111158

112159
for (auto _ : state) {
113-
auto docs = GetDocuments([collection queryWhereField:@"match" isEqualTo:@YES]);
160+
auto docs = GetDocumentsFromCache([collection queryWhereField:@"match" isEqualTo:@YES]);
114161
(void)docs;
115162
}
116163

@@ -134,10 +181,10 @@ void BM_QueryAll(benchmark::State& state) {
134181

135182
FIRFirestore* db = OpenFirestore();
136183
auto collection = [db collectionWithPath:MakeNSString("docs-" + CreateAutoId())];
137-
WriteDocs(collection, total_docs, /* match= */ true);
184+
WriteDocs(collection, total_docs, /*match=*/true);
138185

139186
for (auto _ : state) {
140-
auto docs = GetDocuments(collection);
187+
auto docs = GetDocumentsFromCache(collection);
141188
(void)docs;
142189
}
143190

Firestore/core/src/firebase/firestore/core/target_id_generator.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,9 @@ namespace firebase {
2323
namespace firestore {
2424
namespace core {
2525

26-
TargetIdGenerator::TargetIdGenerator(const TargetIdGenerator& value)
27-
: generator_id_(value.generator_id_), next_id_(value.next_id_) {
28-
}
29-
3026
TargetIdGenerator::TargetIdGenerator(TargetIdGeneratorId generator_id,
3127
TargetId seed)
3228
: generator_id_(generator_id) {
33-
generator_id_ = generator_id;
3429
seek(seed);
3530
}
3631

Firestore/core/src/firebase/firestore/core/target_id_generator.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ class TargetIdGenerator {
4949
// Makes Objective-C++ code happy to provide a default ctor.
5050
TargetIdGenerator() = default;
5151

52-
TargetIdGenerator(const TargetIdGenerator& value);
52+
TargetIdGenerator(const TargetIdGenerator& value) = default;
53+
TargetIdGenerator& operator=(const TargetIdGenerator& value) = default;
5354

5455
/**
5556
* Creates and returns the TargetIdGenerator for the local store.
@@ -84,8 +85,9 @@ class TargetIdGenerator {
8485
private:
8586
TargetIdGenerator(TargetIdGeneratorId generator_id, model::TargetId seed);
8687
void seek(model::TargetId target_id);
87-
TargetIdGeneratorId generator_id_;
88-
model::TargetId next_id_;
88+
89+
TargetIdGeneratorId generator_id_ = TargetIdGeneratorId::TargetCache;
90+
model::TargetId next_id_ = 0;
8991

9092
static const int kReservedBits = 1;
9193
};

Firestore/core/src/firebase/firestore/local/leveldb_persistence.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ util::StatusOr<std::unique_ptr<LevelDbPersistence>> LevelDbPersistence::Create(
9898
std::unique_ptr<LevelDbPersistence> result(
9999
new LevelDbPersistence(std::move(db), std::move(dir), std::move(users),
100100
std::move(serializer), lru_params));
101-
return std::move(result);
101+
return {std::move(result)};
102102
}
103103

104104
LevelDbPersistence::LevelDbPersistence(std::unique_ptr<leveldb::DB> db,

Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.cc

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018 Google
2+
* Copyright 2018 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h"
1818

1919
#include <string>
20+
#include <thread> // NOLINT(build/c++11)
2021
#include <utility>
2122

2223
#include "Firestore/Protos/nanopb/firestore/local/maybe_document.nanopb.h"
@@ -26,13 +27,16 @@
2627
#include "Firestore/core/src/firebase/firestore/local/local_serializer.h"
2728
#include "Firestore/core/src/firebase/firestore/nanopb/message.h"
2829
#include "Firestore/core/src/firebase/firestore/nanopb/reader.h"
30+
#include "Firestore/core/src/firebase/firestore/util/background_queue.h"
31+
#include "Firestore/core/src/firebase/firestore/util/executor.h"
2932
#include "Firestore/core/src/firebase/firestore/util/status.h"
3033
#include "Firestore/core/src/firebase/firestore/util/string_util.h"
3134
#include "leveldb/db.h"
3235

3336
namespace firebase {
3437
namespace firestore {
3538
namespace local {
39+
namespace {
3640

3741
using core::Query;
3842
using leveldb::Status;
@@ -48,12 +52,58 @@ using model::SnapshotVersion;
4852
using nanopb::ByteString;
4953
using nanopb::Message;
5054
using nanopb::StringReader;
55+
using util::BackgroundQueue;
56+
using util::Executor;
57+
58+
/**
59+
* An accumulator for results produced asynchronously. This accumulates
60+
* values in a vector to avoid contention caused by accumulating into more
61+
* complex structures like immutable::SortedMap.
62+
*/
63+
template <typename T>
64+
class AsyncResults {
65+
public:
66+
void Insert(T&& value) {
67+
std::lock_guard<std::mutex> lock(mutex_);
68+
values_.push_back(std::move(value));
69+
}
70+
71+
void Insert(const T& value) {
72+
std::lock_guard<std::mutex> lock(mutex_);
73+
values_.push_back(value);
74+
}
75+
76+
/**
77+
* Returns the accumulated result, moving it out of AsyncResults. The
78+
* AsyncResults object should not be reused.
79+
*/
80+
std::vector<T> Result() {
81+
std::lock_guard<std::mutex> lock(mutex_);
82+
return std::move(values_);
83+
}
84+
85+
private:
86+
std::vector<T> values_;
87+
std::mutex mutex_;
88+
};
89+
90+
} // namespace
5191

5292
LevelDbRemoteDocumentCache::LevelDbRemoteDocumentCache(
5393
LevelDbPersistence* db, LocalSerializer* serializer)
5494
: db_(db), serializer_(NOT_NULL(serializer)) {
95+
auto hw_concurrency = std::thread::hardware_concurrency();
96+
if (hw_concurrency == 0) {
97+
// If the standard library doesn't know, guess something reasonable.
98+
hw_concurrency = 4;
99+
}
100+
executor_ = Executor::CreateConcurrent("com.google.firebase.firestore.query",
101+
static_cast<int>(hw_concurrency));
55102
}
56103

104+
// Out of line because of unique_ptrs to incomplete types.
105+
LevelDbRemoteDocumentCache::~LevelDbRemoteDocumentCache() = default;
106+
57107
void LevelDbRemoteDocumentCache::Add(const MaybeDocument& document,
58108
const SnapshotVersion& read_time) {
59109
const DocumentKey& key = document.key();
@@ -93,7 +143,8 @@ absl::optional<MaybeDocument> LevelDbRemoteDocumentCache::Get(
93143

94144
OptionalMaybeDocumentMap LevelDbRemoteDocumentCache::GetAll(
95145
const DocumentKeySet& keys) {
96-
OptionalMaybeDocumentMap results;
146+
BackgroundQueue tasks(executor_.get());
147+
AsyncResults<std::pair<DocumentKey, absl::optional<MaybeDocument>>> results;
97148

98149
LevelDbRemoteDocumentKey current_key;
99150
auto it = db_->current_transaction()->NewIterator();
@@ -102,13 +153,22 @@ OptionalMaybeDocumentMap LevelDbRemoteDocumentCache::GetAll(
102153
it->Seek(LevelDbRemoteDocumentKey::Key(key));
103154
if (!it->Valid() || !current_key.Decode(it->key()) ||
104155
current_key.document_key() != key) {
105-
results = results.insert(key, absl::nullopt);
156+
results.Insert(std::make_pair(key, absl::nullopt));
106157
} else {
107-
results = results.insert(key, DecodeMaybeDocument(it->value(), key));
158+
const std::string& contents = it->value();
159+
tasks.Execute([this, &results, &key, contents] {
160+
results.Insert(std::make_pair(key, DecodeMaybeDocument(contents, key)));
161+
});
108162
}
109163
}
110164

111-
return results;
165+
tasks.AwaitAll();
166+
167+
OptionalMaybeDocumentMap map;
168+
for (const auto& entry : results.Result()) {
169+
map = map.insert(entry.first, entry.second);
170+
}
171+
return map;
112172
}
113173

114174
DocumentMap LevelDbRemoteDocumentCache::GetAllExisting(
@@ -165,7 +225,8 @@ DocumentMap LevelDbRemoteDocumentCache::GetMatching(
165225

166226
return LevelDbRemoteDocumentCache::GetAllExisting(remote_keys);
167227
} else {
168-
DocumentMap results;
228+
BackgroundQueue tasks(executor_.get());
229+
AsyncResults<Document> results;
169230

170231
// Documents are ordered by key, so we can use a prefix scan to narrow down
171232
// the documents we need to match the query against.
@@ -185,15 +246,26 @@ DocumentMap LevelDbRemoteDocumentCache::GetMatching(
185246
continue;
186247
}
187248

188-
MaybeDocument maybe_doc = DecodeMaybeDocument(it->value(), document_key);
189-
if (!query_path.IsPrefixOf(maybe_doc.key().path())) {
249+
if (!query_path.IsPrefixOf(document_key.path())) {
190250
break;
191-
} else if (maybe_doc.is_document()) {
192-
results = results.insert(maybe_doc.key(), Document(maybe_doc));
193251
}
252+
253+
const std::string& contents = it->value();
254+
tasks.Execute([this, &results, document_key, contents] {
255+
MaybeDocument maybe_doc = DecodeMaybeDocument(contents, document_key);
256+
if (maybe_doc.is_document()) {
257+
results.Insert(Document(maybe_doc));
258+
}
259+
});
194260
}
195261

196-
return results;
262+
tasks.AwaitAll();
263+
264+
DocumentMap map;
265+
for (const Document& doc : results.Result()) {
266+
map = map.insert(doc.key(), doc);
267+
}
268+
return map;
197269
}
198270
}
199271

Firestore/core/src/firebase/firestore/local/leveldb_remote_document_cache.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018 Google
2+
* Copyright 2018 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
1717
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_REMOTE_DOCUMENT_CACHE_H_
1818
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_LOCAL_LEVELDB_REMOTE_DOCUMENT_CACHE_H_
1919

20+
#include <memory>
21+
#include <thread> // NOLINT(build/c++11)
2022
#include <vector>
2123

2224
#include "Firestore/core/src/firebase/firestore/local/remote_document_cache.h"
@@ -29,6 +31,11 @@
2931

3032
namespace firebase {
3133
namespace firestore {
34+
35+
namespace util {
36+
class Executor;
37+
} // namespace util
38+
3239
namespace local {
3340

3441
class LevelDbPersistence;
@@ -39,6 +46,7 @@ class LevelDbRemoteDocumentCache : public RemoteDocumentCache {
3946
public:
4047
LevelDbRemoteDocumentCache(LevelDbPersistence* db,
4148
LocalSerializer* serializer);
49+
~LevelDbRemoteDocumentCache();
4250

4351
void Add(const model::MaybeDocument& document,
4452
const model::SnapshotVersion& read_time) override;
@@ -66,6 +74,8 @@ class LevelDbRemoteDocumentCache : public RemoteDocumentCache {
6674
LevelDbPersistence* db_;
6775
// Owned by LevelDbPersistence.
6876
LocalSerializer* serializer_ = nullptr;
77+
78+
std::unique_ptr<util::Executor> executor_;
6979
};
7080

7181
} // namespace local

Firestore/core/src/firebase/firestore/local/leveldb_target_cache.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ LevelDbTargetCache::TryReadMetadata(leveldb::DB* db) {
6666
}
6767
}
6868

69-
return std::move(result);
69+
return {std::move(result)};
7070
}
7171

7272
Message<firestore_client_TargetGlobal> LevelDbTargetCache::ReadMetadata(

Firestore/core/src/firebase/firestore/model/document_map.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018 Google
2+
* Copyright 2018 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,6 +57,9 @@ using OptionalMaybeDocumentMap =
5757
*/
5858
class DocumentMap {
5959
public:
60+
using key_type = DocumentKey;
61+
using mapped_type = Document;
62+
6063
DocumentMap() = default;
6164

6265
ABSL_MUST_USE_RESULT DocumentMap insert(const DocumentKey& key,

0 commit comments

Comments
 (0)