From db88fbd60277264e7e66b619853da6ce8a62f779 Mon Sep 17 00:00:00 2001 From: Andrei Pechkurov Date: Fri, 5 Jun 2026 14:14:35 +0300 Subject: [PATCH 1/3] Document per-query and per-principal memory limits --- documentation/configuration/cairo-engine.md | 72 ++++++++++++++++++ .../configuration/materialized-views.md | 3 + documentation/configuration/wal.md | 3 + documentation/query/functions/meta.md | 8 ++ documentation/query/sql/acl/alter-group.md | 74 +++++++++++++++++++ .../query/sql/acl/alter-service-account.md | 28 +++++++ documentation/query/sql/acl/alter-user.md | 28 +++++++ documentation/query/sql/show.md | 39 +++++----- documentation/security/rbac.md | 61 +++++++++++++++ documentation/sidebars.js | 4 + 10 files changed, 303 insertions(+), 17 deletions(-) create mode 100644 documentation/query/sql/acl/alter-group.md diff --git a/documentation/configuration/cairo-engine.md b/documentation/configuration/cairo-engine.md index 7112f46b6..d81cae3bc 100644 --- a/documentation/configuration/cairo-engine.md +++ b/documentation/configuration/cairo-engine.md @@ -844,6 +844,78 @@ every window function execution. Prevents stack overflow errors when evaluating complex nested SQL. The value is the approximate number of nested SELECT clauses allowed. +## Memory limits + +These limits cap how much native memory a single workload may allocate, so a +runaway query, materialized view refresh, or WAL apply batch is stopped at its +source before it can drive the whole server toward out-of-memory. Each workload +has its own independent limit: a runaway in one does not draw against another's +budget, nor against the process-wide RSS limit (`ram.usage.limit.bytes` / +`ram.usage.limit.percent`). + +All three default to `0`, which means unlimited, so behavior matches a server +without limits until you opt in. Set each limit as a byte count or a size with a +`K`, `M`, or `G` suffix, for example `512M` or `2G`. The limits are reloadable: +edit `server.conf` and call +[`reload_config()`](/docs/query/functions/meta/#reload_config), and the new value +applies to every workload that starts afterward, with no restart. A workload +already running keeps the limit it started with. + +When a workload exceeds its limit, QuestDB raises an out-of-memory error at the +allocation that crossed the line and aborts that workload, while unrelated +workloads keep running. A user query fails with the error, a materialized view +refresh is invalidated, and a WAL apply suspends the affected table. The message +names the workload so you can tell it apart from a process-wide breach: + +``` +query memory limit exceeded [workload=QUERY, queryId=..., limit=..., used=..., size=..., memoryTag=...] +``` + +:::note + +Coverage is best-effort. A limit constrains the allocation sites that grow with +data volume: hash and GROUP BY tables, sorts, joins, window partition buffers, +set operations, `LATEST BY`, SAMPLE BY fill, and Parquet decode buffers. Memory +that is structurally bounded, or that lives for the life of a process or session +(page-frame buffers, JIT buffers, table readers and writers, symbol tables, +connection buffers, memory-mapped pages), is accounted only against the global +RSS limit. Treat a workload limit as a guard against common runaway patterns, +not a hard ceiling on every allocation. + +::: + +Live usage and the effective limit for each running query are exposed by the +[`query_activity`](/docs/query/functions/meta/#query_activity) view through its +`memory_used` and `memory_limit` columns. QuestDB Enterprise can additionally cap +memory per user, group, or service account, tightening these workload limits for +a principal's queries. See +[role-based access control](/docs/security/rbac/#memory-limits). + +### cairo.mat.view.refresh.memory.limit.bytes + +- **Default**: `0` +- **Reloadable**: yes + +Maximum native memory a single materialized view refresh may allocate. `0` +disables the limit. + +### cairo.query.memory.limit.bytes + +- **Default**: `0` +- **Reloadable**: yes + +Maximum native memory a single user SQL query may allocate. `0` disables the +limit. Subqueries and other nested work share the top-level query's budget +rather than each acquiring their own. + +### cairo.wal.apply.memory.limit.bytes + +- **Default**: `0` +- **Reloadable**: yes + +Maximum native memory a single WAL apply batch may allocate. `0` disables the +limit. + ## Batch operations ### cairo.create.as.select.retry.count diff --git a/documentation/configuration/materialized-views.md b/documentation/configuration/materialized-views.md index 49f73b12c..bf786894f 100644 --- a/documentation/configuration/materialized-views.md +++ b/documentation/configuration/materialized-views.md @@ -7,6 +7,9 @@ These settings control materialized view SQL support and the background refresh job. Materialized views can use dedicated worker threads or share the server's common pool. +To cap the native memory a single refresh may allocate, see +[`cairo.mat.view.refresh.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits). + ## cairo.mat.view.enabled - **Default**: `true` diff --git a/documentation/configuration/wal.md b/documentation/configuration/wal.md index 61c8d0749..4110797b1 100644 --- a/documentation/configuration/wal.md +++ b/documentation/configuration/wal.md @@ -7,6 +7,9 @@ These settings control the Write-Ahead Log (WAL) subsystem, including parallel apply threads, segment rollover, commit squashing, and cleanup of applied WAL files. +To cap the native memory a single WAL apply batch may allocate, see +[`cairo.wal.apply.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits). + ## cairo.wal.apply.parallel.sql.enabled - **Default**: `true` diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index 77ebe6ac8..6e3b14853 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -224,6 +224,14 @@ Returns metadata on running SQL queries, including columns such as: - query_start - timestamp of when query started - state_change - timestamp of latest query state change, such as a cancellation - state - state of running query, can be `active` or `cancelled` +- memory_used - native memory currently allocated by the query, in bytes. Only + the allocation sites covered by the + [per-query memory limit](/docs/configuration/cairo-engine/#memory-limits) are + counted +- memory_limit - effective native memory limit for the query, in bytes, or + `null` when the query runs unlimited. On QuestDB Enterprise this is the + smaller of the configured workload limit and the principal's + [memory limit](/docs/security/rbac/#memory-limits) - query - text of sql query **Examples:** diff --git a/documentation/query/sql/acl/alter-group.md b/documentation/query/sql/acl/alter-group.md new file mode 100644 index 000000000..49642eaf6 --- /dev/null +++ b/documentation/query/sql/acl/alter-group.md @@ -0,0 +1,74 @@ +--- +title: ALTER GROUP reference +sidebar_label: ALTER GROUP +description: + "ALTER GROUP SQL keywords reference documentation. Applies to RBAC in QuestDB + Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER GROUP` modifies group settings. + +For full documentation of the Access Control List and Role-based Access Control, +see the [RBAC operations](/docs/security/rbac) page. + +--- + +## Syntax + +```questdb-sql title="Set or clear memory limit" +ALTER GROUP groupName SET MEMORY LIMIT { size | UNLIMITED }; +``` + +```questdb-sql title="Add or remove external alias" +ALTER GROUP groupName { WITH | DROP } EXTERNAL ALIAS externalAlias; +``` + +## Description + +- `ALTER GROUP groupName SET MEMORY LIMIT size` - caps the native memory that + queries of the group's members may allocate. `size` is a byte count or a size + with a `K`, `M`, or `G` suffix, such as `512M` or `2G`. +- `ALTER GROUP groupName SET MEMORY LIMIT UNLIMITED` - removes the limit. `SET + MEMORY LIMIT 0` does the same. +- `ALTER GROUP groupName WITH EXTERNAL ALIAS externalAlias` - maps an external + OIDC or LDAP group to this group. +- `ALTER GROUP groupName DROP EXTERNAL ALIAS externalAlias` - removes an external + group mapping. + +A member's queries run under the smallest positive memory limit among the user's +own limit and the limits of every group they belong to, capped further at the +configured +[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) +workload limit. Setting a group limit requires the `SET MEMORY LIMIT` permission. +See [memory limits](/docs/security/rbac/#memory-limits) for how per-principal and +workload limits combine. + +For external group mapping with OIDC or LDAP, see the +[OpenID Connect (OIDC) integration](/docs/security/oidc/) guide. + +## Examples + +### Set memory limit + +```questdb-sql +-- cap queries of the group's members at 2 GiB of native memory +ALTER GROUP analysts SET MEMORY LIMIT 2G; +-- remove the limit +ALTER GROUP analysts SET MEMORY LIMIT UNLIMITED; +``` + +The configured value can be verified with `SHOW GROUPS`, which reports it in the +`memory_limit` column. + +### Map an external group + +```questdb-sql +ALTER GROUP analysts WITH EXTERNAL ALIAS 'CN=Analysts,OU=Users,DC=example,DC=com'; +ALTER GROUP analysts DROP EXTERNAL ALIAS 'CN=Analysts,OU=Users,DC=example,DC=com'; +``` diff --git a/documentation/query/sql/acl/alter-service-account.md b/documentation/query/sql/acl/alter-service-account.md index a3de16f56..aa643a1d4 100644 --- a/documentation/query/sql/acl/alter-service-account.md +++ b/documentation/query/sql/acl/alter-service-account.md @@ -39,6 +39,10 @@ ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE { JWK | REST [token] }; ``` +```questdb-sql title="Set or clear memory limit" +ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT { size | UNLIMITED }; +``` + ## Description - `ALTER SERVICE ACCOUNT serviceAccountName ENABLE` - enables service account. @@ -56,6 +60,18 @@ ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE adds REST token to the service account. - `ALTER USER serviceAccountName DROP TOKEN TYPE REST token` - removes REST token from the service account. +- `ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT size` - caps the + native memory the service account's queries may allocate. `size` is a byte + count or a size with a `K`, `M`, or `G` suffix, such as `512M` or `2G`. +- `ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT UNLIMITED` - removes + the limit. `SET MEMORY LIMIT 0` does the same. + +A user who assumes the service account runs under its memory limit. The +effective cap is the smaller of this value and the configured +[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) +workload limit. Setting it requires the `SET MEMORY LIMIT` permission. See +[memory limits](/docs/security/rbac/#memory-limits) for how per-principal and +workload limits combine. ## Examples @@ -165,3 +181,15 @@ SHOW SERVICE ACCOUNT client_app; | Password | true | | JWK Token | false | | REST Token | false | + +### Set memory limit + +```questdb-sql +-- cap the service account's queries at 1 GiB of native memory +ALTER SERVICE ACCOUNT ingest SET MEMORY LIMIT 1G; +-- remove the limit +ALTER SERVICE ACCOUNT ingest SET MEMORY LIMIT UNLIMITED; +``` + +The configured value can be verified with `SHOW SERVICE ACCOUNTS`, which reports +it in the `memory_limit` column. diff --git a/documentation/query/sql/acl/alter-user.md b/documentation/query/sql/acl/alter-user.md index b0823db42..d5004d220 100644 --- a/documentation/query/sql/acl/alter-user.md +++ b/documentation/query/sql/acl/alter-user.md @@ -37,6 +37,10 @@ ALTER USER userName DROP TOKEN TYPE { JWK | REST [token] }; ``` +```questdb-sql title="Set or clear memory limit" +ALTER USER userName SET MEMORY LIMIT { size | UNLIMITED }; +``` + ## Description - `ALTER USER username ENABLE` - enables user account. @@ -54,6 +58,18 @@ ALTER USER userName DROP TOKEN TYPE REST token to user account. - `ALTER USER username DROP TOKEN TYPE REST token` - removes REST token from user account. +- `ALTER USER username SET MEMORY LIMIT size` - caps the native memory the + user's queries may allocate. `size` is a byte count or a size with a `K`, `M`, + or `G` suffix, such as `512M` or `2G`. +- `ALTER USER username SET MEMORY LIMIT UNLIMITED` - removes the limit. `SET + MEMORY LIMIT 0` does the same. + +The limit applies to the user's queries on both the primary and replicas. The +effective cap is the smaller of this value and the configured +[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) +workload limit. Setting it requires the `SET MEMORY LIMIT` permission. See +[memory limits](/docs/security/rbac/#memory-limits) for how per-principal and +workload limits combine. ## Examples @@ -160,3 +176,15 @@ SHOW USER john; | Password | true | | JWK Token | false | | REST Token | false | + +### Set memory limit + +```questdb-sql +-- cap the user's queries at 512 MiB of native memory +ALTER USER john SET MEMORY LIMIT 512M; +-- remove the limit +ALTER USER john SET MEMORY LIMIT UNLIMITED; +``` + +The configured value can be verified with `SHOW USERS`, which reports the +effective limit in the `memory_limit` column. diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index 241caecc5..214f70f96 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -284,10 +284,15 @@ SHOW USER john; SHOW USERS; ``` -| name | -| ----- | -| admin | -| john | +| name | enabled | memory_limit | +| ----- | ------- | ------------ | +| admin | true | null | +| john | true | 536870912 | + +The `memory_limit` column is reported in bytes (`536870912` is 512 MiB) and is +`null` when no limit applies. For a user it is the effective limit after merging +the limits of the user's groups. See +[memory limits](/docs/security/rbac/#memory-limits). ### SHOW GROUPS @@ -301,9 +306,9 @@ or SHOW GROUPS john; ``` -| name | -| ---------- | -| management | +| name | external_alias | memory_limit | +| ---------- | -------------- | ------------ | +| management | null | null | ### SHOW SERVICE ACCOUNT @@ -329,26 +334,26 @@ SHOW SERVICE ACCOUNT ilp_ingestion; SHOW SERVICE ACCOUNTS; ``` -| name | -| ---------- | -| management | -| svc1_admin | +| name | enabled | memory_limit | +| ---------- | ------- | ------------ | +| management | true | null | +| svc1_admin | true | null | ```questdb-sql SHOW SERVICE ACCOUNTS john; ``` -| name | -| ---------- | -| svc1_admin | +| name | enabled | memory_limit | +| ---------- | ------- | ------------ | +| svc1_admin | true | null | ```questdb-sql SHOW SERVICE ACCOUNTS admin_group; ``` -| name | -| ---------- | -| svc1_admin | +| name | enabled | memory_limit | +| ---------- | ------- | ------------ | +| svc1_admin | true | null | ### SHOW PERMISSIONS FOR CURRENT USER diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index b82e3eff7..65c74064c 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -544,6 +544,65 @@ information without these permissions. ::: +## Memory limits {#memory-limits} + +QuestDB Enterprise can cap the native memory a single principal's queries may +allocate, tightening the server-wide query memory limit for a specific user, +group, or service account. Use it to stop one tenant's runaway query from +exhausting memory shared with everyone else. + +Set a limit with [`ALTER USER`](/docs/query/sql/acl/alter-user/), +[`ALTER GROUP`](/docs/query/sql/acl/alter-group/), or +[`ALTER SERVICE ACCOUNT`](/docs/query/sql/acl/alter-service-account/): + +```questdb-sql +ALTER USER johndoe SET MEMORY LIMIT 512M; +ALTER GROUP analysts SET MEMORY LIMIT 2G; +ALTER SERVICE ACCOUNT ingest SET MEMORY LIMIT 1G; +ALTER USER johndoe SET MEMORY LIMIT UNLIMITED; -- clear the override +``` + +The value is a byte count or a size with a `K`, `M`, or `G` suffix. Setting a +limit requires the `SET MEMORY LIMIT` permission, which is included in +`GRANT ALL` and held implicitly by database admins. + +### How limits combine + +A query runs under the smallest positive limit that applies to it: + +- the principal's own limit, +- the limits of every group the principal belongs to, and +- the server-wide + [`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) + workload limit. + +A value of `0` (or `UNLIMITED`) means no limit from that source, so a +per-principal limit only ever tightens the effective cap, never loosens it. A +user who assumes a service account takes on the service account's limit. The cap +applies to the principal's queries on both the primary and replicas. + +Materialized view refresh and WAL apply run under internal contexts rather than a +principal, so they stay bounded only by their own +[workload limits](/docs/configuration/cairo-engine/#memory-limits) and never pick +up a per-principal limit. + +:::note + +A limit bounds a single query. Two concurrent queries by the same principal each +run under the full limit, so the principal's aggregate usage can exceed it. The +cap guards against one runaway workload, not total concurrent usage. + +::: + +### Inspecting limits + +- `SHOW USERS`, `SHOW GROUPS`, and `SHOW SERVICE ACCOUNTS` report the configured + limit in a `memory_limit` column. For a user this is the effective limit after + merging in the user's groups, and is `null` when no limit applies. +- [`query_activity`](/docs/query/functions/meta/#query_activity) exposes the + effective limit and live usage of each running query through its `memory_limit` + and `memory_used` columns. + ## Permissions reference {#permissions} Use `all_permissions()` to see all available permissions: @@ -620,6 +679,7 @@ SELECT * FROM all_permissions(); | REMOVE EXTERNAL ALIAS | Remove external group mappings | | REMOVE PASSWORD | Remove passwords | | REMOVE USER | Remove users from groups | +| SET MEMORY LIMIT | Set memory limits on users, groups, and service accounts | | USER DETAILS | View user/group/service account details | ### Special permissions @@ -634,6 +694,7 @@ SELECT * FROM all_permissions(); ## SQL commands reference - [ADD USER](/docs/query/sql/acl/add-user/) +- [ALTER GROUP](/docs/query/sql/acl/alter-group/) - [ALTER USER](/docs/query/sql/acl/alter-user/) - [ALTER SERVICE ACCOUNT](/docs/query/sql/acl/alter-service-account/) - [ASSUME SERVICE ACCOUNT](/docs/query/sql/acl/assume-service-account/) diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 995512908..5f8f1cf18 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -257,6 +257,10 @@ module.exports = { type: "category", label: "ALTER", items: [ + { + id: "query/sql/acl/alter-group", + type: "doc", + }, { id: "query/sql/acl/alter-service-account", type: "doc", From c10d9c1a116367be4159bb75e7041a3b2ea494c3 Mon Sep 17 00:00:00 2001 From: Andrei Pechkurov Date: Fri, 5 Jun 2026 16:39:16 +0300 Subject: [PATCH 2/3] Correct SHOW entity-variant columns and polish memory-limit docs - show.md: SHOW SERVICE ACCOUNTS returns name, grant_option and SHOW GROUPS 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) --- .../getting-started/capacity-planning.md | 4 +++ documentation/query/functions/meta.md | 11 +++---- documentation/query/sql/acl/alter-user.md | 2 ++ documentation/query/sql/show.md | 29 ++++++++++++------- documentation/security/rbac.md | 2 +- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/documentation/getting-started/capacity-planning.md b/documentation/getting-started/capacity-planning.md index 91a162c3c..856d47c77 100644 --- a/documentation/getting-started/capacity-planning.md +++ b/documentation/getting-started/capacity-planning.md @@ -175,6 +175,10 @@ For relatively small datasets i.e 4-40GB, and a read-heavy workload, performance can be improved by maximising use of the OS page cache. Users should consider increasing available RAM to improve the speed of read operations. +To bound how much native memory a single query, materialized view refresh, or +WAL apply may allocate, so one runaway workload cannot exhaust the server's RAM, +see [memory limits](/docs/configuration/cairo-engine/#memory-limits). + ### Memory page size configuration With frequent out-of-order (O3) writes over a large number of columns/tables, diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index 6e3b14853..57048af66 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -237,13 +237,14 @@ Returns metadata on running SQL queries, including columns such as: **Examples:** ```questdb-sql -SELECT * FROM query_activity(); +SELECT query_id, username, state, memory_used, memory_limit, query +FROM query_activity(); ``` -| query_id | worker_id | worker_pool | username | query_start | state_change | state | query | -| -------- | --------- | ----------- | -------- | --------------------------- | --------------------------- | ------ | --------------------------------------------------------- | -| 62179 | 5 | shared | bob | 2024-01-09T10:03:05.557397Z | 2024-01-09T10:03:05.557397 | active | select \* from query_activity() | -| 57777 | 6 | shared | bob | 2024-01-09T08:58:55.988017Z | 2024-01-09T08:58:55.988017Z | active | SELECT symbol,approx_percentile(price, 50, 2) from trades | +| query_id | username | state | memory_used | memory_limit | query | +| -------- | -------- | ------ | ----------- | ------------ | ---------------------------------------------------------- | +| 62179 | bob | active | 262144 | null | SELECT \* FROM query_activity() | +| 57777 | bob | active | 8388608 | 536870912 | SELECT symbol, approx_percentile(price, 50, 2) FROM trades | ## reader_pool diff --git a/documentation/query/sql/acl/alter-user.md b/documentation/query/sql/acl/alter-user.md index d5004d220..7e90df7d4 100644 --- a/documentation/query/sql/acl/alter-user.md +++ b/documentation/query/sql/acl/alter-user.md @@ -17,6 +17,8 @@ import { EnterpriseNote } from "@site/src/components/EnterpriseNote" For full documentation of the Access Control List and Role-based Access Control, see the [RBAC operations](/docs/security/rbac) page. +--- + ## Syntax ```questdb-sql title="Enable / disable" diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index 214f70f96..da8f2aea3 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -300,15 +300,20 @@ the limits of the user's groups. See SHOW GROUPS; ``` -or +| name | external_alias | memory_limit | +| ---------- | -------------- | ------------ | +| management | null | null | + +Filtering by a user lists the groups that user belongs to, without the +`memory_limit` column: ```questdb-sql SHOW GROUPS john; ``` -| name | external_alias | memory_limit | -| ---------- | -------------- | ------------ | -| management | null | null | +| name | external_alias | +| ---------- | -------------- | +| management | null | ### SHOW SERVICE ACCOUNT @@ -339,21 +344,25 @@ SHOW SERVICE ACCOUNTS; | management | true | null | | svc1_admin | true | null | +Filtering by a user or group instead lists the service accounts that principal +can assume, with a `grant_option` column showing whether they may grant the +assumption to others: + ```questdb-sql SHOW SERVICE ACCOUNTS john; ``` -| name | enabled | memory_limit | -| ---------- | ------- | ------------ | -| svc1_admin | true | null | +| name | grant_option | +| ---------- | ------------ | +| svc1_admin | false | ```questdb-sql SHOW SERVICE ACCOUNTS admin_group; ``` -| name | enabled | memory_limit | -| ---------- | ------- | ------------ | -| svc1_admin | true | null | +| name | grant_option | +| ---------- | ------------ | +| svc1_admin | false | ### SHOW PERMISSIONS FOR CURRENT USER diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index 65c74064c..a8c1f1235 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -695,8 +695,8 @@ SELECT * FROM all_permissions(); - [ADD USER](/docs/query/sql/acl/add-user/) - [ALTER GROUP](/docs/query/sql/acl/alter-group/) -- [ALTER USER](/docs/query/sql/acl/alter-user/) - [ALTER SERVICE ACCOUNT](/docs/query/sql/acl/alter-service-account/) +- [ALTER USER](/docs/query/sql/acl/alter-user/) - [ASSUME SERVICE ACCOUNT](/docs/query/sql/acl/assume-service-account/) - [CREATE GROUP](/docs/query/sql/acl/create-group/) - [CREATE SERVICE ACCOUNT](/docs/query/sql/acl/create-service-account/) From 1ef626de7fb4668d5eca1618f795cf9cd674ad8b Mon Sep 17 00:00:00 2001 From: Andrei Pechkurov Date: Thu, 11 Jun 2026 11:42:05 +0300 Subject: [PATCH 3/3] Make per-entity memory limits override, not min 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) --- documentation/configuration/cairo-engine.md | 6 +-- documentation/query/functions/meta.md | 6 +-- documentation/query/sql/acl/alter-group.md | 14 +++--- .../query/sql/acl/alter-service-account.md | 10 ++-- documentation/query/sql/acl/alter-user.md | 9 ++-- documentation/query/sql/show.md | 4 +- documentation/security/rbac.md | 48 +++++++++++-------- 7 files changed, 56 insertions(+), 41 deletions(-) diff --git a/documentation/configuration/cairo-engine.md b/documentation/configuration/cairo-engine.md index d81cae3bc..ce4d48b9e 100644 --- a/documentation/configuration/cairo-engine.md +++ b/documentation/configuration/cairo-engine.md @@ -886,9 +886,9 @@ not a hard ceiling on every allocation. Live usage and the effective limit for each running query are exposed by the [`query_activity`](/docs/query/functions/meta/#query_activity) view through its -`memory_used` and `memory_limit` columns. QuestDB Enterprise can additionally cap -memory per user, group, or service account, tightening these workload limits for -a principal's queries. See +`memory_used` and `memory_limit` columns. QuestDB Enterprise can additionally set +a memory limit per user, group, or service account, which overrides this workload +limit for a principal's queries. See [role-based access control](/docs/security/rbac/#memory-limits). ### cairo.mat.view.refresh.memory.limit.bytes diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index 57048af66..1cf963a97 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -229,9 +229,9 @@ Returns metadata on running SQL queries, including columns such as: [per-query memory limit](/docs/configuration/cairo-engine/#memory-limits) are counted - memory_limit - effective native memory limit for the query, in bytes, or - `null` when the query runs unlimited. On QuestDB Enterprise this is the - smaller of the configured workload limit and the principal's - [memory limit](/docs/security/rbac/#memory-limits) + `null` when the query runs unlimited. On QuestDB Enterprise a set principal + [memory limit](/docs/security/rbac/#memory-limits) overrides the configured + workload limit; the workload limit applies only when the principal has none - query - text of sql query **Examples:** diff --git a/documentation/query/sql/acl/alter-group.md b/documentation/query/sql/acl/alter-group.md index 49642eaf6..9e51d7c30 100644 --- a/documentation/query/sql/acl/alter-group.md +++ b/documentation/query/sql/acl/alter-group.md @@ -41,13 +41,15 @@ ALTER GROUP groupName { WITH | DROP } EXTERNAL ALIAS externalAlias; - `ALTER GROUP groupName DROP EXTERNAL ALIAS externalAlias` - removes an external group mapping. -A member's queries run under the smallest positive memory limit among the user's -own limit and the limits of every group they belong to, capped further at the -configured +A group limit applies to a member only when that member has no limit of its own; +a user's own limit always takes priority. When several of a user's groups set a +limit, the most restrictive (smallest positive) one applies. A set limit +overrides the configured [`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) -workload limit. Setting a group limit requires the `SET MEMORY LIMIT` permission. -See [memory limits](/docs/security/rbac/#memory-limits) for how per-principal and -workload limits combine. +workload limit and binds even when larger. Setting a group limit requires the +`SET MEMORY LIMIT` permission. See +[memory limits](/docs/security/rbac/#memory-limits) for how per-principal and +workload limits resolve. For external group mapping with OIDC or LDAP, see the [OpenID Connect (OIDC) integration](/docs/security/oidc/) guide. diff --git a/documentation/query/sql/acl/alter-service-account.md b/documentation/query/sql/acl/alter-service-account.md index aa643a1d4..33c19a39a 100644 --- a/documentation/query/sql/acl/alter-service-account.md +++ b/documentation/query/sql/acl/alter-service-account.md @@ -66,12 +66,14 @@ ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT { size | UNLIMITED }; - `ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT UNLIMITED` - removes the limit. `SET MEMORY LIMIT 0` does the same. -A user who assumes the service account runs under its memory limit. The -effective cap is the smaller of this value and the configured +A user who assumes the service account runs under its memory limit. A set limit +overrides the configured [`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) -workload limit. Setting it requires the `SET MEMORY LIMIT` permission. See +workload limit — binding even when larger — and the workload limit applies only +when the service account has none. Group limits are never merged into a service +account. Setting it requires the `SET MEMORY LIMIT` permission. See [memory limits](/docs/security/rbac/#memory-limits) for how per-principal and -workload limits combine. +workload limits resolve. ## Examples diff --git a/documentation/query/sql/acl/alter-user.md b/documentation/query/sql/acl/alter-user.md index 7e90df7d4..73adab4be 100644 --- a/documentation/query/sql/acl/alter-user.md +++ b/documentation/query/sql/acl/alter-user.md @@ -66,12 +66,13 @@ ALTER USER userName SET MEMORY LIMIT { size | UNLIMITED }; - `ALTER USER username SET MEMORY LIMIT UNLIMITED` - removes the limit. `SET MEMORY LIMIT 0` does the same. -The limit applies to the user's queries on both the primary and replicas. The -effective cap is the smaller of this value and the configured +The limit applies to the user's queries on both the primary and replicas. A set +limit takes priority over the user's groups and overrides the configured [`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) -workload limit. Setting it requires the `SET MEMORY LIMIT` permission. See +workload limit, binding even when larger. Setting it requires the +`SET MEMORY LIMIT` permission. See [memory limits](/docs/security/rbac/#memory-limits) for how per-principal and -workload limits combine. +workload limits resolve. ## Examples diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index da8f2aea3..1a91f4bff 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -290,8 +290,8 @@ SHOW USERS; | john | true | 536870912 | The `memory_limit` column is reported in bytes (`536870912` is 512 MiB) and is -`null` when no limit applies. For a user it is the effective limit after merging -the limits of the user's groups. See +`null` when no limit applies. For a user it is the effective limit — its own +limit, or, when it has none, the most restrictive of its groups'. See [memory limits](/docs/security/rbac/#memory-limits). ### SHOW GROUPS diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index a8c1f1235..3b472bf5d 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -546,10 +546,11 @@ information without these permissions. ## Memory limits {#memory-limits} -QuestDB Enterprise can cap the native memory a single principal's queries may -allocate, tightening the server-wide query memory limit for a specific user, +QuestDB Enterprise can set the native memory a single principal's queries may +allocate, overriding the server-wide query memory limit for a specific user, group, or service account. Use it to stop one tenant's runaway query from -exhausting memory shared with everyone else. +exhausting memory shared with everyone else, or to grant a trusted principal more +headroom than the default. Set a limit with [`ALTER USER`](/docs/query/sql/acl/alter-user/), [`ALTER GROUP`](/docs/query/sql/acl/alter-group/), or @@ -566,20 +567,28 @@ The value is a byte count or a size with a `K`, `M`, or `G` suffix. Setting a limit requires the `SET MEMORY LIMIT` permission, which is included in `GRANT ALL` and held implicitly by database admins. -### How limits combine - -A query runs under the smallest positive limit that applies to it: - -- the principal's own limit, -- the limits of every group the principal belongs to, and -- the server-wide - [`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) - workload limit. - -A value of `0` (or `UNLIMITED`) means no limit from that source, so a -per-principal limit only ever tightens the effective cap, never loosens it. A -user who assumes a service account takes on the service account's limit. The cap -applies to the principal's queries on both the primary and replicas. +### How limits resolve + +QuestDB resolves the limit for a query by strict precedence — it takes the first +level that is set, rather than the smallest across levels: + +1. **The principal's own limit.** For a user this is the user's own limit; for a + query that assumes a service account it is the service account's limit. +2. **A group limit** (users only). When a user has no own limit, it inherits the + most restrictive (smallest positive) limit among the groups it belongs to. + Service accounts never inherit group limits. +3. **The workload limit.** Otherwise the server-wide + [`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#memory-limits) + applies. + +A value of `0` (or `UNLIMITED`) means "not set" at that level, so resolution +falls through to the next one. A more specific level, when set, fully overrides +the broader one and binds even when it is larger — so a per-user or per-group +override can raise a principal's ceiling above the workload limit, not only lower +it. Use a smaller override to tighten the cap for a noisy tenant, or a larger one +to lift it for a trusted principal. A user who assumes a service account takes on +the service account's limit. The cap applies to the principal's queries on both +the primary and replicas. Materialized view refresh and WAL apply run under internal contexts rather than a principal, so they stay bounded only by their own @@ -597,8 +606,9 @@ cap guards against one runaway workload, not total concurrent usage. ### Inspecting limits - `SHOW USERS`, `SHOW GROUPS`, and `SHOW SERVICE ACCOUNTS` report the configured - limit in a `memory_limit` column. For a user this is the effective limit after - merging in the user's groups, and is `null` when no limit applies. + limit in a `memory_limit` column. For a user this is the effective limit — its + own limit, or, when it has none, the most restrictive of its groups' — and is + `null` when no limit applies. - [`query_activity`](/docs/query/functions/meta/#query_activity) exposes the effective limit and live usage of each running query through its `memory_limit` and `memory_used` columns.