8000 feat(engine): query memory accounting and query_memory_limit, QRY-012 (#549 PR 1) by adityamukho · Pull Request #676 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

feat(engine): query memory accounting and query_memory_limit, QRY-012 (#549 PR 1) - #676

Merged
adityamukho merged 2 commits into
v3from
feat/549-memory-limit
Oct 21, 2026
Merged

adityamukho merged 2 commits into
v3from
feat/549-memory-limit

Conversation

@adityamukho
Copy link
Copy Markdown
Collaborator

PR 1 of #549 (streaming engine 7/9). Spec: docs/superpowers/specs/2026-10-21-query-memory-limit-design.md (#674). Plan: docs/superpowers/plans/2026-10-21-549-memory-limit.md (in this PR).

What

  • meter.rs: one Meter per query (limit, used, peak), each holder counting through a Held that charges before insert, takes back on a refused try_reserve, and releases on rewind/close/drop.
  • Holders: HashJoin build side, AntiJoin and Union hashed tables, Union's buffered branch rows, Distinct, Aggregate (groups, count-distinct/sum-distinct members, finished rows), Window (cells, values, finished rows), Relations (derived rows, stored-fact keys, entity index), Recursive temporaries, IdSpace (provisional ids, DICT cache). Every container they grow goes through try_reserve.
  • QRY-012, in two forms (limit, allocator). ERROR_REFERENCE row and section; the "reserved" note is gone.
  • OpenOptions::query_memory_limit (pub field and builder, default usize::MAX). It applies to execute, query, prepared and rule queries.
  • Stream::close drops the operator tree and keeps its counters, so the meter is 0 after a drain, an early close, an error or a dropped cursor. Hashed not/or tables stay charged across a correlated parent's close, as they stay held.
  • Oracle accepts an engine QRY-012.
  • Keyless HashJoin build side (spec §6.1): optimizer::product_cost of the outer patterns. The Nightly fuzz failure (main) #575 shape builds on each 3-row inner pattern from the second join on.

Changes from the spec (approved before implementation)

  • Relations::know (stored facts' identity keys) and sum-distinct set members are counted too.
  • PR 2's 1M-group test will use a fixed 32 MiB limit, since an integration test can't read the meter.
  • Not counted: UDF accumulator state, min/max's one value per group, and the transient columns aggregate::windows returns.

Found along the way

Tests

  • 18 new tests: meter.rs (4), engine unit tests (7: charge/release per holder, QRY-012 naming each holder, the allocator form through a test hook, cursor rows then error, rewind/close release, keyless build side), tests/memory_limit_test.rs (7, public API).
  • 1,720 tests (1,709 passing, 11 ignored). Default and test-oracle suites green locally. Clippy is clean (--all-features, wasm32 browser).
  • Codex review: one finding (the relation's entity index grew uncounted in end_round), fixed in the second commit.

Binary size

+13 KiB on the same local toolchain (origin/v3 1,281 → 1,294 KiB; this toolchain builds about 36 KiB larger than CI). Expect about 1,258 of the 1,260 KiB gate on CI.

Follow-ups

PR 2 (feat/549-acceptance: 1M-group and #575 k=13 acceptance, smoke job, measured figures), then the binding PRs. A docs-site PR follows. #549 closes by hand after PR 2.

🤖 Generated with Claude Code

https://claude.ai/code/session_019kY61Y11gcRD91Y6b7jbGT

adityamukho and others added 2 commits October 21, 2026 11:37
…#549)

Every engine holder of state counts a per-entry byte estimate on one
Meter per query (spec §3): HashJoin build sides, AntiJoin and Union
tables, Union's buffered branch rows, Distinct, Aggregate (groups,
count-distinct and sum-distinct members, finished rows), Window,
rule Relations (derived rows and stored-fact keys) and their Recursive
temporaries, and IdSpace (provisional ids and the DICT cache).
Containers grow with try_reserve. OpenOptions::query_memory_limit
(default usize::MAX) and a refused allocation both raise QRY-012;
Stream::close drops the operator tree so the meter returns to 0.

A keyless HashJoin compares its inner pattern with the product of the
outer patterns' estimates (spec §6.1), so the #575 shape builds on each
small inner pattern. The oracle accepts an engine QRY-012.

Prepared queries now get the database's limits; they ran rule queries
with the default max_derived_facts.

Plan: docs/superpowers/plans/2026-10-21-549-memory-limit.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kY61Y11gcRD91Y6b7jbGT
Codex review: end_round grew by_entity uncounted and without try_reserve.
add now indexes each row's entity at its position after the round, charging
a new entity's entry and reserving both containers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kY61Y11gcRD91Y6b7jbGT
@codecov
codecov Bot commented Oct 21, 2026
Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.51972% with 15 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/query/engine/mod.rs 87.87% 4 Missing ⚠️
src/query/engine/relation.rs 91.48% 4 Missing ⚠️
src/db.rs 82.35% 3 Missing ⚠️
src/query/engine/window.rs 93.93% 2 Missing ⚠️
src/query/datalog/optimizer.rs 85.71% 1 Missing ⚠️
src/query/engine/union.rs 97.67% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@adityamukho
adityamukho merged commit 67eedd7 into v3 Oct 21, 2026
24 of 25 checks passed
@adityamukho
adityamukho deleted the feat/549-memory-limit branch October 21, 2026 06:44
adityamukho added a commit that referenced this pull request Oct 21, 2026
…#682)

Backport of the v3 fix in #676. PreparedQuery::execute built its
DatalogExecutor without set_limits, so a prepared rule query ran with the
default max_derived_facts and max_results whatever OpenOptions set: a
lower limit did not stop it, and a higher one did not help it. prepare
now hands the database's (max_derived_facts, max_results) to the
PreparedQuery, which sets them on every execute, as every other read
path in db.rs does.


Claude-Session: https://claude.ai/code/session_019kY61Y11gcRD91Y6b7jbGT

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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

0