Skip to content

Commit 3355258

Browse files
maxtropetsachamayoueddyashton
authored
Fix cache size calculation for supporting TXs (#7755)
Co-authored-by: Amaury Chamayou <amchamay@microsoft.com> Co-authored-by: Amaury Chamayou <amaury@xargs.fr> Co-authored-by: Eddy Ashton <edashton@microsoft.com>
1 parent 02c438d commit 3355258

9 files changed

Lines changed: 496 additions & 105 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1616

1717
### Fixed
1818

19-
- Fixed the Turin SEV-SNP CPUID mapping used for product detection. (#7748)
19+
- Fixed the Turin SEV-SNP CPUID mapping used for product detection (#7748).
20+
- Fixed cache size calculations for historical queries, resolving a bug where signature transactions could become orphaned and fill the cache's useful space, resulting in incoming user-requested stores being immediately evicted (#7755).
2021

2122
## [7.0.0-dev12]
2223

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,11 @@ if(BUILD_TESTS)
12651265
NAME e2e_curl PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/e2e_curl.py
12661266
)
12671267

1268+
add_e2e_test(
1269+
NAME historical_query_cache_test
1270+
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/historical_query_cache.py
1271+
)
1272+
12681273
add_e2e_test(
12691274
NAME consistency_trace_validation
12701275
PYTHON_SCRIPT ${CMAKE_SOURCE_DIR}/tests/consistency_trace_validation.py

doc/schemas/node_openapi.json

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@
234234
],
235235
"type": "object"
236236
},
237+
"GetHistoricalCacheInfo__Out": {
238+
"properties": {
239+
"estimated_size": {
240+
"$ref": "#/components/schemas/uint64"
241+
}
242+
},
243+
"required": [
244+
"estimated_size"
245+
],
246+
"type": "object"
247+
},
237248
"GetNetworkInfo__Out": {
238249
"properties": {
239250
"current_service_create_txid": {
@@ -918,7 +929,7 @@
918929
"info": {
919930
"description": "This API provides public, uncredentialed access to service and node state.",
920931
"title": "CCF Public Node API",
921-
"version": "5.0.3"
932+
"version": "5.0.4"
922933
},
923934
"openapi": "3.0.0",
924935
"paths": {
@@ -1097,6 +1108,29 @@
10971108
}
10981109
}
10991110
},
1111+
"/node/historical_cache": {
1112+
"get": {
1113+
"operationId": "GetNodeHistoricalCache",
1114+
"responses": {
1115+
"200": {
1116+
"content": {
1117+
"application/json": {
1118+
"schema": {
1119+
"$ref": "#/components/schemas/GetHistoricalCacheInfo__Out"
1120+
}
1121+
}
1122+
},
1123+
"description": "Default response description"
1124+
},
1125+
"default": {
1126+
"$ref": "#/components/responses/default"
1127+
}
1128+
},
1129+
"x-ccf-forwarding": {
1130+
"$ref": "#/components/x-ccf-forwarding/sometimes"
1131+
}
1132+
}
1133+
},
11001134
"/node/index/strategies": {
11011135
"get": {
11021136
"operationId": "GetNodeIndexStrategies",

include/ccf/historical_queries_interface.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,5 +196,9 @@ namespace ccf::historical
196196
* more aggressively than waiting for the states to expire.
197197
*/
198198
virtual bool drop_cached_states(RequestHandle handle) = 0;
199+
200+
/** Get the estimated size in bytes of all cached stores.
201+
*/
202+
virtual size_t get_estimated_store_cache_size() = 0;
199203
};
200204
}

src/node/historical_queries.h

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
#ifdef ENABLE_HISTORICAL_VERBOSE_LOGGING
2323
# define HISTORICAL_LOG(...) LOG_INFO_FMT(__VA_ARGS__)
24+
# include <ranges>
2425
#else
2526
# define HISTORICAL_LOG(...)
2627
#endif
@@ -63,7 +64,6 @@ FMT_END_NAMESPACE
6364
namespace ccf::historical
6465
{
6566
static constexpr auto slow_fetch_threshold = std::chrono::milliseconds(1000);
66-
static constexpr size_t soft_to_raw_ratio{5};
6767

6868
static std::optional<ccf::PrimarySignature> get_signature(
6969
const ccf::kv::StorePtr& sig_store)
@@ -269,9 +269,17 @@ namespace ccf::historical
269269
else if (prev_it != my_stores.end() && *new_it > prev_it->first)
270270
{
271271
// No longer looking for a seqno which was previously requested.
272-
// Remove it from my_stores
273-
removed.push_back(prev_it->first);
274-
prev_it = my_stores.erase(prev_it);
272+
if (
273+
supporting_signatures.find(prev_it->first) ==
274+
supporting_signatures.end())
275+
{
276+
removed.push_back(prev_it->first);
277+
prev_it = my_stores.erase(prev_it);
278+
}
279+
else
280+
{
281+
++prev_it;
282+
}
275283
}
276284
else
277285
{
@@ -307,15 +315,31 @@ namespace ccf::historical
307315
if (prev_it != my_stores.end())
308316
{
309317
// If we have a suffix of seqnos previously requested, now
310-
// unrequested, purge them
311-
for (auto it = prev_it; it != my_stores.end(); ++it)
318+
// unrequested, purge them - but keep supporting signature entries
319+
auto it = prev_it;
320+
while (it != my_stores.end())
312321
{
313-
removed.push_back(it->first);
322+
if (
323+
supporting_signatures.find(it->first) ==
324+
supporting_signatures.end())
325+
{
326+
removed.push_back(it->first);
327+
it = my_stores.erase(it);
328+
}
329+
else
330+
{
331+
++it;
332+
}
314333
}
315-
my_stores.erase(prev_it, my_stores.end());
316334
}
317335
}
318336

337+
HISTORICAL_LOG(
338+
"Added seqnos: {}, removed seqnos: {}, supporting signatures: {}",
339+
fmt::join(added, ","),
340+
fmt::join(removed, ","),
341+
fmt::join(std::views::keys(supporting_signatures), ","));
342+
319343
const bool any_diff = !removed.empty() || !added.empty();
320344

321345
if (!any_diff && (should_include_receipts == include_receipts))
@@ -338,13 +362,36 @@ namespace ccf::historical
338362

339363
for (auto seqno : new_seqnos)
340364
{
341-
populate_receipts(seqno);
365+
auto more_to_add = populate_receipts(seqno);
366+
std::sort(added.begin(), added.end());
367+
std::sort(more_to_add.begin(), more_to_add.end());
368+
369+
std::vector<SeqNo> together;
370+
std::merge(
371+
added.begin(),
372+
added.end(),
373+
more_to_add.begin(),
374+
more_to_add.end(),
375+
std::back_inserter(together));
376+
377+
if (more_to_add.size() + added.size() != together.size())
378+
{
379+
LOG_FAIL_FMT(
380+
"Invariant violation in adjust_ranges: more_to_add({}) + "
381+
"added({}) != together({})",
382+
more_to_add.size(),
383+
added.size(),
384+
together.size());
385+
assert(false);
386+
}
387+
388+
std::swap(added, together);
342389
}
343390
}
344391
return {removed, added};
345392
}
346393

347-
void populate_receipts(ccf::SeqNo new_seqno)
394+
std::vector<ccf::SeqNo> populate_receipts(ccf::SeqNo new_seqno)
348395
{
349396
HISTORICAL_LOG(
350397
"Looking at {}, and populating receipts from it", new_seqno);
@@ -372,6 +419,16 @@ namespace ccf::historical
372419
HISTORICAL_LOG("{} is not a signature", new_seqno);
373420
supporting_signatures.erase(new_seqno);
374421

422+
if (new_details->receipt != nullptr)
423+
{
424+
HISTORICAL_LOG(
425+
"Already have a receipt for {}, so no need to populate more",
426+
new_seqno);
427+
return {};
428+
}
429+
430+
std::vector<SeqNo> added;
431+
375432
auto next_seqno = new_seqno + 1;
376433
while (true)
377434
{
@@ -383,6 +440,18 @@ namespace ccf::historical
383440
HISTORICAL_LOG(
384441
"Looking for new supporting signature at {}", next_seqno);
385442
details = std::make_shared<StoreDetails>();
443+
auto my_it = my_stores.find(next_seqno);
444+
if (my_it == my_stores.end())
445+
{
446+
LOG_TRACE_FMT(
447+
"Tracking potential supporting signature for new seqno {} "
448+
"at {}",
449+
new_seqno,
450+
next_seqno);
451+
added.push_back(next_seqno);
452+
my_stores.insert_or_assign(my_it, next_seqno, details);
453+
}
454+
386455
all_stores.insert_or_assign(all_it, next_seqno, details);
387456
}
388457

@@ -396,7 +465,7 @@ namespace ccf::historical
396465
next_seqno,
397466
new_seqno);
398467
supporting_signatures[next_seqno] = details;
399-
return;
468+
return added;
400469
}
401470

402471
if (details->is_signature)
@@ -415,15 +484,18 @@ namespace ccf::historical
415484
new_seqno));
416485
}
417486

418-
return;
487+
return added;
419488
}
420489

421490
// This is a normal transaction, and its already fetched.
422491
// Nothing to do, consider the next.
423492
++next_seqno;
424493
}
494+
495+
return added;
425496
}
426497
}
498+
return {};
427499
}
428500

429501
private:
@@ -525,8 +597,6 @@ namespace ccf::historical
525597
std::unordered_map<ccf::SeqNo, size_t> raw_store_sizes;
526598

527599
CacheSize soft_store_cache_limit{std::numeric_limits<size_t>::max()};
528-
CacheSize soft_store_cache_limit_raw =
529-
soft_store_cache_limit / soft_to_raw_ratio;
530600
CacheSize estimated_store_cache_size{0};
531601

532602
void add_request_ref(SeqNo seq, CompoundHandle handle)
@@ -814,7 +884,11 @@ namespace ccf::historical
814884
request.supporting_signatures.end());
815885
if (seqno_in_this_request)
816886
{
817-
request.populate_receipts(seqno);
887+
auto added = request.populate_receipts(seqno);
888+
for (auto seq : added)
889+
{
890+
add_request_ref(seq, handle);
891+
}
818892
}
819893
}
820894

@@ -1158,7 +1232,6 @@ namespace ccf::historical
11581232
void set_soft_cache_limit(CacheSize cache_limit)
11591233
{
11601234
soft_store_cache_limit = cache_limit;
1161-
soft_store_cache_limit_raw = soft_store_cache_limit / soft_to_raw_ratio;
11621235
}
11631236

11641237
void track_deletes_on_missing_keys(bool track)
@@ -1450,7 +1523,7 @@ namespace ccf::historical
14501523
}
14511524
}
14521525

1453-
lru_shrink_to_fit(soft_store_cache_limit_raw);
1526+
lru_shrink_to_fit(soft_store_cache_limit);
14541527

14551528
{
14561529
auto it = all_stores.begin();
@@ -1648,5 +1721,10 @@ namespace ccf::historical
16481721
{
16491722
return StateCacheImpl::drop_cached_states(make_compound_handle(handle));
16501723
}
1724+
1725+
size_t get_estimated_store_cache_size() override
1726+
{
1727+
return StateCacheImpl::get_estimated_store_cache_size();
1728+
}
16511729
};
16521730
}

src/node/rpc/node_frontend.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,19 @@ namespace ccf
9393
DECLARE_JSON_TYPE(NodeMetrics);
9494
DECLARE_JSON_REQUIRED_FIELDS(NodeMetrics, sessions);
9595

96+
struct GetHistoricalCacheInfo
97+
{
98+
using In = void;
99+
100+
struct Out
101+
{
102+
size_t estimated_size;
103+
};
104+
};
105+
106+
DECLARE_JSON_TYPE(GetHistoricalCacheInfo::Out);
107+
DECLARE_JSON_REQUIRED_FIELDS(GetHistoricalCacheInfo::Out, estimated_size);
108+
96109
struct JavaScriptMetrics
97110
{
98111
uint64_t bytecode_size = 0;
@@ -420,7 +433,7 @@ namespace ccf
420433
openapi_info.description =
421434
"This API provides public, uncredentialed access to service and node "
422435
"state.";
423-
openapi_info.document_version = "5.0.3";
436+
openapi_info.document_version = "5.0.4";
424437
}
425438

426439
void init_handlers() override
@@ -1847,6 +1860,22 @@ namespace ccf
18471860
ccf::node::init_recovery_decision_protocol_handlers(*this, context);
18481861

18491862
ccf::node::init_file_serving_handlers(*this, context);
1863+
1864+
auto historical_cache_info = [this](
1865+
[[maybe_unused]] auto& args,
1866+
[[maybe_unused]] nlohmann::json&&) {
1867+
GetHistoricalCacheInfo::Out result{};
1868+
result.estimated_size =
1869+
this->context.get_historical_state().get_estimated_store_cache_size();
1870+
return make_success(result);
1871+
};
1872+
make_read_only_endpoint(
1873+
"/historical_cache",
1874+
HTTP_GET,
1875+
json_read_only_adapter(historical_cache_info),
1876+
no_auth_required)
1877+
.set_auto_schema<GetHistoricalCacheInfo>()
1878+
.install();
18501879
}
18511880
};
18521881

src/node/rpc/test/node_stub.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@ namespace ccf
268268
{
269269
return true;
270270
}
271+
272+
size_t get_estimated_store_cache_size()
273+
{
274+
return 0;
275+
}
271276
};
272277

273278
struct StubNodeContext : public ccf::AbstractNodeContext

0 commit comments

Comments
 (0)