fix(explorer): commit the loadRangeCached/revision memoization the perf fix depends on
Repairs dev:3ee9099switched the Explorer call sites (and a test) to ExplorerBlockCache::loadRangeCached() / revision(), but those definitions were sitting uncommitted in the working tree (left over from the explorer block-search feature,ebbeb84) and were not staged with that commit — so dev as pushed referenced undefined members and would not compile on a clean checkout. The infra: loadRangeCached() memoizes loadRange() per (min,max), invalidated by revision_, which is bumped on every mutation that can change what loadRange returns (open/close/store/clear/prune/spill-reset); the memo is capped at 16 ranges. My local builds passed only because the working tree already had these. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,7 @@ bool ExplorerBlockCache::ensureOpen()
|
|||||||
}
|
}
|
||||||
|
|
||||||
db_ = openedDb;
|
db_ = openedDb;
|
||||||
|
++revision_;
|
||||||
// UI-thread-only handle: a short wait is enough; a miss falls back to the in-memory spill instead of
|
// UI-thread-only handle: a short wait is enough; a miss falls back to the in-memory spill instead of
|
||||||
// freezing the frame for 2 s.
|
// freezing the frame for 2 s.
|
||||||
sqlite3_busy_timeout(db_, 250);
|
sqlite3_busy_timeout(db_, 250);
|
||||||
@@ -176,6 +177,20 @@ std::map<int, ExplorerBlockSummary> ExplorerBlockCache::loadRange(int minHeight,
|
|||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::map<int, ExplorerBlockSummary>& ExplorerBlockCache::loadRangeCached(int minHeight, int maxHeight)
|
||||||
|
{
|
||||||
|
if (range_cache_revision_ != revision_) { range_cache_.clear(); range_cache_revision_ = revision_; }
|
||||||
|
if (range_cache_.size() >= 16) range_cache_.clear();
|
||||||
|
const std::pair<int, int> key(minHeight, maxHeight);
|
||||||
|
auto it = range_cache_.find(key);
|
||||||
|
if (it == range_cache_.end()) {
|
||||||
|
auto loaded = loadRange(minHeight, maxHeight); // may open the DB (bumps revision_)
|
||||||
|
if (range_cache_revision_ != revision_) { range_cache_.clear(); range_cache_revision_ = revision_; }
|
||||||
|
it = range_cache_.emplace(key, std::move(loaded)).first;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<ExplorerBlockSummary> ExplorerBlockCache::searchBlocks(const std::string& query, int limit)
|
std::vector<ExplorerBlockSummary> ExplorerBlockCache::searchBlocks(const std::string& query, int limit)
|
||||||
{
|
{
|
||||||
std::vector<ExplorerBlockSummary> results;
|
std::vector<ExplorerBlockSummary> results;
|
||||||
@@ -256,6 +271,7 @@ bool ExplorerBlockCache::storeBlockRow(const ExplorerBlockSummary& block)
|
|||||||
bool ExplorerBlockCache::storeBlock(const ExplorerBlockSummary& block)
|
bool ExplorerBlockCache::storeBlock(const ExplorerBlockSummary& block)
|
||||||
{
|
{
|
||||||
if (block.height < 1 || block.hash.empty()) return false;
|
if (block.height < 1 || block.hash.empty()) return false;
|
||||||
|
++revision_;
|
||||||
const bool stored = storeBlockRow(block);
|
const bool stored = storeBlockRow(block);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
spill_.erase(block.height);
|
spill_.erase(block.height);
|
||||||
@@ -297,6 +313,7 @@ int ExplorerBlockCache::cachedBlockCount()
|
|||||||
|
|
||||||
void ExplorerBlockCache::clearBlocks()
|
void ExplorerBlockCache::clearBlocks()
|
||||||
{
|
{
|
||||||
|
++revision_;
|
||||||
spill_.clear();
|
spill_.clear();
|
||||||
if (!ensureOpen()) return;
|
if (!ensureOpen()) return;
|
||||||
exec("DELETE FROM explorer_blocks");
|
exec("DELETE FROM explorer_blocks");
|
||||||
@@ -305,6 +322,7 @@ void ExplorerBlockCache::clearBlocks()
|
|||||||
void ExplorerBlockCache::pruneTo(int maxRows)
|
void ExplorerBlockCache::pruneTo(int maxRows)
|
||||||
{
|
{
|
||||||
if (maxRows < 1 || !db_) return;
|
if (maxRows < 1 || !db_) return;
|
||||||
|
++revision_;
|
||||||
// Keep the maxRows highest blocks. The OFFSET row is the (maxRows+1)-th highest; when the table is smaller the
|
// Keep the maxRows highest blocks. The OFFSET row is the (maxRows+1)-th highest; when the table is smaller the
|
||||||
// subquery is NULL and nothing is deleted. Freed pages are reused, so the file stops growing at its high-water
|
// subquery is NULL and nothing is deleted. Freed pages are reused, so the file stops growing at its high-water
|
||||||
// mark (no VACUUM / auto_vacuum: changing auto_vacuum needs a rebuild of existing databases).
|
// mark (no VACUUM / auto_vacuum: changing auto_vacuum needs a rebuild of existing databases).
|
||||||
@@ -333,8 +351,10 @@ ExplorerBlockCache::SavedTipValidation ExplorerBlockCache::prepareValidation(
|
|||||||
// No DB to validate against: keep the spill honest ourselves (tip regressed, or a different block at the same height).
|
// No DB to validate against: keep the spill honest ourselves (tip regressed, or a different block at the same height).
|
||||||
if (!spill_.empty() && !currentBestHash.empty() &&
|
if (!spill_.empty() && !currentBestHash.empty() &&
|
||||||
(currentHeight < spill_tip_height_ ||
|
(currentHeight < spill_tip_height_ ||
|
||||||
(currentHeight == spill_tip_height_ && currentBestHash != spill_tip_hash_)))
|
(currentHeight == spill_tip_height_ && currentBestHash != spill_tip_hash_))) {
|
||||||
spill_.clear();
|
spill_.clear();
|
||||||
|
++revision_;
|
||||||
|
}
|
||||||
if (!currentBestHash.empty()) { spill_tip_height_ = currentHeight; spill_tip_hash_ = currentBestHash; }
|
if (!currentBestHash.empty()) { spill_tip_height_ = currentHeight; spill_tip_hash_ = currentBestHash; }
|
||||||
return validation;
|
return validation;
|
||||||
}
|
}
|
||||||
@@ -526,6 +546,7 @@ void ExplorerBlockCache::close()
|
|||||||
{
|
{
|
||||||
tip_known_ = false;
|
tip_known_ = false;
|
||||||
if (!db_) return;
|
if (!db_) return;
|
||||||
|
++revision_;
|
||||||
sqlite3_close(db_);
|
sqlite3_close(db_);
|
||||||
db_ = nullptr;
|
db_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
struct sqlite3;
|
struct sqlite3;
|
||||||
@@ -44,6 +45,11 @@ public:
|
|||||||
const std::string& databasePath() const { return database_path_; }
|
const std::string& databasePath() const { return database_path_; }
|
||||||
|
|
||||||
std::map<int, ExplorerBlockSummary> loadRange(int minHeight, int maxHeight);
|
std::map<int, ExplorerBlockSummary> loadRange(int minHeight, int maxHeight);
|
||||||
|
// loadRange memoized per (min,max) and invalidated by revision() so the per-frame Explorer list only hits SQLite
|
||||||
|
// after the cache actually changed (store / clear / prune / spill reset / open / close). The reference is valid
|
||||||
|
// until the next call; copy it if you call again before using it.
|
||||||
|
const std::map<int, ExplorerBlockSummary>& loadRangeCached(int minHeight, int maxHeight);
|
||||||
|
std::uint64_t revision() const { return revision_; }
|
||||||
// Fuzzy search over cached blocks: matches when the query is a substring of the height (as text)
|
// Fuzzy search over cached blocks: matches when the query is a substring of the height (as text)
|
||||||
// or the block hash (case-insensitive). Returns newest-first, capped at `limit`.
|
// or the block hash (case-insensitive). Returns newest-first, capped at `limit`.
|
||||||
std::vector<ExplorerBlockSummary> searchBlocks(const std::string& query, int limit);
|
std::vector<ExplorerBlockSummary> searchBlocks(const std::string& query, int limit);
|
||||||
@@ -102,6 +108,11 @@ private:
|
|||||||
int open_attempts_ = 0;
|
int open_attempts_ = 0;
|
||||||
int stores_since_prune_ = 0;
|
int stores_since_prune_ = 0;
|
||||||
|
|
||||||
|
// Bumped by every mutation that can change what loadRange returns; keys the loadRangeCached memo.
|
||||||
|
std::uint64_t revision_ = 1;
|
||||||
|
std::uint64_t range_cache_revision_ = 0;
|
||||||
|
std::map<std::pair<int, int>, std::map<int, ExplorerBlockSummary>> range_cache_;
|
||||||
|
|
||||||
// Blocks the DB refused, bounded and UI-thread only. Evicts the entry farthest from the last spilled height
|
// Blocks the DB refused, bounded and UI-thread only. Evicts the entry farthest from the last spilled height
|
||||||
// (the Explorer fetches around the visible window). Cleared with clearBlocks() and when the chain tip regresses.
|
// (the Explorer fetches around the visible window). Cleared with clearBlocks() and when the chain tip regresses.
|
||||||
static constexpr std::size_t kMaxSpillBlocks = 512;
|
static constexpr std::size_t kMaxSpillBlocks = 512;
|
||||||
|
|||||||
Reference in New Issue
Block a user