Document per-query and per-principal memory limits - #462
Open
puzpuzpuz wants to merge 18 commits into
Open
Conversation
This was referenced Jun 5, 2026
|
🚀 Build success! Latest successful preview: https://preview-462--questdb-documentation.netlify.app/docs/ Commit SHA: 339231a
|
- show.md: SHOW SERVICE ACCOUNTS <user|group> returns name, grant_option and SHOW GROUPS <user> returns name, external_alias; only the bare listings carry the new memory_limit column - meta.md: query_activity example projects memory_used/memory_limit - rbac.md: alphabetize ALTER entries in the SQL commands reference - alter-user.md: add the --- separator before Syntax for parity with siblings - capacity-planning.md: link the RAM section to the memory limits docs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror questdb/questdb-enterprise@fb4e050: the effective per-principal memory limit resolves by strict precedence (user's own limit -> group limit -> global workload limit) rather than the most-restrictive value. A more specific limit, when set, fully overrides the broader one and binds even when larger, so a per-entity override can raise a principal's ceiling above the workload limit, not only lower it. Service accounts never inherit group limits. Updated rbac.md, alter-user/group/service-account.md, show.md, meta.md, and cairo-engine.md accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mory limit Reviewer note on questdb-enterprise#1039: the memory-limits section named only mat-view refresh and WAL apply as running under internal (non-principal) contexts, but UPDATE applied through the WAL runs under the allow-all root context too (OperationExecutor.getRootContext, reports limit 0), so a large UPDATE is not capped by a SET MEMORY LIMIT override. Name UPDATE explicitly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the gaps Three corrections, each checked against the Enterprise implementation rather than inferred: - `UPDATE` was described as never picking up a per-principal limit. That is false for a non-WAL table: `UpdateOperation.apply()` passes the caller's own SqlExecutionContext to `UpdateOperatorImpl.executeUpdate`, which calls `queryRegistry.register(...)`, which acquires a QUERY tracker from that context's security context. So a non-WAL UPDATE IS capped, and an operator sizing one from the old text would meet an unexpected OOM abort. Only the WAL path -- the default table type -- runs under the WAL apply job's tracker. Replaced with a section that splits what a per-principal limit covers from what it does not. - `SHOW GROUPS <user>` and `SHOW SERVICE ACCOUNTS <user | group>` were shown without a `memory_limit` column, and the prose said so explicitly. They carry it: AbstractShowLinkedEntitiesCursorFactory adds it to both GROUP_METADATA and METADATA, pinned by ShowAclTest. Since these are exactly the results that break a positional client, the omission hid half the breaking change. - The example tables disagreed with each other on the same entity's limit; made them consistent. Documents four things the page did not mention at all: - `COPY ... TO` exports are capped by the issuing principal's limit, so a limit must be sized for exports and not only for interactive queries. - `SET MEMORY LIMIT` is self-escalating. It takes no entity name, so its holder can set any principal's limit including its own, and a per-entity limit overrides the workload limit rather than tightening it -- a non-admin holding it can raise its own ceiling above `cairo.query.memory.limit.bytes`. Plus the `GRANT ALL` expansion, which means an upgrade does not confer it. - The breaking change: five results gain a column on upgrade, and positional clients must be updated. - `sys.acl_entities` stores the value, readable only by the built-in admin. Also notes that `memory_limit` means the effective limit in `SHOW USERS` but the entity's own limit in `SHOW GROUPS` / `SHOW SERVICE ACCOUNTS`, and that an external user's inherited limit refreshes at next login rather than live. Verified with `yarn build`, which is configured to throw on broken links and anchors. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rise PRs Both source PRs merged after the last revision of these docs, and OSS master moved on afterwards. Corrections after re-checking every claim against questdb-enterprise main and questdb master: - There are four workload limits, not three: live views added cairo.live.view.refresh.memory.limit.bytes. Point at the live-views config page, which already documents it, and name live view refresh wherever the workloads are listed, including the RBAC "not covered by a principal limit" list. - A mat view refresh breach is now a transient, retried failure that invalidates the view only after the busy-retry budget is spent (questdb/questdb#7275); a live view refresh breach invalidates immediately. Replace the one-line breach sentence with a per-workload list. - ALTER USER ... SET MEMORY LIMIT on an external (SSO/OIDC) user is rejected; say so on rbac.md and alter-user.md. - Coverage: add covered-column decode buffers, name the vectorized GROUP BY hash table gap, and note that the WAL apply limit rarely fires in practice. - Add the upgrade note on the automatic acl_entities migration and the refusal an operator sees on a not-yet-migrated node. - query_activity memory columns are null for nested registrations. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XECBHc6Z36vw4i4KpduBzH
… note Drop the "coverage is best-effort" paragraph and its list of tracked allocation sites. The one thing an operator needs to know is that memory-mapped memory, such as the column files a query reads, does not count toward a limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XECBHc6Z36vw4i4KpduBzH
The memory-limits section describes a refresh breach as retried after cairo.mat.view.refresh.busy.retry.timeout and invalidated only after cairo.mat.view.refresh.busy.retry.limit consecutive failures, but neither key was documented. Add both to the materialized-views configuration page, note the deprecated no-op cairo.mat.view.refresh.oom.retry.timeout, and link the breach description to the new entries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XECBHc6Z36vw4i4KpduBzH
Correct three statements against the merged code: a WAL apply breach backs off and retries under memory-pressure control before the table is suspended, the WAL apply limit covers only the SQL run inside a batch and not data commits, and query_activity's memory columns are null for SQL run under a background workload's tracker rather than for subqueries. Document ram.usage.limit.bytes and ram.usage.limit.percent, list the four workload tokens of the breach message and what queryId means per workload, state when a changed per-principal limit reaches open sessions, that the statement runs on the primary only, and restore the upgrade migration note. Say that SET MEMORY LIMIT UNLIMITED clears the principal's own limit and the group or workload limit then applies. Show external_alias as empty rather than null, update the stale SHOW result tables on create-group, add-user, grant-assume-service-account, and cancel-query, refresh the SHOW description bullets and grammar, give the ALTER pages specific descriptions, remove em-dashes, and add the changelog entry. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZbDYwQEcex9qR1HPtcBqi
Keep the full limit-resolution rule on the RBAC page and trim the ALTER pages, query_activity, and SHOW to page-specific facts plus a link. State once per page that the K, M, and G suffixes multiply by 1024. Use john throughout and rename the colliding service account example to client_app. Deep-link the cairo.query.memory.limit.bytes key to its own anchor. Link the refresh vocabulary to the refresh strategies section and add the memory breach to both concept pages' invalidation lists. Document cairo.mat.view.max.refresh.retries, which bounds the in-call retries. Link the OIDC guide to the CREATE GROUP and ALTER GROUP pages. Note that COPY TO exports do not appear in query_activity and that SHOW USER does not carry the memory_limit column. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KZbDYwQEcex9qR1HPtcBqi
- Add the trailing memory_limit column to the SHOW SERVICE ACCOUNTS tables that the breaking-change update missed on the grant and revoke assume-service-account pages - Fix the WAL apply suspension error tag: wal_tables() reports `OUT OF MEMORY` in its errorTag column, not `OUT_OF_MEMORY`; document the errorTag and errorMessage columns of wal_tables() - Name the queryId source for every workload, including the materialized view refresh, which was omitted - Document CREATE GROUP ... WITH EXTERNAL ALIAS, which the new OIDC pointer promised but the page lacked, with the IF NOT EXISTS exclusion - Quote the two migration-window refusal messages and say that a persistent refusal calls for a restart, matching the server's own text - Mention memory limits in the RBAC page description - List query_activity columns in their real order, add the missing is_wal column, and make the self-listing example row match the query Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RAJqWAFJXYVbvvUXqDZCYh
Every other row in the section table is a short category label, so naming one feature in the Cairo engine row broke the pattern. Memory limits stay discoverable through the page description, the capacity planning pointer, and the changelog. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RAJqWAFJXYVbvvUXqDZCYh
- rbac: a user can read its own effective cap from query_activity; describe the two migration windows and the statements each refuses; name the breach error a capped query fails with - cairo-engine: ram.usage.limit.* counts tracked native allocations, not RSS, and a global breach lands on whichever workload allocates last - meta: wal_tables is the only source of errorTag/errorMessage; add both and memoryPressure to the example output - monitoring-alerting: link the memory limits from suspended tables, invalid materialized views, and memory pressure Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ7EBdj733Bc1VJ2eU9bTp
Correct the wal_tables() column name (bufferedTxnSize, not writerLagTxnCount) and the memoryPressure value of the OOM-suspended example row, document when an external user's inherited group limit takes effect, and add the previously undocumented cairo.write.back.off.timeout.on.mem.pressure key. Clarify the process-wide limit naming and its cgroup-aware percentage, give a concrete breach message with field semantics and the decimal COPY id, list the statements the query limit covers, state that a limit is per query rather than per principal, and add sizing and recovery guidance. Fix the per-query wording on ALTER USER and ALTER SERVICE ACCOUNT, the ALTER GROUP fallback sentence and intro, the ingest example name, the SHOW permission requirements, and several descriptions. Add inbound links from COPY, UPDATE, WAL, OIDC, RESUME WAL, migrate-to-enterprise and Ignition pages, and complete the changelog entries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rqg12uAgpQjZhtpDuKctTq
- cairo-engine: the query limit's statement list read as exhaustive but omitted COPY ... TO and other registered statements; make it "every statement that appears in query_activity, including ...". State that a breach leaves the client connection open. Add a signpost to the section from the page intro, and a navigation line on the configuration overview page. - show.md / meta.md: the memory_limit column has a different null contract in SHOW USERS (excludes the workload limit) and query_activity (includes it); cross-reference each from the other. Add the positional breaking-change note the changelog already pointed at. - rbac: give the upgrade material its own anchored "Upgrading" subsection with a short breaking-change warning; link it from the changelog and show.md. Drop "new" from the SHOW SERVICE ACCOUNTS groupName filter, which has existed since 2023. - Recovery steps on the materialized view concept, live view concept, and monitoring pages now say to raise the limit before the full refresh, which runs under the same limit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0161gm7KnBBfUrQy7o16Bt5r
- cairo-engine: the query limit coverage sentence contradicted the COPY and RBAC pages, which say exports do not appear in query_activity, and listed CREATE MATERIALIZED VIEW although its initial population runs as a refresh under cairo.mat.view.refresh.memory.limit.bytes - alter-mat-view-resume-wal: replace the stale four-column wal_tables() table with the projected form used on the table RESUME WAL page - meta: approx_percentile takes a fraction, so the query_activity example row reads 0.5 rather than 50 - rbac: the SHOW results carry memory_limit from the first start of the upgraded binary; only the sys.acl_entities column waits for the migration Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KVKCahCEYveYB9JasZCss1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Documents the new memory-limit feature across configuration, monitoring, and access control.
OSS — per-workload memory limits
configuration/cairo-engine.md: new "Memory limits" section covering the reloadable workload limits (cairo.query.memory.limit.bytes,cairo.mat.view.refresh.memory.limit.bytes,cairo.wal.apply.memory.limit.bytes, plus a pointer tocairo.live.view.refresh.memory.limit.bytes, which the live-views config page already documents), per-workload breach behavior, and a note that memory-mapped memory does not count toward a limit. Cross-links added from the WAL, materialized-views, and capacity-planning pages.query/functions/meta.md:query_activitygains thememory_usedandmemory_limitcolumns.configuration/materialized-views.md: documentscairo.mat.view.refresh.busy.retry.limitandcairo.mat.view.refresh.busy.retry.timeout, which govern what happens after a refresh breaches its memory limit and were previously undocumented. The breach description incairo-engine.mdlinks to them.Enterprise — per-principal memory limits
query/sql/acl/alter-user.md,alter-service-account.md, and a newalter-group.md(+ sidebar):SET MEMORY LIMIT { <size> | UNLIMITED }.security/rbac.md: new "Memory limits" section — how limits resolve, what a per-principal limit does and does not cover, how to inspect limits, theSET MEMORY LIMITpermission, andALTER GROUPin the command list.query/sql/show.md: thememory_limitcolumn onSHOW USERS,SHOW GROUPSandSHOW GROUPS <user>, andSHOW SERVICE ACCOUNTSandSHOW SERVICE ACCOUNTS <user | group>.Limit resolution — override, not min. Limits resolve by strict precedence: a principal's own limit → (users only) the most-restrictive limit among its groups → the global
cairo.query.memory.limit.bytesworkload limit. A0/UNLIMITEDvalue at a level means "not set" and falls through to the next. A more specific limit, when set, fully overrides the broader one and binds even when larger, so a per-user or per-group override can raise a principal's ceiling above the workload limit, not only lower it. Service accounts never inherit group limits. (Earlier drafts of these docs described a most-restrictive "min" behavior; revised per questdb/questdb-enterprise@fb4e050 "Make per-entity memory limits override, not min".)Corrections made after checking against the implementation
Two statements in earlier revisions of this PR were wrong, and both were verified against the Enterprise code rather than re-reasoned:
UPDATEwas described as never picking up a per-principal limit. False for a non-WAL table.UpdateOperation.apply()passes the caller's ownSqlExecutionContexttoUpdateOperatorImpl.executeUpdate, which callsqueryRegistry.register(...), which acquires a QUERY-workload tracker from that context's security context — so a non-WALUPDATEis capped bySET MEMORY LIMIT. An operator sizing one from the old text would have met an unexpected OOM abort. Only the WAL path (the default table type) runs under the WAL apply job's tracker. Replaced with a "What a per-principal limit covers" section that splits the two.SHOW GROUPS <user>andSHOW SERVICE ACCOUNTS <user | group>were shown without amemory_limitcolumn, with prose saying so explicitly. They carry it —AbstractShowLinkedEntitiesCursorFactoryadds it to both metadata shapes, pinned byShowAclTest. Since these are among the results that break a positional client, the omission hid half the breaking change.Example tables that disagreed with each other on the same entity's limit were also made consistent.
Behaviour the docs previously did not mention at all
COPY ... TOexports are capped by the issuing principal's limit. The export runs under that principal's own context on the query workload, so a limit must be sized for the largest single thing the principal runs, exports included — not only for its interactive queries.SET MEMORY LIMITis self-escalating. It takes no entity name, so its holder can set any principal's limit including its own; combined with override-not-min, a non-admin holding it can raise its own ceiling abovecairo.query.memory.limit.bytes. Documented as a warning, together with theGRANT ALLexpansion — a principal grantedALLbefore the upgrade does not acquire the new permission.SHOW USERS,SHOW GROUPSandSHOW GROUPS <user>,SHOW SERVICE ACCOUNTSandSHOW SERVICE ACCOUNTS <user | group>), plusSELECT *onsys.acl_entities. Positional clients must be updated; name-based clients are unaffected.sys.acl_entitiesstores the value, and is readable only by the built-in admin — a principal holdingDATABASE ADMINis still denied.memory_limitmeans different things per statement: the effective limit inSHOW USERS, the entity's own limit inSHOW GROUPS/SHOW SERVICE ACCOUNTS, since neither inherits one.Re-validation against the merged code
Both source PRs merged after the last revision of this doc PR (OSS on 2026-06-20, Enterprise on 2026-09-08), and OSS
mastermoved on afterwards. Every claim was re-checked against merged Enterprisemainand OSSmaster(questdbsubmodule pinfef82615), and these were stale:LIVE_VIEW_REFRESHas a workload with its own reloadablecairo.live.view.refresh.memory.limit.bytes. The live-views config page onmainalready documents the key, so the memory-limits section now points to it rather than duplicating it, and the intro, capacity-planning blurb, and the RBAC "not covered by a principal limit" list name live view refresh.cairo.mat.view.refresh.busy.retry.timeoutand the view is invalidated only aftercairo.mat.view.refresh.busy.retry.limitconsecutive failures (MatViewRefreshJob.isRetriableRefreshError, pinned byWorkloadMemoryTrackerTest). A live view refresh breach, by contrast, invalidates immediately (LiveViewRefreshJob). The breach paragraph is now a per-workload list.ALTER USER ... SET MEMORY LIMITon an external (SSO/OIDC) user is rejected (AccessListModel.setMemoryLimit:Cannot set memory limit for external user). The docs previously only said such users inherit from groups; they now say the direct form is refused, on bothrbac.mdandalter-user.md.UPDATEs, metadata ALTERs, and commits, so the entry now says the limit mainly isolates WAL apply from the query budget.memory_limitcolumn is added by an automatic migration (SysMig6) on the first primary boot or on promote; an ACL statement refused with a message naming thememory_limitcolumn migration means the node is not yet migrated. The window normally closes once WAL apply catches up, so retry first; a persistent refusal calls for a restart, which re-runs the migration.query_activity's two memory columns arenullfor nested registrations, which share the outer query's budget.Confirmed unchanged and still accurate: the
SET MEMORY LIMITgrammar and permission name, override-not-min precedence and0/UNLIMITEDsemantics (EntMemoryTrackerProvider.effectiveLimit), thememory_limitcolumn on all fiveSHOWresults including the filtered forms and theenabled/external_alias/grant_optionneighbours (AbstractShowEntitiesCursorFactory,AbstractShowLinkedEntitiesCursorFactory,ShowAclTest), theCOPY ... TOand non-WALUPDATEcoverage, the breach message format (Unsafe.checkPerQueryAllocLimit), reloadability of every limit (DynamicPropServerConfiguration), and the built-in admin being unlimitable.Dependencies
Documents:
Web-console syntax highlighting for the new clause: questdb/sql-parser#27
Verification
yarn build, which this repo configures to throw on broken links, broken markdown links, and broken anchors.Review follow-up
A level-2
/review-prpass (six agents, findings verified against OSSfef82615and Enterprisemain) found no wrong technical claims. Its major findings are fixed in 4be3ffa: staleSHOW SERVICE ACCOUNTStables on the grant and revoke assume-service-account pages, theOUT OF MEMORYerror tag literal and thewal_tables()errorTag/errorMessagecolumns, thequeryIdmapping for materialized view refreshes,CREATE GROUP ... WITH EXTERNAL ALIAS, the two migration-window refusal messages, the RBAC page description, and thequery_activitycolumn order with the missingis_walcolumn.