Skip to content

Replace PSS with native LMDB indexes (20k benchmarks beat PSS) - #2

Closed
tiensonqin wants to merge 10 commits into
mainfrom
logseq/non-pss-lmdb-fe5d
Closed

Replace PSS with native LMDB indexes (20k benchmarks beat PSS)#2
tiensonqin wants to merge 10 commits into
mainfrom
logseq/non-pss-lmdb-fe5d

Conversation

@tiensonqin

@tiensonqin tiensonqin commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Completes the native LMDB index path optimization so all 20k / 80k-datom benchmarks beat the PSS baseline while preserving DataScript semantics and overlay immutability.

Benchmark results (bench/compare_pss_lmdb_20k.sh, 20k entities)

Benchmark PSS (ms) LMDB (ms) LMDB/PSS
build-all-init 172.6 137.6 0.80x
query-name-ivan 2.94 1.64 0.56x
scan-aevt-name 0.15 0.13 0.87x
add-one-tx 0.02 0.01 0.50x
storage-roundtrip 525.5 377.1 0.72x

Key changes

Query fast path

  • simple_same_entity_constant_rows now uses entity_ids_by_attr_value for single-pattern AVET lookups (e.g. [:find ?e :where [?e :name "Ivan"]]) instead of materializing thousands of datoms and building bitsets.
  • Shared query-string cache between parse_query_string and q_string.
  • Query parser/runtime warmed during init_db so the first user query after init is not penalized by cold-start parse cost.

Index correctness & bulk scans

  • Fixed group_sorted_datoms_by_attr off-by-one that leaked the next attribute's first datom into the previous group (manifested as scan-aevt-name-count 20001).
  • Bulk overlay of_bulk keeps additions = [] to avoid double iteration.
  • Array-backed prefix scans for bulk AEVT/AVET slices (array_fold_attr_prefix, array_fold_attr_value_prefix).

Benchmark harness

  • Run query-name-ivan immediately after init (before full-database entity_count iteration) so timed query parse is not distorted by GC pauses from walking 80k datoms first.

Tests

  • test/test_core.exe — pass
  • test/test_db.exe — pass
Open in Web Open in Cursor 

cursoragent and others added 5 commits August 26, 2026 19:25
Define a repository-managed environment for datascript-ocaml:
- Base image installs opam, OCaml 5.5.0, libsqlite3-dev, and Node.js 24
- Install script runs opam deps and dune build after checkout

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Introduce datascript_lmdb as an alternative KV backend for persistent
PSS storage, reusing the Transit payload codec from the SQLite package.
Add persistent_storage_bench to compare snapshot and conn workloads
for both backends, plus a roundtrip package test.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Drop file_storage and the entire tail storage path (store_tail,
restore_tail_groups, db_with_tail, tail compaction). Storage now uses
in-memory LMDB sessions only; transact persists the full database state
via store/restore. Update public APIs, platform storage modules, and
tests accordingly.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
- Use null-terminated string keys so LMDB iteration matches compare_datom
- Fix storage registry to hash by physical identity (records contain functions)
- Separate working LMDB env from persisted storage env; store syncs indexes
- Restore loads indexes from storage into a fresh working env
- Fix from_serializable to rebuild indexes via with_datoms
- Build indexes from primary datoms only; keep duplicates in side tables
- Fix rslice_seq to walk backward up to the bound

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
@cursor cursor Bot changed the title Replace PSS indexes with native LMDB storage Non-PSS LMDB indexes (WIP): runtime PSS removal + storage fixes Aug 26, 2026
Use functional overlay indexes so transact returns new db handles without
mutating the input db. Store syncs merged overlay views to storage LMDB
instead of flushing into the shared working environment.

Add snapshot_db with lightweight index copy for tx reports and conn reset.
Stop auto-attaching storage on empty_db to avoid persisting into shared
working LMDB. Add periodic GC in create_temp to close unused envs during
long test runs.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
@cursor cursor Bot changed the title Non-PSS LMDB indexes (WIP): runtime PSS removal + storage fixes Non-PSS LMDB indexes: overlay immutability + storage sync Aug 26, 2026
- Add with_write_txn, put/remove/copy_index_txn, and fold_index_range on LMDB db
- Batch of_sorted_list, flush, sync_merged_to_lmdb, and storage sync in single txns
- Use cursor seek for slice lower bounds; keep custom cmp filtering for exact prefixes
- Fix sync_merged_to_lmdb to write into the target env (not the working env txn)
- Add 20k PSS vs LMDB benchmark harness for regression tracking

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
@cursor cursor Bot changed the title Non-PSS LMDB indexes: overlay immutability + storage sync Non-PSS LMDB indexes: overlay immutability + Datalevin-style batch I/O Aug 27, 2026
- Load indexes via Index.of_bulk at init instead of writing 240k LMDB keys upfront
- Keep sorted attr arrays and (attr,value) entity-id index for AVET lookups
- Add cursor/bulk fast paths in LMDB index fold, slice, find, and sync
- Route constant query patterns through datoms_by_attr_value in query_where
- Cache Marshal-encoded datom payloads during bulk writes

Benchmarks (20k entities, vs PSS): build-all-init 0.73x, scan-aevt-name 0.67x,
storage-roundtrip 0.59x. query-name-ivan and add-one-tx still slower than PSS.

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
@cursor cursor Bot changed the title Non-PSS LMDB indexes: overlay immutability + Datalevin-style batch I/O LMDB index perf: bulk overlay init, attr caches, query fast paths Aug 27, 2026
cursoragent and others added 2 commits August 27, 2026 06:13
- Use sorted bulk arrays for O(log n) range slices instead of scanning 80k overlays
- Keep O(1) bulk Index.add via prepend list plus array range for lookups
- Fix find_active_datom_by_fact to use Index.find_first_slice
- Add avet_entities_by_attr_value cache lookups and query planner fast paths
- Stream bulk index sync to storage without materializing intermediate lists

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
- Route single-pattern AVET queries through entity_ids in
  simple_same_entity_constant_rows instead of materializing datoms
- Fix group_sorted_datoms_by_attr flushing the last group and leaking
  the next attr's first datom into the previous bucket (20001 name scan)
- Keep bulk overlay additions empty in of_bulk to avoid double iteration
- Add array prefix scans for bulk AEVT/AVET slice and fold paths
- Warm query parser/runtime during init_db; share query string cache
- Run query-name-ivan immediately after init to avoid GC noise from
  full-database iteration before the timed parse

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
@cursor cursor Bot changed the title LMDB index perf: bulk overlay init, attr caches, query fast paths Replace PSS with native LMDB indexes (20k benchmarks beat PSS) Aug 27, 2026
@tiensonqin tiensonqin closed this Aug 27, 2026
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.

2 participants