Skip to main content

performance

Performance rules and known hot paths for this codebase. Use before touching impl/query_*.ml, impl/transact.ml, impl/db.ml, impl/storage.ml, impl/pull_api.ml, impl/entity.ml, or any code that loops over datoms, query rows, or index contents.

Source facts

Repository
logseq/datascript-ocaml
Last source activity
October 2, 2026 at 07:03
Detected SKILL.md language
English
Stars
5
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
performance
description
Performance rules and known hot paths for this codebase. Use before touching impl/query_*.ml, impl/transact.ml, impl/db.ml, impl/storage.ml, impl/pull_api.ml, impl/entity.ml, or any code that loops over datoms, query rows, or index contents.
# Performance Measured profile (large Logseq graphs): queries returning 30k–70k rows dominate; GC ~26%, List ops ~10%. The rules below keep new code off the quadratic paths and keep hot paths allocation-light. ## Index access discipline - Never walk a whole index (`datoms db Eavt ()`, unbounded `Avet ~a` slices) or materialize `entity`/`ent_of_id` inside loops when a bounded seek suffices. Prefer `~e`/`~a`/`~v`-constrained `datoms` queries and datom-level checks. - Whole-index work belongs only in inherently whole-db operations: `diff`, export, serialize/restore, GC (`collect_garbage`), checksum/validate. - `index_datoms_seq`/`reverse_index_datoms_seq` stream without materializing; `visible_index_datoms`/`raw_index_datoms_list` copy the full index — use the seq variants inside loops. ## List anti-patterns banned on hot paths Per-datom, per-row, per-block loops must not contain: - `xs @ [x]`, `List.append`, `List.concat` inside folds/loops — O(n²). Cons onto a reversed accumulator, or use `Hashtbl`-keyed groups / `Queue` / `Rrbvec`, then `List.rev`/`concat` once at the end. - `List.mem` / `List.assoc` / `List.find` / `List.exists` / `List.nth` inside a loop over a growing or large collection — build a `Hashtbl`/Set once, then O(1) lookups. - `List.length` recomputed per iteration — hoist it or thread a counter. - `List.filter`/`map` chains that re-traverse large data when one fold suffices. - `List.sort`/`List.sort_uniq` where a `Hashtbl` dedup or a min-fold applies (e.g. sorting only to take `List.hd`). - `List.combine`/`List.split`/`Array.to_list`/`List.of_seq`/`List.of_array` per row — keep arrays positional, hash the key→index map once. - Non-tail recursion over unbounded lists (stack depth risk at 100k+). Cheap-constant exceptions: fixed small lists like `Schema.schema_fields` (10 elements) — `List.mem` on them is fine. ## Known hot paths (audit Oct 2026) - `impl/query_where.ml` — join/scan loops; `relation.rows` is `query_result array list` with typed-hash dedup already — keep it that way. - `impl/query_api.ml:295` — `collect_find_specs` + `List.combine`/`Array.to_list` per result row; precompute a `Hashtbl attr→index` per query instead. - `impl/db.ml` — `diff` (whole-DB; pairwise scans now `(e,a)`→values Hashtbls), `eavt_datoms`, `remove_facts_from_duplicate_tables`/ `refresh_indexes_with_tx_data` (retract rebuilds now batched per tx — keep them batched), `find_active_datom_by_fact` (min-fold, keep it). - `impl/transact.ml:754` — `dedupe_facts` now Hashtbl buckets on upsert-merge; order must stay first-occurrence. - `impl/storage.ml` — `collect_garbage` (live set is a Hashtbl), `tail_datom_count` (concat-to-count). - `impl/entity.ml:48` — `raw_forward_entity_attrs` assoc/remove_assoc grouping per datom → O(D²) on wide entities. - `impl/pull_api.ml:492/513/604` — `List.mem` ancestry check per recursion level. - `impl/conn.ml:150,178` — `storage_tail @ [report.tx_data]` per tx. ## Checklist before submitting - For each `List.` call inside a loop: what is the input size at this call site, and who reaches it from a public endpoint (`transact`, `q`, `pull`, `entity`, `datoms`, `diff`, storage GC)? - Whole-db scans only in diff/export/restore/GC/checksum. - Allocations: avoid `map|>filter|>map` chains and intermediate lists on >1k-item data; fuse into a single fold. - Verify no `Seq` was forced into a `List` where streaming suffices.
View on GitHub