Skip to content

Commit 8d3e6d5

Browse files
Backport #7755 (#7757)
Co-authored-by: Max <maxtropets@microsoft.com>
1 parent 872919b commit 8d3e6d5

9 files changed

Lines changed: 505 additions & 110 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## [6.0.26]
9+
10+
[6.0.26]: https://github.com/microsoft/CCF/releases/tag/ccf-6.0.26
11+
12+
### Fixed
13+
14+
- 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).
15+
816
## [6.0.25]
917

1018
[6.0.25]: https://github.com/microsoft/CCF/releases/tag/ccf-6.0.25

doc/schemas/node_openapi.json

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,17 @@
237237
],
238238
"type": "object"
239239
},
240+
"GetHistoricalCacheInfo__Out": {
241+
"properties": {
242+
"estimated_size": {
243+
"$ref": "#/components/schemas/uint64"
244+
}
245+
},
246+
"required": [
247+
"estimated_size"
248+
],
249+
"type": "object"
250+
},
240251
"GetNetworkInfo__Out": {
241252
"properties": {
242253
"current_service_create_txid": {
@@ -921,7 +932,7 @@
921932
"info": {
922933
"description": "This API provides public, uncredentialed access to service and node state.",
923934
"title": "CCF Public Node API",
924-
"version": "4.16.0"
935+
"version": "4.17.0"
925936
},
926937
"openapi": "3.0.0",
927938
"paths": {
@@ -1100,6 +1111,29 @@
11001111
}
11011112
}
11021113
},
1114+
"/node/historical_cache": {
1115+
"get": {
1116+
"operationId": "GetNodeHistoricalCache",
1117+
"responses": {
1118+
"200": {
1119+
"content": {
1120+
"application/json": {
1121+
"schema": {
1122+
"$ref": "#/components/schemas/GetHistoricalCacheInfo__Out"
1123+
}
1124+
}
1125+
},
1126+
"description": "Default response description"
1127+
},
1128+
"default": {
1129+
"$ref": "#/components/responses/default"
1130+
}
1131+
},
1132+
"x-ccf-forwarding": {
1133+
"$ref": "#/components/x-ccf-forwarding/sometimes"
1134+
}
1135+
}
1136+
},
11031137
"/node/index/strategies": {
11041138
"get": {
11051139
"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
}

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ccf"
7-
version = "6.0.25"
7+
version = "6.0.26"
88
authors = [
99
{ name="CCF Team", email="CCF-Sec@microsoft.com" },
1010
]

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
@@ -64,7 +65,6 @@ FMT_END_NAMESPACE
6465
namespace ccf::historical
6566
{
6667
static constexpr auto slow_fetch_threshold = std::chrono::milliseconds(1000);
67-
static constexpr size_t soft_to_raw_ratio{5};
6868

6969
static std::optional<ccf::PrimarySignature> get_signature(
7070
const ccf::kv::StorePtr& sig_store)
@@ -273,9 +273,17 @@ namespace ccf::historical
273273
else if (prev_it != my_stores.end() && *new_it > prev_it->first)
274274
{
275275
// No longer looking for a seqno which was previously requested.
276-
// Remove it from my_stores
277-
removed.push_back(prev_it->first);
278-
prev_it = my_stores.erase(prev_it);
276+
if (
277+
supporting_signatures.find(prev_it->first) ==
278+
supporting_signatures.end())
279+
{
280+
removed.push_back(prev_it->first);
281+
prev_it = my_stores.erase(prev_it);
282+
}
283+
else
284+
{
285+
++prev_it;
286+
}
279287
}
280288
else
281289
{
@@ -311,15 +319,31 @@ namespace ccf::historical
311319
if (prev_it != my_stores.end())
312320
{
313321
// If we have a suffix of seqnos previously requested, now
314-
// unrequested, purge them
315-
for (auto it = prev_it; it != my_stores.end(); ++it)
322+
// unrequested, purge them - but keep supporting signature entries
323+
auto it = prev_it;
324+
while (it != my_stores.end())
316325
{
317-
removed.push_back(it->first);
326+
if (
327+
supporting_signatures.find(it->first) ==
328+
supporting_signatures.end())
329+
{
330+
removed.push_back(it->first);
331+
it = my_stores.erase(it);
332+
}
333+
else
334+
{
335+
++it;
336+
}
318337
}
319-
my_stores.erase(prev_it, my_stores.end());
320338
}
321339
}
322340

341+
HISTORICAL_LOG(
342+
"Added seqnos: {}, removed seqnos: {}, supporting signatures: {}",
343+
fmt::join(added, ","),
344+
fmt::join(removed, ","),
345+
fmt::join(std::views::keys(supporting_signatures), ","));
346+
323347
const bool any_diff = !removed.empty() || !added.empty();
324348

325349
if (!any_diff && (should_include_receipts == include_receipts))
@@ -342,13 +366,36 @@ namespace ccf::historical
342366

343367
for (auto seqno : new_seqnos)
344368
{
345-
populate_receipts(seqno);
369+
auto more_to_add = populate_receipts(seqno);
370+
std::sort(added.begin(), added.end());
371+
std::sort(more_to_add.begin(), more_to_add.end());
372+
373+
std::vector<SeqNo> together;
374+
std::merge(
375+
added.begin(),
376+
added.end(),
377+
more_to_add.begin(),
378+
more_to_add.end(),
379+
std::back_inserter(together));
380+
381+
if (more_to_add.size() + added.size() != together.size())
382+
{
383+
LOG_FAIL_FMT(
384+
"Invariant violation in adjust_ranges: more_to_add({}) + "
385+
"added({}) != together({})",
386+
more_to_add.size(),
387+
added.size(),
388+
together.size());
389+
assert(false);
390+
}
391+
392+
std::swap(added, together);
346393
}
347394
}
348395
return {removed, added};
349396
}
350397

351-
void populate_receipts(ccf::SeqNo new_seqno)
398+
std::vector<ccf::SeqNo> populate_receipts(ccf::SeqNo new_seqno)
352399
{
353400
HISTORICAL_LOG(
354401
"Looking at {}, and populating receipts from it", new_seqno);
@@ -376,6 +423,16 @@ namespace ccf::historical
376423
HISTORICAL_LOG("{} is not a signature", new_seqno);
377424
supporting_signatures.erase(new_seqno);
378425

426+
if (new_details->receipt != nullptr)
427+
{
428+
HISTORICAL_LOG(
429+
"Already have a receipt for {}, so no need to populate more",
430+
new_seqno);
431+
return {};
432+
}
433+
434+
std::vector<SeqNo> added;
435+
379436
auto next_seqno = new_seqno + 1;
380437
while (true)
381438
{
@@ -387,6 +444,18 @@ namespace ccf::historical
387444
HISTORICAL_LOG(
388445
"Looking for new supporting signature at {}", next_seqno);
389446
details = std::make_shared<StoreDetails>();
447+
auto my_it = my_stores.find(next_seqno);
448+
if (my_it == my_stores.end())
449+
{
450+
LOG_TRACE_FMT(
451+
"Tracking potential supporting signature for new seqno {} "
452+
"at {}",
453+
new_seqno,
454+
next_seqno);
455+
added.push_back(next_seqno);
456+
my_stores.insert_or_assign(my_it, next_seqno, details);
457+
}
458+
390459
all_stores.insert_or_assign(all_it, next_seqno, details);
391460
}
392461

@@ -400,7 +469,7 @@ namespace ccf::historical
400469
next_seqno,
401470
new_seqno);
402471
supporting_signatures[next_seqno] = details;
403-
return;
472+
return added;
404473
}
405474
else if (details->is_signature)
406475
{
@@ -418,7 +487,7 @@ namespace ccf::historical
418487
new_seqno));
419488
}
420489

421-
return;
490+
return added;
422491
}
423492
else
424493
{
@@ -427,8 +496,11 @@ namespace ccf::historical
427496
++next_seqno;
428497
}
429498
}
499+
500+
return added;
430501
}
431502
}
503+
return {};
432504
}
433505

434506
private:
@@ -521,8 +593,6 @@ namespace ccf::historical
521593
std::unordered_map<ccf::SeqNo, size_t> raw_store_sizes{};
522594

523595
CacheSize soft_store_cache_limit{std::numeric_limits<size_t>::max()};
524-
CacheSize soft_store_cache_limit_raw =
525-
soft_store_cache_limit / soft_to_raw_ratio;
526596
CacheSize estimated_store_cache_size{0};
527597

528598
void add_request_ref(SeqNo seq, CompoundHandle handle)
@@ -806,7 +876,11 @@ namespace ccf::historical
806876
request.supporting_signatures.end());
807877
if (seqno_in_this_request)
808878
{
809-
request.populate_receipts(seqno);
879+
auto added = request.populate_receipts(seqno);
880+
for (auto seq : added)
881+
{
882+
add_request_ref(seq, handle);
883+
}
810884
}
811885
}
812886

@@ -1149,7 +1223,6 @@ namespace ccf::historical
11491223
void set_soft_cache_limit(CacheSize cache_limit)
11501224
{
11511225
soft_store_cache_limit = cache_limit;
1152-
soft_store_cache_limit_raw = soft_store_cache_limit / soft_to_raw_ratio;
11531226
}
11541227

11551228
void track_deletes_on_missing_keys(bool track)
@@ -1438,7 +1511,7 @@ namespace ccf::historical
14381511
}
14391512
}
14401513

1441-
lru_shrink_to_fit(soft_store_cache_limit_raw);
1514+
lru_shrink_to_fit(soft_store_cache_limit);
14421515

14431516
{
14441517
auto it = all_stores.begin();
@@ -1633,5 +1706,10 @@ namespace ccf::historical
16331706
{
16341707
return StateCacheImpl::drop_cached_states(make_compound_handle(handle));
16351708
}
1709+
1710+
size_t get_estimated_store_cache_size() override
1711+
{
1712+
return StateCacheImpl::get_estimated_store_cache_size();
1713+
}
16361714
};
16371715
}

src/node/rpc/node_frontend.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ namespace ccf
8686
DECLARE_JSON_TYPE(NodeMetrics);
8787
DECLARE_JSON_REQUIRED_FIELDS(NodeMetrics, sessions);
8888

89+
struct GetHistoricalCacheInfo
90+
{
91+
using In = void;
92+
93+
struct Out
94+
{
95+
size_t estimated_size;
96+
};
97+
};
98+
99+
DECLARE_JSON_TYPE(GetHistoricalCacheInfo::Out);
100+
DECLARE_JSON_REQUIRED_FIELDS(GetHistoricalCacheInfo::Out, estimated_size);
101+
89102
struct JavaScriptMetrics
90103
{
91104
uint64_t bytecode_size;
@@ -430,7 +443,7 @@ namespace ccf
430443
openapi_info.description =
431444
"This API provides public, uncredentialed access to service and node "
432445
"state.";
433-
openapi_info.document_version = "4.16.0";
446+
openapi_info.document_version = "4.17.0";
434447
}
435448

436449
void init_handlers() override
@@ -1862,6 +1875,22 @@ namespace ccf
18621875
.install();
18631876

18641877
ccf::node::init_file_serving_handlers(*this, context);
1878+
1879+
auto historical_cache_info = [this](
1880+
[[maybe_unused]] auto& args,
1881+
[[maybe_unused]] nlohmann::json&&) {
1882+
GetHistoricalCacheInfo::Out result{};
1883+
result.estimated_size =
1884+
this->context.get_historical_state().get_estimated_store_cache_size();
1885+
return make_success(result);
1886+
};
1887+
make_read_only_endpoint(
1888+
"/historical_cache",
1889+
HTTP_GET,
1890+
json_read_only_adapter(historical_cache_info),
1891+
no_auth_required)
1892+
.set_auto_schema<GetHistoricalCacheInfo>()
1893+
.install();
18651894
}
18661895
};
18671896

src/node/rpc/test/node_stub.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,11 @@ namespace ccf
270270
{
271271
return true;
272272
}
273+
274+
size_t get_estimated_store_cache_size()
275+
{
276+
return 0;
277+
}
273278
};
274279

275280
struct StubNodeContext : public ccf::AbstractNodeContext

0 commit comments

Comments
 (0)