Skip to content

feat(hotblocks): add transaction hash index#93

Merged
mo4islona merged 1 commit into
masterfrom
feat/transaction-hash-index
Jul 15, 2026
Merged

feat(hotblocks): add transaction hash index#93
mo4islona merged 1 commit into
masterfrom
feat/transaction-hash-index

Conversation

@mo4islona

@mo4islona mo4islona commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add an independent, opt-in EVM transaction hash index and GET /datasets/{id}/hashes/{hash}/transaction
  • maintain transaction entries atomically across ingest, forks, retention, compaction, and logical dataset deletion; purge physical DROP residue in bounded batches
  • keep point lookups outside query/waiter budgets and Tokio runtime workers
  • expose hotblocks_write_duration_seconds{dataset,stage,outcome} for prepare, tables, commit, and retention, with separate nested block_hash_index and transaction_hash_index stages
  • add RocksDB Bloom filters to both hash indexes, LZ4 transaction-index compression, per-CF metrics, conformance coverage, and a server-listener startup log

The index is off by default, independent from the block hash index, and intentionally does not backfill.

Performance

Release-mode Criterion results on the development machine:

  • incremental index write cost: approximately 0.91-1.03 microseconds per transaction
  • hot-cache lookup at 1,000 entries: approximately 0.64-0.66 microseconds
  • hot-cache lookup at 100,000 entries: approximately 0.91-0.93 microseconds
  • a staged 10,000-entry optimistic transaction did not block committed reads or an independent dataset writer; uncommitted entries remained invisible

Write-stage instrumentation does not update Prometheus per index entry. Storage reads the clock once around each block/transaction index scan, aggregates timings across optimistic-transaction retries, and publishes one histogram observation per completed top-level write after the storage operation returns. The read path is not instrumented by these write metrics.

Disk footprint

A controlled release-mode A/B probe used identical random-looking 32-byte hashes, production key/value encodings, Bloom filters, and full compaction. Figures are live SST bytes for the named CF only, excluding WAL, memtables, and table data:

Entries/index Block, Snappy Block, LZ4 Transaction, Snappy Transaction, LZ4
100,000 8,487,039 (84.87 B/e) 8,486,380 (84.86 B/e) 8,903,864 (89.04 B/e) 8,904,184 (89.04 B/e)
1,000,000 72,238,847 (72.24 B/e) 71,885,934 (71.89 B/e) 75,310,111 (75.31 B/e) 75,037,601 (75.04 B/e)

At one million entries, LZ4 saves only 0.49% for the block index and 0.36% for the transaction index. Use 70–90 B per retained entry as an initial capacity range: approximately 67 GiB per billion blocks and 70 GiB per billion transactions after compaction.

Compression compatibility

Before this PR, the block-index CF used RocksDB default Snappy compression. The A/B difference is too small to justify recompression, so this PR now keeps BLOCK_HASHES explicitly on Snappy and configures only the new TRANSACTION_HASHES CF as LZ4.

No block-index codec migration, logical migration, or rebuild is required. Existing block SSTs and newly produced block SSTs remain Snappy.

Validation

  • cargo test -p sqd-storage --lib --test block_hash_index --test transaction_hash_index
  • cargo test -p sqd-storage --test database_ops --test cleanup_reclaim
  • cargo test -p sqd-storage --lib create_purges_crash_residue_before_publishing_the_dataset_label
  • cargo test -p sqd-hotblocks --test block_hash_index --test transaction_hash_index
  • cargo test -p sqd-hotblocks --bin sqd-hotblocks metrics::tests
  • cargo test -p sqd-storage --release --lib measure_hash_index_compression_disk_size -- --ignored --nocapture
  • cargo clippy -p sqd-storage -p sqd-hotblocks-harness -p sqd-hotblocks --all-targets
  • cargo +nightly fmt --all -- --check
  • git diff --check

Add independent EVM transaction hash indexing, lookup APIs, lifecycle cleanup, write-stage metrics, compression sizing, benchmarks, and regression coverage.

Co-Authored-By: Codex <codex@openai.com>
@mo4islona mo4islona force-pushed the feat/transaction-hash-index branch from 7fef7ca to ec0dd5d Compare July 15, 2026 10:50
@mo4islona mo4islona merged commit c01144a into master Jul 15, 2026
3 checks passed
dzhelezov added a commit to dzhelezov/data that referenced this pull request Jul 15, 2026
Broader-instrumentation delta rebased on top of upstream/master (tx/block
hash indexes subsquid#93, RocksDB stall metrics subsquid#90, conformance harness).

Reconciled against upstream's stall-metric collector (canonical):
- upstream RocksDbCollector (intrinsic per-CF/DB-wide property gauges) kept
  as-is; our duplicate collector + ROCKSDB_CF/DB_PROPERTIES constants and the
  redundant db.rs int-property/column_families accessors removed.
- our unique delta preserved and wired into upstream's collect_rocksdb_metrics:
  cumulative RocksDB statistics tickers/histograms (block-cache, stalls, bytes,
  compaction) via encode_rocksdb_statistics + db.rocksdb_statistics().
- slow-response detector (SLOW_RESPONSES/SlowResponseConfig), long_poll TTFB
  split, per-dataset labels retained as the additive, upstream-PR-able remainder.

cargo build + test green (metrics: parses_ticker/histogram + upstream
write_duration/per-cf collector tests all pass).
dzhelezov added a commit to dzhelezov/data that referenced this pull request Jul 15, 2026
Broader-instrumentation delta rebased on top of upstream/master (tx/block
hash indexes subsquid#93, RocksDB stall metrics subsquid#90, conformance harness).

Reconciled against upstream's stall-metric collector (canonical):
- upstream RocksDbCollector (intrinsic per-CF/DB-wide property gauges) kept
  as-is; our duplicate collector, property constants, and duplicate storage
  accessors removed (db.rs is unchanged vs upstream).
- our unique delta preserved and wired into upstream's collect_rocksdb_metrics:
  cumulative RocksDB statistics tickers/histograms (block-cache, stalls, bytes,
  compaction) via encode_rocksdb_statistics + upstream's db.get_statistics().
- slow-response detector (SLOW_RESPONSES/SlowResponseConfig), long_poll TTFB
  split, per-dataset labels, write_duration stage timing = additive remainder.

nightly rustfmt-clean, clippy correctness-clean, cargo test green (metrics:
parses_ticker/histogram + upstream write_duration/per-cf collector tests).
dzhelezov added a commit to dzhelezov/data that referenced this pull request Jul 15, 2026
…on (#1)

Broader-instrumentation delta rebased on top of upstream/master (tx/block
hash indexes subsquid#93, RocksDB stall metrics subsquid#90, conformance harness).

Reconciled against upstream's stall-metric collector (canonical):
- upstream RocksDbCollector (intrinsic per-CF/DB-wide property gauges) kept
  as-is; our duplicate collector, property constants, and duplicate storage
  accessors removed (db.rs is unchanged vs upstream).
- our unique delta preserved and wired into upstream's collect_rocksdb_metrics:
  cumulative RocksDB statistics tickers/histograms (block-cache, stalls, bytes,
  compaction) via encode_rocksdb_statistics + upstream's db.get_statistics().
- slow-response detector (SLOW_RESPONSES/SlowResponseConfig), long_poll TTFB
  split, per-dataset labels, write_duration stage timing = additive remainder.

nightly rustfmt-clean, clippy correctness-clean, cargo test green (metrics:
parses_ticker/histogram + upstream write_duration/per-cf collector tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant