From 8c906ac51cbbc6884546c3af167c26deeb0b0d1c Mon Sep 17 00:00:00 2001 From: victor Date: Wed, 9 Sep 2026 16:34:03 +0800 Subject: [PATCH 01/11] docs: document QuestDB Enterprise resource groups Add three pages and wire them into the sidebar. The concept page covers how a query is assigned to a group, what is managed, and how strong each of the four controls actually is: admission is an exact gate, CPU weight is a share that only bites under contention, the CPU cap is a rate over a short window, and memory limits are batched per worker. It also explains why CPU control is cooperative, what happens on a replica and after an internal fault, and what the feature costs when nothing competes. The operations page covers day-to-day use: quick start, requirements, the instance settings, group and mapping statements, policy parameters, six worked scenarios, the inspection functions, the per-group metrics, the errors clients see, and troubleshooting. The scenarios start with an instance that stops answering while CPU looks idle, which is the case resource groups address most directly, since a query that reaches a cooperative checkpoint releases its worker instead of holding it to completion. The configuration page documents the seven instance settings, including that the feature turns itself off rather than refusing to start when it was left at its default and an SQL pool is in legacy mode. Complete the query_activity() column list, document current_resource_group(), resource_groups() and resource_group_mappings() in the function reference, and add the resource group series to the metrics reference. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Nua5uVhTxq3uBnUD31b1aD --- documentation/concepts/resource-groups.md | 223 +++++++ .../configuration/resource-groups.md | 127 ++++ documentation/operations/logging-metrics.md | 105 +++- documentation/operations/resource-groups.md | 563 ++++++++++++++++++ documentation/query/functions/meta.md | 271 +++++++-- documentation/sidebars.js | 15 + 6 files changed, 1210 insertions(+), 94 deletions(-) create mode 100644 documentation/concepts/resource-groups.md create mode 100644 documentation/configuration/resource-groups.md create mode 100644 documentation/operations/resource-groups.md diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md new file mode 100644 index 000000000..7a8418133 --- /dev/null +++ b/documentation/concepts/resource-groups.md @@ -0,0 +1,223 @@ +--- +title: Resource groups +sidebar_label: Resource groups +description: + Resource groups isolate query workloads inside one QuestDB instance. Learn how + a query is assigned to a group, and what admission, CPU weight, CPU caps and + memory limits actually guarantee. +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +A resource group is a named policy that limits what a set of principals may +consume while their queries run. One instance typically serves several workloads +at once: dashboards that must answer in milliseconds, an ad-hoc analyst, and a +nightly report that scans a year of data. When these workloads compete without +resource controls, the report can increase dashboard latency. + +Resource groups control four things at the query execution boundary: + +- **Admission** — how many queries a group may run at once, how many may wait, + and how long they may wait. +- **Weighted CPU** — the share of query CPU a group receives while groups + compete. +- **A CPU rate limit** — an absolute ceiling expressed as a percentage of + instance capacity. +- **Memory** — process and group budgets for tracked native query memory. + +The design is cooperative. QuestDB executes query work on shared worker pools, +and resource groups do not create one operating-system thread pool per group. A +query and all of its parallel tasks use the same resource group, while every +worker stays available to every group. + +## How a query is assigned to a group + +Assignment follows the authenticated principal, not the statement: + +1. A **direct mapping** on the user or service account wins. +2. Otherwise, for users only, QuestDB looks at the mappings of the ACL groups + the user belongs to and takes the highest `mapping_priority`. Ties go to the + most recently created mapping. +3. Otherwise the query runs in **DEFAULT**. + +Service accounts inherit nothing from ACL groups; they are either mapped +directly or they run in DEFAULT. + +The group is resolved once, when the query registers, and stays fixed for the +statement's lifetime. Changing a mapping affects statements that start after the +change, never one already running. + +`DEFAULT` always exists. By default it carries no limits of its own, so unmapped +principals run with a CPU weight of 100, no CPU cap, unlimited admission, and +the instance-wide memory limits. You can change its policy, but you cannot drop +or rename it. + +The default behaviour is: + +| Setting | Behaviour | +| ----------------------------------- | ------------------------------------------------------ | +| Feature enabled | Yes, when the SQL worker pools support Fiber execution | +| Group admission | Unlimited active and queued queries | +| Group CPU | Weight 100; no percentage cap | +| Group and process memory budgets | Unlimited unless configured | +| Existing single-query memory limits | Still apply, including principal-specific limits | +| Memory accounting without limits | Remains enabled for tracked native query memory | + +## What is managed + +Resource groups govern the statements that read data: + +- `SELECT` and `EXPLAIN` +- the source query of `CREATE TABLE ... AS SELECT` and `INSERT ... SELECT` +- query exports + +Everything else runs outside the feature and consumes no admission slot, CPU +grant or group memory budget: value `INSERT`, `UPDATE`, ordinary DDL, `COPY`, +transaction and session control, ILP ingestion, WAL apply, materialized and live +view refresh, and QuestDB's own internal SQL. + +For `CREATE TABLE ... AS SELECT` and `INSERT ... SELECT` the owner covers cursor +open, the source scan, transforms, parallel query work and the row pump. Source +evaluation and writer append are fused in that pump, so inseparable foreground +CPU may be charged conservatively to the group. Durability, the commit and any +work handed to writer or WAL queues are outside the guarantee. + +Resource groups account **tracked native query memory**. They do not represent +JVM heap, resident set size, memory-mapped table pages or long-lived engine +caches. Existing process memory protection remains the outer boundary. + +## What each control guarantees + +The four controls differ in how strong their guarantee is, which matters when +you decide what to configure. + +### Admission is a hard gate + +`max_active_queries` is an exact count. A group at its limit queues the next +query until a slot frees, up to `max_queued_queries`; beyond that the query is +rejected immediately. A queued query that waits longer than `queue_timeout` +fails. + +A slot is held only while the query is actually executing a segment. A protocol +cursor that is suspended between pages releases its slot and passes through the +gate again when the client asks for more rows, so a paging client does not hold +capacity while the application thinks. The consequence is that admission can be +refused on a later page: a client that received its first rows may still see the +queue-full or timeout error when it asks for more, and the connection stays +usable. + +### CPU weight is a share, not a reservation + +Weights only matter when groups compete. A group that is alone on the instance +uses everything it can, regardless of its weight. When two groups both have +work, the scheduler hands out CPU so that measured CPU divided by `cpu_weight` +stays balanced: weights 100 and 50 converge to a 2:1 split of query CPU. + +Weights are relative. 100 and 50 are the same as 2 and 1. A group that becomes +active starts level with the groups already running, so it neither banks the CPU +it did not use while idle nor is punished for having been busy. + +Within a group, pending execution requests are served in arrival order. A +parallel query can submit more than one request, so this does not promise equal +CPU shares between individual queries. + +### The CPU cap is a rate, not an instantaneous ceiling + +`cpu_max_percent` is enforced with a token bucket measured in CPU nanoseconds +against the instance's CPU capacity. It is an average over a short window, not a +per-instant limit: a capped group that has been idle may burst for about 100 ms +of accumulated allowance before it is pushed back to its configured rate. Usage +beyond a grant becomes debt that must be repaid before the group runs again, so +the average holds even when an individual query overruns. + +Capacity comes from `resource.groups.cpu.capacity.cores`, which detects +container CPU quota by default. On a fractional quota such as 500m, detection +preserves the fraction, so a 50% cap really means half of half a core. + +### Memory limits use batched accounting + +Accounting has three levels: query, group and process. Allocation and release +deltas accumulate locally on the executing worker and are published to the +shared counters at an adaptive threshold or an execution boundary. Exceeding a +checked limit fails the query with `query memory limit exceeded`; it does not +queue the allocation until memory becomes available. + +The single-query ceiling starts with the principal's effective query memory +limit, when set, or the instance default `cairo.query.memory.limit.bytes`. Any +group `memory_limit` and process memory budget further cap that ceiling. The +group budget also bounds the total tracked memory held by its queries; the +process budget covers tracked native query memory across groups. + +An unset group `memory_limit` adds no group ceiling. A process budget of `0` +adds no process ceiling. Existing single-query limits still apply, and memory +accounting remains enabled even when all limits are unlimited. + +The counters can temporarily omit worker-local deltas. A group can therefore +briefly overshoot its limit by a bounded amount related to the number of workers +running its queries. These budgets are not byte-exact, instantaneous ceilings. + +## Why CPU control is cooperative + +QuestDB does not preempt a running query. The scheduler grants a query a short +slice of CPU on a worker and expects it to reach a cooperative checkpoint, which +is the same circuit breaker check that makes queries cancellable. At that point +the query either renews its grant or yields the worker to another group. + +Yielding returns the worker to its dispatch loop rather than to the end of the +query. The loop interleaves other queries on that worker and, within a bounded +window, hands it back to network I/O so new connections are accepted. A long +single-threaded query that reaches these checkpoints can therefore share its +worker before finishing. This improves responsiveness while heavy queries run, +even before any custom group policy is written. Resource groups add this CPU +time slicing to the existing cancellation and I/O suspension mechanisms. + +Two consequences follow. + +The guarantee is statistical over a short window. Between checkpoints a query +holds its worker, so instantaneous CPU can deviate from the configured share. +Settlement charges the measured CPU either way, so a query that overran repays +it and the average is preserved. + +A query that cannot reach a checkpoint keeps its worker. When managed CPU +accounting is engaged, its CPU is charged when the grant settles, but no +cooperative limit can shorten that stretch. + +## Behaviour under failure and on replicas + +Resource groups are stored in a replicated system catalog, so a read-only +replica receives group definitions and mappings through normal replication. + +- A **fresh replica** that has not yet received the catalog runs queries + unmanaged, exactly as if the feature were disabled, and reports how many + queries took that path. It does not reject queries or serve them under a + policy it cannot see yet. +- If **CPU scheduling** hits an internal fault, it degrades: queries continue to + run without CPU grants, and the condition is visible in metrics until the + instance restarts. Admission and memory limits do not depend on CPU scheduling + and stay enforced. +- A **fault on one query** fences only that query's owner. Other queries and + other groups are unaffected, and the faulted segment is charged conservatively + rather than being dropped from the accounting. + +## Cost when nothing competes + +While a single uncapped group owns all running queries, dispatch takes a +lock-free path that avoids CPU sampling and weighted scheduling accounting. +Query registration, admission, cooperative checks and memory accounting still +run, so this does not imply the same cost as disabling the feature. Actual +overhead depends on the workload. Managed scheduling engages as soon as a second +group has work or a capped group is active, and disengages again when it does +not. The transition happens at the next dispatch boundary, not at a query +boundary, so a newly arriving group does not wait for a long query to finish +before its policy applies. + +## See also + +- [Configure and use resource groups](/docs/operations/resource-groups/) +- [Resource groups configuration](/docs/configuration/resource-groups/) +- [Role-based access control](/docs/security/rbac/) diff --git a/documentation/configuration/resource-groups.md b/documentation/configuration/resource-groups.md new file mode 100644 index 000000000..f87252777 --- /dev/null +++ b/documentation/configuration/resource-groups.md @@ -0,0 +1,127 @@ +--- +title: Resource groups +sidebar_label: Resource groups +description: + Configuration settings for QuestDB Enterprise resource groups, covering the + master switch, CPU capacity, memory ceiling, admission defaults and catalog + limits. +--- + +:::note + +Resource groups are [Enterprise](/enterprise/) only. + +::: + +[Resource groups](/docs/concepts/resource-groups/) isolate competing query +workloads inside one instance. These settings are instance-wide. The per-group +policy that decides admission, CPU share and memory budgets is set in SQL, not +here. See [Configure and use resource groups](/docs/operations/resource-groups/) +for those statements. + +None of these settings are reloadable: changing any of them requires a restart. + +Resource groups also require access control to be enabled (`acl.enabled=true`) +before principals can be mapped to a group, and every pool that executes SQL +must run in Fiber mode, which is the default. What happens when a pool is in +legacy mode depends on how the feature was turned on. Left at its default, it +turns itself off and logs an error naming the pool and the setting to change. +Asked for explicitly, it fails startup with the same error, because an explicit +request and a legacy pool cannot both be honoured. + +## General + +### resource.groups.enabled + +- **Default**: `true` +- **Reloadable**: no + +Master switch. When `false`, resource group admission, CPU scheduling and group +memory accounting are disabled. Group definitions and principal mappings remain +in the catalog, so turning the feature back on restores the policies that were +already there. + +Existing principal-specific and instance-default single-query memory limits +continue to apply when resource groups are disabled. + +Left unset, this resolves to `false` on an instance whose SQL pools are in +legacy mode, so upgrading such an instance does not turn the feature on and does +not stop the instance from starting. `SHOW PARAMETERS` then reports `false`, +which is the value that took effect. Set it to `true` explicitly and a legacy +pool becomes a startup error instead. + +### resource.groups.cpu.capacity.cores + +- **Default**: `auto` +- **Reloadable**: no + +The CPU capacity that `cpu_max_percent` is a percentage of, as `auto` or a +positive decimal number of cores. `auto` detects the process affinity mask and +the most restrictive cgroup quota, preserving fractional quotas such as `500m`, +so a 50% cap on half a core really means a quarter of a core. An explicit value +is capped by successful detection; if detection fails, the explicit value stands +and the failure is logged. + +When detection fails and no explicit value is set, capacity falls back to the +processor count and `questdb_resource_groups_cpu_capacity_fallback` reports `1`. + +### resource.groups.process.memory.limit.bytes + +- **Default**: `0` +- **Reloadable**: no + +Ceiling for tracked native query memory across all groups. `0` leaves the +instance without a process ceiling, which is the default. When set, it bounds +every group and every query, so no group policy can grant more than this. + +An unlimited process budget does not disable memory accounting or remove an +existing single-query limit. The group-level SQL parameter `memory_limit` uses +`RESET (memory_limit)` to clear its ceiling; it does not accept `0`. + +This is not a process RSS limit. It covers tracked query memory only, not JVM +heap, memory-mapped table pages or long-lived engine caches. + +### resource.groups.queue.timeout.millis + +- **Default**: `30000` +- **Reloadable**: no + +How long a queued query waits for an admission slot in a group that does not set +its own `queue_timeout`. A query that waits longer fails with +`Resource Group admission queue timeout`. + +## Catalog limits + +Group definitions and principal mappings live in a replicated system catalog. +These bounds limit the catalog size. Increase them if the deployment requires +more definitions or mappings. + +### resource.groups.catalog.max.snapshot.bytes + +- **Default**: `16777216` +- **Reloadable**: no + +Size ceiling for one serialized catalog snapshot. + +### resource.groups.max.user.groups + +- **Default**: `4096` +- **Reloadable**: no + +Maximum number of user-created resource groups, not counting `DEFAULT`. Dropped +groups stop counting towards this limit immediately, even while their existing +queries finish. + +### resource.groups.max.principal.links + +- **Default**: `65536` +- **Reloadable**: no + +Maximum number of principal mappings, counting users, service accounts and ACL +groups together. + +## See also + +- [Resource groups concept](/docs/concepts/resource-groups/) +- [Configure and use resource groups](/docs/operations/resource-groups/) +- [Identity and Access Management configuration](/docs/configuration/iam/) diff --git a/documentation/operations/logging-metrics.md b/documentation/operations/logging-metrics.md index 240647f3c..ce3e3bf94 100644 --- a/documentation/operations/logging-metrics.md +++ b/documentation/operations/logging-metrics.md @@ -1,10 +1,12 @@ --- title: Logging and metrics -description: Configure and understand QuestDB logging and metrics, including log levels, configuration options, and Prometheus integration. +description: + Configure and understand QuestDB logging and metrics, including log levels, + configuration options, and Prometheus integration. --- - -This page outlines logging in QuestDB. It covers how to configure logs via `log.conf` and expose metrics via Prometheus. +This page outlines logging in QuestDB. It covers how to configure logs via +`log.conf` and expose metrics via Prometheus. - [Logging](/docs/operations/logging-metrics/#logging) - [Metrics](/docs/operations/logging-metrics/#metrics) @@ -206,19 +208,20 @@ For configuration options, see the :::warning On systems with -[8 Cores and less](/docs/getting-started/capacity-planning/#cpu-cores), contention -for threads might increase the latency of health check service responses. If you -use a load balancer, and it thinks the QuestDB service is dead with nothing -apparent in the QuestDB logs, you may need to configure a dedicated thread pool -for the health check service. To do so, increase `http.min.worker.count` to `1`. +[8 Cores and less](/docs/getting-started/capacity-planning/#cpu-cores), +contention for threads might increase the latency of health check service +responses. If you use a load balancer, and it thinks the QuestDB service is dead +with nothing apparent in the QuestDB logs, you may need to configure a dedicated +thread pool for the health check service. To do so, increase +`http.min.worker.count` to `1`. ::: #### Lifecycle endpoint `GET /lifecycle` on the same port returns the startup and shutdown state of -every server component as JSON, for probes and coordinators that need more -than the `200` of the health check: +every server component as JSON, for probes and coordinators that need more than +the `200` of the health check: ```shell curl http://127.0.0.1:9003/lifecycle @@ -338,23 +341,23 @@ When [cold storage](/docs/concepts/cold-storage/) is enabled, the endpoint exposes fifteen additional metrics under the `questdb_cold_chunk_` prefix, covering the chunk cache and the range reads that serve remote partitions: -| Metric | Type | Description | -| ------ | ---- | ----------- | -| `questdb_cold_chunk_acquire_full_hit_total` | counter | Reads that found every chunk already resident | -| `questdb_cold_chunk_acquire_partial_hit_total` | counter | Reads that found some chunks and fetched the rest | -| `questdb_cold_chunk_acquire_full_miss_total` | counter | Reads where every chunk had to be fetched | -| `questdb_cold_chunk_acquire_hit_chunks_total` | counter | Chunk lookups served from the cache | -| `questdb_cold_chunk_acquire_miss_chunks_total` | counter | Chunk lookups that had to be fetched | -| `questdb_cold_chunk_download_started_total` | counter | Range requests dispatched, one per coalesced group | -| `questdb_cold_chunk_download_finished_total` | counter | Range requests that returned data | -| `questdb_cold_chunk_download_failed_total` | counter | Range requests that failed after retries | -| `questdb_cold_chunk_download_coalesced_total` | counter | Readers that attached to an in-flight download instead of starting a new one | -| `questdb_cold_chunk_release_evictions_total` | counter | Chunks evicted when their last lease was released | -| `questdb_cold_chunk_in_flight_downloads` | gauge | Range requests dispatched but not yet complete | -| `questdb_cold_chunk_pending_batches` | gauge | Batches the read coordinator is tracking | -| `questdb_cold_chunk_busy_leases` | gauge | Currently allocated leases | -| `questdb_cold_chunk_ready_chunks` | gauge | Chunks resident in the ready cache | -| `questdb_cold_chunk_pinned_bytes` | gauge | Compressed bytes resident in the ready cache | +| Metric | Type | Description | +| ---------------------------------------------- | ------- | ---------------------------------------------------------------------------- | +| `questdb_cold_chunk_acquire_full_hit_total` | counter | Reads that found every chunk already resident | +| `questdb_cold_chunk_acquire_partial_hit_total` | counter | Reads that found some chunks and fetched the rest | +| `questdb_cold_chunk_acquire_full_miss_total` | counter | Reads where every chunk had to be fetched | +| `questdb_cold_chunk_acquire_hit_chunks_total` | counter | Chunk lookups served from the cache | +| `questdb_cold_chunk_acquire_miss_chunks_total` | counter | Chunk lookups that had to be fetched | +| `questdb_cold_chunk_download_started_total` | counter | Range requests dispatched, one per coalesced group | +| `questdb_cold_chunk_download_finished_total` | counter | Range requests that returned data | +| `questdb_cold_chunk_download_failed_total` | counter | Range requests that failed after retries | +| `questdb_cold_chunk_download_coalesced_total` | counter | Readers that attached to an in-flight download instead of starting a new one | +| `questdb_cold_chunk_release_evictions_total` | counter | Chunks evicted when their last lease was released | +| `questdb_cold_chunk_in_flight_downloads` | gauge | Range requests dispatched but not yet complete | +| `questdb_cold_chunk_pending_batches` | gauge | Batches the read coordinator is tracking | +| `questdb_cold_chunk_busy_leases` | gauge | Currently allocated leases | +| `questdb_cold_chunk_ready_chunks` | gauge | Chunks resident in the ready cache | +| `questdb_cold_chunk_pinned_bytes` | gauge | Compressed bytes resident in the ready cache | Watch rates and ratios rather than raw totals. Sustained `download_failed_total`, `pending_batches` sitting at its configured cap, or @@ -369,10 +372,52 @@ _Enterprise only._ Two gauges describe the state of an in-place [role switch](/docs/high-availability/failover/): -| Metric | Type | Description | -| ------ | ---- | ----------- | +| Metric | Type | Description | +| ---------------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `questdb_replication_pending_upload_txn` | gauge | Committed transactions not yet uploaded to the object store, summed over the replicated tables. Poll it before demoting a primary: a demote that cannot bring it to zero within its timeout is not completed | -| `questdb_backup_active_at_last_demote` | gauge | `1` if a backup was still running when the node was last demoted, `0` otherwise. Cleared by the next promotion | +| `questdb_backup_active_at_last_demote` | gauge | `1` if a backup was still running when the node was last demoted, `0` otherwise. Cleared by the next promotion | + +### Resource group metrics + +_Enterprise only._ + +When [resource groups](/docs/concepts/resource-groups/) are enabled, the +endpoint exposes one series per group, labelled with `resource_group`: + +| Metric | Type | Description | +| --------------------------------------------------- | ------- | ------------------------------------------------------------ | +| `questdb_resource_group_active_queries` | gauge | Queries holding an admission slot | +| `questdb_resource_group_queued_queries` | gauge | Queries waiting for a slot | +| `questdb_resource_group_oldest_queue_wait_millis` | gauge | How long the longest waiting query has waited | +| `questdb_resource_group_memory_bytes` | gauge | Tracked query memory in use | +| `questdb_resource_group_memory_limit_bytes` | gauge | Effective group memory ceiling, `0` when the group sets none | +| `questdb_resource_group_cpu_nanos_total` | counter | CPU charged to the group | +| `questdb_resource_group_cpu_wait_nanos_total` | counter | Time the group spent waiting for CPU | +| `questdb_resource_group_cpu_max_percent` | gauge | Effective CPU cap, `-1` when uncapped | +| `questdb_resource_group_admission_rejections_total` | counter | Queries rejected because the queue was full | +| `questdb_resource_group_admission_timeouts_total` | counter | Queries that timed out while queued | + +The single uncapped group dispatch path does not sample CPU. Consequently, +`cpu_nanos_total` counts CPU measured by managed scheduling, not every query's +CPU consumption. A flat counter does not imply that the group is idle; also +check `questdb_resource_groups_cpu_managed_dispatch` and query activity. Memory +gauges show published accounting and can lag worker-local deltas. + +Instance-wide series describe the feature itself: + +| Metric | Type | Description | +| ------------------------------------------------------- | ------- | --------------------------------------------------------------------- | +| `questdb_resource_groups_enabled` | gauge | `1` when the feature is on | +| `questdb_resource_groups_catalog_current` | gauge | `1` when the group catalog is current; `0` while a replica catches up | +| `questdb_resource_groups_catalog_lag_unmanaged_queries` | counter | Queries that ran unmanaged because the catalog was not current yet | +| `questdb_resource_groups_cpu_capacity_microcores` | gauge | Capacity that `cpu_max_percent` applies to | +| `questdb_resource_groups_cpu_capacity_fallback` | gauge | `1` when capacity detection failed and the processor count was used | +| `questdb_resource_groups_cpu_managed_dispatch` | gauge | `1` while managed CPU scheduling is engaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` when CPU scheduling has degraded to unmanaged | + +A non-zero `questdb_resource_groups_cpu_scheduler_degraded` means CPU shares are +no longer enforced until the instance restarts. Admission and memory limits stay +enforced. ### Prometheus Alertmanager diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md new file mode 100644 index 000000000..3e8a07211 --- /dev/null +++ b/documentation/operations/resource-groups.md @@ -0,0 +1,563 @@ +--- +title: Resource groups +sidebar_label: Resource groups +description: + Create resource groups, map users and ACL groups to them, and tune admission, + CPU and memory limits so one workload cannot starve another. +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +This page covers day-to-day use: creating groups, mapping principals, choosing +limits, and watching the result. For what the limits actually guarantee, read +[the concept page](/docs/concepts/resource-groups/) first. + +## Quick start + +This example separates reporting from the default workload and limits its +concurrency and memory. Run it as an administrator on an instance that meets the +[requirements](#requirements). Use unused example names and replace the password +placeholders. Later examples on this page can be adapted independently. + +First create the ACL principals and allow SQL connections: + +```questdb-sql +CREATE GROUP analysts; +GRANT HTTP, PGWIRE TO analysts; +CREATE USER reporting_user WITH PASSWORD ''; +ADD USER reporting_user TO analysts; + +CREATE USER nightly_batch WITH PASSWORD ''; +GRANT HTTP, PGWIRE TO nightly_batch; +``` + +Then create the resource group and mappings: + +```questdb-sql +-- 1. Create a group. Unset parameters fall back to the instance defaults. +CREATE RESOURCE GROUP reporting WITH ( + cpu_weight = 50, + max_active_queries = 4, + max_queued_queries = 32, + queue_timeout = '15s', + memory_limit = '2G' +); + +-- 2. reporting_user inherits this mapping unless a higher-precedence one applies. +ALTER GROUP analysts SET RESOURCE GROUP reporting MAPPING PRIORITY 10; + +-- 3. Map one user directly. A direct mapping beats any ACL group mapping. +ALTER USER nightly_batch SET RESOURCE GROUP reporting; +``` + +Verify: + +```questdb-sql +SELECT name, cpu_weight, max_active_queries, active_queries, queued_queries +FROM resource_groups(); + +SELECT * FROM resource_group_mappings(); +``` + +Reconnect as `reporting_user` or `nightly_batch` and run: + +```questdb-sql +SELECT current_resource_group(); +``` + +| current_resource_group | +| ---------------------- | +| reporting | + +These grants allow connections. Grant access to the application's tables +separately, as described in [RBAC](/docs/security/rbac/). + +Everything not mapped keeps running in `DEFAULT`, which has a CPU weight of 100. +Against `reporting`'s weight of 50, that is a 2:1 split of query CPU while both +have work. + +## Requirements + +- QuestDB Enterprise. +- Access control enabled (`acl.enabled=true`). Groups can be created without it, + but mapping statements require it, since mappings attach to ACL principals. +- The pools that execute SQL must run in Fiber mode, which is the default, + because cooperative admission and CPU control cannot be made complete on the + legacy path. On an instance whose pools are in legacy mode, resource groups + left at their default turn themselves off and log an error naming the pool and + the setting to change. Setting `resource.groups.enabled=true` on such an + instance fails startup with that same error. +- Administrator rights for group management, mappings and instance-wide + inspection. Ordinary users can call `current_resource_group()` to check their + own query's group. + +## Configuration + +Resource groups are enabled by default. These are instance-wide settings; the +per-group policy is set in SQL. Each setting is described in full in the +[resource groups configuration reference](/docs/configuration/resource-groups/). + +| Property | Default | Meaning | +| -------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------- | +| `resource.groups.enabled` | `true` | Set to `false` to disable resource group enforcement. Existing single-query memory limits still apply. | +| `resource.groups.cpu.capacity.cores` | `auto` | CPU capacity that `cpu_max_percent` is a percentage of. `auto` detects container quota, including fractional quotas. | +| `resource.groups.process.memory.limit.bytes` | `0` | Ceiling for tracked query memory across all groups, `0` for none. Every group limit is capped by it. | +| `resource.groups.queue.timeout.millis` | `30000` | Default admission queue timeout for groups that do not set `queue_timeout`. | +| `resource.groups.catalog.max.snapshot.bytes` | `16777216` | Size ceiling for the serialized catalog. | +| `resource.groups.max.user.groups` | `4096` | Maximum number of user-created resource groups. | +| `resource.groups.max.principal.links` | `65536` | Maximum number of principal mappings. | + +Turning the feature off is a restart with `resource.groups.enabled=false`. +Definitions and mappings stay in the catalog, so nothing is lost and the +policies apply again when it is re-enabled. + +## Managing groups + +```questdb-sql +CREATE RESOURCE GROUP analytics; + +CREATE RESOURCE GROUP IF NOT EXISTS analytics WITH (cpu_weight = 300); + +ALTER RESOURCE GROUP analytics SET (cpu_weight = 300, cpu_max_percent = 25.5); + +-- Clear parameters so they fall back to the instance defaults again. +ALTER RESOURCE GROUP analytics RESET (memory_limit, cpu_max_percent); + +ALTER RESOURCE GROUP analytics RENAME TO reporting; + +DROP RESOURCE GROUP reporting; +DROP RESOURCE GROUP IF EXISTS reporting; +``` + +A group policy change applies online to the shared group budget. It does not +cancel existing queries at the moment `ALTER` runs: + +| Change | Effect on existing work | +| ---------------------- | ------------------------------------------------------------------------------------------------- | +| CPU weight or cap | Subsequent scheduling uses the new policy; issued CPU grants are settled normally | +| Active-query limit | Existing slots are retained; subsequent admission, including a resumed cursor, uses the new limit | +| Queue limit or timeout | New admission requests use the new settings; an already queued request keeps its deadline | +| Group memory limit | Subsequent allocations check the new budget; existing memory is released normally | + +Lowering a memory budget below current usage can make subsequent allocations +fail. The principal-specific or instance-default single-query limit is captured +when the query starts; updating the group budget does not replace that limit. +Changing a principal mapping affects new queries only. + +`DROP` is refused while any live principal is still mapped to the group; unmap +them first. Once unmapped, a group can be dropped while queries still use it. It +disappears from `resource_groups()` immediately. Running and queued queries, +including suspended cursors, continue using the deleted group's existing +settings. Their memory still counts towards the process budget. + +Recreating a group with the same name starts fresh usage counters. Queries that +still use the deleted group do not move to the new group or use its settings. +Map principals to the new group to assign their subsequent queries to it. + +`DEFAULT` cannot be dropped or renamed, but it can be altered: + +```questdb-sql +ALTER RESOURCE GROUP DEFAULT SET (max_active_queries = 16); +``` + +## Mapping principals + +```questdb-sql +ALTER USER alice SET RESOURCE GROUP analytics; +ALTER SERVICE ACCOUNT ingest_bot SET RESOURCE GROUP analytics; +ALTER GROUP analysts SET RESOURCE GROUP analytics MAPPING PRIORITY 10; + +ALTER USER alice UNSET RESOURCE GROUP; +ALTER GROUP analysts UNSET RESOURCE GROUP; +``` + +`MAPPING PRIORITY` is a non-negative integer and applies only to ACL group +mappings, because a user can belong to several ACL groups. The highest priority +wins; ties go to the most recent mapping. It defaults to 0 and is rejected on +user and service account mappings, which are one-to-one. + +Resolution order for a query is: direct mapping on the principal, then the +highest-priority mapping among the user's ACL groups, then `DEFAULT`. Service +accounts do not inherit ACL group mappings. + +## Policy parameters + +All parameters are optional. An unset parameter is not "unlimited" in every +case: it falls back to the instance default shown here. + +| Parameter | Accepted values | Unset behaviour | +| -------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------- | +| `cpu_weight` | integer, 1 to 10000 | 100 | +| `cpu_max_percent` | 0.01 to 100, at most two decimals | no cap | +| `max_active_queries` | integer, 1 or more | unlimited | +| `max_queued_queries` | integer, 0 or more | unlimited | +| `queue_timeout` | a positive whole number of milliseconds, or a duration such as `'15s'`, `'2m'` | `resource.groups.queue.timeout.millis` | +| `memory_limit` | a positive byte size, plain or suffixed such as `'8G'` | no group ceiling; other memory limits still apply | + +`memory_limit` is the budget for everything the group runs at once. Where the +instance sets `resource.groups.process.memory.limit.bytes`, the group budget is +capped by it, so a group cannot be granted more than the instance allows. A +group ceiling only lowers what its queries may use; it never raises a limit set +elsewhere. + +A group that does not set `memory_limit` carries no ceiling of its own, and +`resource_groups().memory_limit_bytes` reports `0` for it. Its queries are then +bounded by any existing single-query limit and the process limit. A principal's +effective query memory limit takes precedence over the instance default +`cairo.query.memory.limit.bytes`; group and process budgets can only lower the +resulting ceiling. Resource groups do not have a separate `query_memory_limit` +policy parameter. + +To remove a group memory ceiling, use +`ALTER RESOURCE GROUP reporting RESET (memory_limit)`. Setting the SQL parameter +to `0` is invalid; `0` means unlimited for the instance process-memory property. +Accounting continues when limits are unlimited. + +Two examples of what the values mean in practice: + +```questdb-sql +-- A share: reporting gets a third of query CPU when DEFAULT also has work, +-- and all of it when DEFAULT is idle. +CREATE RESOURCE GROUP reporting WITH (cpu_weight = 50); + +-- A ceiling: exports never average more than a quarter of instance CPU, +-- even when the instance is otherwise idle. +CREATE RESOURCE GROUP exports WITH (cpu_max_percent = 25); +``` + +Use `cpu_weight` to decide who wins under contention, and `cpu_max_percent` to +leave headroom for work that resource groups do not manage, such as ingestion +and WAL apply. Setting a cap on a group also switches the whole instance to +managed scheduling while that group has queries. + +## Common scenarios + +### The instance stops answering while CPU looks idle + +Every HTTP or PGWire worker is occupied by a long query, new requests are not +picked up, and instance CPU is low because those queries run on one core each. +Clients time out and retry, which produces more of the same queries. A plan such +as a `LATEST ON` over a non-indexed filter is a typical cause: it scans frames +on one thread, so it is slow without ever being CPU-hungry. + +Four steps. The first is a prerequisite to confirm, the second is what enabling +the feature already gives you, and the last two are policy you choose. + +**1. Confirm the SQL pools are Fiber pools.** This is the prerequisite for +everything below. A protocol runs either on its own pool, when its worker count +is above zero, or on the shared network pool. The setting that matters is the +one for the pool it actually uses: + +| Where the protocol runs | Setting to check | +| ---------------------------------------------------- | ------------------------------------- | +| Its own HTTP pool (`http.worker.count` above zero) | `http.worker.fiber.enabled` | +| Its own PGWire pool (`pg.worker.count` above zero) | `pg.worker.fiber.enabled` | +| The shared network pool (worker count zero, default) | `shared.network.worker.fiber.enabled` | + +Parallel query work is separate and follows `shared.query.worker.fiber.enabled` +whenever the shared query pool has workers. A shared query pool set to zero +workers turns parallel SQL off by default and needs no check of its own. + +The first two settings default to `true`, so a dedicated pool is a Fiber pool +unless someone turned it off. `shared.network.worker.fiber.enabled` defaults to +`true` exactly when HTTP or PGWire actually runs there, which is the case out of +the box because both worker counts default to zero. You normally have nothing to +change here; check these only when the instance was tuned by hand. + +After the restart, confirm the feature came up. `SHOW PARAMETERS` must report +`resource.groups.enabled` as `true`, and `questdb_resource_groups_enabled` must +be `1`. If a pool that executes SQL is in legacy mode and resource groups were +left at their default, the feature disables itself and logs the reason. An +explicit `resource.groups.enabled=true` fails startup in that configuration. + +**2. Enabling the feature already frees the workers.** A query yields its worker +at the checkpoints that already make it cancellable, so a long single-threaded +scan releases the worker while it is still running and the instance keeps +accepting connections. This needs no group and no policy, and it holds even when +every query resolves to `DEFAULT`. + +There is no separate switch to verify. Cooperative yielding is on exactly when +resource groups are on, which step 1 already confirmed. Fiber pools on their own +do not produce it: the checkpoints are compiled into every build, but they only +yield while resource groups are enabled. A query that never reaches a checkpoint +still holds its worker, so this does not remove every cause of an unresponsive +instance. + +**3. Separate the workloads so shares apply.** While a single uncapped group +owns every running query, dispatch stays on the unmanaged path and weights have +nothing to arbitrate. Two groups with queries in flight at the same time, or any +group with a `cpu_max_percent`, is what engages weighted scheduling: + +```questdb-sql +CREATE RESOURCE GROUP dashboards WITH (cpu_weight = 400); +CREATE RESOURCE GROUP adhoc WITH (cpu_weight = 100); + +ALTER USER app SET RESOURCE GROUP dashboards; +ALTER USER analyst SET RESOURCE GROUP adhoc; +``` + +**4. Bound concurrent requests with admission.** + +```questdb-sql +ALTER RESOURCE GROUP adhoc SET ( + max_active_queries = 4, + max_queued_queries = 8, + queue_timeout = '5s' +); +``` + +The fifth concurrent query waits instead of running, and it does not hold a +worker while it waits. The thirteenth fails immediately with +`Resource Group admission queue is full`, so a client that keeps resending gets +a clear answer in seconds instead of adding to the pile. + +Admission directly bounds the number of concurrent queries. A CPU cap bounds +their combined CPU rate and can also slow a single-threaded query: on an 8-core +instance, a 10% cap permits 0.8 cores of CPU. Choose a cap when that rate limit +is useful; it does not replace the admission limits in this scenario. Clients +should use bounded retries with backoff after admission failures. + +Afterwards the symptom is also diagnosable rather than mysterious. Low instance +CPU together with a high `queued_queries` and a rising +`oldest_queue_wait_millis` on one group says the work is being held at the +admission gate, not that the machine is busy. `query_activity()` shows which +group each running query was admitted to. + +What resource groups do not do here: they do not make the slow plan faster, and +they do not bound how long one query may run. Wall-clock limits still come from +the instance-wide +[`query.timeout`](/docs/configuration/cairo-engine/#querytimeout). + +### Dashboards must stay responsive while analysts run heavy queries + +Use weights. Shares are per group, not per query, so a group running fifty +queries does not outvote a group running one: + +```questdb-sql +CREATE RESOURCE GROUP dashboards WITH (cpu_weight = 400); +CREATE RESOURCE GROUP analysts WITH (cpu_weight = 100); +``` + +When these are the only competing groups and both can use their shares, weights +target a 4:1 split of managed query CPU. Actual use also depends on runnable +work, available parallelism and any CPU caps. When analysts are idle, dashboards +can use the available query CPU. Weights are integers from 1 to 10000 and every +group starts at 100, so a group left alone keeps an equal share against any +group you do not change. + +### A background job must never take the whole instance + +Use a cap, which applies whether or not anything else is running: + +```questdb-sql +CREATE RESOURCE GROUP exports WITH (cpu_max_percent = 20); +``` + +This also leaves headroom for work resource groups do not manage, such as +ingestion and WAL apply. The cap accepts two decimals, down to `0.01`, and is a +percentage of the +[detected CPU capacity](/docs/configuration/resource-groups/#resourcegroupscpucapacitycores), +not of the host's core count, so it stays correct under a container quota. + +### One workload must not exhaust query memory + +Bound the group rather than each query, so the limit holds however many queries +the workload starts: + +```questdb-sql +CREATE RESOURCE GROUP reporting WITH (memory_limit = '8G'); +``` + +A query that would push the group over its budget fails with +`query memory limit exceeded` and releases what it held. + +### An ingestion or automation account runs queries too + +Service accounts resolve differently from users: they honour a direct mapping, +but they never inherit a mapping from an ACL group. A service account with no +direct mapping runs in `DEFAULT` however its ACL groups are mapped, so map it +explicitly: + +```questdb-sql +CREATE RESOURCE GROUP automation WITH (cpu_weight = 50, max_active_queries = 2); + +ALTER SERVICE ACCOUNT ingest_bot SET RESOURCE GROUP automation; +``` + +This governs the queries the account runs. It does not throttle ingestion +itself, which resource groups do not manage. + +### Many teams share one instance + +Map ACL groups rather than individual users, and use `MAPPING PRIORITY` to +decide what happens to someone who belongs to more than one: + +```questdb-sql +ALTER GROUP analysts SET RESOURCE GROUP adhoc MAPPING PRIORITY 10; +ALTER GROUP oncall SET RESOURCE GROUP dashboards MAPPING PRIORITY 20; +``` + +Someone in both groups resolves to `dashboards`, because the higher priority +wins. If two mappings tie on priority, the more recently created one wins. A +direct mapping on the user beats every group mapping regardless of priority, +which is the way to make one person an exception without touching the groups: + +```questdb-sql +ALTER USER lead_analyst SET RESOURCE GROUP dashboards; +``` + +`MAPPING PRIORITY` is rejected on user and service account mappings, because +those are one-to-one and have nothing to break a tie between. Confirm any of +this from the client's own session with `SELECT current_resource_group();`. + +## Inspecting + +`resource_groups()` returns one row per group, combining the configured policy +with live counters: + +| Column | Meaning | +| ------------------------------------------------------------------ | --------------------------------------------------- | +| `name` | Group name | +| `memory_limit_bytes` | Effective group memory budget | +| `max_active_queries`, `max_queued_queries`, `queue_timeout_millis` | Effective admission policy | +| `cpu_weight`, `cpu_max_percent` | Effective CPU policy | +| `active_queries`, `queued_queries` | Live admission state | +| `oldest_queue_wait_millis` | How long the longest waiting query has waited | +| `memory_used_bytes` | Tracked query memory in use | +| `cpu_nanos_total`, `cpu_wait_nanos_total` | Cumulative CPU consumed and spent waiting for CPU | +| `admission_rejections`, `admission_timeouts` | Cumulative queue-full rejections and queue timeouts | + +`resource_group_mappings()` returns one row per mapping with `principal_type`, +`principal_name`, `principal_generation`, `resource_group_id`, `resource_group`, +`mapping_priority` and `mapping_revision`. + +`current_resource_group()` returns the calling query's group, which is the +quickest way to confirm a mapping from the client's own connection: + +```questdb-sql +SELECT current_resource_group(); +``` + +It returns `NULL` when that execution is unmanaged, including when the feature +is disabled or a replica's group catalog is not ready. See the +[function reference](/docs/query/functions/meta/#current_resource_group) for +permissions and return values, and the references for +[`resource_groups()`](/docs/query/functions/meta/#resource_groups) and +[`resource_group_mappings()`](/docs/query/functions/meta/#resource_group_mappings) +for complete schemas. + +`query_activity()` carries a `resource_group` column, so you can see which group +each running query was admitted to. It is `NULL` for executions that resource +groups do not manage: + +```questdb-sql +SELECT resource_group, username, query_start, query +FROM query_activity() +WHERE resource_group IS NOT NULL +ORDER BY query_start; +``` + +## Monitoring + +The Prometheus endpoint exposes one series per group, labelled with +`resource_group`. The full list lives in the +[metrics reference](/docs/operations/logging-metrics/#resource-group-metrics): + +``` +questdb_resource_group_active_queries{resource_group="reporting"} +questdb_resource_group_queued_queries{resource_group="reporting"} +questdb_resource_group_oldest_queue_wait_millis{resource_group="reporting"} +questdb_resource_group_memory_bytes{resource_group="reporting"} +questdb_resource_group_memory_limit_bytes{resource_group="reporting"} +questdb_resource_group_cpu_nanos_total{resource_group="reporting"} +questdb_resource_group_cpu_wait_nanos_total{resource_group="reporting"} +questdb_resource_group_cpu_max_percent{resource_group="reporting"} +questdb_resource_group_admission_rejections_total{resource_group="reporting"} +questdb_resource_group_admission_timeouts_total{resource_group="reporting"} +``` + +Instance-wide series: + +| Metric | Meaning | +| ------------------------------------------------------- | --------------------------------------------------------------------- | +| `questdb_resource_groups_enabled` | 1 when the feature is on | +| `questdb_resource_groups_catalog_current` | 1 when the catalog is current; 0 while a replica is still catching up | +| `questdb_resource_groups_catalog_lag_unmanaged_queries` | Queries that ran unmanaged because the catalog was not current yet | +| `questdb_resource_groups_cpu_capacity_microcores` | Capacity that `cpu_max_percent` applies to | +| `questdb_resource_groups_cpu_capacity_fallback` | 1 when capacity detection failed and the processor count was used | +| `questdb_resource_groups_cpu_managed_dispatch` | 1 while managed CPU scheduling is engaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | 1 when CPU scheduling has degraded to unmanaged | + +Two signals are worth alerting on: a non-zero +`questdb_resource_groups_cpu_scheduler_degraded`, which means CPU shares are no +longer enforced until the next restart, and a steadily growing +`questdb_resource_group_admission_timeouts_total`, which means a group's queue +settings are rejecting work the application expects to succeed. + +## Errors clients see + +| Message | Cause | Usual fix | +| ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| `Resource Group admission queue is full` | The group is at `max_active_queries` and its queue is at `max_queued_queries` | Raise the limits, or let the client retry | +| `Resource Group admission queue timeout` | The query waited longer than `queue_timeout` | Raise `queue_timeout` or `max_active_queries`, or reduce concurrency | +| Either admission error while fetching a later page | A suspended cursor re-enters admission when the client asks for more rows | Adjust admission limits or retry the query with backoff; the failed cursor cannot continue | +| `query memory limit exceeded` | A single-query, group or process memory limit rejected an allocation | Inspect `query_activity().memory_limit` and the group/process budgets; reduce memory use or adjust the relevant limit | +| `Resource Group is referenced by an active principal link` | `DROP RESOURCE GROUP` while principals are still mapped | `UNSET RESOURCE GROUP` on those principals first | +| `built-in Resource Group cannot be dropped` / `cannot be renamed` | `DROP` or `RENAME` on `DEFAULT` | Alter it instead | + +## Troubleshooting + +**A group's CPU share is not what I configured.** Weights only apply while +groups compete. Check `active_queries` on both groups at the same moment: if one +is idle, the other is expected to use everything. Also confirm the work you are +watching is managed at all, since ingestion, WAL apply and view refresh are +outside the feature. `query_activity()` shows the group each running query +belongs to, which is the quickest way to tell whether the load you are watching +is attributed where you expect. + +**A capped group is slower than the cap suggests.** Very small caps release CPU +in pulses. The cap is a rate over roughly a 100 ms window, so a group whose +share works out to less than one 2 ms slice per window waits between slices. For +example, 0.1% of an 8-core instance allows about 8 ms of CPU per second. Small +caps still allow progress, but the waits between slices can substantially +increase latency; there is no special 0.25% cutoff. + +**Queries on a fresh replica are not limited.** Until the catalog has +replicated, a replica runs queries unmanaged and counts them in +`questdb_resource_groups_catalog_lag_unmanaged_queries`. The counter stops +growing once `questdb_resource_groups_catalog_current` reaches 1. + +**Startup fails naming a worker pool.** A pool that executes SQL is in legacy +mode while `resource.groups.enabled=true` was set explicitly. Either restore the +default Fiber mode for that pool or stop setting the property, which lets the +instance start with resource groups off. + +**The feature is off although the default is on.** Check the log at startup for +an error naming a worker pool, and check `SHOW PARAMETERS` for the value that +took effect. A legacy SQL pool turns the feature off when the property is left +unset. + +## Limitations + +- Only query statements are managed. See + [what is managed](/docs/concepts/resource-groups/#what-is-managed). +- Memory accounting covers tracked native query memory, not JVM heap, resident + set size or memory-mapped table pages. +- CPU control is cooperative, so shares hold over a short window rather than + instantaneously, and a query that cannot reach a cooperative checkpoint holds + its worker until it does. +- Principal mapping changes affect new queries. Group budgets change online; + dropping a group retains its runtime state for existing queries until they + finish. + +## See also + +- [Resource groups concept](/docs/concepts/resource-groups/) +- [Resource groups configuration](/docs/configuration/resource-groups/) +- [Role-based access control](/docs/security/rbac/) +- [Logging and metrics](/docs/operations/logging-metrics/) diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index d2156a88f..22c7a4003 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -61,9 +61,9 @@ SELECT current_data_id(); ## current database, schema, or user -`current_database()`, `current_schema()`, `current_user()`, and -`session_user()` are standard SQL functions that return information about the -current database, schema, and user. +`current_database()`, `current_schema()`, `current_user()`, and `session_user()` +are standard SQL functions that return information about the current database, +schema, and user. ```questdb-sql -- Get the current database @@ -86,6 +86,28 @@ statement without any arguments. and are interchangeable in QuestDB. Both report the user that authenticated on the current connection, whichever protocol it arrived on. +## current_resource_group + +_QuestDB Enterprise only._ + +Returns the resource group assigned to the calling query. Ordinary users can use +this function to check their own assignment; administrator rights are not +required. See [resource groups](/docs/concepts/resource-groups/) for mapping +precedence and the scope of managed execution. + +**Arguments:** none. + +**Return value:** `STRING`. Returns `NULL` when the execution is unmanaged, +including when resource groups are disabled or a replica's catalog is not ready. +A managed query without a principal mapping returns `DEFAULT`. + +```questdb-sql +SELECT current_resource_group(); +``` + +The result follows the query's acquired group, including across suspended cursor +pages. A subsequent mapping change affects the next query. + ## flush_query_cache() `flush_query_cache' invalidates cached query execution plans. @@ -314,7 +336,6 @@ materialized_views(); | trades_OHLC_15m | immediate | trades | 2025-05-30T16:40:37.562421Z | 2025-05-30T16:40:37.568800Z | SELECT timestamp, symbol, first(price) AS open, max(price) as high, min(price) as low, last(price) AS close, sum(amount) AS volume FROM trades SAMPLE BY 15m | trades_OHLC_15m~27 | null | valid | 55141609 | 55141609 | 0 | null | null | 0 | null | | trades_latest_1d | immediate | trades | 2025-05-30T16:40:37.554274Z | 2025-05-30T16:40:37.562049Z | SELECT timestamp, symbol, side, last(price) AS price, last(amount) AS amount, last(timestamp) as latest FROM trades SAMPLE BY 1d | trades_latest_1d~28 | null | valid | 55141609 | 55141609 | 0 | null | null | 0 | null | - ## memory_metrics **Arguments:** @@ -382,10 +403,10 @@ SELECT node_role(); :::warning `node_role()` cannot be used in a materialized view or a live view. Avoid it in -`UPDATE` on a WAL table as well: the statement is re-executed on every node of -a replicated cluster and each node evaluates its own role, so the primary and -its replicas would write different values. Tagging rows on `INSERT` is safe, -because inserted rows replicate as data. +`UPDATE` on a WAL table as well: the statement is re-executed on every node of a +replicated cluster and each node evaluates its own role, so the primary and its +replicas would write different values. Tagging rows on `INSERT` is safe, because +inserted rows replicate as data. ::: @@ -410,18 +431,33 @@ Returns metadata on running SQL queries, including columns such as: - state_change - timestamp of latest query state change, such as a cancellation - state - state of running query, can be `active` or `cancelled` - query - text of sql query +- is_wal - whether the query runs as part of WAL apply +- memory_used, memory_limit - tracked native memory the query holds and its + ceiling, `NULL` when no tracker is bound; `memory_limit` is also `NULL` when + the query has no ceiling +- resource_group - the [resource group](/docs/concepts/resource-groups/) the + query was admitted to in QuestDB Enterprise, `NULL` when resource groups do + not manage the execution **Examples:** ```questdb-sql -SELECT * FROM query_activity(); +SELECT query_id, worker_id, worker_pool, username, query_start, state_change, state, 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() | +| 62179 | 5 | shared | bob | 2024-01-09T10:03:05.557397Z | 2024-01-09T10:03:05.557397Z | active | SELECT count() FROM trades | | 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 | +To inspect query memory and resource group assignment in QuestDB Enterprise: + +```questdb-sql +SELECT query_id, username, resource_group, memory_used, memory_limit +FROM query_activity(); +``` + ## reader_pool **Arguments:** @@ -470,15 +506,94 @@ Edit `server.conf` and run `reload_config`: SELECT reload_config(); ``` +## resource_group_mappings + +_QuestDB Enterprise only. Requires administrator rights._ + +Returns the principal mappings in the resource group catalog. Definitions remain +available when resource group enforcement is disabled. + +**Arguments:** none. + +**Return value:** a table with these columns: + +| Column | Type | Description | +| ---------------------- | --------- | -------------------------------------------------------------------- | +| `principal_type` | `VARCHAR` | `USER`, `GROUP` or `SERVICE_ACCOUNT` | +| `principal_name` | `VARCHAR` | ACL principal name | +| `principal_generation` | `LONG` | Distinguishes a principal from a later recreation of the same name | +| `resource_group_id` | `LONG` | System-assigned identifier of the mapped resource group | +| `resource_group` | `VARCHAR` | Group name | +| `mapping_priority` | `INT` | Priority for ACL group mappings; defaults to `0` | +| `mapping_revision` | `LONG` | Revision used to break equal-priority ties; the higher revision wins | + +```questdb-sql +SELECT principal_type, principal_name, resource_group, mapping_priority +FROM resource_group_mappings() +ORDER BY principal_type, principal_name; +``` + +This lists mappings rather than expanding inherited assignments into one row per +user. Use `current_resource_group()` from a user's own session to confirm the +resolved assignment. + +## resource_groups + +_QuestDB Enterprise only. Requires administrator rights._ + +Returns one row per current catalog group, including `DEFAULT`, with resolved +policies and live counters. Group definitions remain visible when enforcement is +disabled; their runtime counters are zero. + +**Arguments:** none. + +**Return value:** a table with these columns: + +| Column | Type | Description | +| -------------------------- | --------- | ------------------------------------------------------------------------------------------------------- | +| `name` | `VARCHAR` | Group name | +| `memory_limit_bytes` | `LONG` | Effective group ceiling in bytes, capped by the process budget when enabled; `0` means no group ceiling | +| `max_active_queries` | `INT` | Concurrent admission limit; `2147483647` represents unlimited | +| `max_queued_queries` | `INT` | Queue capacity; `2147483647` represents unlimited, and `0` disables queueing | +| `queue_timeout_millis` | `LONG` | Effective admission timeout in milliseconds | +| `cpu_weight` | `INT` | Relative scheduling weight | +| `cpu_max_percent` | `DOUBLE` | CPU percentage cap; `NULL` when uncapped | +| `active_queries` | `LONG` | Queries currently holding admission slots | +| `queued_queries` | `LONG` | Queries waiting for admission | +| `oldest_queue_wait_millis` | `LONG` | Age of the oldest admission waiter in milliseconds; `0` when none | +| `memory_used_bytes` | `LONG` | Published tracked native query memory in bytes | +| `cpu_nanos_total` | `LONG` | CPU nanoseconds measured by managed scheduling | +| `cpu_wait_nanos_total` | `LONG` | Cumulative query waiting time for CPU, in nanoseconds | +| `admission_rejections` | `LONG` | Cumulative queue-full rejections | +| `admission_timeouts` | `LONG` | Cumulative admission timeouts | + +```questdb-sql +SELECT name, memory_limit_bytes, memory_used_bytes, active_queries, queued_queries +FROM resource_groups() +ORDER BY name; +``` + +Counters describe the current runtime and reset on restart or group recreation. +Worker-local memory deltas can be temporarily unpublished. The single uncapped +group dispatch path does not sample CPU, so `cpu_nanos_total` does not cover all +query CPU use. Dropped groups disappear from this table while their existing +queries finish using retained state. + +For the corresponding +[Prometheus metrics](/docs/operations/logging-metrics/#resource-group-metrics), +an uncapped CPU limit is represented by `-1`, whereas SQL returns `NULL`. An +unlimited group memory ceiling is `0` in both interfaces; it does not remove +principal-specific, instance-default single-query or process memory limits. + ## sleep() -Pauses the query for the given number of seconds, then returns the timestamp -at which it resumed. Intended for testing and demonstration, for example to -hold a query open while inspecting -[`query_activity()`](#query_activity) from another session. +Pauses the query for the given number of seconds, then returns the timestamp at +which it resumed. Intended for testing and demonstration, for example to hold a +query open while inspecting [`query_activity()`](#query_activity) from another +session. -`sleep()` does not hold a worker thread while it waits, so many concurrent -calls can be parked at once without exhausting the shared worker pool. +`sleep()` does not hold a worker thread while it waits, so many concurrent calls +can be parked at once without exhausting the shared worker pool. **Arguments:** @@ -502,8 +617,8 @@ SELECT * FROM sleep(1); :::note -Storage policies — and the `storage_policies` view — are available in -**QuestDB Enterprise** only. +Storage policies — and the `storage_policies` view — are available in **QuestDB +Enterprise** only. ::: @@ -532,9 +647,9 @@ SELECT * FROM storage_policies; - TTL values are rendered in two units: `h` for hours and `m` for **months**. Hour-, day-, and week-based durations are stored as hours (e.g. `3 DAYS` → `72h`, `1 WEEK` → `168h`). Month- and year-based durations are stored as - months (e.g. `1 MONTH` → `1m`, `1 YEAR` → `12m`). Despite the visual - collision with "minute", `m` in this view is **months**; QuestDB's duration - shorthand has no unit for minutes. + months (e.g. `1 MONTH` → `1m`, `1 YEAR` → `12m`). Despite the visual collision + with "minute", `m` in this view is **months**; QuestDB's duration shorthand + has no unit for minutes. - An unset stage renders as `0h`, not blank. **Example:** @@ -557,11 +672,15 @@ stage set and has been temporarily disabled. Every unset stage renders as `0h`. :::note -[Cold storage](/docs/concepts/cold-storage/) and the `table_cold_partitions()` function are available in **QuestDB Enterprise** only. +[Cold storage](/docs/concepts/cold-storage/) and the `table_cold_partitions()` +function are available in **QuestDB Enterprise** only. ::: -`table_cold_partitions('tableName')` returns one row per partition in the table's remote manifest, with the state of its object in the store. Use it to follow a partition through upload and sealing, and to find partitions that are not progressing. +`table_cold_partitions('tableName')` returns one row per partition in the +table's remote manifest, with the state of its object in the store. Use it to +follow a partition through upload and sealing, and to find partitions that are +not progressing. **Arguments:** @@ -609,9 +728,16 @@ WHERE state = 'pending'; **Notes:** -- The cold storage manager answers from its own in-memory view. A refresher answers from its mirrored copy, which it updates when the catalog generation changes, so the two can differ briefly. -- While an instance is transitioning between the manager and refresher roles, the function returns zero rows rather than blocking. Check the live role with [`SWITCH COLD STORAGE STATUS`](/docs/query/sql/switch-cold-storage-role/). -- The function reflects the remote manifest, not local partition state. Use [`SHOW PARTITIONS`](/docs/query/sql/show/#show-partitions) or [`table_partitions()`](#table_partitions) to see whether a partition is actually being served remotely. +- The cold storage manager answers from its own in-memory view. A refresher + answers from its mirrored copy, which it updates when the catalog generation + changes, so the two can differ briefly. +- While an instance is transitioning between the manager and refresher roles, + the function returns zero rows rather than blocking. Check the live role with + [`SWITCH COLD STORAGE STATUS`](/docs/query/sql/switch-cold-storage-role/). +- The function reflects the remote manifest, not local partition state. Use + [`SHOW PARTITIONS`](/docs/query/sql/show/#show-partitions) or + [`table_partitions()`](#table_partitions) to see whether a partition is + actually being served remotely. ## table_columns @@ -634,14 +760,14 @@ Returns a `table` with the following columns: - `symbolCached` - whether this `symbol` column is cached - `symbolCapacity` - how many distinct values this column of `symbol` type is expected to have -- `symbolTableSize` - current number of distinct values stored in this - `symbol` column's table +- `symbolTableSize` - current number of distinct values stored in this `symbol` + column's table - `designated` - if this is set as the designated timestamp column for this table - `upsertKey` - if this column is a part of UPSERT KEYS list for table [deduplication](/docs/concepts/deduplication) -- `indexType` - the [index type](/docs/concepts/deep-dive/indexes/) - (`POSTING`, `POSTING DELTA`, `POSTING EF`, `BITMAP`, or empty) +- `indexType` - the [index type](/docs/concepts/deep-dive/indexes/) (`POSTING`, + `POSTING DELTA`, `POSTING EF`, `BITMAP`, or empty) - `indexInclude` - comma-separated names of columns included in a [posting index's](/docs/concepts/deep-dive/posting-index/) covering sidecar @@ -719,16 +845,16 @@ Returns a table with the following columns: partition will contain the `.detached` extension) - `attachable` - _BOOLEAN_, true if the partition is detached and can be attached (`name` of the partition will contain the `.attachable` extension) -- `hasParquetGenerated` - _BOOLEAN_, true if a Parquet copy of the partition - has been generated. Set by either +- `hasParquetGenerated` - _BOOLEAN_, true if a Parquet copy of the partition has + been generated. Set by either [manual Parquet conversion](/docs/concepts/parquet/#in-place-conversion) (`ALTER TABLE ... CONVERT PARTITION TO PARQUET`) or by a [storage policy](/docs/concepts/storage-policy/)'s `TO PARQUET` stage (Enterprise) - `isParquet` - _BOOLEAN_, true if the partition is stored in Parquet format: - the native files have been removed and reads are served from the Parquet - file. Set the same way as `hasParquetGenerated`: either manually or by a - storage policy's `TO PARQUET` stage + the native files have been removed and reads are served from the Parquet file. + Set the same way as `hasParquetGenerated`: either manually or by a storage + policy's `TO PARQUET` stage - `parquetFileSize` - _LONG_, size in bytes of the partition's `data.parquet` file when `hasParquetGenerated` or `isParquet` is true; `-1` otherwise - `seqTxn` - _LONG_, WAL transaction version the partition was last written at @@ -889,7 +1015,7 @@ Returns a `table` with the following columns: ::: -### Table metrics (table_* prefix) +### Table metrics (table\_\* prefix) | Column | Type | Description | | ----------------------------- | --------- | ----------------------------------------------------------------------------------------- | @@ -912,16 +1038,18 @@ Returns a `table` with the following columns: | `table_merge_rate_p99` | LONG | Throughput that 99% of jobs **exceeded** (slowest 1%) | | `table_merge_rate_max` | LONG | Maximum throughput in rows/second | -Write amplification measures O3 (out-of-order) merge overhead as `physicalRowsWritten / logicalRows`. -A ratio of `1.0` means no amplification. Higher values indicate O3 merge overhead. +Write amplification measures O3 (out-of-order) merge overhead as +`physicalRowsWritten / logicalRows`. A ratio of `1.0` means no amplification. +Higher values indicate O3 merge overhead. :::note -Merge rate P99 shows the *lowest* throughput (worst performance), not the highest. +Merge rate P99 shows the _lowest_ throughput (worst performance), not the +highest. ::: -### WAL metrics (wal_* prefix) +### WAL metrics (wal\_\* prefix) | Column | Type | Description | | --------------------------------- | --------- | ------------------------------------------------------------- | @@ -935,9 +1063,10 @@ Merge rate P99 shows the *lowest* throughput (worst performance), not the highes | `wal_tx_size_p99` | LONG | 99th percentile transaction size in rows | | `wal_tx_size_max` | LONG | Maximum transaction size in rows | -### Replica metrics (replica_* prefix) +### Replica metrics (replica\_\* prefix) -These columns are populated on **replicas only** via replication download tracking: +These columns are populated on **replicas only** via replication download +tracking: | Column | Type | Description | | ------------------------ | ------- | ------------------------------------------------------------------------ | @@ -954,17 +1083,31 @@ On primary instances, these columns will be `0` or `false`. These values are approximations, not precise real-time metrics: -- **Null when not tracked**: Values are `null` for tables not written to since server start, or evicted from the tracker -- **Writer stats updated on pool return**: `table_row_count`, `table_last_write_timestamp`, `table_txn` are captured when TableWriter returns to the pool, not on every commit. A writer held for a long time won't update these columns until released. +- **Null when not tracked**: Values are `null` for tables not written to since + server start, or evicted from the tracker +- **Writer stats updated on pool return**: `table_row_count`, + `table_last_write_timestamp`, `table_txn` are captured when TableWriter + returns to the pool, not on every commit. A writer held for a long time won't + update these columns until released. - **WAL stats updated in real-time**: - - On WAL commit: `wal_pending_row_count` (incremented), `wal_txn`, `wal_max_timestamp`, `wal_tx_size_*` histogram - - On WAL apply: `wal_pending_row_count` (decremented), `wal_dedup_row_count_since_start`, `table_min_timestamp`, `table_max_timestamp`, `table_write_amp_*`, `table_merge_rate_*` -- **LRU eviction**: Tracker maintains bounded memory (default 1000 tables). Least recently written tables are evicted when capacity is exceeded -- **Startup hydration**: Values are hydrated from table metadata (`TxReader`) on startup, but diverge as writes occur - -**Non-WAL tables**: `wal_txn`, `wal_max_timestamp`, `wal_pending_row_count`, `wal_dedup_row_count_since_start`, `table_min_timestamp`, `table_max_timestamp`, `table_memory_pressure_level`, and histogram columns are `null` or `0`. - -**WAL tables**: All columns populated when tracked. `wal_max_timestamp` reflects the max data timestamp from the WAL transaction, not wall-clock time. `table_min_timestamp` and `table_max_timestamp` reflect the actual data range in the table after WAL merge. + - On WAL commit: `wal_pending_row_count` (incremented), `wal_txn`, + `wal_max_timestamp`, `wal_tx_size_*` histogram + - On WAL apply: `wal_pending_row_count` (decremented), + `wal_dedup_row_count_since_start`, `table_min_timestamp`, + `table_max_timestamp`, `table_write_amp_*`, `table_merge_rate_*` +- **LRU eviction**: Tracker maintains bounded memory (default 1000 tables). + Least recently written tables are evicted when capacity is exceeded +- **Startup hydration**: Values are hydrated from table metadata (`TxReader`) on + startup, but diverge as writes occur + +**Non-WAL tables**: `wal_txn`, `wal_max_timestamp`, `wal_pending_row_count`, +`wal_dedup_row_count_since_start`, `table_min_timestamp`, `table_max_timestamp`, +`table_memory_pressure_level`, and histogram columns are `null` or `0`. + +**WAL tables**: All columns populated when tracked. `wal_max_timestamp` reflects +the max data timestamp from the WAL transaction, not wall-clock time. +`table_min_timestamp` and `table_max_timestamp` reflect the actual data range in +the table after WAL merge. ### Configuration @@ -1206,9 +1349,8 @@ concurrent waiters is not bounded by the shared worker pool. **Arguments:** -- `tableName` (`string`): name of the table to wait for. Must be a constant, - not a column reference. On a non-WAL table the call returns `true` - immediately. +- `tableName` (`string`): name of the table to wait for. Must be a constant, not + a column reference. On a non-WAL table the call returns `true` immediately. - `seqTxn` (optional, `long`): the sequencer transaction to wait for. When omitted, the call captures the table's current `seqTxn` when it starts and waits for that, which is what you want after your own write. @@ -1218,8 +1360,8 @@ concurrent waiters is not bounded by the shared worker pool. Returns `boolean`. `true` once the writer has caught up. Throws if the table is dropped while the call is waiting, and if the table -becomes [suspended](/docs/query/sql/alter-table-resume-wal/), since a -suspended table would otherwise never catch up. +becomes [suspended](/docs/query/sql/alter-table-resume-wal/), since a suspended +table would otherwise never catch up. **Examples:** @@ -1243,11 +1385,10 @@ SELECT wait_wal_table('trades', 42); :::note -For monitoring and observability, use [`tables()`](#tables) instead. -`tables()` provides all the same information plus additional metrics -(pending rows, memory pressure, deduplication stats, throughput histograms), -and is fully in-memory. `wal_tables()` reads from disk and is less suitable -for frequent polling. +For monitoring and observability, use [`tables()`](#tables) instead. `tables()` +provides all the same information plus additional metrics (pending rows, memory +pressure, deduplication stats, throughput histograms), and is fully in-memory. +`wal_tables()` reads from disk and is less suitable for frequent polling. ::: @@ -1265,11 +1406,13 @@ Returns a `table` including the following information: - `name` - table or materialized view name - `suspended` - suspended status flag -- `writerTxn` - the last committed transaction in TableWriter (equivalent to `table_txn` in `tables()`) +- `writerTxn` - the last committed transaction in TableWriter (equivalent to + `table_txn` in `tables()`) - `writerLagTxnCount` - the number of transactions that are kept invisible when writing to the table; these transactions will be eventually moved to the table data and become visible for readers (equivalent to `wal_txn - table_txn`) -- `sequencerTxn` - the last committed transaction in the sequencer (equivalent to `wal_txn` in `tables()`) +- `sequencerTxn` - the last committed transaction in the sequencer (equivalent + to `wal_txn` in `tables()`) **Examples:** diff --git a/documentation/sidebars.js b/documentation/sidebars.js index f0b4f587f..a324b8b78 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -635,6 +635,11 @@ module.exports = { type: "doc", label: "Cold Storage", }, + { + id: "concepts/resource-groups", + type: "doc", + label: "Resource Groups", + }, "concepts/write-ahead-log", ], }, @@ -698,6 +703,11 @@ module.exports = { "configuration/postgres-wire-protocol", "configuration/qwp", "configuration/database-replication", + { + id: "configuration/resource-groups", + type: "doc", + label: "Resource groups", + }, "configuration/shared-workers", "configuration/storage-policy", "configuration/telemetry", @@ -787,6 +797,11 @@ module.exports = { type: "doc", label: "Cold storage", }, + { + id: "operations/resource-groups", + type: "doc", + label: "Resource groups", + }, "operations/logging-metrics", "operations/monitoring-alerting", "operations/data-retention", From b5f4f4df5a348907379adf4ab3f30f52a0cf6d91 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 10 Sep 2026 11:25:15 +0800 Subject: [PATCH 02/11] update docs --- documentation/concepts/resource-groups.md | 12 ++++--- .../configuration/resource-groups.md | 33 +------------------ documentation/operations/resource-groups.md | 15 ++++----- 3 files changed, 15 insertions(+), 45 deletions(-) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index 7a8418133..fa90633d8 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -72,14 +72,18 @@ The default behaviour is: Resource groups govern the statements that read data: -- `SELECT` and `EXPLAIN` +- `SELECT` - the source query of `CREATE TABLE ... AS SELECT` and `INSERT ... SELECT` - query exports Everything else runs outside the feature and consumes no admission slot, CPU -grant or group memory budget: value `INSERT`, `UPDATE`, ordinary DDL, `COPY`, -transaction and session control, ILP ingestion, WAL apply, materialized and live -view refresh, and QuestDB's own internal SQL. +grant or group memory budget: `EXPLAIN`, value `INSERT`, `UPDATE`, ordinary DDL, +`COPY`, transaction and session control, ILP ingestion, WAL apply, materialized +and live view refresh, and QuestDB's own internal SQL. + +`EXPLAIN` is deliberately outside the feature. It walks a plan tree and opens no +base cursor, so it reads no data, and holding an admission slot for it would +block the one statement an operator reaches for while a group is saturated. For `CREATE TABLE ... AS SELECT` and `INSERT ... SELECT` the owner covers cursor open, the source scan, transforms, parallel query work and the row pump. Source diff --git a/documentation/configuration/resource-groups.md b/documentation/configuration/resource-groups.md index f87252777..002050f7a 100644 --- a/documentation/configuration/resource-groups.md +++ b/documentation/configuration/resource-groups.md @@ -3,8 +3,7 @@ title: Resource groups sidebar_label: Resource groups description: Configuration settings for QuestDB Enterprise resource groups, covering the - master switch, CPU capacity, memory ceiling, admission defaults and catalog - limits. + master switch, CPU capacity, memory ceiling and admission defaults. --- :::note @@ -90,36 +89,6 @@ How long a queued query waits for an admission slot in a group that does not set its own `queue_timeout`. A query that waits longer fails with `Resource Group admission queue timeout`. -## Catalog limits - -Group definitions and principal mappings live in a replicated system catalog. -These bounds limit the catalog size. Increase them if the deployment requires -more definitions or mappings. - -### resource.groups.catalog.max.snapshot.bytes - -- **Default**: `16777216` -- **Reloadable**: no - -Size ceiling for one serialized catalog snapshot. - -### resource.groups.max.user.groups - -- **Default**: `4096` -- **Reloadable**: no - -Maximum number of user-created resource groups, not counting `DEFAULT`. Dropped -groups stop counting towards this limit immediately, even while their existing -queries finish. - -### resource.groups.max.principal.links - -- **Default**: `65536` -- **Reloadable**: no - -Maximum number of principal mappings, counting users, service accounts and ACL -groups together. - ## See also - [Resource groups concept](/docs/concepts/resource-groups/) diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 3e8a07211..f1604f9bb 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -102,15 +102,12 @@ Resource groups are enabled by default. These are instance-wide settings; the per-group policy is set in SQL. Each setting is described in full in the [resource groups configuration reference](/docs/configuration/resource-groups/). -| Property | Default | Meaning | -| -------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------- | -| `resource.groups.enabled` | `true` | Set to `false` to disable resource group enforcement. Existing single-query memory limits still apply. | -| `resource.groups.cpu.capacity.cores` | `auto` | CPU capacity that `cpu_max_percent` is a percentage of. `auto` detects container quota, including fractional quotas. | -| `resource.groups.process.memory.limit.bytes` | `0` | Ceiling for tracked query memory across all groups, `0` for none. Every group limit is capped by it. | -| `resource.groups.queue.timeout.millis` | `30000` | Default admission queue timeout for groups that do not set `queue_timeout`. | -| `resource.groups.catalog.max.snapshot.bytes` | `16777216` | Size ceiling for the serialized catalog. | -| `resource.groups.max.user.groups` | `4096` | Maximum number of user-created resource groups. | -| `resource.groups.max.principal.links` | `65536` | Maximum number of principal mappings. | +| Property | Default | Meaning | +| -------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | +| `resource.groups.enabled` | `true` | Set to `false` to disable resource group enforcement. Existing single-query memory limits still apply. | +| `resource.groups.cpu.capacity.cores` | `auto` | CPU capacity that `cpu_max_percent` is a percentage of. `auto` detects container quota, including fractional quotas. | +| `resource.groups.process.memory.limit.bytes` | `0` | Ceiling for tracked query memory across all groups, `0` for none. Every group limit is capped by it. | +| `resource.groups.queue.timeout.millis` | `30000` | Default admission queue timeout for groups that do not set `queue_timeout`. | Turning the feature off is a restart with `resource.groups.enabled=false`. Definitions and mappings stay in the catalog, so nothing is lost and the From 4cc0d2df42196fbe3c9765f1af8130220d581ce6 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 10 Sep 2026 14:16:23 +0800 Subject: [PATCH 03/11] update docs --- documentation/concepts/resource-groups.md | 12 ++++++++++++ documentation/configuration/resource-groups.md | 5 +++++ documentation/operations/resource-groups.md | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index fa90633d8..c8fed80aa 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -200,6 +200,18 @@ replica receives group definitions and mappings through normal replication. unmanaged, exactly as if the feature were disabled, and reports how many queries took that path. It does not reject queries or serve them under a policy it cannot see yet. +- A **replica being promoted** validates the catalog after replication has + switched and before writes are admitted. If the old primary predated resource + groups and never created the catalog table, the promoted node creates it and + continues. With the feature enabled, a catalog that is unreadable or still + lagging refuses the promotion: the switch fails part-way, the node lands in + the `UNKNOWN` role and keeps serving reads as before, and the failure reason + starts with `RESOURCE_GROUP_CATALOG_UNAVAILABLE` or + `RESOURCE_GROUP_CATALOG_LAGGING`. Retrying the switch repeats the check. With + the feature disabled the condition is logged and the promotion proceeds. +- At **startup** an unreadable catalog stops an instance with the feature + enabled from starting, in either role. A lagging catalog does not: the + instance starts and the refresh job catches up. - If **CPU scheduling** hits an internal fault, it degrades: queries continue to run without CPU grants, and the condition is visible in metrics until the instance restarts. Admission and memory limits do not depend on CPU scheduling diff --git a/documentation/configuration/resource-groups.md b/documentation/configuration/resource-groups.md index 002050f7a..d2cbca800 100644 --- a/documentation/configuration/resource-groups.md +++ b/documentation/configuration/resource-groups.md @@ -40,6 +40,11 @@ memory accounting are disabled. Group definitions and principal mappings remain in the catalog, so turning the feature back on restores the policies that were already there. +`true` also makes the catalog a hard dependency: an instance whose catalog +cannot be read does not start, and a replica whose catalog is not current is not +promoted. With `false`, both conditions are logged and ignored. See +[Behaviour under failure and on replicas](/docs/concepts/resource-groups/#behaviour-under-failure-and-on-replicas). + Existing principal-specific and instance-default single-query memory limits continue to apply when resource groups are disabled. diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index f1604f9bb..759f38ea8 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -529,6 +529,24 @@ replicated, a replica runs queries unmanaged and counts them in `questdb_resource_groups_catalog_lag_unmanaged_queries`. The counter stops growing once `questdb_resource_groups_catalog_current` reaches 1. +**Promotion fails naming the resource group catalog.** With the feature enabled, +`SWITCH ROLE TO PRIMARY` does not admit writes over a catalog that is unreadable +or lagging. The node lands in the `UNKNOWN` role and still serves reads; +`GET /lifecycle` and the server log carry a reason that starts with +`RESOURCE_GROUP_CATALOG_LAGGING` or `RESOURCE_GROUP_CATALOG_UNAVAILABLE`. +Lagging means the replica has not finished applying the transactions the catalog +depends on: wait for WAL apply to catch up and run `SWITCH ROLE TO PRIMARY` +again. Unavailable means the catalog table is missing or its contents cannot be +read, and retrying does not help: promote another replica, or restart this node +as primary with `resource.groups.enabled=false`, which turns the check into a +logged error. See +[Refusals and the torn state](/docs/high-availability/failover/#refusals-and-the-torn-state). + +**Startup fails naming the resource group catalog.** The catalog table cannot be +read while the feature is enabled; the log says +`Resource Group catalog startup validation failed`. Starting with +`resource.groups.enabled=false` logs the condition instead of failing. + **Startup fails naming a worker pool.** A pool that executes SQL is in legacy mode while `resource.groups.enabled=true` was set explicitly. Either restore the default Fiber mode for that pool or stop setting the property, which lets the From 8d2f7345f010ee66651f3201840ea75c99eedc97 Mon Sep 17 00:00:00 2001 From: victor Date: Thu, 10 Sep 2026 17:32:08 +0800 Subject: [PATCH 04/11] update docs --- documentation/concepts/resource-groups.md | 8 ++++---- documentation/operations/resource-groups.md | 17 ++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index c8fed80aa..9466ca879 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -205,10 +205,10 @@ replica receives group definitions and mappings through normal replication. groups and never created the catalog table, the promoted node creates it and continues. With the feature enabled, a catalog that is unreadable or still lagging refuses the promotion: the switch fails part-way, the node lands in - the `UNKNOWN` role and keeps serving reads as before, and the failure reason - starts with `RESOURCE_GROUP_CATALOG_UNAVAILABLE` or - `RESOURCE_GROUP_CATALOG_LAGGING`. Retrying the switch repeats the check. With - the feature disabled the condition is logged and the promotion proceeds. + the `UNKNOWN` role and keeps serving reads as before, and the log names + `RESOURCE_GROUP_CATALOG_UNAVAILABLE` or `RESOURCE_GROUP_CATALOG_LAGGING`. + Retrying the switch repeats the check. With the feature disabled the condition + is logged and the promotion proceeds. - At **startup** an unreadable catalog stops an instance with the feature enabled from starting, in either role. A lagging catalog does not: the instance starts and the refresh job catches up. diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 759f38ea8..e44999e16 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -531,15 +531,14 @@ growing once `questdb_resource_groups_catalog_current` reaches 1. **Promotion fails naming the resource group catalog.** With the feature enabled, `SWITCH ROLE TO PRIMARY` does not admit writes over a catalog that is unreadable -or lagging. The node lands in the `UNKNOWN` role and still serves reads; -`GET /lifecycle` and the server log carry a reason that starts with -`RESOURCE_GROUP_CATALOG_LAGGING` or `RESOURCE_GROUP_CATALOG_UNAVAILABLE`. -Lagging means the replica has not finished applying the transactions the catalog -depends on: wait for WAL apply to catch up and run `SWITCH ROLE TO PRIMARY` -again. Unavailable means the catalog table is missing or its contents cannot be -read, and retrying does not help: promote another replica, or restart this node -as primary with `resource.groups.enabled=false`, which turns the check into a -logged error. See +or lagging. The node lands in the `UNKNOWN` role and still serves reads; the +server log names `RESOURCE_GROUP_CATALOG_LAGGING` or +`RESOURCE_GROUP_CATALOG_UNAVAILABLE`. Lagging means the replica has not finished +applying the access control transactions the catalog depends on: wait for WAL +apply to catch up and run `SWITCH ROLE TO PRIMARY` again. Unavailable means the +catalog table is missing or its contents cannot be read, and retrying does not +help: promote another replica, or restart this node as primary with +`resource.groups.enabled=false`, which turns the check into a logged error. See [Refusals and the torn state](/docs/high-availability/failover/#refusals-and-the-torn-state). **Startup fails naming the resource group catalog.** The catalog table cannot be From ee78f22910d115508446007a04a52ad4f7c62822 Mon Sep 17 00:00:00 2001 From: victor Date: Fri, 11 Sep 2026 15:29:05 +0800 Subject: [PATCH 05/11] update docs --- documentation/concepts/resource-groups.md | 182 +++++------ documentation/configuration/cairo-engine.md | 5 + .../configuration/resource-groups.md | 30 +- documentation/operations/logging-metrics.md | 20 +- documentation/operations/resource-groups.md | 286 ++++++++---------- documentation/query/functions/meta.md | 28 +- 6 files changed, 242 insertions(+), 309 deletions(-) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index 9466ca879..15e5628bb 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -3,8 +3,8 @@ title: Resource groups sidebar_label: Resource groups description: Resource groups isolate query workloads inside one QuestDB instance. Learn how - a query is assigned to a group, and what admission, CPU weight, CPU caps and - memory limits actually guarantee. + a query is assigned to a group, and what admission, CPU weight and memory + limits actually guarantee. --- import { EnterpriseNote } from "@site/src/components/EnterpriseNote" @@ -20,14 +20,12 @@ at once: dashboards that must answer in milliseconds, an ad-hoc analyst, and a nightly report that scans a year of data. When these workloads compete without resource controls, the report can increase dashboard latency. -Resource groups control four things at the query execution boundary: +Resource groups control three things at the query execution boundary: - **Admission** — how many queries a group may run at once, how many may wait, and how long they may wait. - **Weighted CPU** — the share of query CPU a group receives while groups compete. -- **A CPU rate limit** — an absolute ceiling expressed as a percentage of - instance capacity. - **Memory** — process and group budgets for tracked native query memory. The design is cooperative. QuestDB executes query work on shared worker pools, @@ -35,38 +33,39 @@ and resource groups do not create one operating-system thread pool per group. A query and all of its parallel tasks use the same resource group, while every worker stays available to every group. +With no configuration the feature is on and no group policy is in force: + +| Setting | Behaviour | +| ----------------------------------- | ------------------------------------------------------- | +| Feature enabled | Yes; turns itself off when a SQL pool is in legacy mode | +| Group admission | Unlimited active and queued queries | +| Group CPU | Weight 100 | +| Group and process memory budgets | Unlimited unless configured | +| Existing single-query memory limits | Still apply, including principal-specific limits | +| Memory accounting without limits | Remains enabled for tracked native query memory | + ## How a query is assigned to a group Assignment follows the authenticated principal, not the statement: 1. A **direct mapping** on the user or service account wins. 2. Otherwise, for users only, QuestDB looks at the mappings of the ACL groups - the user belongs to and takes the highest `mapping_priority`. Ties go to the - most recently created mapping. + the user belongs to and takes the highest `mapping_priority`. If two tie, the + mapping to the resource group that was created first wins, so give the groups + distinct priorities when the order matters. 3. Otherwise the query runs in **DEFAULT**. Service accounts inherit nothing from ACL groups; they are either mapped directly or they run in DEFAULT. -The group is resolved once, when the query registers, and stays fixed for the +The group is resolved once, when the query starts, and stays fixed for the statement's lifetime. Changing a mapping affects statements that start after the change, never one already running. `DEFAULT` always exists. By default it carries no limits of its own, so unmapped -principals run with a CPU weight of 100, no CPU cap, unlimited admission, and -the instance-wide memory limits. You can change its policy, but you cannot drop -or rename it. - -The default behaviour is: - -| Setting | Behaviour | -| ----------------------------------- | ------------------------------------------------------ | -| Feature enabled | Yes, when the SQL worker pools support Fiber execution | -| Group admission | Unlimited active and queued queries | -| Group CPU | Weight 100; no percentage cap | -| Group and process memory budgets | Unlimited unless configured | -| Existing single-query memory limits | Still apply, including principal-specific limits | -| Memory accounting without limits | Remains enabled for tracked native query memory | +principals run with a CPU weight of 100, unlimited admission, and the +instance-wide memory limits. You can change its policy, but you cannot drop or +rename it. ## What is managed @@ -78,18 +77,14 @@ Resource groups govern the statements that read data: Everything else runs outside the feature and consumes no admission slot, CPU grant or group memory budget: `EXPLAIN`, value `INSERT`, `UPDATE`, ordinary DDL, -`COPY`, transaction and session control, ILP ingestion, WAL apply, materialized -and live view refresh, and QuestDB's own internal SQL. +`COPY`, transaction and session control, ILP and QWP ingestion, WAL apply, +materialized and live view refresh, and QuestDB's own internal SQL. -`EXPLAIN` is deliberately outside the feature. It walks a plan tree and opens no -base cursor, so it reads no data, and holding an admission slot for it would -block the one statement an operator reaches for while a group is saturated. - -For `CREATE TABLE ... AS SELECT` and `INSERT ... SELECT` the owner covers cursor -open, the source scan, transforms, parallel query work and the row pump. Source -evaluation and writer append are fused in that pump, so inseparable foreground -CPU may be charged conservatively to the group. Durability, the commit and any -work handed to writer or WAL queues are outside the guarantee. +For `CREATE TABLE ... AS SELECT` and `INSERT ... SELECT` the group is charged +for reading the source and producing rows, including parallel work. Where +writing a row cannot be separated from producing it, that CPU is charged to the +group as well. The commit, durability and any work handed to writer or WAL +queues are outside the guarantee. Resource groups account **tracked native query memory**. They do not represent JVM heap, resident set size, memory-mapped table pages or long-lived engine @@ -97,7 +92,7 @@ caches. Existing process memory protection remains the outer boundary. ## What each control guarantees -The four controls differ in how strong their guarantee is, which matters when +The three controls differ in how strong their guarantee is, which matters when you decide what to configure. ### Admission is a hard gate @@ -105,11 +100,12 @@ you decide what to configure. `max_active_queries` is an exact count. A group at its limit queues the next query until a slot frees, up to `max_queued_queries`; beyond that the query is rejected immediately. A queued query that waits longer than `queue_timeout` -fails. +fails, and the instance-wide `query.timeout` keeps running while it waits, so +whichever of the two expires first ends the wait. -A slot is held only while the query is actually executing a segment. A protocol -cursor that is suspended between pages releases its slot and passes through the -gate again when the client asks for more rows, so a paging client does not hold +A slot is held only while the query is running on a worker. A protocol cursor +that is suspended between pages releases its slot and passes through the gate +again when the client asks for more rows, so a paging client does not hold capacity while the application thinks. The consequence is that admission can be refused on a later page: a client that received its first rows may still see the queue-full or timeout error when it asks for more, and the connection stays @@ -118,38 +114,27 @@ usable. ### CPU weight is a share, not a reservation Weights only matter when groups compete. A group that is alone on the instance -uses everything it can, regardless of its weight. When two groups both have -work, the scheduler hands out CPU so that measured CPU divided by `cpu_weight` -stays balanced: weights 100 and 50 converge to a 2:1 split of query CPU. +uses everything it can, regardless of its weight, and a query that started while +its group was alone keeps running that way until it next suspends or finishes. +When two groups both have work, the scheduler hands out CPU so that measured CPU +divided by `cpu_weight` stays balanced: weights 100 and 50 converge to a 2:1 +split of query CPU. Weights are relative. 100 and 50 are the same as 2 and 1. A group that becomes active starts level with the groups already running, so it neither banks the CPU it did not use while idle nor is punished for having been busy. -Within a group, pending execution requests are served in arrival order. A -parallel query can submit more than one request, so this does not promise equal -CPU shares between individual queries. - -### The CPU cap is a rate, not an instantaneous ceiling - -`cpu_max_percent` is enforced with a token bucket measured in CPU nanoseconds -against the instance's CPU capacity. It is an average over a short window, not a -per-instant limit: a capped group that has been idle may burst for about 100 ms -of accumulated allowance before it is pushed back to its configured rate. Usage -beyond a grant becomes debt that must be repaid before the group runs again, so -the average holds even when an individual query overruns. - -Capacity comes from `resource.groups.cpu.capacity.cores`, which detects -container CPU quota by default. On a fractional quota such as 500m, detection -preserves the fraction, so a 50% cap really means half of half a core. +Shares are between groups, not between queries. Within a group, work is served +in arrival order, and a parallel query can hold several places in that order, so +there is no promise of equal CPU between individual queries. ### Memory limits use batched accounting Accounting has three levels: query, group and process. Allocation and release -deltas accumulate locally on the executing worker and are published to the -shared counters at an adaptive threshold or an execution boundary. Exceeding a -checked limit fails the query with `query memory limit exceeded`; it does not -queue the allocation until memory becomes available. +deltas accumulate on the executing worker and are published to the shared +counters in batches. Exceeding a checked limit fails the query with +`query memory limit exceeded`; it does not queue the allocation until memory +becomes available. The single-query ceiling starts with the principal's effective query memory limit, when set, or the instance default `cairo.query.memory.limit.bytes`. Any @@ -157,13 +142,14 @@ group `memory_limit` and process memory budget further cap that ceiling. The group budget also bounds the total tracked memory held by its queries; the process budget covers tracked native query memory across groups. -An unset group `memory_limit` adds no group ceiling. A process budget of `0` -adds no process ceiling. Existing single-query limits still apply, and memory -accounting remains enabled even when all limits are unlimited. +An unset group `memory_limit`, or one set to `0` or `UNLIMITED`, adds no group +ceiling. A process budget of `0` adds no process ceiling. Existing single-query +limits still apply, and memory accounting remains enabled even when all limits +are unlimited. -The counters can temporarily omit worker-local deltas. A group can therefore -briefly overshoot its limit by a bounded amount related to the number of workers -running its queries. These budgets are not byte-exact, instantaneous ceilings. +The counters can temporarily omit worker-local deltas, so a group can briefly +overshoot its limit by less than 64 KiB per worker running its queries. These +budgets are not byte-exact, instantaneous ceilings. ## Why CPU control is cooperative @@ -172,23 +158,22 @@ slice of CPU on a worker and expects it to reach a cooperative checkpoint, which is the same circuit breaker check that makes queries cancellable. At that point the query either renews its grant or yields the worker to another group. -Yielding returns the worker to its dispatch loop rather than to the end of the -query. The loop interleaves other queries on that worker and, within a bounded -window, hands it back to network I/O so new connections are accepted. A long -single-threaded query that reaches these checkpoints can therefore share its -worker before finishing. This improves responsiveness while heavy queries run, -even before any custom group policy is written. Resource groups add this CPU -time slicing to the existing cancellation and I/O suspension mechanisms. +A yielded worker runs other queries and, within a bounded window, returns to +accepting connections; the query that yielded resumes later. A long +single-threaded query that reaches these checkpoints therefore shares its worker +before finishing, which keeps the instance responsive while heavy queries run. +Slicing happens only under managed scheduling: while no policy is in force, a +query holds its worker exactly as it does with the feature disabled. Two consequences follow. The guarantee is statistical over a short window. Between checkpoints a query holds its worker, so instantaneous CPU can deviate from the configured share. -Settlement charges the measured CPU either way, so a query that overran repays -it and the average is preserved. +The CPU actually used is charged either way, so a query that overran repays it +and the average is preserved. -A query that cannot reach a checkpoint keeps its worker. When managed CPU -accounting is engaged, its CPU is charged when the grant settles, but no +A query that cannot reach a checkpoint keeps its worker. While managed +scheduling is engaged its CPU is still charged when the slice ends, but no cooperative limit can shorten that stretch. ## Behaviour under failure and on replicas @@ -197,18 +182,18 @@ Resource groups are stored in a replicated system catalog, so a read-only replica receives group definitions and mappings through normal replication. - A **fresh replica** that has not yet received the catalog runs queries - unmanaged, exactly as if the feature were disabled, and reports how many - queries took that path. It does not reject queries or serve them under a - policy it cannot see yet. + unmanaged, exactly as if the feature were disabled, and reports the catalog as + not current. It does not reject queries or serve them under a policy it cannot + see yet. - A **replica being promoted** validates the catalog after replication has switched and before writes are admitted. If the old primary predated resource groups and never created the catalog table, the promoted node creates it and - continues. With the feature enabled, a catalog that is unreadable or still - lagging refuses the promotion: the switch fails part-way, the node lands in - the `UNKNOWN` role and keeps serving reads as before, and the log names - `RESOURCE_GROUP_CATALOG_UNAVAILABLE` or `RESOURCE_GROUP_CATALOG_LAGGING`. - Retrying the switch repeats the check. With the feature disabled the condition - is logged and the promotion proceeds. + continues. With the feature enabled, a catalog that is unreadable or that the + replica has not received yet refuses the promotion: the switch fails part-way, + the node lands in the `UNKNOWN` role and keeps serving reads as before, and + the log names `RESOURCE_GROUP_CATALOG_UNAVAILABLE` with the reason. Retrying + the switch repeats the check. With the feature disabled the condition is + logged and the promotion proceeds. - At **startup** an unreadable catalog stops an instance with the feature enabled from starting, in either role. A lagging catalog does not: the instance starts and the refresh job catches up. @@ -216,21 +201,20 @@ replica receives group definitions and mappings through normal replication. run without CPU grants, and the condition is visible in metrics until the instance restarts. Admission and memory limits do not depend on CPU scheduling and stay enforced. -- A **fault on one query** fences only that query's owner. Other queries and - other groups are unaffected, and the faulted segment is charged conservatively - rather than being dropped from the accounting. +- An **internal fault in one query** affects only that query. Other queries and + other groups are unaffected, and the CPU it used is still charged to its + group. ## Cost when nothing competes -While a single uncapped group owns all running queries, dispatch takes a -lock-free path that avoids CPU sampling and weighted scheduling accounting. -Query registration, admission, cooperative checks and memory accounting still -run, so this does not imply the same cost as disabling the feature. Actual -overhead depends on the workload. Managed scheduling engages as soon as a second -group has work or a capped group is active, and disengages again when it does -not. The transition happens at the next dispatch boundary, not at a query -boundary, so a newly arriving group does not wait for a long query to finish -before its policy applies. +While a single group owns all running queries, dispatch is unmanaged: no CPU is +sampled, no query yields, and every query holds its worker exactly as it does +with the feature disabled. Registration, admission and memory accounting still +run, so this is not free, but the cost is a fixed few microseconds per query. +Managed scheduling engages as soon as a second group has work, and disengages +again when it does not. A query that is already running stays unmanaged until it +next suspends or finishes; the new policy applies to queries that start or +resume after the change. ## See also diff --git a/documentation/configuration/cairo-engine.md b/documentation/configuration/cairo-engine.md index e809a8adc..b52d45143 100644 --- a/documentation/configuration/cairo-engine.md +++ b/documentation/configuration/cairo-engine.md @@ -62,6 +62,11 @@ When `false`, disables the `reload_config()` SQL function. A global timeout for long-running queries, given as a duration: `500ms`, `120s`, `2m` and `1h` are all valid, and a plain number is read as milliseconds. +The timer starts when the server receives the statement and includes any time +the query spends queued for Resource Group admission. Over PGWire each `Execute` +message restarts it, so a client that fetches a cursor in batches is timed per +batch rather than across the whole result. + This key replaces `query.timeout.sec`. When both are set, `query.timeout` takes precedence; when neither is set, queries time out after 60 seconds. diff --git a/documentation/configuration/resource-groups.md b/documentation/configuration/resource-groups.md index d2cbca800..6440bf562 100644 --- a/documentation/configuration/resource-groups.md +++ b/documentation/configuration/resource-groups.md @@ -3,7 +3,7 @@ title: Resource groups sidebar_label: Resource groups description: Configuration settings for QuestDB Enterprise resource groups, covering the - master switch, CPU capacity, memory ceiling and admission defaults. + master switch and the process memory ceiling. --- :::note @@ -54,21 +54,6 @@ not stop the instance from starting. `SHOW PARAMETERS` then reports `false`, which is the value that took effect. Set it to `true` explicitly and a legacy pool becomes a startup error instead. -### resource.groups.cpu.capacity.cores - -- **Default**: `auto` -- **Reloadable**: no - -The CPU capacity that `cpu_max_percent` is a percentage of, as `auto` or a -positive decimal number of cores. `auto` detects the process affinity mask and -the most restrictive cgroup quota, preserving fractional quotas such as `500m`, -so a 50% cap on half a core really means a quarter of a core. An explicit value -is capped by successful detection; if detection fails, the explicit value stands -and the failure is logged. - -When detection fails and no explicit value is set, capacity falls back to the -processor count and `questdb_resource_groups_cpu_capacity_fallback` reports `1`. - ### resource.groups.process.memory.limit.bytes - **Default**: `0` @@ -79,21 +64,12 @@ instance without a process ceiling, which is the default. When set, it bounds every group and every query, so no group policy can grant more than this. An unlimited process budget does not disable memory accounting or remove an -existing single-query limit. The group-level SQL parameter `memory_limit` uses -`RESET (memory_limit)` to clear its ceiling; it does not accept `0`. +existing single-query limit. The group-level SQL parameter `memory_limit` treats +`0` and `UNLIMITED` as no ceiling, as does `RESET (memory_limit)`. This is not a process RSS limit. It covers tracked query memory only, not JVM heap, memory-mapped table pages or long-lived engine caches. -### resource.groups.queue.timeout.millis - -- **Default**: `30000` -- **Reloadable**: no - -How long a queued query waits for an admission slot in a group that does not set -its own `queue_timeout`. A query that waits longer fails with -`Resource Group admission queue timeout`. - ## See also - [Resource groups concept](/docs/concepts/resource-groups/) diff --git a/documentation/operations/logging-metrics.md b/documentation/operations/logging-metrics.md index ce3e3bf94..e91401a92 100644 --- a/documentation/operations/logging-metrics.md +++ b/documentation/operations/logging-metrics.md @@ -391,13 +391,12 @@ endpoint exposes one series per group, labelled with `resource_group`: | `questdb_resource_group_oldest_queue_wait_millis` | gauge | How long the longest waiting query has waited | | `questdb_resource_group_memory_bytes` | gauge | Tracked query memory in use | | `questdb_resource_group_memory_limit_bytes` | gauge | Effective group memory ceiling, `0` when the group sets none | -| `questdb_resource_group_cpu_nanos_total` | counter | CPU charged to the group | +| `questdb_resource_group_cpu_nanos_total` | counter | CPU measured under managed scheduling | | `questdb_resource_group_cpu_wait_nanos_total` | counter | Time the group spent waiting for CPU | -| `questdb_resource_group_cpu_max_percent` | gauge | Effective CPU cap, `-1` when uncapped | | `questdb_resource_group_admission_rejections_total` | counter | Queries rejected because the queue was full | | `questdb_resource_group_admission_timeouts_total` | counter | Queries that timed out while queued | -The single uncapped group dispatch path does not sample CPU. Consequently, +The single-group dispatch path does not sample CPU. Consequently, `cpu_nanos_total` counts CPU measured by managed scheduling, not every query's CPU consumption. A flat counter does not imply that the group is idle; also check `questdb_resource_groups_cpu_managed_dispatch` and query activity. Memory @@ -405,15 +404,12 @@ gauges show published accounting and can lag worker-local deltas. Instance-wide series describe the feature itself: -| Metric | Type | Description | -| ------------------------------------------------------- | ------- | --------------------------------------------------------------------- | -| `questdb_resource_groups_enabled` | gauge | `1` when the feature is on | -| `questdb_resource_groups_catalog_current` | gauge | `1` when the group catalog is current; `0` while a replica catches up | -| `questdb_resource_groups_catalog_lag_unmanaged_queries` | counter | Queries that ran unmanaged because the catalog was not current yet | -| `questdb_resource_groups_cpu_capacity_microcores` | gauge | Capacity that `cpu_max_percent` applies to | -| `questdb_resource_groups_cpu_capacity_fallback` | gauge | `1` when capacity detection failed and the processor count was used | -| `questdb_resource_groups_cpu_managed_dispatch` | gauge | `1` while managed CPU scheduling is engaged | -| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` when CPU scheduling has degraded to unmanaged | +| Metric | Type | Description | +| ------------------------------------------------ | ----- | --------------------------------------------------------------------- | +| `questdb_resource_groups_enabled` | gauge | `1` when the feature is on | +| `questdb_resource_groups_catalog_current` | gauge | `1` when the group catalog is current; `0` while a replica catches up | +| `questdb_resource_groups_cpu_managed_dispatch` | gauge | `1` while managed CPU scheduling is engaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` when CPU scheduling has degraded to unmanaged | A non-zero `questdb_resource_groups_cpu_scheduler_degraded` means CPU shares are no longer enforced until the instance restarts. Admission and memory limits stay diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index e44999e16..5fefe1285 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -86,28 +86,46 @@ have work. - QuestDB Enterprise. - Access control enabled (`acl.enabled=true`). Groups can be created without it, but mapping statements require it, since mappings attach to ACL principals. -- The pools that execute SQL must run in Fiber mode, which is the default, - because cooperative admission and CPU control cannot be made complete on the - legacy path. On an instance whose pools are in legacy mode, resource groups - left at their default turn themselves off and log an error naming the pool and - the setting to change. Setting `resource.groups.enabled=true` on such an - instance fails startup with that same error. +- The pools that execute SQL must run in Fiber mode, which is the default and + which the feature depends on. On an instance whose pools are in legacy mode, + resource groups left at their default turn themselves off and log an error + naming the pool and the setting to change. Setting + `resource.groups.enabled=true` on such an instance fails startup with that + same error. - Administrator rights for group management, mappings and instance-wide inspection. Ordinary users can call `current_resource_group()` to check their own query's group. +A protocol runs on its own pool when its worker count is above zero, otherwise +on the shared network pool. The setting that matters is the one for the pool it +actually uses: + +| Where the protocol runs | Setting to check | +| ---------------------------------------------------- | ------------------------------------- | +| Its own HTTP pool (`http.worker.count` above zero) | `http.worker.fiber.enabled` | +| Its own PGWire pool (`pg.worker.count` above zero) | `pg.worker.fiber.enabled` | +| The shared network pool (worker count zero, default) | `shared.network.worker.fiber.enabled` | + +Parallel query work is separate and follows `shared.query.worker.fiber.enabled` +whenever the shared query pool has workers. A shared query pool set to zero +workers turns parallel SQL off by default and needs no check of its own. + +The first two settings default to `true`, so a dedicated pool is a Fiber pool +unless someone turned it off. `shared.network.worker.fiber.enabled` defaults to +`true` exactly when HTTP or PGWire actually runs there, which is the case out of +the box because both worker counts default to zero. Check these only when the +instance was tuned by hand. + ## Configuration Resource groups are enabled by default. These are instance-wide settings; the per-group policy is set in SQL. Each setting is described in full in the [resource groups configuration reference](/docs/configuration/resource-groups/). -| Property | Default | Meaning | -| -------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------- | -| `resource.groups.enabled` | `true` | Set to `false` to disable resource group enforcement. Existing single-query memory limits still apply. | -| `resource.groups.cpu.capacity.cores` | `auto` | CPU capacity that `cpu_max_percent` is a percentage of. `auto` detects container quota, including fractional quotas. | -| `resource.groups.process.memory.limit.bytes` | `0` | Ceiling for tracked query memory across all groups, `0` for none. Every group limit is capped by it. | -| `resource.groups.queue.timeout.millis` | `30000` | Default admission queue timeout for groups that do not set `queue_timeout`. | +| Property | Default | Meaning | +| -------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | +| `resource.groups.enabled` | `true` | Set to `false` to disable resource group enforcement. Existing single-query memory limits still apply. | +| `resource.groups.process.memory.limit.bytes` | `0` | Ceiling for tracked query memory across all groups, `0` for none. Every group limit is capped by it. | Turning the feature off is a restart with `resource.groups.enabled=false`. Definitions and mappings stay in the catalog, so nothing is lost and the @@ -120,10 +138,10 @@ CREATE RESOURCE GROUP analytics; CREATE RESOURCE GROUP IF NOT EXISTS analytics WITH (cpu_weight = 300); -ALTER RESOURCE GROUP analytics SET (cpu_weight = 300, cpu_max_percent = 25.5); +ALTER RESOURCE GROUP analytics SET (cpu_weight = 300, max_active_queries = 8); -- Clear parameters so they fall back to the instance defaults again. -ALTER RESOURCE GROUP analytics RESET (memory_limit, cpu_max_percent); +ALTER RESOURCE GROUP analytics RESET (memory_limit, max_active_queries); ALTER RESOURCE GROUP analytics RENAME TO reporting; @@ -136,7 +154,7 @@ cancel existing queries at the moment `ALTER` runs: | Change | Effect on existing work | | ---------------------- | ------------------------------------------------------------------------------------------------- | -| CPU weight or cap | Subsequent scheduling uses the new policy; issued CPU grants are settled normally | +| CPU weight | Subsequent scheduling uses the new policy | | Active-query limit | Existing slots are retained; subsequent admission, including a resumed cursor, uses the new limit | | Queue limit or timeout | New admission requests use the new settings; an already queued request keeps its deadline | | Group memory limit | Subsequent allocations check the new budget; existing memory is released normally | @@ -175,8 +193,9 @@ ALTER GROUP analysts UNSET RESOURCE GROUP; `MAPPING PRIORITY` is a non-negative integer and applies only to ACL group mappings, because a user can belong to several ACL groups. The highest priority -wins; ties go to the most recent mapping. It defaults to 0 and is rejected on -user and service account mappings, which are one-to-one. +wins; if two ACL groups tie, the mapping to the resource group that was created +first wins, so give them distinct priorities when the order matters. It defaults +to 0 and is rejected on user and service account mappings, which are one-to-one. Resolution order for a query is: direct mapping on the principal, then the highest-priority mapping among the user's ACL groups, then `DEFAULT`. Service @@ -187,14 +206,13 @@ accounts do not inherit ACL group mappings. All parameters are optional. An unset parameter is not "unlimited" in every case: it falls back to the instance default shown here. -| Parameter | Accepted values | Unset behaviour | -| -------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------- | -| `cpu_weight` | integer, 1 to 10000 | 100 | -| `cpu_max_percent` | 0.01 to 100, at most two decimals | no cap | -| `max_active_queries` | integer, 1 or more | unlimited | -| `max_queued_queries` | integer, 0 or more | unlimited | -| `queue_timeout` | a positive whole number of milliseconds, or a duration such as `'15s'`, `'2m'` | `resource.groups.queue.timeout.millis` | -| `memory_limit` | a positive byte size, plain or suffixed such as `'8G'` | no group ceiling; other memory limits still apply | +| Parameter | Accepted values | Unset behaviour | +| -------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------- | +| `cpu_weight` | integer, 1 to 10000 | 100 | +| `max_active_queries` | integer, 1 or more | unlimited | +| `max_queued_queries` | integer, 0 or more | unlimited | +| `queue_timeout` | a positive whole number of milliseconds, or a duration such as `'15s'`, `'2m'` | 30 seconds | +| `memory_limit` | a byte size, plain or suffixed such as `'8G'`, or `0` or `UNLIMITED` for no group ceiling | no group ceiling; other memory limits still apply | `memory_limit` is the budget for everything the group runs at once. Where the instance sets `resource.groups.process.memory.limit.bytes`, the group budget is @@ -207,88 +225,48 @@ A group that does not set `memory_limit` carries no ceiling of its own, and bounded by any existing single-query limit and the process limit. A principal's effective query memory limit takes precedence over the instance default `cairo.query.memory.limit.bytes`; group and process budgets can only lower the -resulting ceiling. Resource groups do not have a separate `query_memory_limit` -policy parameter. +resulting ceiling. To remove a group memory ceiling, use -`ALTER RESOURCE GROUP reporting RESET (memory_limit)`. Setting the SQL parameter -to `0` is invalid; `0` means unlimited for the instance process-memory property. -Accounting continues when limits are unlimited. +`ALTER RESOURCE GROUP reporting RESET (memory_limit)` or set `memory_limit` to +`0` or `UNLIMITED`; `0` matches the instance process-memory property. Accounting +continues when limits are unlimited. -Two examples of what the values mean in practice: +An example of what a weight means in practice: ```questdb-sql -- A share: reporting gets a third of query CPU when DEFAULT also has work, -- and all of it when DEFAULT is idle. CREATE RESOURCE GROUP reporting WITH (cpu_weight = 50); - --- A ceiling: exports never average more than a quarter of instance CPU, --- even when the instance is otherwise idle. -CREATE RESOURCE GROUP exports WITH (cpu_max_percent = 25); ``` -Use `cpu_weight` to decide who wins under contention, and `cpu_max_percent` to -leave headroom for work that resource groups do not manage, such as ingestion -and WAL apply. Setting a cap on a group also switches the whole instance to -managed scheduling while that group has queries. +Weights arbitrate only between groups that have work at the same time; they +never hold CPU back from a group that is alone. ## Common scenarios ### The instance stops answering while CPU looks idle -Every HTTP or PGWire worker is occupied by a long query, new requests are not +Every HTTP or PGWire worker is occupied by a large query, new requests are not picked up, and instance CPU is low because those queries run on one core each. -Clients time out and retry, which produces more of the same queries. A plan such -as a `LATEST ON` over a non-indexed filter is a typical cause: it scans frames -on one thread, so it is slow without ever being CPU-hungry. +Clients time out and retry, which produces more of the same queries. -Four steps. The first is a prerequisite to confirm, the second is what enabling -the feature already gives you, and the last two are policy you choose. +Three steps. The first is a prerequisite to confirm; the other two are policy +you choose. **1. Confirm the SQL pools are Fiber pools.** This is the prerequisite for -everything below. A protocol runs either on its own pool, when its worker count -is above zero, or on the shared network pool. The setting that matters is the -one for the pool it actually uses: - -| Where the protocol runs | Setting to check | -| ---------------------------------------------------- | ------------------------------------- | -| Its own HTTP pool (`http.worker.count` above zero) | `http.worker.fiber.enabled` | -| Its own PGWire pool (`pg.worker.count` above zero) | `pg.worker.fiber.enabled` | -| The shared network pool (worker count zero, default) | `shared.network.worker.fiber.enabled` | - -Parallel query work is separate and follows `shared.query.worker.fiber.enabled` -whenever the shared query pool has workers. A shared query pool set to zero -workers turns parallel SQL off by default and needs no check of its own. - -The first two settings default to `true`, so a dedicated pool is a Fiber pool -unless someone turned it off. `shared.network.worker.fiber.enabled` defaults to -`true` exactly when HTTP or PGWire actually runs there, which is the case out of -the box because both worker counts default to zero. You normally have nothing to -change here; check these only when the instance was tuned by hand. - -After the restart, confirm the feature came up. `SHOW PARAMETERS` must report -`resource.groups.enabled` as `true`, and `questdb_resource_groups_enabled` must -be `1`. If a pool that executes SQL is in legacy mode and resource groups were -left at their default, the feature disables itself and logs the reason. An -explicit `resource.groups.enabled=true` fails startup in that configuration. - -**2. Enabling the feature already frees the workers.** A query yields its worker -at the checkpoints that already make it cancellable, so a long single-threaded -scan releases the worker while it is still running and the instance keeps -accepting connections. This needs no group and no policy, and it holds even when -every query resolves to `DEFAULT`. - -There is no separate switch to verify. Cooperative yielding is on exactly when -resource groups are on, which step 1 already confirmed. Fiber pools on their own -do not produce it: the checkpoints are compiled into every build, but they only -yield while resource groups are enabled. A query that never reaches a checkpoint -still holds its worker, so this does not remove every cause of an unresponsive -instance. - -**3. Separate the workloads so shares apply.** While a single uncapped group -owns every running query, dispatch stays on the unmanaged path and weights have -nothing to arbitrate. Two groups with queries in flight at the same time, or any -group with a `cpu_max_percent`, is what engages weighted scheduling: +everything below; the [requirements](#requirements) list which setting governs +each pool. With the instance running, `SHOW PARAMETERS` must report +`resource.groups.enabled` as `true` and `questdb_resource_groups_enabled` must +be `1`. Anything else means a SQL pool is in legacy mode and the feature turned +itself off; the startup log names the pool. + +**2. Separate the workloads into groups.** With no policy written, queries hold +their workers exactly as they do with the feature disabled. Managed scheduling +engages while two groups have queries in flight at the same time. Under it a +query yields its worker at the checkpoints that already make it cancellable, so +a long single-threaded scan releases the worker while it is still running and +the instance keeps accepting connections, and CPU is split by weight: ```questdb-sql CREATE RESOURCE GROUP dashboards WITH (cpu_weight = 400); @@ -298,7 +276,13 @@ ALTER USER app SET RESOURCE GROUP dashboards; ALTER USER analyst SET RESOURCE GROUP adhoc; ``` -**4. Bound concurrent requests with admission.** +`questdb_resource_groups_cpu_managed_dispatch` reports `1` while managed +scheduling is engaged. A query that started while its group was alone keeps its +worker until it finishes, and a query that never reaches a checkpoint holds its +worker either way, so this does not remove every cause of an unresponsive +instance. + +**3. Bound concurrent requests with admission.** ```questdb-sql ALTER RESOURCE GROUP adhoc SET ( @@ -313,11 +297,9 @@ worker while it waits. The thirteenth fails immediately with `Resource Group admission queue is full`, so a client that keeps resending gets a clear answer in seconds instead of adding to the pile. -Admission directly bounds the number of concurrent queries. A CPU cap bounds -their combined CPU rate and can also slow a single-threaded query: on an 8-core -instance, a 10% cap permits 0.8 cores of CPU. Choose a cap when that rate limit -is useful; it does not replace the admission limits in this scenario. Clients -should use bounded retries with backoff after admission failures. +Admission directly bounds the number of concurrent queries; weights do not, so +the two are complementary. Clients should use bounded retries with backoff after +admission failures. Afterwards the symptom is also diagnosable rather than mysterious. Low instance CPU together with a high `queued_queries` and a rising @@ -328,7 +310,10 @@ group each running query was admitted to. What resource groups do not do here: they do not make the slow plan faster, and they do not bound how long one query may run. Wall-clock limits still come from the instance-wide -[`query.timeout`](/docs/configuration/cairo-engine/#querytimeout). +[`query.timeout`](/docs/configuration/cairo-engine/#querytimeout), and that +clock includes time spent waiting in the admission queue: a query can time out +before it starts, and the error then says +`while queued for Resource Group admission`. ### Dashboards must stay responsive while analysts run heavy queries @@ -342,24 +327,23 @@ CREATE RESOURCE GROUP analysts WITH (cpu_weight = 100); When these are the only competing groups and both can use their shares, weights target a 4:1 split of managed query CPU. Actual use also depends on runnable -work, available parallelism and any CPU caps. When analysts are idle, dashboards -can use the available query CPU. Weights are integers from 1 to 10000 and every -group starts at 100, so a group left alone keeps an equal share against any -group you do not change. +work and available parallelism. When analysts are idle, dashboards can use the +available query CPU. Weights are integers from 1 to 10000 and every group starts +at 100, so a group left alone keeps an equal share against any group you do not +change. -### A background job must never take the whole instance +### A background job must yield to everything else -Use a cap, which applies whether or not anything else is running: +Give it a small weight and a small concurrency limit: ```questdb-sql -CREATE RESOURCE GROUP exports WITH (cpu_max_percent = 20); +CREATE RESOURCE GROUP exports WITH (cpu_weight = 10, max_active_queries = 1); ``` -This also leaves headroom for work resource groups do not manage, such as -ingestion and WAL apply. The cap accepts two decimals, down to `0.01`, and is a -percentage of the -[detected CPU capacity](/docs/configuration/resource-groups/#resourcegroupscpucapacitycores), -not of the host's core count, so it stays correct under a container quota. +Under contention the job receives a tenth of the CPU that a group at the default +weight of 100 receives, and it runs one query at a time. When nothing else has +work it uses the CPU that would otherwise be idle; resource groups do not hold +CPU back from a group that is alone. ### One workload must not exhaust query memory @@ -371,7 +355,8 @@ CREATE RESOURCE GROUP reporting WITH (memory_limit = '8G'); ``` A query that would push the group over its budget fails with -`query memory limit exceeded` and releases what it held. +`query memory limit exceeded` reporting `scope=group`, and releases what it +held. ### An ingestion or automation account runs queries too @@ -400,9 +385,10 @@ ALTER GROUP oncall SET RESOURCE GROUP dashboards MAPPING PRIORITY 20; ``` Someone in both groups resolves to `dashboards`, because the higher priority -wins. If two mappings tie on priority, the more recently created one wins. A -direct mapping on the user beats every group mapping regardless of priority, -which is the way to make one person an exception without touching the groups: +wins. If two mappings tie on priority, the one whose resource group was created +first wins, so keep priorities distinct. A direct mapping on the user beats +every group mapping regardless of priority, which is the way to make one person +an exception without touching the groups: ```questdb-sql ALTER USER lead_analyst SET RESOURCE GROUP dashboards; @@ -422,7 +408,7 @@ with live counters: | `name` | Group name | | `memory_limit_bytes` | Effective group memory budget | | `max_active_queries`, `max_queued_queries`, `queue_timeout_millis` | Effective admission policy | -| `cpu_weight`, `cpu_max_percent` | Effective CPU policy | +| `cpu_weight` | Effective CPU weight | | `active_queries`, `queued_queries` | Live admission state | | `oldest_queue_wait_millis` | How long the longest waiting query has waited | | `memory_used_bytes` | Tracked query memory in use | @@ -430,8 +416,7 @@ with live counters: | `admission_rejections`, `admission_timeouts` | Cumulative queue-full rejections and queue timeouts | `resource_group_mappings()` returns one row per mapping with `principal_type`, -`principal_name`, `principal_generation`, `resource_group_id`, `resource_group`, -`mapping_priority` and `mapping_revision`. +`principal_name`, `resource_group_id`, `resource_group` and `mapping_priority`. `current_resource_group()` returns the calling query's group, which is the quickest way to confirm a mapping from the client's own connection: @@ -473,22 +458,18 @@ questdb_resource_group_memory_bytes{resource_group="reporting"} questdb_resource_group_memory_limit_bytes{resource_group="reporting"} questdb_resource_group_cpu_nanos_total{resource_group="reporting"} questdb_resource_group_cpu_wait_nanos_total{resource_group="reporting"} -questdb_resource_group_cpu_max_percent{resource_group="reporting"} questdb_resource_group_admission_rejections_total{resource_group="reporting"} questdb_resource_group_admission_timeouts_total{resource_group="reporting"} ``` Instance-wide series: -| Metric | Meaning | -| ------------------------------------------------------- | --------------------------------------------------------------------- | -| `questdb_resource_groups_enabled` | 1 when the feature is on | -| `questdb_resource_groups_catalog_current` | 1 when the catalog is current; 0 while a replica is still catching up | -| `questdb_resource_groups_catalog_lag_unmanaged_queries` | Queries that ran unmanaged because the catalog was not current yet | -| `questdb_resource_groups_cpu_capacity_microcores` | Capacity that `cpu_max_percent` applies to | -| `questdb_resource_groups_cpu_capacity_fallback` | 1 when capacity detection failed and the processor count was used | -| `questdb_resource_groups_cpu_managed_dispatch` | 1 while managed CPU scheduling is engaged | -| `questdb_resource_groups_cpu_scheduler_degraded` | 1 when CPU scheduling has degraded to unmanaged | +| Metric | Meaning | +| ------------------------------------------------ | --------------------------------------------------------------------- | +| `questdb_resource_groups_enabled` | 1 when the feature is on | +| `questdb_resource_groups_catalog_current` | 1 when the catalog is current; 0 while a replica is still catching up | +| `questdb_resource_groups_cpu_managed_dispatch` | 1 while managed CPU scheduling is engaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | 1 when CPU scheduling has degraded to unmanaged | Two signals are worth alerting on: a non-zero `questdb_resource_groups_cpu_scheduler_degraded`, which means CPU shares are no @@ -498,52 +479,47 @@ settings are rejecting work the application expects to succeed. ## Errors clients see -| Message | Cause | Usual fix | -| ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| `Resource Group admission queue is full` | The group is at `max_active_queries` and its queue is at `max_queued_queries` | Raise the limits, or let the client retry | -| `Resource Group admission queue timeout` | The query waited longer than `queue_timeout` | Raise `queue_timeout` or `max_active_queries`, or reduce concurrency | -| Either admission error while fetching a later page | A suspended cursor re-enters admission when the client asks for more rows | Adjust admission limits or retry the query with backoff; the failed cursor cannot continue | -| `query memory limit exceeded` | A single-query, group or process memory limit rejected an allocation | Inspect `query_activity().memory_limit` and the group/process budgets; reduce memory use or adjust the relevant limit | -| `Resource Group is referenced by an active principal link` | `DROP RESOURCE GROUP` while principals are still mapped | `UNSET RESOURCE GROUP` on those principals first | -| `built-in Resource Group cannot be dropped` / `cannot be renamed` | `DROP` or `RENAME` on `DEFAULT` | Alter it instead | +| Message | Cause | Usual fix | +| ----------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| `Resource Group admission queue is full` | The group is at `max_active_queries` and its queue is at `max_queued_queries` | Raise the limits, or let the client retry | +| `Resource Group admission queue timeout` | The query waited longer than `queue_timeout` | Raise `queue_timeout` or `max_active_queries`, or reduce concurrency | +| Either admission error while fetching a later page | A suspended cursor re-enters admission when the client asks for more rows | Adjust admission limits or retry the query with backoff; the failed cursor cannot continue | +| `query memory limit exceeded` | A single-query, group or process memory limit rejected an allocation | `scope` in the message names the level: `query`, `group` or `process`. Reduce memory use or raise that limit | +| `Resource Group is assigned to an ACL entity` | `DROP RESOURCE GROUP` while principals are still mapped | `UNSET RESOURCE GROUP` on those principals first | +| `built-in Resource Group cannot be dropped` / `cannot be renamed` | `DROP` or `RENAME` on `DEFAULT` | Alter it instead | ## Troubleshooting **A group's CPU share is not what I configured.** Weights only apply while groups compete. Check `active_queries` on both groups at the same moment: if one -is idle, the other is expected to use everything. Also confirm the work you are -watching is managed at all, since ingestion, WAL apply and view refresh are -outside the feature. `query_activity()` shows the group each running query -belongs to, which is the quickest way to tell whether the load you are watching -is attributed where you expect. - -**A capped group is slower than the cap suggests.** Very small caps release CPU -in pulses. The cap is a rate over roughly a 100 ms window, so a group whose -share works out to less than one 2 ms slice per window waits between slices. For -example, 0.1% of an 8-core instance allows about 8 ms of CPU per second. Small -caps still allow progress, but the waits between slices can substantially -increase latency; there is no special 0.25% cutoff. +is idle, the other is expected to use everything. A query that started while its +group was alone stays outside CPU scheduling until it next suspends or finishes, +so only work that starts or resumes under contention is weighted. Also confirm +the work you are watching is managed at all, since ingestion, WAL apply and view +refresh are outside the feature. `query_activity()` shows the group each running +query belongs to, which is the quickest way to tell whether the load you are +watching is attributed where you expect. **Queries on a fresh replica are not limited.** Until the catalog has -replicated, a replica runs queries unmanaged and counts them in -`questdb_resource_groups_catalog_lag_unmanaged_queries`. The counter stops -growing once `questdb_resource_groups_catalog_current` reaches 1. +replicated, a replica runs queries unmanaged and +`questdb_resource_groups_catalog_current` stays at 0. Queries are managed again +once it reaches 1. **Promotion fails naming the resource group catalog.** With the feature enabled, `SWITCH ROLE TO PRIMARY` does not admit writes over a catalog that is unreadable -or lagging. The node lands in the `UNKNOWN` role and still serves reads; the -server log names `RESOURCE_GROUP_CATALOG_LAGGING` or -`RESOURCE_GROUP_CATALOG_UNAVAILABLE`. Lagging means the replica has not finished -applying the access control transactions the catalog depends on: wait for WAL -apply to catch up and run `SWITCH ROLE TO PRIMARY` again. Unavailable means the -catalog table is missing or its contents cannot be read, and retrying does not +or that the replica has not received yet. The node lands in the `UNKNOWN` role +and still serves reads; the server log names +`RESOURCE_GROUP_CATALOG_UNAVAILABLE` with the reason. When the reason is +`Resource Group catalog table is not locally available`, replication has not +delivered the catalog table yet: wait for it and run `SWITCH ROLE TO PRIMARY` +again. Any other reason means the table cannot be read, and retrying does not help: promote another replica, or restart this node as primary with `resource.groups.enabled=false`, which turns the check into a logged error. See [Refusals and the torn state](/docs/high-availability/failover/#refusals-and-the-torn-state). **Startup fails naming the resource group catalog.** The catalog table cannot be -read while the feature is enabled; the log says -`Resource Group catalog startup validation failed`. Starting with +created or read while the feature is enabled; the startup error names the +Resource Group catalog and the reason. Starting with `resource.groups.enabled=false` logs the condition instead of failing. **Startup fails naming a worker pool.** A pool that executes SQL is in legacy diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index 22c7a4003..d1fbf4003 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -517,15 +517,13 @@ available when resource group enforcement is disabled. **Return value:** a table with these columns: -| Column | Type | Description | -| ---------------------- | --------- | -------------------------------------------------------------------- | -| `principal_type` | `VARCHAR` | `USER`, `GROUP` or `SERVICE_ACCOUNT` | -| `principal_name` | `VARCHAR` | ACL principal name | -| `principal_generation` | `LONG` | Distinguishes a principal from a later recreation of the same name | -| `resource_group_id` | `LONG` | System-assigned identifier of the mapped resource group | -| `resource_group` | `VARCHAR` | Group name | -| `mapping_priority` | `INT` | Priority for ACL group mappings; defaults to `0` | -| `mapping_revision` | `LONG` | Revision used to break equal-priority ties; the higher revision wins | +| Column | Type | Description | +| ------------------- | --------- | -------------------------------------------------------------------------------------------------------------- | +| `principal_type` | `VARCHAR` | `USER`, `GROUP` or `SERVICE_ACCOUNT` | +| `principal_name` | `VARCHAR` | ACL principal name | +| `resource_group_id` | `LONG` | System-assigned identifier of the mapped resource group | +| `resource_group` | `VARCHAR` | Group name | +| `mapping_priority` | `INT` | Priority for ACL group mappings; defaults to `0`. Equal priorities resolve to the resource group created first | ```questdb-sql SELECT principal_type, principal_name, resource_group, mapping_priority @@ -557,7 +555,6 @@ disabled; their runtime counters are zero. | `max_queued_queries` | `INT` | Queue capacity; `2147483647` represents unlimited, and `0` disables queueing | | `queue_timeout_millis` | `LONG` | Effective admission timeout in milliseconds | | `cpu_weight` | `INT` | Relative scheduling weight | -| `cpu_max_percent` | `DOUBLE` | CPU percentage cap; `NULL` when uncapped | | `active_queries` | `LONG` | Queries currently holding admission slots | | `queued_queries` | `LONG` | Queries waiting for admission | | `oldest_queue_wait_millis` | `LONG` | Age of the oldest admission waiter in milliseconds; `0` when none | @@ -574,15 +571,14 @@ ORDER BY name; ``` Counters describe the current runtime and reset on restart or group recreation. -Worker-local memory deltas can be temporarily unpublished. The single uncapped -group dispatch path does not sample CPU, so `cpu_nanos_total` does not cover all -query CPU use. Dropped groups disappear from this table while their existing -queries finish using retained state. +Worker-local memory deltas can be temporarily unpublished. The single-group +dispatch path does not sample CPU, so `cpu_nanos_total` does not cover all query +CPU use. Dropped groups disappear from this table while their existing queries +finish using retained state. For the corresponding [Prometheus metrics](/docs/operations/logging-metrics/#resource-group-metrics), -an uncapped CPU limit is represented by `-1`, whereas SQL returns `NULL`. An -unlimited group memory ceiling is `0` in both interfaces; it does not remove +an unlimited group memory ceiling is `0` in both interfaces; it does not remove principal-specific, instance-default single-query or process memory limits. ## sleep() From abbdc211fba563b2e77fcc3125b25fc3bb11573b Mon Sep 17 00:00:00 2001 From: victor Date: Fri, 11 Sep 2026 17:21:30 +0800 Subject: [PATCH 06/11] update docs --- documentation/concepts/resource-groups.md | 10 ++++++---- documentation/operations/logging-metrics.md | 13 ++++++------ documentation/operations/resource-groups.md | 22 +++++++++++---------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index 15e5628bb..fcf25ed3e 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -56,7 +56,9 @@ Assignment follows the authenticated principal, not the statement: 3. Otherwise the query runs in **DEFAULT**. Service accounts inherit nothing from ACL groups; they are either mapped -directly or they run in DEFAULT. +directly or they run in DEFAULT. A session that assumes a service account keeps +the group of the principal that logged in; the service account's own mapping +applies to sessions that authenticate as that account. The group is resolved once, when the query starts, and stays fixed for the statement's lifetime. Changing a mapping affects statements that start after the @@ -182,9 +184,9 @@ Resource groups are stored in a replicated system catalog, so a read-only replica receives group definitions and mappings through normal replication. - A **fresh replica** that has not yet received the catalog runs queries - unmanaged, exactly as if the feature were disabled, and reports the catalog as - not current. It does not reject queries or serve them under a policy it cannot - see yet. + unmanaged, exactly as if the feature were disabled, and counts them in + `questdb_resource_groups_catalog_lag_unmanaged_queries_total`. It does not + reject queries or serve them under a policy it cannot see yet. - A **replica being promoted** validates the catalog after replication has switched and before writes are admitted. If the old primary predated resource groups and never created the catalog table, the promoted node creates it and diff --git a/documentation/operations/logging-metrics.md b/documentation/operations/logging-metrics.md index e91401a92..46e5a6620 100644 --- a/documentation/operations/logging-metrics.md +++ b/documentation/operations/logging-metrics.md @@ -404,12 +404,13 @@ gauges show published accounting and can lag worker-local deltas. Instance-wide series describe the feature itself: -| Metric | Type | Description | -| ------------------------------------------------ | ----- | --------------------------------------------------------------------- | -| `questdb_resource_groups_enabled` | gauge | `1` when the feature is on | -| `questdb_resource_groups_catalog_current` | gauge | `1` when the group catalog is current; `0` while a replica catches up | -| `questdb_resource_groups_cpu_managed_dispatch` | gauge | `1` while managed CPU scheduling is engaged | -| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` when CPU scheduling has degraded to unmanaged | +| Metric | Type | Description | +| ------------------------------------------------------------- | ------- | --------------------------------------------------------------------- | +| `questdb_resource_groups_enabled` | gauge | `1` when the feature is on | +| `questdb_resource_groups_catalog_current` | gauge | `1` when the group catalog is current; `0` while a replica catches up | +| `questdb_resource_groups_catalog_lag_unmanaged_queries_total` | counter | Queries that ran unmanaged because the catalog was not current yet | +| `questdb_resource_groups_cpu_managed_dispatch` | gauge | `1` while managed CPU scheduling is engaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` when CPU scheduling has degraded to unmanaged | A non-zero `questdb_resource_groups_cpu_scheduler_degraded` means CPU shares are no longer enforced until the instance restarts. Admission and memory limits stay diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 5fefe1285..19096ede4 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -199,7 +199,8 @@ to 0 and is rejected on user and service account mappings, which are one-to-one. Resolution order for a query is: direct mapping on the principal, then the highest-priority mapping among the user's ACL groups, then `DEFAULT`. Service -accounts do not inherit ACL group mappings. +accounts do not inherit ACL group mappings. `ASSUME SERVICE ACCOUNT` does not +change the group: the session keeps the group of the principal that logged in. ## Policy parameters @@ -464,12 +465,13 @@ questdb_resource_group_admission_timeouts_total{resource_group="reporting"} Instance-wide series: -| Metric | Meaning | -| ------------------------------------------------ | --------------------------------------------------------------------- | -| `questdb_resource_groups_enabled` | 1 when the feature is on | -| `questdb_resource_groups_catalog_current` | 1 when the catalog is current; 0 while a replica is still catching up | -| `questdb_resource_groups_cpu_managed_dispatch` | 1 while managed CPU scheduling is engaged | -| `questdb_resource_groups_cpu_scheduler_degraded` | 1 when CPU scheduling has degraded to unmanaged | +| Metric | Meaning | +| ------------------------------------------------------------- | --------------------------------------------------------------------- | +| `questdb_resource_groups_enabled` | 1 when the feature is on | +| `questdb_resource_groups_catalog_current` | 1 when the catalog is current; 0 while a replica is still catching up | +| `questdb_resource_groups_catalog_lag_unmanaged_queries_total` | Queries that ran unmanaged because the catalog was not current yet | +| `questdb_resource_groups_cpu_managed_dispatch` | 1 while managed CPU scheduling is engaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | 1 when CPU scheduling has degraded to unmanaged | Two signals are worth alerting on: a non-zero `questdb_resource_groups_cpu_scheduler_degraded`, which means CPU shares are no @@ -501,9 +503,9 @@ query belongs to, which is the quickest way to tell whether the load you are watching is attributed where you expect. **Queries on a fresh replica are not limited.** Until the catalog has -replicated, a replica runs queries unmanaged and -`questdb_resource_groups_catalog_current` stays at 0. Queries are managed again -once it reaches 1. +replicated, a replica runs queries unmanaged and counts them in +`questdb_resource_groups_catalog_lag_unmanaged_queries_total`. The counter stops +growing once `questdb_resource_groups_catalog_current` reaches 1. **Promotion fails naming the resource group catalog.** With the feature enabled, `SWITCH ROLE TO PRIMARY` does not admit writes over a catalog that is unreadable From f5fc7e21411661917a8189cbbe9f17dc08a58dd0 Mon Sep 17 00:00:00 2001 From: victor Date: Fri, 11 Sep 2026 18:07:14 +0800 Subject: [PATCH 07/11] update docs --- documentation/operations/resource-groups.md | 18 +-- documentation/query/functions/meta.md | 29 ----- documentation/query/sql/show.md | 133 ++++++++++++-------- 3 files changed, 89 insertions(+), 91 deletions(-) diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 19096ede4..2d7585281 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -61,7 +61,8 @@ Verify: SELECT name, cpu_weight, max_active_queries, active_queries, queued_queries FROM resource_groups(); -SELECT * FROM resource_group_mappings(); +SELECT name, resource_group FROM (SHOW USERS); +SELECT name, resource_group, resource_group_priority FROM (SHOW GROUPS); ``` Reconnect as `reporting_user` or `nightly_batch` and run: @@ -416,8 +417,12 @@ with live counters: | `cpu_nanos_total`, `cpu_wait_nanos_total` | Cumulative CPU consumed and spent waiting for CPU | | `admission_rejections`, `admission_timeouts` | Cumulative queue-full rejections and queue timeouts | -`resource_group_mappings()` returns one row per mapping with `principal_type`, -`principal_name`, `resource_group_id`, `resource_group` and `mapping_priority`. +Mappings are attributes of the principals themselves. `SHOW USERS` and +`SHOW SERVICE ACCOUNTS` carry a `resource_group` column, and `SHOW GROUPS` +carries `resource_group` and `resource_group_priority`; all are `NULL` for an +unmapped principal. Each statement can be used as a subquery, so +`SELECT name FROM (SHOW GROUPS) WHERE resource_group = 'reporting'` lists the +ACL groups mapped to one resource group. `current_resource_group()` returns the calling query's group, which is the quickest way to confirm a mapping from the client's own connection: @@ -429,10 +434,9 @@ SELECT current_resource_group(); It returns `NULL` when that execution is unmanaged, including when the feature is disabled or a replica's group catalog is not ready. See the [function reference](/docs/query/functions/meta/#current_resource_group) for -permissions and return values, and the references for -[`resource_groups()`](/docs/query/functions/meta/#resource_groups) and -[`resource_group_mappings()`](/docs/query/functions/meta/#resource_group_mappings) -for complete schemas. +permissions and return values, and the +[`resource_groups()`](/docs/query/functions/meta/#resource_groups) reference for +its complete schema. `query_activity()` carries a `resource_group` column, so you can see which group each running query was admitted to. It is `NULL` for executions that resource diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index d1fbf4003..5a259a46e 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -506,35 +506,6 @@ Edit `server.conf` and run `reload_config`: SELECT reload_config(); ``` -## resource_group_mappings - -_QuestDB Enterprise only. Requires administrator rights._ - -Returns the principal mappings in the resource group catalog. Definitions remain -available when resource group enforcement is disabled. - -**Arguments:** none. - -**Return value:** a table with these columns: - -| Column | Type | Description | -| ------------------- | --------- | -------------------------------------------------------------------------------------------------------------- | -| `principal_type` | `VARCHAR` | `USER`, `GROUP` or `SERVICE_ACCOUNT` | -| `principal_name` | `VARCHAR` | ACL principal name | -| `resource_group_id` | `LONG` | System-assigned identifier of the mapped resource group | -| `resource_group` | `VARCHAR` | Group name | -| `mapping_priority` | `INT` | Priority for ACL group mappings; defaults to `0`. Equal priorities resolve to the resource group created first | - -```questdb-sql -SELECT principal_type, principal_name, resource_group, mapping_priority -FROM resource_group_mappings() -ORDER BY principal_type, principal_name; -``` - -This lists mappings rather than expanding inherited assignments into one row per -user. Use `current_resource_group()` from a user's own session to confirm the -resolved assignment. - ## resource_groups _QuestDB Enterprise only. Requires administrator rights._ diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index 533ba5a33..65752b3b9 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -36,8 +36,8 @@ SHOW { COLUMNS FROM tableName - `SHOW COLUMNS` returns all the columns and their metadata for the selected table. -- `SHOW CREATE DATABASE` returns DDL statements that recreate every object - in the database, one per row, ordered so dependencies come first. +- `SHOW CREATE DATABASE` returns DDL statements that recreate every object in + the database, one per row, ordered so dependencies come first. - `SHOW CREATE LIVE VIEW` returns a DDL query that allows you to recreate a live view. - `SHOW CREATE MATERIALIZED VIEW` returns a DDL query that allows you to @@ -45,7 +45,7 @@ SHOW { COLUMNS FROM tableName - `SHOW CREATE TABLE` returns a DDL query that allows you to recreate the table. - `SHOW CREATE VIEW` returns a DDL query that allows you to recreate a view. - `SHOW GROUPS` shows all groups the user belongs or all groups in the system - (enterprise-only) + (enterprise-only) - `SHOW PARAMETERS` shows configuration keys and their matching `env_var_name`, their values and the source of the value - `SHOW PARTITIONS` returns the partition information for the selected table. @@ -67,6 +67,7 @@ SHOW { COLUMNS FROM tableName SHOW COLUMNS FROM trades; ``` + | column | type | indexed | indexBlockCapacity | symbolCached | symbolCapacity | symbolTableSize | designated | upsertKey | indexType | indexInclude | | --------- | --------- | ------- | ------------------ | ------------ | -------------- | --------------- | ---------- | --------- | --------- | ------------ | | symbol | SYMBOL | false | 0 | true | 256 | 42 | false | false | | | @@ -76,10 +77,10 @@ SHOW COLUMNS FROM trades; | timestamp | TIMESTAMP | false | 0 | false | 0 | 0 | true | false | | | The `indexType` column shows the index type (`POSTING`, `POSTING DELTA`, -`POSTING EF`, `BITMAP`, or empty for non-indexed columns). The -`indexInclude` column lists the names of columns included in a -[posting index's](/docs/concepts/deep-dive/posting-index/) covering -sidecar, as a comma-separated string. +`POSTING EF`, `BITMAP`, or empty for non-indexed columns). The `indexInclude` +column lists the names of columns included in a +[posting index's](/docs/concepts/deep-dive/posting-index/) covering sidecar, as +a comma-separated string. ### SHOW CREATE DATABASE @@ -110,10 +111,9 @@ all valid. Called without a clause, the statement dumps the whole database: SHOW CREATE DATABASE; ``` -The result set has a single `ddl` column with one self-contained statement -per row. Run against a database holding the -[demo](https://demo.questdb.io) tables and materialized views, it returns one -row per object: +The result set has a single `ddl` column with one self-contained statement per +row. Run against a database holding the [demo](https://demo.questdb.io) tables +and materialized views, it returns one row per object: | ddl | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -198,10 +198,9 @@ statements carry no password or token, so set these after replaying the dump. The Enterprise ACL categories are `USERS`, `GROUPS`, `SERVICE_ACCOUNTS`, and `PERMISSIONS`, grouped by the `ACL` umbrella. Each requires the matching `LIST` -or `USER DETAILS` permission, -while the schema categories need no access control permission, so a user with -only `SELECT` can still dump the structure. When access control is disabled the -command degrades to a schema-only dump. +or `USER DETAILS` permission, while the schema categories need no access control +permission, so a user with only `SELECT` can still dump the structure. When +access control is disabled the command degrades to a schema-only dump. ### SHOW CREATE LIVE VIEW @@ -237,11 +236,12 @@ materialized view, including its base table, refresh strategy, and partitioning. SHOW CREATE TABLE trades; ``` -| ddl | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ddl | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | CREATE TABLE trades (symbol SYMBOL CAPACITY 256 CACHE, side SYMBOL CAPACITY 256 CACHE, price DOUBLE, amount DOUBLE, timestamp TIMESTAMP) timestamp(timestamp) PARTITION BY DAY WITH maxUncommittedRows=500000, o3MaxLag=600000000us; | -This is printed with formatting, so when pasted into a text editor that support formatting characters, you will see: +This is printed with formatting, so when pasted into a text editor that support +formatting characters, you will see: ```questdb-sql CREATE TABLE trades ( @@ -256,9 +256,9 @@ WITH maxUncommittedRows=500000, o3MaxLag=600000000us; #### Posting index with covering columns -When a symbol column has a posting index with `INCLUDE`, the DDL reflects -the index type and covered columns. The designated timestamp is appended -to the `INCLUDE` list automatically, so a table created with +When a symbol column has a posting index with `INCLUDE`, the DDL reflects the +index type and covered columns. The designated timestamp is appended to the +`INCLUDE` list automatically, so a table created with `INCLUDE (price, exchange)` round-trips as `INCLUDE (price, exchange, timestamp)`: @@ -313,7 +313,8 @@ policy is not shown in `SHOW CREATE TABLE`. See #### Enterprise variant -[QuestDB Enterprise](/enterprise/) will include an additional `OWNED BY` clause populated with the current user. +[QuestDB Enterprise](/enterprise/) will include an additional `OWNED BY` clause +populated with the current user. For example, @@ -331,8 +332,8 @@ OWNED BY 'admin'; This clause assigns permissions for the table to that user. -If permissions should be assigned to a different user, -please modify this clause appropriately. +If permissions should be assigned to a different user, please modify this clause +appropriately. ### SHOW CREATE VIEW @@ -344,8 +345,8 @@ SHOW CREATE VIEW my_view; | ---------------------------------------------------------------- | | CREATE VIEW 'my_view' AS (SELECT ts, symbol, price FROM trades); | -This returns the `CREATE VIEW` statement that would recreate the view, -including any `DECLARE` parameters if the view is parameterized. +This returns the `CREATE VIEW` statement that would recreate the view, including +any `DECLARE` parameters if the view is parameterized. ### SHOW GROUPS @@ -355,15 +356,23 @@ _Enterprise only._ SHOW GROUPS; ``` -or +| name | external_alias | memory_limit | resource_group | resource_group_priority | +| ---------- | -------------- | ------------ | -------------- | ----------------------- | +| management | | null | reporting | 10 | +| analysts | analysts-sso | 1073741824 | null | null | + +`memory_limit` is the group's own query memory limit in bytes, `NULL` when none +is set. `resource_group` and `resource_group_priority` are the group's +[resource group](/docs/operations/resource-groups/#mapping-principals) mapping, +`NULL` when the group is not mapped. ```questdb-sql SHOW GROUPS john; ``` -| name | -| ---------- | -| management | +| name | external_alias | memory_limit | +| ---------- | -------------- | ------------ | +| management | | null | ### SHOW PARAMETERS @@ -378,18 +387,18 @@ The output demonstrates: - `value`: the current value of the key - `value_source`: how the value is set (default, conf or env) - `sensitive`: if it is a sensitive value (passwords) -- `reloadable`: if the value can be [reloaded without a server restart](/docs/configuration/overview/#reloadable-settings) - -| property_path | env_var_name | value | value_source | sensitive | reloadable | -| -------------------------------------------- | ------------------------------------------------ | ------ | ------------ | --------- | ---------- | -| http.min.net.connection.limit | QDB_HTTP_MIN_NET_CONNECTION_LIMIT | 64 | default | false | false | -| line.http.enabled | QDB_LINE_HTTP_ENABLED | true | default | false | false | -| cairo.parquet.export.row.group.size | QDB_CAIRO_PARQUET_EXPORT_ROW_GROUP_SIZE | 100000 | default | false | false | -| http.security.interrupt.on.closed.connection | QDB_HTTP_SECURITY_INTERRUPT_ON_CLOSED_CONNECTION | true | conf | false | false | -| pg.readonly.user.enabled | QDB_PG_READONLY_USER_ENABLED | true | conf | false | true | -| pg.readonly.password | QDB_PG_READONLY_PASSWORD | **** | default | true | true | -| http.password | QDB_HTTP_PASSWORD | **** | default | true | false | - +- `reloadable`: if the value can be + [reloaded without a server restart](/docs/configuration/overview/#reloadable-settings) + +| property_path | env_var_name | value | value_source | sensitive | reloadable | +| -------------------------------------------- | ------------------------------------------------ | -------- | ------------ | --------- | ---------- | +| http.min.net.connection.limit | QDB_HTTP_MIN_NET_CONNECTION_LIMIT | 64 | default | false | false | +| line.http.enabled | QDB_LINE_HTTP_ENABLED | true | default | false | false | +| cairo.parquet.export.row.group.size | QDB_CAIRO_PARQUET_EXPORT_ROW_GROUP_SIZE | 100000 | default | false | false | +| http.security.interrupt.on.closed.connection | QDB_HTTP_SECURITY_INTERRUPT_ON_CLOSED_CONNECTION | true | conf | false | false | +| pg.readonly.user.enabled | QDB_PG_READONLY_USER_ENABLED | true | conf | false | true | +| pg.readonly.password | QDB_PG_READONLY_PASSWORD | \*\*\*\* | default | true | true | +| http.password | QDB_HTTP_PASSWORD | \*\*\*\* | default | true | false | You can optionally chain `SHOW PARAMETERS` with other clauses: @@ -424,11 +433,15 @@ See [`table_partitions()`](/docs/query/functions/meta/#table_partitions) for the full column list, including `hasParquetGenerated`, `isParquet`, `parquetFileSize`, `seqTxn`, and `isRemotelyServed`. -`isRemotelyServed` is `true` when the partition's data lives in object storage and is fetched with range reads. See [cold storage](/docs/concepts/cold-storage/) (Enterprise). +`isRemotelyServed` is `true` when the partition's data lives in object storage +and is fetched with range reads. See +[cold storage](/docs/concepts/cold-storage/) (Enterprise). :::note -`seqTxn` and `isRemotelyServed` are appended at the end of the result set. Tools that bind `SHOW PARTITIONS` columns by position rather than by name must account for the two new trailing columns. +`seqTxn` and `isRemotelyServed` are appended at the end of the result set. Tools +that bind `SHOW PARTITIONS` columns by position rather than by name must account +for the two new trailing columns. ::: @@ -524,18 +537,23 @@ _Enterprise only._ SHOW SERVICE ACCOUNTS; ``` -| name | -| ---------- | -| management | -| svc1_admin | +| name | enabled | memory_limit | resource_group | +| ---------- | ------- | ------------ | -------------- | +| management | true | null | null | +| svc1_admin | true | 536870912 | automation | + +`memory_limit` is the account's own query memory limit in bytes and +`resource_group` its +[resource group](/docs/operations/resource-groups/#mapping-principals) mapping, +each `NULL` when not set. ```questdb-sql SHOW SERVICE ACCOUNTS john; ``` -| name | -| ---------- | -| svc1_admin | +| name | grant_option | memory_limit | +| ---------- | ------------ | ------------ | +| svc1_admin | false | 536870912 | ```questdb-sql SHOW SERVICE ACCOUNTS admin_group; @@ -587,10 +605,15 @@ _Enterprise only._ SHOW USERS; ``` -| name | -| ----- | -| admin | -| john | +| name | enabled | memory_limit | resource_group | +| ----- | ------- | ------------ | -------------- | +| admin | true | null | null | +| john | true | 1073741824 | reporting | + +`memory_limit` is the user's effective query memory limit in bytes and +`resource_group` the user's direct +[resource group](/docs/operations/resource-groups/#mapping-principals) mapping; +a user mapped only through an ACL group shows `NULL` here. ## See also From 3239316db25a48c750c7897b3b04bcaa8c6b339c Mon Sep 17 00:00:00 2001 From: victor Date: Mon, 14 Sep 2026 11:50:10 +0800 Subject: [PATCH 08/11] update docs --- documentation/concepts/resource-groups.md | 29 +++++++++++-------- documentation/operations/resource-groups.md | 31 +++++++++++---------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index fcf25ed3e..0f11f5c81 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -116,11 +116,10 @@ usable. ### CPU weight is a share, not a reservation Weights only matter when groups compete. A group that is alone on the instance -uses everything it can, regardless of its weight, and a query that started while -its group was alone keeps running that way until it next suspends or finishes. -When two groups both have work, the scheduler hands out CPU so that measured CPU -divided by `cpu_weight` stays balanced: weights 100 and 50 converge to a 2:1 -split of query CPU. +uses everything it can, regardless of its weight. When two groups both have +work, the scheduler hands out CPU so that measured CPU divided by `cpu_weight` +stays balanced: weights 100 and 50 converge to a 2:1 split of query CPU. A query +that was running alone starts sharing at its next checkpoint. Weights are relative. 100 and 50 are the same as 2 and 1. A group that becomes active starts level with the groups already running, so it neither banks the CPU @@ -164,8 +163,9 @@ A yielded worker runs other queries and, within a bounded window, returns to accepting connections; the query that yielded resumes later. A long single-threaded query that reaches these checkpoints therefore shares its worker before finishing, which keeps the instance responsive while heavy queries run. -Slicing happens only under managed scheduling: while no policy is in force, a -query holds its worker exactly as it does with the feature disabled. +Slicing happens only under managed scheduling, which engages once a second +resource group exists. With `DEFAULT` alone, a query holds its worker exactly as +it does with the feature disabled. Two consequences follow. @@ -209,14 +209,19 @@ replica receives group definitions and mappings through normal replication. ## Cost when nothing competes -While a single group owns all running queries, dispatch is unmanaged: no CPU is +While `DEFAULT` is the only resource group, dispatch is unmanaged: no CPU is sampled, no query yields, and every query holds its worker exactly as it does with the feature disabled. Registration, admission and memory accounting still run, so this is not free, but the cost is a fixed few microseconds per query. -Managed scheduling engages as soon as a second group has work, and disengages -again when it does not. A query that is already running stays unmanaged until it -next suspends or finishes; the new policy applies to queries that start or -resume after the change. +Managed scheduling engages when a second group is created and disengages once +the last other group has been dropped and its queries have finished. A query +that is already running when a group is created stays unmanaged until it next +suspends or finishes; the new policy applies to queries that start or resume +after the change. + +Under managed scheduling a query whose group is alone still keeps its worker: at +each checkpoint it renews its grant in place and yields only when another query +is waiting for its worker. ## See also diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 2d7585281..0bc1375b2 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -263,12 +263,12 @@ each pool. With the instance running, `SHOW PARAMETERS` must report be `1`. Anything else means a SQL pool is in legacy mode and the feature turned itself off; the startup log names the pool. -**2. Separate the workloads into groups.** With no policy written, queries hold -their workers exactly as they do with the feature disabled. Managed scheduling -engages while two groups have queries in flight at the same time. Under it a -query yields its worker at the checkpoints that already make it cancellable, so -a long single-threaded scan releases the worker while it is still running and -the instance keeps accepting connections, and CPU is split by weight: +**2. Separate the workloads into groups.** While `DEFAULT` is the only group, +queries hold their workers exactly as they do with the feature disabled. Managed +scheduling engages as soon as a second group exists. Under it a query yields its +worker at the checkpoints that already make it cancellable, so a long +single-threaded scan releases the worker while it is still running and the +instance keeps accepting connections, and CPU is split by weight: ```questdb-sql CREATE RESOURCE GROUP dashboards WITH (cpu_weight = 400); @@ -279,8 +279,9 @@ ALTER USER analyst SET RESOURCE GROUP adhoc; ``` `questdb_resource_groups_cpu_managed_dispatch` reports `1` while managed -scheduling is engaged. A query that started while its group was alone keeps its -worker until it finishes, and a query that never reaches a checkpoint holds its +scheduling is engaged, which is whenever a group besides `DEFAULT` exists. A +query that started before the second group was created keeps its worker until it +next suspends or finishes, and a query that never reaches a checkpoint holds its worker either way, so this does not remove every cause of an unresponsive instance. @@ -498,13 +499,13 @@ settings are rejecting work the application expects to succeed. **A group's CPU share is not what I configured.** Weights only apply while groups compete. Check `active_queries` on both groups at the same moment: if one -is idle, the other is expected to use everything. A query that started while its -group was alone stays outside CPU scheduling until it next suspends or finishes, -so only work that starts or resumes under contention is weighted. Also confirm -the work you are watching is managed at all, since ingestion, WAL apply and view -refresh are outside the feature. `query_activity()` shows the group each running -query belongs to, which is the quickest way to tell whether the load you are -watching is attributed where you expect. +is idle, the other is expected to use everything. A query that started before +the second group was created stays outside CPU scheduling until it next suspends +or finishes; every other query starts sharing at its next checkpoint. Also +confirm the work you are watching is managed at all, since ingestion, WAL apply +and view refresh are outside the feature. `query_activity()` shows the group +each running query belongs to, which is the quickest way to tell whether the +load you are watching is attributed where you expect. **Queries on a fresh replica are not limited.** Until the catalog has replicated, a replica runs queries unmanaged and counts them in From d010896eda6afe795751ec118ebb8a05b02ded2a Mon Sep 17 00:00:00 2001 From: javier Date: Wed, 16 Sep 2026 19:00:15 +0200 Subject: [PATCH 09/11] docs: add resource group SQL reference, restructure concept and operations pages The resource group statements had no SQL reference at all: CREATE, ALTER and DROP RESOURCE GROUP were undocumented, and the SET/UNSET RESOURCE GROUP clause was missing from ALTER USER, ALTER GROUP and ALTER SERVICE ACCOUNT. All of it lived only as prose on the operations page, which is why that page had grown to carry syntax, parameter tables and error semantics. Adds the three RESOURCE GROUP statement pages, and splits ALTER USER, ALTER GROUP and ALTER SERVICE ACCOUNT into one page per statement form, matching the ALTER TABLE convention. The three former index pages are removed and every inbound link repointed at the specific clause page. Concept page reorganised so principals and managed statements come before the resolution rules, the cooperative scheduling material is stated once instead of four times, and the replication section covers the three catalog-lag cases rather than mixing them with startup and internal faults. Operations page drops the quick start and the tables it duplicated from the reference pages, keeping requirements, limit selection, the six scenarios, inspection, monitoring, errors and troubleshooting. Documents the five Fiber mode properties resource groups depend on, on the configuration pages that own each pool. Corrections found by testing against a running instance: - resource_groups() reports null, not 2147483647, for an unset admission limit, and mixes raw and effective columns - current_resource_group() returns DEFAULT, not null, while CPU scheduling is disengaged; null means the query was never admitted to a group - query_activity().memory_limit is capped by the group budget - administrator rights for group management are specifically SQL ENGINE ADMIN - a service account cannot belong to an ACL group, so it has nothing to inherit - cpu_nanos_total reads 0 while DEFAULT is the only group Also fixes a stale SHOW GROUPS sample on CREATE GROUP, and two pre-existing errors on ALTER SERVICE ACCOUNT that documented ALTER USER syntax. --- documentation/changelog.mdx | 19 +- documentation/concepts/resource-groups.md | 282 +++++++---- documentation/configuration/http-server.md | 16 + .../configuration/materialized-views.md | 13 + .../configuration/postgres-wire-protocol.md | 16 + .../configuration/resource-groups.md | 4 +- documentation/configuration/shared-workers.md | 30 ++ documentation/high-availability/overview.md | 11 + documentation/operations/logging-metrics.md | 19 +- documentation/operations/resource-groups.md | 439 ++++++------------ documentation/query/functions/meta.md | 352 +++++++++++--- .../acl/alter-group-drop-external-alias.md | 48 ++ .../sql/acl/alter-group-set-memory-limit.md | 64 +++ .../sql/acl/alter-group-set-resource-group.md | 84 ++++ .../acl/alter-group-unset-resource-group.md | 66 +++ .../acl/alter-group-with-external-alias.md | 60 +++ documentation/query/sql/acl/alter-group.md | 81 ---- .../query/sql/acl/alter-resource-group.md | 108 +++++ .../acl/alter-service-account-create-token.md | 89 ++++ .../sql/acl/alter-service-account-disable.md | 47 ++ .../acl/alter-service-account-drop-token.md | 63 +++ .../sql/acl/alter-service-account-enable.md | 46 ++ .../alter-service-account-set-memory-limit.md | 65 +++ ...lter-service-account-set-resource-group.md | 67 +++ ...er-service-account-unset-resource-group.md | 62 +++ .../alter-service-account-with-no-password.md | 58 +++ .../alter-service-account-with-password.md | 53 +++ .../query/sql/acl/alter-service-account.md | 200 -------- .../query/sql/acl/alter-user-create-token.md | 85 ++++ .../query/sql/acl/alter-user-disable.md | 46 ++ .../query/sql/acl/alter-user-drop-token.md | 63 +++ .../query/sql/acl/alter-user-enable.md | 46 ++ .../sql/acl/alter-user-set-memory-limit.md | 65 +++ .../sql/acl/alter-user-set-resource-group.md | 68 +++ .../acl/alter-user-unset-resource-group.md | 66 +++ .../sql/acl/alter-user-with-no-password.md | 56 +++ .../query/sql/acl/alter-user-with-password.md | 52 +++ documentation/query/sql/acl/alter-user.md | 195 -------- documentation/query/sql/acl/create-group.md | 20 +- .../query/sql/acl/create-resource-group.md | 111 +++++ .../query/sql/acl/create-service-account.md | 2 +- documentation/query/sql/acl/create-user.md | 6 +- .../query/sql/acl/drop-resource-group.md | 89 ++++ documentation/query/sql/show.md | 6 +- documentation/query/sql/switch-role.md | 5 + documentation/security/oidc.mdx | 2 +- documentation/security/rbac.md | 38 +- documentation/sidebars.js | 54 ++- 48 files changed, 2549 insertions(+), 988 deletions(-) create mode 100644 documentation/query/sql/acl/alter-group-drop-external-alias.md create mode 100644 documentation/query/sql/acl/alter-group-set-memory-limit.md create mode 100644 documentation/query/sql/acl/alter-group-set-resource-group.md create mode 100644 documentation/query/sql/acl/alter-group-unset-resource-group.md create mode 100644 documentation/query/sql/acl/alter-group-with-external-alias.md delete mode 100644 documentation/query/sql/acl/alter-group.md create mode 100644 documentation/query/sql/acl/alter-resource-group.md create mode 100644 documentation/query/sql/acl/alter-service-account-create-token.md create mode 100644 documentation/query/sql/acl/alter-service-account-disable.md create mode 100644 documentation/query/sql/acl/alter-service-account-drop-token.md create mode 100644 documentation/query/sql/acl/alter-service-account-enable.md create mode 100644 documentation/query/sql/acl/alter-service-account-set-memory-limit.md create mode 100644 documentation/query/sql/acl/alter-service-account-set-resource-group.md create mode 100644 documentation/query/sql/acl/alter-service-account-unset-resource-group.md create mode 100644 documentation/query/sql/acl/alter-service-account-with-no-password.md create mode 100644 documentation/query/sql/acl/alter-service-account-with-password.md delete mode 100644 documentation/query/sql/acl/alter-service-account.md create mode 100644 documentation/query/sql/acl/alter-user-create-token.md create mode 100644 documentation/query/sql/acl/alter-user-disable.md create mode 100644 documentation/query/sql/acl/alter-user-drop-token.md create mode 100644 documentation/query/sql/acl/alter-user-enable.md create mode 100644 documentation/query/sql/acl/alter-user-set-memory-limit.md create mode 100644 documentation/query/sql/acl/alter-user-set-resource-group.md create mode 100644 documentation/query/sql/acl/alter-user-unset-resource-group.md create mode 100644 documentation/query/sql/acl/alter-user-with-no-password.md create mode 100644 documentation/query/sql/acl/alter-user-with-password.md delete mode 100644 documentation/query/sql/acl/alter-user.md create mode 100644 documentation/query/sql/acl/create-resource-group.md create mode 100644 documentation/query/sql/acl/drop-resource-group.md diff --git a/documentation/changelog.mdx b/documentation/changelog.mdx index 1c99cc8d7..c3e923206 100644 --- a/documentation/changelog.mdx +++ b/documentation/changelog.mdx @@ -18,15 +18,28 @@ This page tracks significant updates to the QuestDB documentation. - [Memory limits](/docs/configuration/cairo-engine/#memory-limits) - New section covering the per-query, materialized view refresh, WAL apply, and live view refresh memory limits, what counts toward them, and what happens on a breach, plus the previously undocumented [`cairo.mat.view.max.refresh.retries`](/docs/configuration/materialized-views/#cairomatviewmaxrefreshretries), [`cairo.mat.view.refresh.busy.retry.limit`](/docs/configuration/materialized-views/#cairomatviewrefreshbusyretrylimit), [`cairo.mat.view.refresh.busy.retry.timeout`](/docs/configuration/materialized-views/#cairomatviewrefreshbusyretrytimeout), [`cairo.write.back.off.timeout.on.mem.pressure`](/docs/configuration/cairo-engine/#cairowritebackofftimeoutonmempressure), [`ram.usage.limit.bytes`](/docs/configuration/cairo-engine/#ramusagelimitbytes), and [`ram.usage.limit.percent`](/docs/configuration/cairo-engine/#ramusagelimitpercent) keys - [RBAC memory limits](/docs/security/rbac/#memory-limits) - Per-user, per-group, and per-service-account query memory limits in QuestDB Enterprise: `SET MEMORY LIMIT` on `ALTER USER`, `ALTER GROUP`, and `ALTER SERVICE ACCOUNT`, how limits resolve, the `SET MEMORY LIMIT` permission, and the upgrade migration -- [ALTER GROUP](/docs/query/sql/acl/alter-group/) - New reference page covering `SET MEMORY LIMIT` and external alias mapping +- [Resource groups](/docs/concepts/resource-groups/) - Isolate competing query workloads inside one QuestDB Enterprise instance. Covers who a policy applies to, which statements are managed, and what admission, weighted CPU and memory limits each actually guarantee, including why CPU control is cooperative and what happens while a replica's catalog is behind +- [Configure and use resource groups](/docs/operations/resource-groups/) - Requirements, choosing limits, and six worked scenarios: an instance that stops answering while CPU looks idle, keeping dashboards responsive, throttling a background job, bounding a workload's memory, governing a service account, and sharing one instance between teams, plus inspection, monitoring, client errors and troubleshooting +- [Resource groups configuration](/docs/configuration/resource-groups/) - The `resource.groups.enabled` master switch and `resource.groups.process.memory.limit.bytes` process ceiling, neither reloadable +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/), [ALTER RESOURCE GROUP](/docs/query/sql/acl/alter-resource-group/) and [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) - New reference pages for the policy statements, with the parameter table, the effect of an online change on running queries, and what happens to queries still using a dropped group +- `ALTER USER`, `ALTER GROUP` and `ALTER SERVICE ACCOUNT` are now one page per statement form, matching `ALTER TABLE`. Twenty-three pages covering `ENABLE`, `DISABLE`, `WITH PASSWORD`, `WITH NO PASSWORD`, `CREATE TOKEN`, `DROP TOKEN`, [SET MEMORY LIMIT](/docs/query/sql/acl/alter-group-set-memory-limit/), [SET RESOURCE GROUP](/docs/query/sql/acl/alter-user-set-resource-group/), [UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-user-unset-resource-group/), [WITH EXTERNAL ALIAS](/docs/query/sql/acl/alter-group-with-external-alias/) and `DROP EXTERNAL ALIAS` - [Migrate QuestDB onto the Kubernetes Operator](/docs/enterprise-kubernetes-operator/getting-started/migrate/) - Move an existing Enterprise deployment onto the Operator with a replica-first cutover: restore the source backup, consume replication WAL, then promote after a controlled source drain - [Copy a schema to another instance](/docs/cookbook/operations/copy-schema-between-instances/) - Recreate one instance's tables, views, and materialized views on another from `SHOW CREATE DATABASE`, either by replaying the statements over the REST API or by dumping them to a `.sql` file, with `INCLUDE (SCHEMA)` and `INCLUDE (ACL)` for separating structure from permissions on Enterprise, plus the Web Console schema explorer as a manual alternative and the ordering caveat that comes with it +### Reference + +- Added [`resource_groups()`](/docs/query/functions/meta/#resource_groups), which returns each group's policy alongside its live admission, CPU and memory counters, and [`current_resource_group()`](/docs/query/functions/meta/#current_resource_group), which reports the group a session's queries run in. `current_resource_group()` returns `DEFAULT` for an unmapped principal and `null` only when a query was never admitted to a group +- Added the [resource group metrics](/docs/operations/logging-metrics/#resource-group-metrics): nine Prometheus series per group labelled with `resource_group`, plus five instance-wide series for activation, catalog health, managed dispatch and scheduler degradation +- Added the Fiber mode properties that resource groups depend on: [`http.worker.fiber.enabled`](/docs/configuration/http-server/#httpworkerfiberenabled), [`pg.worker.fiber.enabled`](/docs/configuration/postgres-wire-protocol/#pgworkerfiberenabled), [`shared.network.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharednetworkworkerfiberenabled), [`shared.query.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharedqueryworkerfiberenabled) and [`mat.view.refresh.worker.fiber.enabled`](/docs/configuration/materialized-views/#matviewrefreshworkerfiberenabled), all defaulting to `true` and none reloadable + ### Updated -- [query_activity()](/docs/query/functions/meta/#query_activity) - Documented the `is_wal`, `memory_used`, and `memory_limit` columns +- [SQL ENGINE ADMIN](/docs/security/rbac/#permissions) - Now also gates creating, altering and dropping resource groups, mapping principals to them, and reading `resource_groups()` +- [query.timeout](/docs/configuration/cairo-engine/#querytimeout) - The clock includes time spent queued for resource group admission, so a query can time out before it starts. Over PGWire each `Execute` message restarts it +- [Replication](/docs/high-availability/overview/#resource-groups-in-a-replicated-cluster) and [SWITCH ROLE](/docs/query/sql/switch-role/) - Resource group policies replicate through the WAL pipeline while enforcement stays local, so a lagging replica applies the policy it has, and a replica that has not received the catalog cannot be promoted +- [query_activity()](/docs/query/functions/meta/#query_activity) - Documented the `is_wal`, `memory_used`, and `memory_limit` columns, and added a `resource_group` column showing which group each running query was admitted to. `memory_limit` is now capped by the group budget as well, so a principal with no limit of its own still reports one when its group sets a ceiling - [wal_tables()](/docs/query/functions/meta/#wal_tables) - Documented the `errorTag`, `errorMessage`, and `memoryPressure` columns; `errorTag` reads `OUT OF MEMORY` after a WAL apply memory limit breach -- [SHOW](/docs/query/sql/show/) - `SHOW USERS`, `SHOW GROUPS`, and `SHOW SERVICE ACCOUNTS`, including their filtered forms, gain a trailing `memory_limit` column. Clients that read these results by position need [updating](/docs/security/rbac/#memory-limit-upgrade) +- [SHOW](/docs/query/sql/show/) - `SHOW USERS`, `SHOW GROUPS`, and `SHOW SERVICE ACCOUNTS`, including their filtered forms, gain a trailing `memory_limit` column. Clients that read these results by position need [updating](/docs/security/rbac/#memory-limit-upgrade). The unfiltered forms also gain `resource_group`, and `SHOW GROUPS` additionally `resource_group_priority` - [CREATE GROUP](/docs/query/sql/acl/create-group/) - Documented the `WITH EXTERNAL ALIAS` form that creates a group and its OIDC or LDAP mapping in one statement - [Kafka connector](/docs/connect/message-brokers/kafka/) - Updated for QWP with a quick start, guidance on preventing duplicates and recovering from outages, and migration steps for existing HTTP pipelines - [Kubernetes Operator](/docs/enterprise-kubernetes-operator/) - Refreshed for Operator 0.2.1 across installation, configuration, high availability, backup and restore, known limitations, troubleshooting, and the generated [API reference](/docs/enterprise-kubernetes-operator/reference/api/) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index 0f11f5c81..0b3fece60 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -19,57 +19,88 @@ consume while their queries run. One instance typically serves several workloads at once: dashboards that must answer in milliseconds, an ad-hoc analyst, and a nightly report that scans a year of data. When these workloads compete without resource controls, the report can increase dashboard latency. - -Resource groups control three things at the query execution boundary: - -- **Admission** — how many queries a group may run at once, how many may wait, - and how long they may wait. -- **Weighted CPU** — the share of query CPU a group receives while groups - compete. -- **Memory** — process and group budgets for tracked native query memory. - -The design is cooperative. QuestDB executes query work on shared worker pools, -and resource groups do not create one operating-system thread pool per group. A -query and all of its parallel tasks use the same resource group, while every -worker stays available to every group. - -With no configuration the feature is on and no group policy is in force: - -| Setting | Behaviour | -| ----------------------------------- | ------------------------------------------------------- | -| Feature enabled | Yes; turns itself off when a SQL pool is in legacy mode | -| Group admission | Unlimited active and queued queries | -| Group CPU | Weight 100 | -| Group and process memory budgets | Unlimited unless configured | -| Existing single-query memory limits | Still apply, including principal-specific limits | -| Memory accounting without limits | Remains enabled for tracked native query memory | - -## How a query is assigned to a group - -Assignment follows the authenticated principal, not the statement: - -1. A **direct mapping** on the user or service account wins. -2. Otherwise, for users only, QuestDB looks at the mappings of the ACL groups - the user belongs to and takes the highest `mapping_priority`. If two tie, the - mapping to the resource group that was created first wins, so give the groups - distinct priorities when the order matters. -3. Otherwise the query runs in **DEFAULT**. - -Service accounts inherit nothing from ACL groups; they are either mapped -directly or they run in DEFAULT. A session that assumes a service account keeps -the group of the principal that logged in; the service account's own mapping -applies to sessions that authenticate as that account. +[Common scenarios](/docs/operations/resource-groups/#common-scenarios) works +through that case and five others as policies you can copy. + +Creating one takes two statements: +[`CREATE RESOURCE GROUP`](/docs/query/sql/acl/create-resource-group/) for the +policy, and a mapping that puts principals under it. + +```questdb-sql title="Half the default CPU share, four concurrent queries, and a 2 GiB memory budget" +CREATE RESOURCE GROUP reporting WITH ( + cpu_weight = 50, + max_active_queries = 4, + memory_limit = '2G' +); + +ALTER USER analyst SET RESOURCE GROUP reporting; +``` + +A policy sets three controls, and they differ in how strong a guarantee they +give: + +- [Admission](#admission-is-a-hard-gate) is how many queries a group may run at + once, how many may wait, and how long they may wait. It is an exact limit. +- [Weighted CPU](#cpu-weight-is-a-share-not-a-reservation) is the share of query + CPU a group receives while groups compete. It is a share, not a reservation. +- [Memory](#memory-limits-use-batched-accounting) is the process and group + budgets for tracked native query memory. It is approximate at the margin. + +The design is [cooperative](#how-cooperative-cpu-scheduling-works). QuestDB +executes query work on shared worker pools, and resource groups do not create +one operating-system thread pool per group. A query and all of its parallel +tasks use the same resource group, while every worker stays available to every +group. + +## Who a resource group applies to + +A policy attaches to **principals**, and a principal is one of three things: a +user, an ACL group, or a service account. Assignment follows the authenticated +principal, not the statement. + +```questdb-sql title="The three kinds of principal you can map" +ALTER USER analyst SET RESOURCE GROUP reporting; +ALTER GROUP analysts SET RESOURCE GROUP reporting MAPPING PRIORITY 10; +ALTER SERVICE ACCOUNT ingest_bot SET RESOURCE GROUP reporting; +``` + +Mapping an ACL group covers a team without naming each member. Only users +inherit a mapping this way, since only a user can belong to an ACL group, and a +mapping on the user itself beats the one it would inherit. Each clause is +documented with the statement it belongs to: +[`ALTER USER`](/docs/query/sql/acl/alter-user-set-resource-group/), +[`ALTER GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/) and +[`ALTER SERVICE ACCOUNT`](/docs/query/sql/acl/alter-service-account-set-resource-group/). + +A user can belong to several ACL groups, so each ACL group mapping carries a +priority and the highest one wins: + +```questdb-sql title="A user in both groups resolves to dashboards, the higher priority" +ALTER GROUP analysts SET RESOURCE GROUP adhoc MAPPING PRIORITY 10; +ALTER GROUP oncall SET RESOURCE GROUP dashboards MAPPING PRIORITY 20; +``` + +:::note + +If two ACL groups carry the same priority, the tie breaks on whichever resource +group was created first. That is rarely what anyone intends, so give ACL group +mappings distinct priorities when the order matters. + +::: + +Anything not mapped runs in `DEFAULT`, which always exists and carries no limits +of its own: a CPU weight of 100, unlimited admission, and the instance-wide +memory limits. You can change its policy, but you cannot drop or rename it. + +A session that assumes a service account keeps the group of the principal that +logged in, while the service account's own mapping applies to sessions that +authenticate as that account. The group is resolved once, when the query starts, and stays fixed for the statement's lifetime. Changing a mapping affects statements that start after the change, never one already running. -`DEFAULT` always exists. By default it carries no limits of its own, so unmapped -principals run with a CPU weight of 100, unlimited admission, and the -instance-wide memory limits. You can change its policy, but you cannot drop or -rename it. - -## What is managed +## Which statements are managed Resource groups govern the statements that read data: @@ -94,11 +125,16 @@ caches. Existing process memory protection remains the outer boundary. ## What each control guarantees -The three controls differ in how strong their guarantee is, which matters when -you decide what to configure. - ### Admission is a hard gate +```questdb-sql title="At most four running, 32 more waiting, and no wait longer than 15 seconds" +CREATE RESOURCE GROUP reporting WITH ( + max_active_queries = 4, + max_queued_queries = 32, + queue_timeout = '15s' +); +``` + `max_active_queries` is an exact count. A group at its limit queues the next query until a slot frees, up to `max_queued_queries`; beyond that the query is rejected immediately. A queued query that waits longer than `queue_timeout` @@ -115,6 +151,11 @@ usable. ### CPU weight is a share, not a reservation +```questdb-sql title="Dashboards get twice the CPU of ad-hoc work while both are busy" +CREATE RESOURCE GROUP dashboards WITH (cpu_weight = 100); +CREATE RESOURCE GROUP adhoc WITH (cpu_weight = 50); +``` + Weights only matter when groups compete. A group that is alone on the instance uses everything it can, regardless of its weight. When two groups both have work, the scheduler hands out CPU so that measured CPU divided by `cpu_weight` @@ -131,6 +172,10 @@ there is no promise of equal CPU between individual queries. ### Memory limits use batched accounting +```questdb-sql title="Everything this group runs at once must fit in 8 GiB" +CREATE RESOURCE GROUP reporting WITH (memory_limit = '8G'); +``` + Accounting has three levels: query, group and process. Allocation and release deltas accumulate on the executing worker and are published to the shared counters in batches. Exceeding a checked limit fails the query with @@ -152,7 +197,7 @@ The counters can temporarily omit worker-local deltas, so a group can briefly overshoot its limit by less than 64 KiB per worker running its queries. These budgets are not byte-exact, instantaneous ceilings. -## Why CPU control is cooperative +## How cooperative CPU scheduling works QuestDB does not preempt a running query. The scheduler grants a query a short slice of CPU on a worker and expects it to reach a cooperative checkpoint, which @@ -163,9 +208,6 @@ A yielded worker runs other queries and, within a bounded window, returns to accepting connections; the query that yielded resumes later. A long single-threaded query that reaches these checkpoints therefore shares its worker before finishing, which keeps the instance responsive while heavy queries run. -Slicing happens only under managed scheduling, which engages once a second -resource group exists. With `DEFAULT` alone, a query holds its worker exactly as -it does with the feature disabled. Two consequences follow. @@ -174,57 +216,101 @@ holds its worker, so instantaneous CPU can deviate from the configured share. The CPU actually used is charged either way, so a query that overran repays it and the average is preserved. -A query that cannot reach a checkpoint keeps its worker. While managed -scheduling is engaged its CPU is still charged when the slice ends, but no -cooperative limit can shorten that stretch. - -## Behaviour under failure and on replicas - -Resource groups are stored in a replicated system catalog, so a read-only -replica receives group definitions and mappings through normal replication. - -- A **fresh replica** that has not yet received the catalog runs queries - unmanaged, exactly as if the feature were disabled, and counts them in - `questdb_resource_groups_catalog_lag_unmanaged_queries_total`. It does not - reject queries or serve them under a policy it cannot see yet. -- A **replica being promoted** validates the catalog after replication has - switched and before writes are admitted. If the old primary predated resource - groups and never created the catalog table, the promoted node creates it and - continues. With the feature enabled, a catalog that is unreadable or that the - replica has not received yet refuses the promotion: the switch fails part-way, - the node lands in the `UNKNOWN` role and keeps serving reads as before, and - the log names `RESOURCE_GROUP_CATALOG_UNAVAILABLE` with the reason. Retrying - the switch repeats the check. With the feature disabled the condition is - logged and the promotion proceeds. -- At **startup** an unreadable catalog stops an instance with the feature - enabled from starting, in either role. A lagging catalog does not: the - instance starts and the refresh job catches up. -- If **CPU scheduling** hits an internal fault, it degrades: queries continue to - run without CPU grants, and the condition is visible in metrics until the - instance restarts. Admission and memory limits do not depend on CPU scheduling - and stay enforced. -- An **internal fault in one query** affects only that query. Other queries and - other groups are unaffected, and the CPU it used is still charged to its - group. - -## Cost when nothing competes - -While `DEFAULT` is the only resource group, dispatch is unmanaged: no CPU is -sampled, no query yields, and every query holds its worker exactly as it does -with the feature disabled. Registration, admission and memory accounting still -run, so this is not free, but the cost is a fixed few microseconds per query. -Managed scheduling engages when a second group is created and disengages once -the last other group has been dropped and its queries have finished. A query -that is already running when a group is created stays unmanaged until it next +A query that cannot reach a checkpoint keeps its worker. While CPU scheduling is +engaged its CPU is still charged when the slice ends, but no cooperative limit +can shorten that stretch. + +Faults are contained rather than escalated. If the scheduler itself hits an +internal fault it degrades: queries keep running without CPU grants, admission +and memory limits stay enforced, and +`questdb_resource_groups_cpu_scheduler_degraded` reports `1` until the instance +restarts. A fault in one query affects only that query, leaving other queries +and other groups alone, and its CPU is still charged to its group. + +### When scheduling engages + +Slicing happens only while CPU scheduling is engaged, and that requires a second +resource group to exist. While `DEFAULT` is the only resource group, scheduling +stays disengaged: no CPU is sampled, no query yields, and every query holds its +worker exactly as it does with the feature disabled. Registration, admission and +memory accounting still run, so this is not free, but the cost is a fixed few +microseconds per query. + +Scheduling engages when a second group is created and disengages once the last +other group has been dropped and its queries have finished. A query that is +already running when a group is created stays outside scheduling until it next suspends or finishes; the new policy applies to queries that start or resume after the change. -Under managed scheduling a query whose group is alone still keeps its worker: at -each checkpoint it renews its grant in place and yields only when another query -is waiting for its worker. +Once scheduling is engaged, a query whose group is alone still keeps its worker: +at each checkpoint it renews its grant in place and yields only when another +query is waiting for its worker. + +Queries are assigned to a group throughout, whether or not scheduling is +engaged. Disengaged scheduling means no CPU slicing, not that the feature +stepped aside, so +[`current_resource_group()`](/docs/query/functions/meta/#current_resource_group) +returns `DEFAULT` on such an instance rather than `null`. `null` is reserved for +queries that were never admitted to a group at all, which happens when the +feature is disabled and on a replica whose catalog has not arrived. What does go +quiet is the CPU accounting: `cpu_nanos_total` and `cpu_wait_nanos_total` both +stay at `0` while scheduling is disengaged, because no CPU is sampled and no +query waits for a grant. Admission and memory counters keep working throughout. + +## Replication and catalog lag {#behaviour-under-failure-and-on-replicas} + +Group definitions and mappings live in a replicated system catalog, so a replica +receives them through normal replication. Three things follow while a replica is +behind. + +**A replica that has never received the catalog runs its queries unmanaged.** No +admission slot, no CPU grant, no group memory budget, and +[`current_resource_group()`](/docs/query/functions/meta/#current_resource_group) +returns `null`. They are neither rejected nor quietly run under `DEFAULT`, and +they are counted in +`questdb_resource_groups_catalog_lag_unmanaged_queries_total`. + +**A replica that is behind on a policy change keeps applying the policy it +has.** Once it has the catalog, its queries are managed against the snapshot it +holds, so a group whose weight you just changed keeps the old weight on that +replica until the change arrives. Nothing blocks or errors. + +**A lagging replica cannot be promoted while the feature is enabled.** Promotion +validates the catalog before writes are admitted, because a node about to accept +writes must not enforce a policy it cannot see. The switch is refused and the +node keeps serving reads; wait for replication and retry. With the feature +disabled the condition is logged and the promotion proceeds. A primary that +predated resource groups may never have created the catalog table at all, in +which case the promoted node creates it and continues. + +For the recovery steps, see +[troubleshooting](/docs/operations/resource-groups/#troubleshooting), and for how +a refused switch behaves in general, see +[refusals and the torn state](/docs/high-availability/failover/#refusals-and-the-torn-state). ## See also - [Configure and use resource groups](/docs/operations/resource-groups/) - [Resource groups configuration](/docs/configuration/resource-groups/) + +Managing groups: + +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [ALTER RESOURCE GROUP](/docs/query/sql/acl/alter-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) + +Mapping principals: + +- [ALTER USER SET RESOURCE GROUP](/docs/query/sql/acl/alter-user-set-resource-group/) + and [UNSET](/docs/query/sql/acl/alter-user-unset-resource-group/) +- [ALTER GROUP SET RESOURCE GROUP](/docs/query/sql/acl/alter-group-set-resource-group/) + and [UNSET](/docs/query/sql/acl/alter-group-unset-resource-group/) +- [ALTER SERVICE ACCOUNT SET RESOURCE GROUP](/docs/query/sql/acl/alter-service-account-set-resource-group/) + and [UNSET](/docs/query/sql/acl/alter-service-account-unset-resource-group/) + +Inspecting: + +- [`resource_groups()`](/docs/query/functions/meta/#resource_groups) +- [`current_resource_group()`](/docs/query/functions/meta/#current_resource_group) +- [Resource group metrics](/docs/operations/logging-metrics/#resource-group-metrics) - [Role-based access control](/docs/security/rbac/) diff --git a/documentation/configuration/http-server.md b/documentation/configuration/http-server.md index cb8971ee4..d94598855 100644 --- a/documentation/configuration/http-server.md +++ b/documentation/configuration/http-server.md @@ -63,6 +63,22 @@ worker count. Number of threads in the private HTTP worker pool. When `0`, the HTTP server uses the shared worker pool. Values above `0` enable a private pool. +### http.worker.fiber.enabled + +- **Default**: `true` +- **Reloadable**: no + +Runs the private HTTP worker pool in Fiber mode, where a query can suspend and +release its worker instead of holding it until it finishes. Setting this to +`false` puts the pool in legacy mode. + +This applies only when `http.worker.count` is above `0`. With the default of +`0`, HTTP runs on the shared network pool and +[`shared.network.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharednetworkworkerfiberenabled) +is the setting that governs it instead. QuestDB Enterprise +[resource groups](/docs/concepts/resource-groups/) require Fiber mode on +whichever pool actually serves HTTP. + ### http.worker.haltOnError - **Default**: `false` diff --git a/documentation/configuration/materialized-views.md b/documentation/configuration/materialized-views.md index 92182160b..5a445e983 100644 --- a/documentation/configuration/materialized-views.md +++ b/documentation/configuration/materialized-views.md @@ -85,6 +85,19 @@ Comma-separated list of numerical CPU core indexes. Number of dedicated worker threads assigned to refresh materialized views. When `0`, uses the shared worker pool. +## mat.view.refresh.worker.fiber.enabled + +- **Default**: `true` +- **Reloadable**: no + +Runs the materialized view refresh pool in Fiber mode, where work can suspend +and release its worker instead of holding it until it finishes. Setting this to +`false` puts the pool in legacy mode. + +Unlike the pools that serve user queries, this one is not a prerequisite for +QuestDB Enterprise [resource groups](/docs/concepts/resource-groups/), because +materialized view refresh runs outside them. + ## mat.view.refresh.worker.haltOnError - **Default**: `false` diff --git a/documentation/configuration/postgres-wire-protocol.md b/documentation/configuration/postgres-wire-protocol.md index 129f91507..4d28b700e 100644 --- a/documentation/configuration/postgres-wire-protocol.md +++ b/documentation/configuration/postgres-wire-protocol.md @@ -31,6 +31,22 @@ Comma-separated list of CPU core indexes to pin worker threads to. Example: Number of dedicated worker threads for PostgreSQL wire protocol queries. When `0`, uses the shared worker pool. +### pg.worker.fiber.enabled + +- **Default**: `true` +- **Reloadable**: no + +Runs the dedicated PostgreSQL worker pool in Fiber mode, where a query can +suspend and release its worker instead of holding it until it finishes. Setting +this to `false` puts the pool in legacy mode. + +This applies only when `pg.worker.count` is above `0`. With the default of `0`, +PostgreSQL runs on the shared network pool and +[`shared.network.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharednetworkworkerfiberenabled) +governs it instead. QuestDB Enterprise +[resource groups](/docs/concepts/resource-groups/) require Fiber mode on +whichever pool actually serves PostgreSQL. + ### pg.daemon.pool - **Default**: `true` diff --git a/documentation/configuration/resource-groups.md b/documentation/configuration/resource-groups.md index 6440bf562..838eef819 100644 --- a/documentation/configuration/resource-groups.md +++ b/documentation/configuration/resource-groups.md @@ -22,7 +22,9 @@ None of these settings are reloadable: changing any of them requires a restart. Resource groups also require access control to be enabled (`acl.enabled=true`) before principals can be mapped to a group, and every pool that executes SQL -must run in Fiber mode, which is the default. What happens when a pool is in +must run in Fiber mode, which is the default. The +[requirements](/docs/operations/resource-groups/#requirements) list the Fiber +setting that governs each pool. What happens when a pool is in legacy mode depends on how the feature was turned on. Left at its default, it turns itself off and logs an error naming the pool and the setting to change. Asked for explicitly, it fails startup with the same error, because an explicit diff --git a/documentation/configuration/shared-workers.md b/documentation/configuration/shared-workers.md index e585b5765..be064d648 100644 --- a/documentation/configuration/shared-workers.md +++ b/documentation/configuration/shared-workers.md @@ -45,6 +45,22 @@ Number of worker threads for the network pool, which handles HTTP, PostgreSQL, and ILP server I/O. Increasing this value raises network I/O parallelism at the expense of CPU resources available to queries and writes. +## shared.network.worker.fiber.enabled + +- **Default**: `true` +- **Reloadable**: no + +Runs the network pool in Fiber mode, where a query can suspend and release its +worker instead of holding it until it finishes. Setting this to `false` puts the +pool in legacy mode. + +QuestDB Enterprise [resource groups](/docs/concepts/resource-groups/) require +Fiber mode on every pool that executes SQL. HTTP and PostgreSQL run on this pool +whenever their own worker counts are zero, which is the default, so this is +usually the setting that matters. With the pool in legacy mode, resource groups +left at their default disable themselves at startup and log the pool and setting +responsible, while `resource.groups.enabled=true` set explicitly fails startup. + ## shared.query.worker.affinity - **Default**: none @@ -63,6 +79,20 @@ operations such as filters and group-by. Increasing this value raises query parallelism at the expense of CPU resources available to network I/O and writes. +## shared.query.worker.fiber.enabled + +- **Default**: `true` +- **Reloadable**: no + +Runs the query pool in Fiber mode, where parallel query work can suspend and +release its worker instead of holding it until it finishes. Setting this to +`false` puts the pool in legacy mode. + +QuestDB Enterprise [resource groups](/docs/concepts/resource-groups/) require +Fiber mode here whenever this pool has workers and HTTP or PostgreSQL is +enabled. A query pool set to zero workers turns parallel SQL off and needs no +check of its own. + ## shared.worker.haltOnError - **Default**: `false` diff --git a/documentation/high-availability/overview.md b/documentation/high-availability/overview.md index 46d6a9209..9239f9ab1 100644 --- a/documentation/high-availability/overview.md +++ b/documentation/high-availability/overview.md @@ -132,6 +132,17 @@ The remote stages behave differently from the local ones. [Cold storage](/docs/c See [Operating cold storage](/docs/operations/cold-storage/) for the manager handoff procedure and its preconditions. +## Resource groups in a replicated cluster + +[Resource group](/docs/concepts/resource-groups/) policies and their principal +mappings live in a WAL-backed system catalog, so they replicate to every +instance through the same pipeline as user data. Enforcement then runs locally +on each instance, against the catalog snapshot that instance holds. + +What that means while a replica is behind, including why a lagging replica +cannot be promoted, is covered in +[Replication and catalog lag](/docs/concepts/resource-groups/#behaviour-under-failure-and-on-replicas). + ## Bring Your Own Cloud (BYOC) QuestDB Enterprise can be self-managed or operated by QuestDB's team under the diff --git a/documentation/operations/logging-metrics.md b/documentation/operations/logging-metrics.md index 46e5a6620..b0db855ac 100644 --- a/documentation/operations/logging-metrics.md +++ b/documentation/operations/logging-metrics.md @@ -396,11 +396,18 @@ endpoint exposes one series per group, labelled with `resource_group`: | `questdb_resource_group_admission_rejections_total` | counter | Queries rejected because the queue was full | | `questdb_resource_group_admission_timeouts_total` | counter | Queries that timed out while queued | -The single-group dispatch path does not sample CPU. Consequently, -`cpu_nanos_total` counts CPU measured by managed scheduling, not every query's -CPU consumption. A flat counter does not imply that the group is idle; also -check `questdb_resource_groups_cpu_managed_dispatch` and query activity. Memory -gauges show published accounting and can lag worker-local deltas. +`cpu_nanos_total` counts only CPU that managed scheduling measured, so treat it +as a floor rather than a full account. While `DEFAULT` is the only group, CPU +scheduling is disengaged and nothing is sampled, so the series reads `0` however +busy the instance is. It also undercounts once scheduling is engaged: a query +already running when a second group was created stays outside scheduling until +it next suspends, and a degraded scheduler stops sampling altogether. + +A flat counter therefore does not imply an idle group. Check +`questdb_resource_groups_cpu_managed_dispatch` and +`questdb_resource_groups_cpu_scheduler_degraded` before drawing that conclusion, +along with query activity. Memory gauges show published accounting and can lag +worker-local deltas. Instance-wide series describe the feature itself: @@ -410,7 +417,7 @@ Instance-wide series describe the feature itself: | `questdb_resource_groups_catalog_current` | gauge | `1` when the group catalog is current; `0` while a replica catches up | | `questdb_resource_groups_catalog_lag_unmanaged_queries_total` | counter | Queries that ran unmanaged because the catalog was not current yet | | `questdb_resource_groups_cpu_managed_dispatch` | gauge | `1` while managed CPU scheduling is engaged | -| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` when CPU scheduling has degraded to unmanaged | +| `questdb_resource_groups_cpu_scheduler_degraded` | gauge | `1` after an internal fault has disengaged CPU scheduling | A non-zero `questdb_resource_groups_cpu_scheduler_degraded` means CPU shares are no longer enforced until the instance restarts. Admission and memory limits stay diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 0bc1375b2..806575042 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -13,237 +13,104 @@ import { EnterpriseNote } from "@site/src/components/EnterpriseNote" instance. -This page covers day-to-day use: creating groups, mapping principals, choosing -limits, and watching the result. For what the limits actually guarantee, read -[the concept page](/docs/concepts/resource-groups/) first. - -## Quick start - -This example separates reporting from the default workload and limits its -concurrency and memory. Run it as an administrator on an instance that meets the -[requirements](#requirements). Use unused example names and replace the password -placeholders. Later examples on this page can be adapted independently. - -First create the ACL principals and allow SQL connections: - -```questdb-sql -CREATE GROUP analysts; -GRANT HTTP, PGWIRE TO analysts; -CREATE USER reporting_user WITH PASSWORD ''; -ADD USER reporting_user TO analysts; - -CREATE USER nightly_batch WITH PASSWORD ''; -GRANT HTTP, PGWIRE TO nightly_batch; -``` - -Then create the resource group and mappings: - -```questdb-sql --- 1. Create a group. Unset parameters fall back to the instance defaults. -CREATE RESOURCE GROUP reporting WITH ( - cpu_weight = 50, - max_active_queries = 4, - max_queued_queries = 32, - queue_timeout = '15s', - memory_limit = '2G' -); - --- 2. reporting_user inherits this mapping unless a higher-precedence one applies. -ALTER GROUP analysts SET RESOURCE GROUP reporting MAPPING PRIORITY 10; - --- 3. Map one user directly. A direct mapping beats any ACL group mapping. -ALTER USER nightly_batch SET RESOURCE GROUP reporting; -``` - -Verify: - -```questdb-sql -SELECT name, cpu_weight, max_active_queries, active_queries, queued_queries -FROM resource_groups(); - -SELECT name, resource_group FROM (SHOW USERS); -SELECT name, resource_group, resource_group_priority FROM (SHOW GROUPS); -``` - -Reconnect as `reporting_user` or `nightly_batch` and run: - -```questdb-sql -SELECT current_resource_group(); -``` - -| current_resource_group | -| ---------------------- | -| reporting | - -These grants allow connections. Grant access to the application's tables -separately, as described in [RBAC](/docs/security/rbac/). - -Everything not mapped keeps running in `DEFAULT`, which has a CPU weight of 100. -Against `reporting`'s weight of 50, that is a 2:1 split of query CPU while both -have work. +This page covers running the feature: what an instance needs, how to choose +limits, what to watch, and what to do when something looks wrong. For what the +limits guarantee, read [the concept page](/docs/concepts/resource-groups/). For +statement syntax, see +[`CREATE RESOURCE GROUP`](/docs/query/sql/acl/create-resource-group/) and its +siblings. ## Requirements - QuestDB Enterprise. - Access control enabled (`acl.enabled=true`). Groups can be created without it, but mapping statements require it, since mappings attach to ACL principals. -- The pools that execute SQL must run in Fiber mode, which is the default and - which the feature depends on. On an instance whose pools are in legacy mode, - resource groups left at their default turn themselves off and log an error - naming the pool and the setting to change. Setting - `resource.groups.enabled=true` on such an instance fails startup with that - same error. -- Administrator rights for group management, mappings and instance-wide - inspection. Ordinary users can call `current_resource_group()` to check their - own query's group. - -A protocol runs on its own pool when its worker count is above zero, otherwise -on the shared network pool. The setting that matters is the one for the pool it -actually uses: - -| Where the protocol runs | Setting to check | -| ---------------------------------------------------- | ------------------------------------- | -| Its own HTTP pool (`http.worker.count` above zero) | `http.worker.fiber.enabled` | -| Its own PGWire pool (`pg.worker.count` above zero) | `pg.worker.fiber.enabled` | -| The shared network pool (worker count zero, default) | `shared.network.worker.fiber.enabled` | - -Parallel query work is separate and follows `shared.query.worker.fiber.enabled` -whenever the shared query pool has workers. A shared query pool set to zero -workers turns parallel SQL off by default and needs no check of its own. - -The first two settings default to `true`, so a dedicated pool is a Fiber pool -unless someone turned it off. `shared.network.worker.fiber.enabled` defaults to -`true` exactly when HTTP or PGWire actually runs there, which is the case out of -the box because both worker counts default to zero. Check these only when the -instance was tuned by hand. - -## Configuration - -Resource groups are enabled by default. These are instance-wide settings; the -per-group policy is set in SQL. Each setting is described in full in the +- The worker pools that execute SQL must run in Fiber mode, which is the + default. +- The [`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission for group + management, mappings and instance-wide inspection. Ordinary users need no + permission to call `current_resource_group()` and check their own query's + group. + +Mapping a principal to a resource group grants it nothing by itself. The user +still needs `HTTP` or `PGWIRE` to connect and `SELECT` on the tables it queries, +as described in [RBAC](/docs/security/rbac/). + +### Fiber mode + +Every Fiber setting defaults to `true`, so a stock instance already satisfies +this requirement and there is nothing to check. It matters only on an instance +whose worker pools were tuned by hand. + +If a SQL pool is in legacy mode, resource groups left at their default turn +themselves off at startup and log an error naming the pool and the setting to +change. Setting `resource.groups.enabled=true` explicitly on such an instance +fails startup with that same error. + +Each protocol is governed by the setting for the pool that actually serves it: + +- HTTP and PostgreSQL run on their own pool when `http.worker.count` or + `pg.worker.count` is above zero, governed by + [`http.worker.fiber.enabled`](/docs/configuration/http-server/#httpworkerfiberenabled) + and + [`pg.worker.fiber.enabled`](/docs/configuration/postgres-wire-protocol/#pgworkerfiberenabled). +- With those counts at their default of zero, both run on the shared network + pool, governed by + [`shared.network.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharednetworkworkerfiberenabled). +- Parallel query work follows + [`shared.query.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharedqueryworkerfiberenabled) + whenever the shared query pool has workers. A pool set to zero workers turns + parallel SQL off and needs no check of its own. + +The two instance-wide settings, `resource.groups.enabled` and +`resource.groups.process.memory.limit.bytes`, are described in the [resource groups configuration reference](/docs/configuration/resource-groups/). - -| Property | Default | Meaning | -| -------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------ | -| `resource.groups.enabled` | `true` | Set to `false` to disable resource group enforcement. Existing single-query memory limits still apply. | -| `resource.groups.process.memory.limit.bytes` | `0` | Ceiling for tracked query memory across all groups, `0` for none. Every group limit is capped by it. | - -Turning the feature off is a restart with `resource.groups.enabled=false`. -Definitions and mappings stay in the catalog, so nothing is lost and the -policies apply again when it is re-enabled. - -## Managing groups - -```questdb-sql -CREATE RESOURCE GROUP analytics; - -CREATE RESOURCE GROUP IF NOT EXISTS analytics WITH (cpu_weight = 300); - -ALTER RESOURCE GROUP analytics SET (cpu_weight = 300, max_active_queries = 8); - --- Clear parameters so they fall back to the instance defaults again. -ALTER RESOURCE GROUP analytics RESET (memory_limit, max_active_queries); - -ALTER RESOURCE GROUP analytics RENAME TO reporting; - -DROP RESOURCE GROUP reporting; -DROP RESOURCE GROUP IF EXISTS reporting; -``` - -A group policy change applies online to the shared group budget. It does not -cancel existing queries at the moment `ALTER` runs: - -| Change | Effect on existing work | -| ---------------------- | ------------------------------------------------------------------------------------------------- | -| CPU weight | Subsequent scheduling uses the new policy | -| Active-query limit | Existing slots are retained; subsequent admission, including a resumed cursor, uses the new limit | -| Queue limit or timeout | New admission requests use the new settings; an already queued request keeps its deadline | -| Group memory limit | Subsequent allocations check the new budget; existing memory is released normally | - -Lowering a memory budget below current usage can make subsequent allocations -fail. The principal-specific or instance-default single-query limit is captured -when the query starts; updating the group budget does not replace that limit. -Changing a principal mapping affects new queries only. - -`DROP` is refused while any live principal is still mapped to the group; unmap -them first. Once unmapped, a group can be dropped while queries still use it. It -disappears from `resource_groups()` immediately. Running and queued queries, -including suspended cursors, continue using the deleted group's existing -settings. Their memory still counts towards the process budget. - -Recreating a group with the same name starts fresh usage counters. Queries that -still use the deleted group do not move to the new group or use its settings. -Map principals to the new group to assign their subsequent queries to it. - -`DEFAULT` cannot be dropped or renamed, but it can be altered: - -```questdb-sql -ALTER RESOURCE GROUP DEFAULT SET (max_active_queries = 16); -``` - -## Mapping principals - -```questdb-sql -ALTER USER alice SET RESOURCE GROUP analytics; -ALTER SERVICE ACCOUNT ingest_bot SET RESOURCE GROUP analytics; -ALTER GROUP analysts SET RESOURCE GROUP analytics MAPPING PRIORITY 10; - -ALTER USER alice UNSET RESOURCE GROUP; -ALTER GROUP analysts UNSET RESOURCE GROUP; -``` - -`MAPPING PRIORITY` is a non-negative integer and applies only to ACL group -mappings, because a user can belong to several ACL groups. The highest priority -wins; if two ACL groups tie, the mapping to the resource group that was created -first wins, so give them distinct priorities when the order matters. It defaults -to 0 and is rejected on user and service account mappings, which are one-to-one. - -Resolution order for a query is: direct mapping on the principal, then the -highest-priority mapping among the user's ACL groups, then `DEFAULT`. Service -accounts do not inherit ACL group mappings. `ASSUME SERVICE ACCOUNT` does not -change the group: the session keeps the group of the principal that logged in. - -## Policy parameters - -All parameters are optional. An unset parameter is not "unlimited" in every -case: it falls back to the instance default shown here. - -| Parameter | Accepted values | Unset behaviour | -| -------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------- | -| `cpu_weight` | integer, 1 to 10000 | 100 | -| `max_active_queries` | integer, 1 or more | unlimited | -| `max_queued_queries` | integer, 0 or more | unlimited | -| `queue_timeout` | a positive whole number of milliseconds, or a duration such as `'15s'`, `'2m'` | 30 seconds | -| `memory_limit` | a byte size, plain or suffixed such as `'8G'`, or `0` or `UNLIMITED` for no group ceiling | no group ceiling; other memory limits still apply | - -`memory_limit` is the budget for everything the group runs at once. Where the -instance sets `resource.groups.process.memory.limit.bytes`, the group budget is -capped by it, so a group cannot be granted more than the instance allows. A -group ceiling only lowers what its queries may use; it never raises a limit set -elsewhere. - -A group that does not set `memory_limit` carries no ceiling of its own, and -`resource_groups().memory_limit_bytes` reports `0` for it. Its queries are then -bounded by any existing single-query limit and the process limit. A principal's -effective query memory limit takes precedence over the instance default -`cairo.query.memory.limit.bytes`; group and process budgets can only lower the -resulting ceiling. - -To remove a group memory ceiling, use -`ALTER RESOURCE GROUP reporting RESET (memory_limit)` or set `memory_limit` to -`0` or `UNLIMITED`; `0` matches the instance process-memory property. Accounting -continues when limits are unlimited. - -An example of what a weight means in practice: - -```questdb-sql --- A share: reporting gets a third of query CPU when DEFAULT also has work, --- and all of it when DEFAULT is idle. -CREATE RESOURCE GROUP reporting WITH (cpu_weight = 50); -``` - -Weights arbitrate only between groups that have work at the same time; they -never hold CPU back from a group that is alone. +Neither is reloadable, so turning the feature off is a restart. Definitions and +mappings stay in the catalog either way, so nothing is lost while it is off. + +## Changing a live instance + +Three things are worth knowing before you change policy on a running system: + +- **Changes apply online.** An `ALTER` does not cancel anything running at that + moment. Slots already held are kept, an already queued request keeps its + deadline, and only subsequent allocations check a new memory budget. See + [effect on queries already running](/docs/query/sql/acl/alter-resource-group/#effect-on-queries-already-running). +- **A drop is refused while principals are still mapped.** Unmap them first. Once + unmapped, the group can be dropped while its queries are still running, and + they carry on under the settings it had. See + [what happens to queries still using it](/docs/query/sql/acl/drop-resource-group/#what-happens-to-queries-still-using-it). +- **`DEFAULT` is alterable but cannot be dropped or renamed.** Giving it limits + is how you bound everything that is not explicitly mapped. + +Mapping changes affect queries that start after the change, never one already +running. For the resolution order, including `ASSUME SERVICE ACCOUNT`, see +[who a resource group applies to](/docs/concepts/resource-groups/#who-a-resource-group-applies-to). + +On a replicated cluster a change takes effect on each instance as the catalog +reaches it, so a replica that is behind keeps applying the previous policy. See +[Replication and catalog lag](/docs/concepts/resource-groups/#behaviour-under-failure-and-on-replicas). + +## Choosing limits + +The accepted values and their defaults are in the +[`CREATE RESOURCE GROUP` parameter table](/docs/query/sql/acl/create-resource-group/#parameters). +What matters when picking them: + +- **An unset parameter is not always "unlimited".** `cpu_weight` falls back to + 100 and `queue_timeout` to 30 seconds, while the two admission counts really + are unlimited when unset. +- **Weights are only meaningful relative to other groups.** A group at + `cpu_weight = 50` gets a third of query CPU while `DEFAULT` also has work, and + all of it when `DEFAULT` is idle. Setting a weight on a lone group does + nothing. +- **No group memory ceiling does not mean unlimited memory.** A principal's own + limit, `cairo.query.memory.limit.bytes` and the process budget all still + apply, and a group ceiling only ever lowers the result. See + [memory limits](/docs/concepts/resource-groups/#memory-limits-use-batched-accounting). + +Start permissive, watch the counters in [inspecting](#inspecting), then tighten. +Policy changes apply online, so there is no need to get this right first time. ## Common scenarios @@ -253,22 +120,12 @@ Every HTTP or PGWire worker is occupied by a large query, new requests are not picked up, and instance CPU is low because those queries run on one core each. Clients time out and retry, which produces more of the same queries. -Three steps. The first is a prerequisite to confirm; the other two are policy -you choose. - -**1. Confirm the SQL pools are Fiber pools.** This is the prerequisite for -everything below; the [requirements](#requirements) list which setting governs -each pool. With the instance running, `SHOW PARAMETERS` must report -`resource.groups.enabled` as `true` and `questdb_resource_groups_enabled` must -be `1`. Anything else means a SQL pool is in legacy mode and the feature turned -itself off; the startup log names the pool. - -**2. Separate the workloads into groups.** While `DEFAULT` is the only group, -queries hold their workers exactly as they do with the feature disabled. Managed -scheduling engages as soon as a second group exists. Under it a query yields its +**1. Separate the workloads into groups.** While `DEFAULT` is the only group, +queries hold their workers exactly as they do with the feature disabled. CPU +scheduling engages as soon as a second group exists, and a query then yields its worker at the checkpoints that already make it cancellable, so a long -single-threaded scan releases the worker while it is still running and the -instance keeps accepting connections, and CPU is split by weight: +single-threaded scan releases the worker while it is still running, the instance +keeps accepting connections, and CPU is split by weight: ```questdb-sql CREATE RESOURCE GROUP dashboards WITH (cpu_weight = 400); @@ -278,14 +135,14 @@ ALTER USER app SET RESOURCE GROUP dashboards; ALTER USER analyst SET RESOURCE GROUP adhoc; ``` -`questdb_resource_groups_cpu_managed_dispatch` reports `1` while managed -scheduling is engaged, which is whenever a group besides `DEFAULT` exists. A -query that started before the second group was created keeps its worker until it -next suspends or finishes, and a query that never reaches a checkpoint holds its +`questdb_resource_groups_cpu_managed_dispatch` reports `1` while scheduling is +engaged, which is whenever a group besides `DEFAULT` exists. A query that +started before the second group was created keeps its worker until it next +suspends or finishes, and a query that never reaches a checkpoint holds its worker either way, so this does not remove every cause of an unresponsive instance. -**3. Bound concurrent requests with admission.** +**2. Bound concurrent requests with admission.** ```questdb-sql ALTER RESOURCE GROUP adhoc SET ( @@ -363,10 +220,8 @@ held. ### An ingestion or automation account runs queries too -Service accounts resolve differently from users: they honour a direct mapping, -but they never inherit a mapping from an ACL group. A service account with no -direct mapping runs in `DEFAULT` however its ACL groups are mapped, so map it -explicitly: +A service account cannot belong to an ACL group, so it has no mapping to +inherit. Without a direct mapping it runs in `DEFAULT`, so map it explicitly: ```questdb-sql CREATE RESOURCE GROUP automation WITH (cpu_weight = 50, max_active_queries = 2); @@ -403,20 +258,16 @@ this from the client's own session with `SELECT current_resource_group();`. ## Inspecting -`resource_groups()` returns one row per group, combining the configured policy -with live counters: - -| Column | Meaning | -| ------------------------------------------------------------------ | --------------------------------------------------- | -| `name` | Group name | -| `memory_limit_bytes` | Effective group memory budget | -| `max_active_queries`, `max_queued_queries`, `queue_timeout_millis` | Effective admission policy | -| `cpu_weight` | Effective CPU weight | -| `active_queries`, `queued_queries` | Live admission state | -| `oldest_queue_wait_millis` | How long the longest waiting query has waited | -| `memory_used_bytes` | Tracked query memory in use | -| `cpu_nanos_total`, `cpu_wait_nanos_total` | Cumulative CPU consumed and spent waiting for CPU | -| `admission_rejections`, `admission_timeouts` | Cumulative queue-full rejections and queue timeouts | +[`resource_groups()`](/docs/query/functions/meta/#resource_groups) returns one +row per group, combining the configured policy with live counters. The columns +that matter day to day: + +- `active_queries` and `queued_queries` for live admission state, and + `oldest_queue_wait_millis` for how long the longest waiter has waited. A high + queue with a rising wait means work is held at the gate, not that the machine + is busy. +- `admission_rejections` and `admission_timeouts` for work already turned away. +- `memory_used_bytes` against `memory_limit_bytes` for headroom. Mappings are attributes of the principals themselves. `SHOW USERS` and `SHOW SERVICE ACCOUNTS` carry a `resource_group` column, and `SHOW GROUPS` @@ -432,16 +283,17 @@ quickest way to confirm a mapping from the client's own connection: SELECT current_resource_group(); ``` -It returns `NULL` when that execution is unmanaged, including when the feature -is disabled or a replica's group catalog is not ready. See the +An unmapped principal gets `DEFAULT`, not `NULL`. `NULL` means the query was +never admitted to a group at all, which happens when the feature is disabled and +on a replica whose group catalog is not ready. Note that `DEFAULT` is returned +even while CPU scheduling is disengaged, because assignment and CPU slicing are +separate things. See the [function reference](/docs/query/functions/meta/#current_resource_group) for -permissions and return values, and the -[`resource_groups()`](/docs/query/functions/meta/#resource_groups) reference for -its complete schema. +permissions and return values. `query_activity()` carries a `resource_group` column, so you can see which group -each running query was admitted to. It is `NULL` for executions that resource -groups do not manage: +each running query was admitted to. It follows the same rule, `DEFAULT` for an +unmapped principal and `NULL` only when no group was assigned: ```questdb-sql SELECT resource_group, username, query_start, query @@ -452,31 +304,11 @@ ORDER BY query_start; ## Monitoring -The Prometheus endpoint exposes one series per group, labelled with -`resource_group`. The full list lives in the -[metrics reference](/docs/operations/logging-metrics/#resource-group-metrics): - -``` -questdb_resource_group_active_queries{resource_group="reporting"} -questdb_resource_group_queued_queries{resource_group="reporting"} -questdb_resource_group_oldest_queue_wait_millis{resource_group="reporting"} -questdb_resource_group_memory_bytes{resource_group="reporting"} -questdb_resource_group_memory_limit_bytes{resource_group="reporting"} -questdb_resource_group_cpu_nanos_total{resource_group="reporting"} -questdb_resource_group_cpu_wait_nanos_total{resource_group="reporting"} -questdb_resource_group_admission_rejections_total{resource_group="reporting"} -questdb_resource_group_admission_timeouts_total{resource_group="reporting"} -``` - -Instance-wide series: - -| Metric | Meaning | -| ------------------------------------------------------------- | --------------------------------------------------------------------- | -| `questdb_resource_groups_enabled` | 1 when the feature is on | -| `questdb_resource_groups_catalog_current` | 1 when the catalog is current; 0 while a replica is still catching up | -| `questdb_resource_groups_catalog_lag_unmanaged_queries_total` | Queries that ran unmanaged because the catalog was not current yet | -| `questdb_resource_groups_cpu_managed_dispatch` | 1 while managed CPU scheduling is engaged | -| `questdb_resource_groups_cpu_scheduler_degraded` | 1 when CPU scheduling has degraded to unmanaged | +Metrics require `metrics.enabled=true`, which is off by default. The Prometheus +endpoint then exposes one series per group, labelled with `resource_group`, plus +five instance-wide series describing the feature itself. Every name, type and +meaning is in the +[metrics reference](/docs/operations/logging-metrics/#resource-group-metrics). Two signals are worth alerting on: a non-zero `questdb_resource_groups_cpu_scheduler_degraded`, which means CPU shares are no @@ -492,7 +324,7 @@ settings are rejecting work the application expects to succeed. | `Resource Group admission queue timeout` | The query waited longer than `queue_timeout` | Raise `queue_timeout` or `max_active_queries`, or reduce concurrency | | Either admission error while fetching a later page | A suspended cursor re-enters admission when the client asks for more rows | Adjust admission limits or retry the query with backoff; the failed cursor cannot continue | | `query memory limit exceeded` | A single-query, group or process memory limit rejected an allocation | `scope` in the message names the level: `query`, `group` or `process`. Reduce memory use or raise that limit | -| `Resource Group is assigned to an ACL entity` | `DROP RESOURCE GROUP` while principals are still mapped | `UNSET RESOURCE GROUP` on those principals first | +| `Resource Group is assigned to an ACL entity` | `DROP RESOURCE GROUP` while principals are still mapped | `UNSET RESOURCE GROUP` on those principals first; the message names one of them in `[entity=...]` | | `built-in Resource Group cannot be dropped` / `cannot be renamed` | `DROP` or `RENAME` on `DEFAULT` | Alter it instead | ## Troubleshooting @@ -537,24 +369,31 @@ instance start with resource groups off. **The feature is off although the default is on.** Check the log at startup for an error naming a worker pool, and check `SHOW PARAMETERS` for the value that took effect. A legacy SQL pool turns the feature off when the property is left -unset. +unset. `SHOW PARAMETERS` reporting `resource.groups.enabled` as `true`, together +with `questdb_resource_groups_enabled` at `1`, confirms the feature is actually +running. ## Limitations - Only query statements are managed. See - [what is managed](/docs/concepts/resource-groups/#what-is-managed). + [which statements are managed](/docs/concepts/resource-groups/#which-statements-are-managed). - Memory accounting covers tracked native query memory, not JVM heap, resident - set size or memory-mapped table pages. + set size or memory-mapped table pages. See + [memory limits](/docs/concepts/resource-groups/#memory-limits-use-batched-accounting). - CPU control is cooperative, so shares hold over a short window rather than instantaneously, and a query that cannot reach a cooperative checkpoint holds - its worker until it does. + its worker until it does. See + [cooperative CPU scheduling](/docs/concepts/resource-groups/#how-cooperative-cpu-scheduling-works). - Principal mapping changes affect new queries. Group budgets change online; dropping a group retains its runtime state for existing queries until they - finish. + finish. See [changing a live instance](#changing-a-live-instance). ## See also - [Resource groups concept](/docs/concepts/resource-groups/) - [Resource groups configuration](/docs/configuration/resource-groups/) +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [ALTER RESOURCE GROUP](/docs/query/sql/acl/alter-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) - [Role-based access control](/docs/security/rbac/) - [Logging and metrics](/docs/operations/logging-metrics/) diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index 1b3f3e5fb..dfa141760 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -36,7 +36,7 @@ SELECT build(); | ---------------------------------------------------------------------------------------------- | | Build Information: QuestDB 9.0.0, JDK 25, Commit Hash 460b817b0a3705c5633619a8ef9efb5163f1569c | -## current_data_id() +## current_data_id Returns the data ID: a UUID that identifies this database, generated on first start and stored in the database root. Replication and restore use it to tell @@ -61,56 +61,141 @@ SELECT current_data_id(); | ------------------------------------ | | 75f5a084-7a53-5d7f-941a-c2b4c4c6f127 | -## current database, schema, or user +## current_database -`current_database()`, `current_schema()`, `current_user()`, and `session_user()` -are standard SQL functions that return information about the current database, -schema, and user. +Returns the name of the database the session is connected to. + +**Arguments:** + +- `current_database()` does not require arguments. + +**Return value:** + +Returns a `string`. + +**Examples:** ```questdb-sql --- Get the current database SELECT current_database(); +``` --- Get the current schema -SELECT current_schema(); +| current_database | +| ---------------- | +| qdb | --- Get the current user -SELECT current_user(); +## current_resource_group --- Get the authenticated user of the current session -SELECT session_user(); +:::note + +Resource groups and the `current_resource_group()` function are available in +**QuestDB Enterprise** only. + +::: + +`current_resource_group()` returns the name of the +[resource group](/docs/concepts/resource-groups/) the calling query was admitted +to. Run it from a client session to confirm which group that session's queries +land in, which is the quickest way to check that a principal mapping took +effect. Any authenticated session can call it, with no permission of its own, +unlike [`resource_groups()`](#resource_groups) which requires +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions). An ordinary user can +therefore check their own group but not read anyone else's policy. + +**Arguments:** + +- `current_resource_group()` does not require arguments. + +**Return value:** + +Returns a string naming the group. A principal with no mapping of its own +resolves to `DEFAULT`, so on an instance where nobody has been mapped yet every +session sees `DEFAULT`: + +```questdb-sql title="Check which group the calling session's queries run in" +SELECT current_resource_group(); ``` -Each of these functions returns a single value, so you can use them in a SELECT -statement without any arguments. +| current_resource_group | +| ---------------------- | +| DEFAULT | -`current_user()` and `session_user()` both return the authenticated principal -and are interchangeable in QuestDB. Both report the user that authenticated on -the current connection, whichever protocol it arrived on. +It returns `null` only when the query was never admitted to a group at all: when +`resource.groups.enabled` is `false`, or on a replica that has not yet received +the group catalog. A group name and `null` therefore mean +different things: `DEFAULT` says the feature placed the query in the built-in +group, while `null` says the feature was not in force for that query. -## current_resource_group +The group is resolved when the query starts and stays fixed for its lifetime, +including across the pages of a suspended cursor. Changing a mapping affects the +next query, never one already running. -_QuestDB Enterprise only._ +**Confirming a mapping end to end:** + +```questdb-sql title="Map a user, then confirm from that user's own session" +ALTER USER analyst SET RESOURCE GROUP reporting; +``` -Returns the resource group assigned to the calling query. Ordinary users can use -this function to check their own assignment; administrator rights are not -required. See [resource groups](/docs/concepts/resource-groups/) for mapping -precedence and the scope of managed execution. +Reconnect as `analyst` and run: + +```questdb-sql title="Confirm the mapping took effect" +SELECT current_resource_group(); +``` -**Arguments:** none. +| current_resource_group | +| ---------------------- | +| reporting | + +A principal with `SQL ENGINE ADMIN` can check the same thing without +reconnecting, because [`query_activity()`](#query_activity) carries a +`resource_group` column for every running query. + +## current_schema + +Returns the name of the current schema. + +**Arguments:** -**Return value:** `STRING`. Returns `NULL` when the execution is unmanaged, -including when resource groups are disabled or a replica's catalog is not ready. -A managed query without a principal mapping returns `DEFAULT`. +- `current_schema()` does not require arguments. + +**Return value:** + +Returns a `string`. + +**Examples:** ```questdb-sql -SELECT current_resource_group(); +SELECT current_schema(); ``` -The result follows the query's acquired group, including across suspended cursor -pages. A subsequent mapping change affects the next query. +| current_schema | +| -------------- | +| public | + +## current_user + +Returns the user that authenticated on the current connection, whichever +protocol it arrived on. [`session_user()`](#session_user) is a compatibility +alias for it, and the two are interchangeable in QuestDB. + +**Arguments:** -## flush_query_cache() +- `current_user()` does not require arguments. + +**Return value:** + +Returns a `string`. + +**Examples:** + +```questdb-sql +SELECT current_user(); +``` + +| current_user | +| ------------ | +| admin | + +## flush_query_cache `flush_query_cache' invalidates cached query execution plans. @@ -150,7 +235,7 @@ functions(); | and | and(TT) | and(boolean, boolean) | FALSE | STANDARD | | not | not(T) | not(boolean) | FALSE | STANDARD | -## hydrate_table_metadata('table1', 'table2' ...) +## hydrate_table_metadata `hydrate_table_metadata' re-reads table metadata from disk to update the static metadata cache. @@ -445,13 +530,18 @@ Returns metadata on running SQL queries, with the following columns: tracked by the [per-query memory limit](/docs/configuration/cairo-engine/#memory-limits) - memory_limit - effective native memory limit for the query, in bytes, or - `null` when the query runs unlimited. On QuestDB Enterprise this is the + `null` when the query runs unlimited. On QuestDB Enterprise it starts from the principal's [memory limit](/docs/security/rbac/#memory-limits) when one is - set, otherwise the workload limit. Unlike the `memory_limit` column of - `SHOW USERS`, it includes the workload limit + set, otherwise the workload limit, and is then capped by the + [resource group](/docs/concepts/resource-groups/) budget and the process + budget. Unlike the `memory_limit` column of `SHOW USERS`, it includes the + workload limit and the group budget, so a principal with no limit of its own + still reports a value here when its group sets one - resource_group - the [resource group](/docs/concepts/resource-groups/) the - query was admitted to in QuestDB Enterprise, `null` when resource groups do - not manage the execution + query was admitted to on QuestDB Enterprise. A query whose principal has no + mapping reports `DEFAULT`; `null` means the query was never admitted to a + group at all, which happens when resource groups are disabled and when a + replica has not yet received the group catalog `memory_used` is a live gauge with no peak value, and it is reported even when `memory_limit` is `null`. Both memory columns are `null` for SQL that runs under @@ -473,11 +563,20 @@ FROM query_activity(); To inspect query memory and resource group assignment in QuestDB Enterprise: -```questdb-sql +```questdb-sql title="See which group each running query was admitted to" SELECT query_id, username, resource_group, memory_used, memory_limit FROM query_activity(); ``` +| query_id | username | resource_group | memory_used | memory_limit | +| -------- | -------- | -------------- | ----------- | ------------ | +| 47 | analyst | reporting | 0 | 2147483648 | + +`analyst` has no memory limit of its own here, so the 2 GiB ceiling is the one +the `reporting` group sets. Joining this against +[`resource_groups()`](#resource_groups) shows how much of a group's budget its +running queries are actually holding. + ## reader_pool **Arguments:** @@ -503,7 +602,7 @@ SELECT * FROM reader_pool(); | ---------- | --------------- | --------------------------- | ----------- | | sensors | null | 2023-12-01T19:28:14.311703Z | 1 | -## reload_config() +## reload_config `reload_config' reloads server configuration file's contents (`server.conf`) without server restart. The list of reloadable settings can be found @@ -528,51 +627,150 @@ SELECT reload_config(); ## resource_groups -_QuestDB Enterprise only. Requires administrator rights._ - -Returns one row per current catalog group, including `DEFAULT`, with resolved -policies and live counters. Group definitions remain visible when enforcement is -disabled; their runtime counters are zero. - -**Arguments:** none. - -**Return value:** a table with these columns: - -| Column | Type | Description | -| -------------------------- | --------- | ------------------------------------------------------------------------------------------------------- | -| `name` | `VARCHAR` | Group name | -| `memory_limit_bytes` | `LONG` | Effective group ceiling in bytes, capped by the process budget when enabled; `0` means no group ceiling | -| `max_active_queries` | `INT` | Concurrent admission limit; `2147483647` represents unlimited | -| `max_queued_queries` | `INT` | Queue capacity; `2147483647` represents unlimited, and `0` disables queueing | -| `queue_timeout_millis` | `LONG` | Effective admission timeout in milliseconds | -| `cpu_weight` | `INT` | Relative scheduling weight | -| `active_queries` | `LONG` | Queries currently holding admission slots | -| `queued_queries` | `LONG` | Queries waiting for admission | -| `oldest_queue_wait_millis` | `LONG` | Age of the oldest admission waiter in milliseconds; `0` when none | -| `memory_used_bytes` | `LONG` | Published tracked native query memory in bytes | -| `cpu_nanos_total` | `LONG` | CPU nanoseconds measured by managed scheduling | -| `cpu_wait_nanos_total` | `LONG` | Cumulative query waiting time for CPU, in nanoseconds | -| `admission_rejections` | `LONG` | Cumulative queue-full rejections | -| `admission_timeouts` | `LONG` | Cumulative admission timeouts | +:::note -```questdb-sql -SELECT name, memory_limit_bytes, memory_used_bytes, active_queries, queued_queries +Resource groups and the `resource_groups()` function are available in **QuestDB +Enterprise** only. + +::: + +`resource_groups()` returns one row per +[resource group](/docs/concepts/resource-groups/) that currently exists, +including the built-in `DEFAULT`, pairing each group's policy with its live +counters. It is the main way to see what a group is configured to do and what it +is doing right now. Groups stay visible when `resource.groups.enabled` is +`false`; only their counters sit at zero. + +Calling it requires the database-level +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, the same +permission that gates managing groups and listing or cancelling running queries. +A principal without it gets `Access denied for [SQL ENGINE ADMIN]`. + +**Arguments:** + +- `resource_groups()` does not require arguments. + +**Return value:** + +Returns a table with these columns: + +| Column | Type | Description | +| -------------------------- | --------- | -------------------------------------------------------------------------------------------- | +| `name` | `VARCHAR` | Group name | +| `memory_limit_bytes` | `LONG` | Effective memory ceiling in bytes, after the process budget caps it; `0` means no group ceiling | +| `max_active_queries` | `INT` | Concurrent admission limit as configured; `null` when the group sets none | +| `max_queued_queries` | `INT` | Queue capacity as configured; `null` when the group sets none, `0` to disable queueing | +| `queue_timeout_millis` | `LONG` | Effective admission timeout in milliseconds, falling back to the instance default | +| `cpu_weight` | `INT` | Effective relative scheduling weight; `100` when the group sets none | +| `active_queries` | `LONG` | Queries currently holding admission slots | +| `queued_queries` | `LONG` | Queries waiting for admission | +| `oldest_queue_wait_millis` | `LONG` | Age of the oldest admission waiter in milliseconds; `0` when none | +| `memory_used_bytes` | `LONG` | Published tracked native query memory in bytes | +| `cpu_nanos_total` | `LONG` | CPU nanoseconds measured by managed scheduling | +| `cpu_wait_nanos_total` | `LONG` | Cumulative time queries spent waiting for CPU, in nanoseconds | +| `admission_rejections` | `LONG` | Cumulative queue-full rejections | +| `admission_timeouts` | `LONG` | Cumulative admission timeouts | + +The policy columns do not all report the same thing. `max_active_queries` and +`max_queued_queries` show what the group itself sets, and are `null` when it sets +nothing. `memory_limit_bytes`, `queue_timeout_millis` and `cpu_weight` show the +effective value after instance defaults and the process budget have been applied, +so they are never `null`. + +**Inspecting group policy:** + +Given a group created like this: + +```questdb-sql title="Create a group with an explicit policy" +CREATE RESOURCE GROUP reporting WITH ( + cpu_weight = 50, + max_active_queries = 4, + max_queued_queries = 32, + memory_limit = '2G' +); +``` + +```questdb-sql title="Compare each group's policy against its live admission state" +SELECT name, cpu_weight, max_active_queries, max_queued_queries, + memory_limit_bytes, active_queries, queued_queries FROM resource_groups() ORDER BY name; ``` -Counters describe the current runtime and reset on restart or group recreation. -Worker-local memory deltas can be temporarily unpublished. The single-group -dispatch path does not sample CPU, so `cpu_nanos_total` does not cover all query -CPU use. Dropped groups disappear from this table while their existing queries -finish using retained state. +| name | cpu_weight | max_active_queries | max_queued_queries | memory_limit_bytes | active_queries | queued_queries | +| --------- | ---------- | ------------------ | ------------------ | ------------------ | -------------- | -------------- | +| DEFAULT | 100 | null | null | 0 | 1 | 0 | +| reporting | 50 | 4 | 32 | 2147483648 | 0 | 0 | -For the corresponding +`DEFAULT` sets no admission limits of its own, so both columns are `null`, while +its `cpu_weight` of 100 is the effective default rather than something anyone +configured. Its `active_queries` counts the introspection query itself, which is +why the group you are querying from is never idle in its own output. + +**Finding groups under admission pressure:** + +```questdb-sql title="List groups where queries are waiting or being turned away" +SELECT name, active_queries, queued_queries, oldest_queue_wait_millis, + admission_rejections, admission_timeouts +FROM resource_groups() +WHERE queued_queries > 0 + OR admission_rejections > 0 + OR admission_timeouts > 0; +``` + +A high `queued_queries` with a rising `oldest_queue_wait_millis` means work is +being held at the admission gate rather than by a busy machine, which is the +signal that distinguishes a concurrency limit from a slow query. + +**Constraints and edge cases:** + +- Counters describe the current runtime. They reset when the instance restarts, + and recreating a group under the same name starts it from zero. +- `cpu_nanos_total` counts only CPU that managed scheduling actually measured, so + it is a floor rather than a full account of query CPU. While `DEFAULT` is the + only group, scheduling is disengaged and nothing is sampled, so the column sits + at `0` however busy the instance is. It also undercounts once scheduling is + engaged: a query already running when a second group was created stays outside + scheduling until it next suspends or finishes, and a scheduler that has + degraded after an internal fault stops sampling from then on, which + `questdb_resource_groups_cpu_scheduler_degraded` reports as `1`. +- `memory_used_bytes` can lag real usage, because workers accumulate allocation + deltas locally and publish them to the shared counter in batches. +- A dropped group disappears from this table immediately, while its running and + queued queries finish under the settings it had. + +The same values are published as [Prometheus metrics](/docs/operations/logging-metrics/#resource-group-metrics), -an unlimited group memory ceiling is `0` in both interfaces; it does not remove -principal-specific, instance-default single-query or process memory limits. +where a group with no memory ceiling also reports `0`. + +No group ceiling does not mean unlimited memory. A principal-specific limit, the +instance default `cairo.query.memory.limit.bytes` and the process budget +`resource.groups.process.memory.limit.bytes` all still apply. + +## session_user + +Compatibility alias for [`current_user()`](#current_user). The two are +interchangeable in QuestDB. + +**Arguments:** + +- `session_user()` does not require arguments. + +**Return value:** + +Returns a `string`. + +**Examples:** + +```questdb-sql +SELECT session_user(); +``` + +| session_user | +| ------------ | +| admin | -## sleep() +## sleep Pauses the query for the given number of seconds, then returns the timestamp at which it resumed. Intended for testing and demonstration, for example to hold a @@ -1320,7 +1518,7 @@ WHERE view_status = 'invalid'; SELECT * FROM views() ORDER BY view_name; ``` -## wait_wal_table() +## wait_wal_table Blocks until the WAL writer for a table has applied its transactions up to a target sequencer transaction, then returns `true`. diff --git a/documentation/query/sql/acl/alter-group-drop-external-alias.md b/documentation/query/sql/acl/alter-group-drop-external-alias.md new file mode 100644 index 000000000..936c506a9 --- /dev/null +++ b/documentation/query/sql/acl/alter-group-drop-external-alias.md @@ -0,0 +1,48 @@ +--- +title: ALTER GROUP DROP EXTERNAL ALIAS reference +sidebar_label: DROP EXTERNAL ALIAS +description: + "ALTER GROUP DROP EXTERNAL ALIAS removes an external OIDC or LDAP group + mapping. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER GROUP ... DROP EXTERNAL ALIAS` removes an external OIDC or LDAP group +mapping. + +--- + +## Syntax + +```questdb-sql +ALTER GROUP groupName DROP EXTERNAL ALIAS externalAlias; +``` + +## Description + +Removing an alias requires the `REMOVE EXTERNAL ALIAS` permission. Quote the +alias when it contains commas, spaces, or `=`, as LDAP distinguished names do. + +Members of the external group stop inheriting this group's permissions on their +next login. The QuestDB group itself, and any user explicitly added to it, are +unaffected. + +## Examples + +```questdb-sql +ALTER GROUP analysts DROP EXTERNAL ALIAS 'CN=Analysts,OU=Users,DC=example,DC=com'; +``` + +[`SHOW GROUPS`](/docs/query/sql/show/#show-groups) then reports an empty +`external_alias` column for the group. + +## See also + +- [ALTER GROUP WITH EXTERNAL ALIAS](/docs/query/sql/acl/alter-group-with-external-alias/) +- [DROP GROUP](/docs/query/sql/acl/drop-group/) +- [OpenID Connect (OIDC) integration](/docs/security/oidc/#mapping-user-permissions) diff --git a/documentation/query/sql/acl/alter-group-set-memory-limit.md b/documentation/query/sql/acl/alter-group-set-memory-limit.md new file mode 100644 index 000000000..beeeb7f4c --- /dev/null +++ b/documentation/query/sql/acl/alter-group-set-memory-limit.md @@ -0,0 +1,64 @@ +--- +title: ALTER GROUP SET MEMORY LIMIT reference +sidebar_label: SET MEMORY LIMIT +description: + "ALTER GROUP SET MEMORY LIMIT caps the native memory each query run by a + member of the group may allocate. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER GROUP ... SET MEMORY LIMIT` caps the native memory each query run by a +member of the group may allocate. + +--- + +## Syntax + +```questdb-sql +ALTER GROUP groupName SET MEMORY LIMIT { size | UNLIMITED }; +``` + +## Description + +- `ALTER GROUP groupName SET MEMORY LIMIT size` caps the native memory that each + query run by a member of the group 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` clears the group's limit. + Members without a limit of their own then fall back to the most restrictive + limit among their other groups, or to the workload limit + (`cairo.query.memory.limit.bytes`). `SET MEMORY LIMIT 0` does the same. + +A group limit applies to a member only when that member has no limit of its own. +When several of a user's groups set a limit, the most restrictive one applies. + +Setting a group limit requires the `SET MEMORY LIMIT` permission. See +[memory limits](/docs/security/rbac/#memory-limits) for how a group limit +interacts with the +[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#cairoquerymemorylimitbytes) +workload limit. + +This is a per-query ceiling for the group's members. To bound what a whole +workload may hold at once instead, use a +[resource group](/docs/query/sql/acl/alter-group-set-resource-group/). + +## Examples + +```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`](/docs/query/sql/show/#show-groups), which reports it in the +`memory_limit` column. + +## See also + +- [Memory limits](/docs/security/rbac/#memory-limits) diff --git a/documentation/query/sql/acl/alter-group-set-resource-group.md b/documentation/query/sql/acl/alter-group-set-resource-group.md new file mode 100644 index 000000000..eb7ab2e41 --- /dev/null +++ b/documentation/query/sql/acl/alter-group-set-resource-group.md @@ -0,0 +1,84 @@ +--- +title: ALTER GROUP SET RESOURCE GROUP reference +sidebar_label: SET RESOURCE GROUP +description: + "ALTER GROUP SET RESOURCE GROUP places every member's queries under a resource + group, with MAPPING PRIORITY breaking ties. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER GROUP ... SET RESOURCE GROUP` places the queries of every user in the +group under a [resource group](/docs/concepts/resource-groups/), which governs +their admission, CPU share and memory budget. + +--- + +## Syntax + +```questdb-sql +ALTER GROUP groupName SET RESOURCE GROUP resourceGroupName + [MAPPING PRIORITY priority]; +``` + +## Description + +Mapping an ACL group is how you cover a team without naming each member. It +requires the [`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, +and affects queries that start after the change rather than one already running. + +This is workload mapping: it decides how much of the instance the group's +queries may consume. It is unrelated to +[`WITH EXTERNAL ALIAS`](/docs/query/sql/acl/alter-group-with-external-alias/), +which decides which QuestDB group an external identity lands in. + +### MAPPING PRIORITY + +A user can belong to several ACL groups, so each ACL group mapping carries a +priority and the highest one wins. `MAPPING PRIORITY` is a non-negative integer +that defaults to 0. + +If two carry the same priority, the tie falls to whichever resource group was +created first. That is rarely intended, so give them distinct priorities when +the order matters. + +The clause is accepted only here. It is rejected on +[`ALTER USER`](/docs/query/sql/acl/alter-user-set-resource-group/) and +[`ALTER SERVICE ACCOUNT`](/docs/query/sql/acl/alter-service-account-set-resource-group/), +which are one-to-one and have nothing to break a tie between. + +A direct mapping on a user beats every ACL group mapping regardless of priority. + +## Examples + +```questdb-sql +-- every member's queries run under the adhoc workload policy +ALTER GROUP analysts SET RESOURCE GROUP adhoc MAPPING PRIORITY 10; +-- oncall wins for anyone who is in both groups +ALTER GROUP oncall SET RESOURCE GROUP dashboards MAPPING PRIORITY 20; +``` + +Verify with [`SHOW GROUPS`](/docs/query/sql/show/#show-groups), which reports the +mapping in its `resource_group` and `resource_group_priority` columns: + +```questdb-sql +SELECT name, resource_group, resource_group_priority FROM (SHOW GROUPS); +``` + +| name | resource_group | resource_group_priority | +| -------- | -------------- | ----------------------- | +| analysts | adhoc | 10 | +| oncall | dashboards | 20 | + +Both columns are `null` for a group that is not mapped. + +## See also + +- [ALTER GROUP UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-group-unset-resource-group/) +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [Resource groups](/docs/concepts/resource-groups/) diff --git a/documentation/query/sql/acl/alter-group-unset-resource-group.md b/documentation/query/sql/acl/alter-group-unset-resource-group.md new file mode 100644 index 000000000..40d671936 --- /dev/null +++ b/documentation/query/sql/acl/alter-group-unset-resource-group.md @@ -0,0 +1,66 @@ +--- +title: ALTER GROUP UNSET RESOURCE GROUP reference +sidebar_label: UNSET RESOURCE GROUP +description: + "ALTER GROUP UNSET RESOURCE GROUP removes an ACL group's resource group + mapping. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER GROUP ... UNSET RESOURCE GROUP` removes the ACL group's +[resource group](/docs/concepts/resource-groups/) mapping, along with its +priority. + +--- + +## Syntax + +```questdb-sql +ALTER GROUP groupName UNSET RESOURCE GROUP; +``` + +## Description + +Members stop inheriting the mapping from this group. Each one falls back to the +highest-priority mapping among its remaining ACL groups, or to `DEFAULT`. A +member with a direct mapping of its own is unaffected, because a direct mapping +always won anyway. + +The priority is removed with the mapping; there is no way to clear one while +keeping the other. + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +queries that start after the change rather than one already running. + +Unmapping is also the prerequisite for dropping a resource group: +[`DROP RESOURCE GROUP`](/docs/query/sql/acl/drop-resource-group/) is refused +while any principal is still mapped to it. + +## Examples + +```questdb-sql +ALTER GROUP analysts UNSET RESOURCE GROUP; +``` + +`SHOW GROUPS` then reports `null` in both columns: + +```questdb-sql +SELECT name, resource_group, resource_group_priority FROM (SHOW GROUPS); +``` + +| name | resource_group | resource_group_priority | +| -------- | -------------- | ----------------------- | +| analysts | null | null | + +## See also + +- [ALTER GROUP SET RESOURCE GROUP](/docs/query/sql/acl/alter-group-set-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) +- [Resource groups](/docs/concepts/resource-groups/) diff --git a/documentation/query/sql/acl/alter-group-with-external-alias.md b/documentation/query/sql/acl/alter-group-with-external-alias.md new file mode 100644 index 000000000..0428a4ebe --- /dev/null +++ b/documentation/query/sql/acl/alter-group-with-external-alias.md @@ -0,0 +1,60 @@ +--- +title: ALTER GROUP WITH EXTERNAL ALIAS reference +sidebar_label: WITH EXTERNAL ALIAS +description: + "ALTER GROUP WITH EXTERNAL ALIAS maps an external OIDC or LDAP group to a + QuestDB group. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER GROUP ... WITH EXTERNAL ALIAS` maps an external OIDC or LDAP group to a +QuestDB group, so members of the external group inherit its permissions on +login. + +--- + +## Syntax + +```questdb-sql +ALTER GROUP groupName WITH EXTERNAL ALIAS externalAlias; +``` + +## Description + +Adding an alias requires the `ADD EXTERNAL ALIAS` permission. Quote the alias +when it contains commas, spaces, or `=`, as LDAP distinguished names do. + +For the external group mapping flow, see the +[OpenID Connect (OIDC) integration](/docs/security/oidc/#mapping-user-permissions) +guide. To create a group and its alias in one statement, use +[`CREATE GROUP ... WITH EXTERNAL ALIAS`](/docs/query/sql/acl/create-group/). + +:::note + +This is identity mapping: it decides which QuestDB group an external identity +lands in. It is unrelated to +[`SET RESOURCE GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/), +which decides how much of the instance that group's queries may consume. + +::: + +## Examples + +```questdb-sql +ALTER GROUP analysts WITH EXTERNAL ALIAS 'CN=Analysts,OU=Users,DC=example,DC=com'; +``` + +The alias can be verified with +[`SHOW GROUPS`](/docs/query/sql/show/#show-groups), which reports it in the +`external_alias` column. + +## See also + +- [ALTER GROUP DROP EXTERNAL ALIAS](/docs/query/sql/acl/alter-group-drop-external-alias/) +- [CREATE GROUP](/docs/query/sql/acl/create-group/) +- [OpenID Connect (OIDC) integration](/docs/security/oidc/#mapping-user-permissions) diff --git a/documentation/query/sql/acl/alter-group.md b/documentation/query/sql/acl/alter-group.md deleted file mode 100644 index 4ba801339..000000000 --- a/documentation/query/sql/acl/alter-group.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: ALTER GROUP reference -sidebar_label: ALTER GROUP -description: - "ALTER GROUP sets a per-group query memory limit or maps an external OIDC or - LDAP group alias. 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 - each query run by a member of the group 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` - clears the group's limit. - Members without a limit of their own then fall back to the most restrictive - limit among their other groups, or to the workload limit - (`cairo.query.memory.limit.bytes`). `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 group limit applies to a member only when that member has no limit of its own. -When several of a user's groups set a limit, the most restrictive one applies. -Setting a group limit requires the `SET MEMORY LIMIT` permission. See -[memory limits](/docs/security/rbac/#memory-limits) for how a group limit -interacts with the -[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#cairoquerymemorylimitbytes) -workload limit. - -Adding an alias requires the `ADD EXTERNAL ALIAS` permission and removing one -requires `REMOVE EXTERNAL ALIAS`. Quote the alias when it contains commas, -spaces, or `=`, as LDAP distinguished names do. For external group mapping with -OIDC or LDAP, see the -[OpenID Connect (OIDC) integration](/docs/security/oidc/#mapping-user-permissions) -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`](/docs/query/sql/show/#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-resource-group.md b/documentation/query/sql/acl/alter-resource-group.md new file mode 100644 index 000000000..65ab40062 --- /dev/null +++ b/documentation/query/sql/acl/alter-resource-group.md @@ -0,0 +1,108 @@ +--- +title: ALTER RESOURCE GROUP reference +sidebar_label: ALTER RESOURCE GROUP +description: + "ALTER RESOURCE GROUP changes a group's admission, CPU weight and memory + policy, clears parameters, or renames the group. Applies to QuestDB + Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER RESOURCE GROUP` changes an existing query workload policy. + +For what the limits actually guarantee, see +[resource groups](/docs/concepts/resource-groups/). + +--- + +## Syntax + +```questdb-sql title="Set parameters" +ALTER RESOURCE GROUP groupName + SET ( parameter = value [, parameter = value ...] ); +``` + +```questdb-sql title="Clear parameters back to the instance defaults" +ALTER RESOURCE GROUP groupName RESET ( parameter [, parameter ...] ); +``` + +```questdb-sql title="Rename" +ALTER RESOURCE GROUP groupName RENAME TO newName; +``` + +The parameters are the same ones +[`CREATE RESOURCE GROUP`](/docs/query/sql/acl/create-resource-group/#parameters) +accepts: `cpu_weight`, `max_active_queries`, `max_queued_queries`, +`queue_timeout` and `memory_limit`. + +## Description + +`SET` changes only the parameters named; anything else the group already sets is +left alone. `RESET` clears a parameter so it falls back to the instance default, +which is not always "unlimited": `RESET (cpu_weight)` returns the group to a +weight of 100, and `RESET (queue_timeout)` returns it to 30 seconds. + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission. + +`DEFAULT` can be altered but not renamed. `ALTER RESOURCE GROUP DEFAULT RENAME TO` +fails with `built-in Resource Group cannot be renamed`. + +Renaming preserves the policy and every principal mapping, because mappings +attach to the group's identity rather than its name. + +### Effect on queries already running + +A policy change applies online. It does not cancel anything running at the +moment `ALTER` executes: + +| Change | Effect on existing work | +| ---------------------- | ------------------------------------------------------------------------------------------------- | +| `cpu_weight` | Subsequent scheduling uses the new policy | +| `max_active_queries` | Existing slots are retained; subsequent admission, including a resumed cursor, uses the new limit | +| `max_queued_queries`, `queue_timeout` | New admission requests use the new settings; an already queued request keeps its deadline | +| `memory_limit` | Subsequent allocations check the new budget; existing memory is released normally | + +Lowering `memory_limit` below current usage does not fail running queries +retroactively, but it can make their subsequent allocations fail. The +single-query ceiling is captured when a query starts, so changing the group +budget does not replace a principal's own limit. + +## Examples + +```questdb-sql title="Raise the weight and cap concurrency" +ALTER RESOURCE GROUP analytics SET (cpu_weight = 300, max_active_queries = 8); +``` + +```questdb-sql title="Remove the group's memory ceiling" +ALTER RESOURCE GROUP reporting SET (memory_limit = UNLIMITED); +``` + +`RESET (memory_limit)` and `SET (memory_limit = 0)` do the same thing. + +```questdb-sql title="Clear parameters back to the instance defaults" +ALTER RESOURCE GROUP analytics RESET (memory_limit, max_active_queries); +``` + +```questdb-sql title="Rename" +ALTER RESOURCE GROUP analytics RENAME TO reporting; +``` + +`DEFAULT` takes limits like any other group, which is how you bound everything +that is not explicitly mapped: + +```questdb-sql title="Cap the default workload" +ALTER RESOURCE GROUP DEFAULT SET (max_active_queries = 16); +``` + +## See also + +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) +- [Configure and use resource groups](/docs/operations/resource-groups/) diff --git a/documentation/query/sql/acl/alter-service-account-create-token.md b/documentation/query/sql/acl/alter-service-account-create-token.md new file mode 100644 index 000000000..129a10845 --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-create-token.md @@ -0,0 +1,89 @@ +--- +title: ALTER SERVICE ACCOUNT CREATE TOKEN reference +sidebar_label: CREATE TOKEN +description: + "ALTER SERVICE ACCOUNT CREATE TOKEN adds a JWK or REST API token to a service + account, with an optional TTL and REFRESH. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... CREATE TOKEN` adds a JSON Web Key or a REST API token +to a service account. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName CREATE TOKEN TYPE + { JWK | REST WITH TTL timeUnit [REFRESH] }; +``` + +## Description + +- `ALTER SERVICE ACCOUNT serviceAccountName CREATE TOKEN TYPE JWK` adds a JSON + Web Key to the service account. It returns the public key (x, y) and the + private key. **The private key is not stored in QuestDB**, so capture it when + it is returned. +- `ALTER SERVICE ACCOUNT serviceAccountName CREATE TOKEN TYPE REST WITH TTL timeUnit [REFRESH]` + adds a REST API token to the service account. + +### TTL and REFRESH + +The TTL value is an integer and a unit, such as `1m`. The supported units are: + +- `s` for second +- `m` for minute +- `h` for hour +- `d` for day + +The minimum allowable TTL value is 1 minute and the maximum is 10 years (10 \* +365 days). + +`REFRESH` is optional. When specified, the token's expiration timestamp is +refreshed on each successful authentication. + +### REST API tokens and database replication + +Many [QuestDB Enterprise](/enterprise/) instances run within active +[database replication](/docs/high-availability/setup/) clusters. With replication +enabled, the REST API token is refreshed on successful authentication to the +**primary** node. The token is **not** refreshed during successful +authentications to **replica** nodes. + +Therefore, tokens with the `REFRESH` modifier are for use only on the **primary** +node. + +## Examples + +```questdb-sql title="Add a JSON Web Key" +ALTER SERVICE ACCOUNT client_app CREATE TOKEN TYPE JWK; +``` + +```questdb-sql title="Add a REST API token" +-- generate a token with no TTL refresh +ALTER SERVICE ACCOUNT client_app CREATE TOKEN TYPE REST WITH TTL '1m'; +-- generate a token with TTL refresh +ALTER SERVICE ACCOUNT client_app CREATE TOKEN TYPE REST WITH TTL '1m' REFRESH; +``` + +Verify with: + +```questdb-sql +SHOW SERVICE ACCOUNT client_app; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | false | +| JWK Token | true | +| REST Token | false | + +## See also + +- [DROP TOKEN](/docs/query/sql/acl/alter-service-account-drop-token/) diff --git a/documentation/query/sql/acl/alter-service-account-disable.md b/documentation/query/sql/acl/alter-service-account-disable.md new file mode 100644 index 000000000..f1746e931 --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-disable.md @@ -0,0 +1,47 @@ +--- +title: ALTER SERVICE ACCOUNT DISABLE reference +sidebar_label: DISABLE +description: + "ALTER SERVICE ACCOUNT DISABLE turns off a service account without deleting + it. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... DISABLE` turns off a service account without deleting +it. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName DISABLE; +``` + +## Description + +A disabled service account keeps its permissions and tokens; it simply cannot +authenticate, and cannot be assumed, until it is enabled again. + +To remove one permanently instead, use +[`DROP SERVICE ACCOUNT`](/docs/query/sql/acl/drop-service-account/). + +## Examples + +```questdb-sql +ALTER SERVICE ACCOUNT client_app DISABLE; +``` + +Verify with +[`SHOW SERVICE ACCOUNTS`](/docs/query/sql/show/#show-service-accounts), which +reports `false` in its `enabled` column. + +## See also + +- [ALTER SERVICE ACCOUNT ENABLE](/docs/query/sql/acl/alter-service-account-enable/) +- [DROP SERVICE ACCOUNT](/docs/query/sql/acl/drop-service-account/) diff --git a/documentation/query/sql/acl/alter-service-account-drop-token.md b/documentation/query/sql/acl/alter-service-account-drop-token.md new file mode 100644 index 000000000..0a93028bb --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-drop-token.md @@ -0,0 +1,63 @@ +--- +title: ALTER SERVICE ACCOUNT DROP TOKEN reference +sidebar_label: DROP TOKEN +description: + "ALTER SERVICE ACCOUNT DROP TOKEN removes a JWK or one or all REST API tokens + from a service account. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... DROP TOKEN` removes a JSON Web Key or a REST API +token from a service account. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE + { JWK | REST [token] }; +``` + +## Description + +- `ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE JWK` removes the JSON + Web Key from the service account. +- `ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE REST token` removes + that REST token from the service account. +- `ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE REST` with no token + removes **all** of the service account's REST tokens. + +## Examples + +```questdb-sql title="Remove the JSON Web Key" +ALTER SERVICE ACCOUNT client_app DROP TOKEN TYPE JWK; +``` + +```questdb-sql title="Remove REST API tokens" +-- drop a single REST API token +ALTER SERVICE ACCOUNT client_app DROP TOKEN TYPE REST 'qt1cNK6s2t79f76GmTBN9k7XTWm5wwOtF7C0UBxiHGPn44'; +-- drop all REST API tokens for the given service account +ALTER SERVICE ACCOUNT client_app DROP TOKEN TYPE REST; +``` + +Verify with: + +```questdb-sql +SHOW SERVICE ACCOUNT client_app; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | true | +| JWK Token | false | +| REST Token | false | + +## See also + +- [CREATE TOKEN](/docs/query/sql/acl/alter-service-account-create-token/) diff --git a/documentation/query/sql/acl/alter-service-account-enable.md b/documentation/query/sql/acl/alter-service-account-enable.md new file mode 100644 index 000000000..4ebe6158d --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-enable.md @@ -0,0 +1,46 @@ +--- +title: ALTER SERVICE ACCOUNT ENABLE reference +sidebar_label: ENABLE +description: + "ALTER SERVICE ACCOUNT ENABLE turns a disabled service account back on. + Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... ENABLE` turns a disabled service account back on. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName ENABLE; +``` + +## Description + +The service account can authenticate and be assumed again, with the permissions +and tokens it had before it was disabled. + +A service account is enabled when created, so this is only needed after +[`DISABLE`](/docs/query/sql/acl/alter-service-account-disable/). + +## Examples + +```questdb-sql +ALTER SERVICE ACCOUNT client_app ENABLE; +``` + +Verify with +[`SHOW SERVICE ACCOUNTS`](/docs/query/sql/show/#show-service-accounts), which +reports `true` in its `enabled` column. + +## See also + +- [ALTER SERVICE ACCOUNT DISABLE](/docs/query/sql/acl/alter-service-account-disable/) +- [CREATE SERVICE ACCOUNT](/docs/query/sql/acl/create-service-account/) diff --git a/documentation/query/sql/acl/alter-service-account-set-memory-limit.md b/documentation/query/sql/acl/alter-service-account-set-memory-limit.md new file mode 100644 index 000000000..8db6d5533 --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-set-memory-limit.md @@ -0,0 +1,65 @@ +--- +title: ALTER SERVICE ACCOUNT SET MEMORY LIMIT reference +sidebar_label: SET MEMORY LIMIT +description: + "ALTER SERVICE ACCOUNT SET MEMORY LIMIT caps the native memory each of a + service account's queries may allocate. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... SET MEMORY LIMIT` caps the native memory each of the +service account's queries may allocate. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT { size | UNLIMITED }; +``` + +## Description + +- `ALTER SERVICE ACCOUNT serviceAccountName SET MEMORY LIMIT size` caps the + native memory each of 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` clears + the service account's limit. The workload limit + (`cairo.query.memory.limit.bytes`) then applies. `SET MEMORY LIMIT 0` does the + same. + +A user who assumes the service account runs under its memory limit. No group +limit can apply, because a service account cannot belong to an ACL group, so +either it has a limit of its own or only the workload limit applies. + +Setting it requires the `SET MEMORY LIMIT` permission. See +[memory limits](/docs/security/rbac/#memory-limits) for how the limit interacts +with the +[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#cairoquerymemorylimitbytes) +workload limit. + +This is a per-query ceiling. To bound what a whole workload may hold at once +instead, use a +[resource group](/docs/query/sql/acl/alter-service-account-set-resource-group/). + +## Examples + +```questdb-sql +-- cap the service account's queries at 1 GiB of native memory +ALTER SERVICE ACCOUNT client_app SET MEMORY LIMIT 1G; +-- remove the limit +ALTER SERVICE ACCOUNT client_app SET MEMORY LIMIT UNLIMITED; +``` + +The configured value can be verified with +[`SHOW SERVICE ACCOUNTS`](/docs/query/sql/show/#show-service-accounts), which +reports it in the `memory_limit` column. + +## See also + +- [Memory limits](/docs/security/rbac/#memory-limits) diff --git a/documentation/query/sql/acl/alter-service-account-set-resource-group.md b/documentation/query/sql/acl/alter-service-account-set-resource-group.md new file mode 100644 index 000000000..fc8a3c81b --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-set-resource-group.md @@ -0,0 +1,67 @@ +--- +title: ALTER SERVICE ACCOUNT SET RESOURCE GROUP reference +sidebar_label: SET RESOURCE GROUP +description: + "ALTER SERVICE ACCOUNT SET RESOURCE GROUP places a service account's queries + under a resource group. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER SERVICE ACCOUNT ... SET RESOURCE GROUP` places the service account's +queries under a [resource group](/docs/concepts/resource-groups/), which governs +their admission, CPU share and memory budget. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName SET RESOURCE GROUP resourceGroupName; +``` + +## Description + +A session that assumes the service account keeps the resource group of the +principal that logged in. The account's own mapping applies to sessions that +authenticate as it. + +`MAPPING PRIORITY` is **not** accepted here, because a service account has +exactly one mapping and there is nothing to break a tie between. It belongs to +[`ALTER GROUP SET RESOURCE GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/). + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +queries that start after the change rather than one already running. + +This governs the queries the account runs. It does not throttle ingestion, which +resource groups do not manage. + +## Examples + +```questdb-sql +ALTER SERVICE ACCOUNT client_app SET RESOURCE GROUP automation; +``` + +Verify with +[`SHOW SERVICE ACCOUNTS`](/docs/query/sql/show/#show-service-accounts), which +reports the mapping in its `resource_group` column: + +```questdb-sql +SELECT name, resource_group FROM (SHOW SERVICE ACCOUNTS); +``` + +| name | resource_group | +| ---------- | -------------- | +| client_app | automation | + +## See also + +- [ALTER SERVICE ACCOUNT UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-service-account-unset-resource-group/) +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [Resource groups](/docs/concepts/resource-groups/) diff --git a/documentation/query/sql/acl/alter-service-account-unset-resource-group.md b/documentation/query/sql/acl/alter-service-account-unset-resource-group.md new file mode 100644 index 000000000..186103d80 --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-unset-resource-group.md @@ -0,0 +1,62 @@ +--- +title: ALTER SERVICE ACCOUNT UNSET RESOURCE GROUP reference +sidebar_label: UNSET RESOURCE GROUP +description: + "ALTER SERVICE ACCOUNT UNSET RESOURCE GROUP removes a service account's + resource group mapping. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER SERVICE ACCOUNT ... UNSET RESOURCE GROUP` removes the service account's +[resource group](/docs/concepts/resource-groups/) mapping. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName UNSET RESOURCE GROUP; +``` + +## Description + +The service account returns to `DEFAULT`. Unlike a user, it has no ACL group +mapping to fall back to, because a service account cannot belong to a group. +Unsetting therefore leaves it governed only by `DEFAULT`'s policy. + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +queries that start after the change rather than one already running. + +Unmapping is also the prerequisite for dropping a resource group: +[`DROP RESOURCE GROUP`](/docs/query/sql/acl/drop-resource-group/) is refused +while any principal is still mapped to it. + +## Examples + +```questdb-sql +ALTER SERVICE ACCOUNT client_app UNSET RESOURCE GROUP; +``` + +`SHOW SERVICE ACCOUNTS` then reports `null` in the `resource_group` column, +which for a service account means `DEFAULT`: + +```questdb-sql +SELECT name, resource_group FROM (SHOW SERVICE ACCOUNTS); +``` + +| name | resource_group | +| ---------- | -------------- | +| client_app | null | + +## See also + +- [ALTER SERVICE ACCOUNT SET RESOURCE GROUP](/docs/query/sql/acl/alter-service-account-set-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) +- [Resource groups](/docs/concepts/resource-groups/) diff --git a/documentation/query/sql/acl/alter-service-account-with-no-password.md b/documentation/query/sql/acl/alter-service-account-with-no-password.md new file mode 100644 index 000000000..7c0f9e482 --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-with-no-password.md @@ -0,0 +1,58 @@ +--- +title: ALTER SERVICE ACCOUNT WITH NO PASSWORD reference +sidebar_label: WITH NO PASSWORD +description: + "ALTER SERVICE ACCOUNT WITH NO PASSWORD removes a service account's password. + Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... WITH NO PASSWORD` removes a service account's +password. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName WITH NO PASSWORD; +``` + +## Description + +This is the only way to clear a password. `WITH PASSWORD ''` does not work, +because empty passwords are rejected. + +Removing the password does not disable the account. If it still holds a JWK or +REST token it can continue to authenticate with that; to stop access entirely, +use [`DISABLE`](/docs/query/sql/acl/alter-service-account-disable/) or drop its +tokens with +[`DROP TOKEN`](/docs/query/sql/acl/alter-service-account-drop-token/). + +## Examples + +```questdb-sql +ALTER SERVICE ACCOUNT client_app WITH NO PASSWORD; +``` + +Verify with: + +```questdb-sql +SHOW SERVICE ACCOUNT client_app; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | false | +| JWK Token | true | +| REST Token | false | + +## See also + +- [ALTER SERVICE ACCOUNT WITH PASSWORD](/docs/query/sql/acl/alter-service-account-with-password/) +- [ALTER SERVICE ACCOUNT DISABLE](/docs/query/sql/acl/alter-service-account-disable/) diff --git a/documentation/query/sql/acl/alter-service-account-with-password.md b/documentation/query/sql/acl/alter-service-account-with-password.md new file mode 100644 index 000000000..a97e3308c --- /dev/null +++ b/documentation/query/sql/acl/alter-service-account-with-password.md @@ -0,0 +1,53 @@ +--- +title: ALTER SERVICE ACCOUNT WITH PASSWORD reference +sidebar_label: WITH PASSWORD +description: + "ALTER SERVICE ACCOUNT WITH PASSWORD sets a service account's password. + Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER SERVICE ACCOUNT ... WITH PASSWORD` sets a service account's password. + +--- + +## Syntax + +```questdb-sql +ALTER SERVICE ACCOUNT serviceAccountName WITH PASSWORD password; +``` + +## Description + +Setting a password replaces any existing one. Empty passwords are rejected, so +`WITH PASSWORD ''` cannot be used to clear one; use +[`WITH NO PASSWORD`](/docs/query/sql/acl/alter-service-account-with-no-password/) +instead. + +## Examples + +```questdb-sql +ALTER SERVICE ACCOUNT client_app WITH PASSWORD '1m@re@lh@cker'; +``` + +Verify with: + +```questdb-sql +SHOW SERVICE ACCOUNT client_app; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | true | +| JWK Token | false | +| REST Token | false | + +## See also + +- [ALTER SERVICE ACCOUNT WITH NO PASSWORD](/docs/query/sql/acl/alter-service-account-with-no-password/) +- [CREATE SERVICE ACCOUNT](/docs/query/sql/acl/create-service-account/) diff --git a/documentation/query/sql/acl/alter-service-account.md b/documentation/query/sql/acl/alter-service-account.md deleted file mode 100644 index ca73247ac..000000000 --- a/documentation/query/sql/acl/alter-service-account.md +++ /dev/null @@ -1,200 +0,0 @@ ---- -title: ALTER SERVICE ACCOUNT reference -sidebar_label: ALTER SERVICE ACCOUNT -description: - "ALTER SERVICE ACCOUNT enables or disables a service account, manages - passwords and tokens, and sets its query memory limit. Applies to RBAC in - QuestDB Enterprise." ---- - -import { EnterpriseNote } from "@site/src/components/EnterpriseNote" - - - RBAC provides fine-grained database permissions management. - - -`ALTER SERVICE ACCOUNT` modifies service account 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="Enable / disable" -ALTER SERVICE ACCOUNT serviceAccountName { ENABLE | DISABLE }; -``` - -```questdb-sql title="Set or remove password" -ALTER SERVICE ACCOUNT serviceAccountName WITH { PASSWORD password | NO PASSWORD }; -``` - -```questdb-sql title="Create token" -ALTER SERVICE ACCOUNT serviceAccountName CREATE TOKEN TYPE - { JWK | REST WITH TTL timeUnit [REFRESH] }; -``` - -```questdb-sql title="Drop token" -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. -- `ALTER SERVICE ACCOUNT serviceAccountName DISABLE` - disables service account. -- `ALTER SERVICE ACCOUNT serviceAccountName WITH PASSWORD password` - sets - password for the service account. -- `ALTER SERVICE ACCOUNT serviceAccountName WITH NO PASSWORD` - removes password - for the service account. -- `ALTER SERVICE ACCOUNT serviceAccountName CREATE TOKEN TYPE JWK` - adds Json - Web Key to the service account. Returns public key (x, y) and private key. The - private key is not stored in QuestDB. -- `ALTER SERVICE ACCOUNT serviceAccountName DROP TOKEN TYPE JWK` - removes Json - Web Key from the service account. -- `ALTER USER serviceAccountName CREATE TOKEN TYPE REST WITH TTL timeUnit REFRESH` - - 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 each of 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` - clears - the service account's limit. The workload limit - (`cairo.query.memory.limit.bytes`) then applies. `SET MEMORY LIMIT 0` does the - same. - -A user who assumes the service account runs under its memory limit. 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 the limit interacts -with the -[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#cairoquerymemorylimitbytes) -workload limit. - -## Examples - -### Enable service account - -```questdb-sql -ALTER SERVICE ACCOUNT client_app ENABLE; -``` - -### Disable service account - -```questdb-sql -ALTER SERVICE ACCOUNT client_app DISABLE; -``` - -### Set password - -```questdb-sql -ALTER SERVICE ACCOUNT client_app WITH PASSWORD '1m@re@lh@cker'; -``` - -### Remove password - -```questdb-sql -ALTER SERVICE ACCOUNT client_app WITH NO PASSWORD; -``` - -Removing a password is not possible using `WITH PASSWORD ''` as the database -will reject empty passwords. - -### Add Json Web Key - -```questdb-sql -ALTER SERVICE ACCOUNT client_app CREATE TOKEN TYPE JWK; -``` - -### Remove Json Web Key - -```questdb-sql -ALTER SERVICE ACCOUNT client_app DROP TOKEN TYPE JWK; -``` - -Result of commands above can be verified with `SHOW USER`, e.g. - -```questdb-sql -SHOW SERVICE ACCOUNT client_app; -``` - -| auth_type | enabled | -| ---------- | ------- | -| Password | false | -| JWK Token | true | -| REST Token | false | - -### Add REST API token - -```questdb-sql --- generate a token with no TTL refresh -ALTER SERVICE ACCOUNT client_app CREATE TOKEN TYPE REST WITH TTL '1m'; --- generate a token with TTL refresh -ALTER SERVICE ACCOUNT client_app CREATE TOKEN TYPE REST WITH TTL '1m' REFRESH; -``` - -Here, the TTL (Time-to-Live) value should contain an integer and a unit, such as -`1m`. The supported units are: - -- `s` - second -- `m` - minute -- `h` - hour -- `d` - day - -The minimum allowable TTL value is 1 minute and the maximum value is 10 years -(10 \* 365 days). - -The `REFRESH` modifier is optional. When the `REFRESH` modifier is specified, -the token's expiration timestamp will be refreshed on each successful -authentication. - -#### Rest API tokens and database replication - -Many [QuestDB Enterprise](/enterprise/) instances run within active -[database replication](/docs/high-availability/setup/) clusters. With replication -enabled, the REST API token will be refreshed on successful authentication to -the **primary** node. The token will **not** be refreshed during successful -authentications to **replica** nodes. - -Therefore, tokens with the `REFRESH` modifier are for use only on the -**primary** node. - -### Remove REST API token - -```questdb-sql --- drop single REST API token -ALTER SERVICE ACCOUNT client_app DROP TOKEN TYPE REST 'qt1cNK6s2t79f76GmTBN9k7XTWm5wwOtF7C0UBxiHGPn44'; --- drop all REST API tokens for the given service account -ALTER SERVICE ACCOUNT client_app DROP TOKEN TYPE REST; -``` - -The result of the above commands can be verified with `SHOW SERVICE ACCOUNT`: - -```questdb-sql -SHOW SERVICE ACCOUNT client_app; -``` - -| auth_type | enabled | -| ---------- | ------- | -| 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 client_app SET MEMORY LIMIT 1G; --- remove the limit -ALTER SERVICE ACCOUNT client_app SET MEMORY LIMIT UNLIMITED; -``` - -The configured value can be verified with -[`SHOW SERVICE ACCOUNTS`](/docs/query/sql/show/#show-service-accounts), which -reports it in the `memory_limit` column. diff --git a/documentation/query/sql/acl/alter-user-create-token.md b/documentation/query/sql/acl/alter-user-create-token.md new file mode 100644 index 000000000..6143aa9c7 --- /dev/null +++ b/documentation/query/sql/acl/alter-user-create-token.md @@ -0,0 +1,85 @@ +--- +title: ALTER USER CREATE TOKEN reference +sidebar_label: CREATE TOKEN +description: + "ALTER USER CREATE TOKEN adds a JWK or REST API token to a user account, with + an optional TTL and REFRESH. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... CREATE TOKEN` adds a JSON Web Key or a REST API token to a user +account. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName CREATE TOKEN TYPE + { JWK | REST WITH TTL timeUnit [REFRESH] }; +``` + +## Description + +- `ALTER USER username CREATE TOKEN TYPE JWK` adds a JSON Web Key to the user + account. It returns the public key (x, y) and the private key. **The private + key is not stored in QuestDB**, so capture it when it is returned. +- `ALTER USER username CREATE TOKEN TYPE REST WITH TTL timeUnit [REFRESH]` adds a + REST API token to the user account. + +### TTL and REFRESH + +The TTL value is an integer and a unit, such as `1m`. The supported units are: + +- `s` for second +- `m` for minute +- `h` for hour +- `d` for day + +The minimum allowed TTL is 1 minute and the maximum is 10 years (10 \* 365 +days). + +`REFRESH` is optional. When specified, the token's expiration timestamp is +refreshed on each successful authentication. + +:::note + +When replication is used, the token is not refreshed on successful +authentication on replicas, only on the primary node. This makes tokens with the +`REFRESH` modifier meaningful for use on the primary node only. + +::: + +## Examples + +```questdb-sql title="Add a JSON Web Key" +ALTER USER john CREATE TOKEN TYPE JWK; +``` + +```questdb-sql title="Add a REST API token" +-- generate a token with no TTL refresh +ALTER USER john CREATE TOKEN TYPE REST WITH TTL '1m'; +-- generate a token with TTL refresh +ALTER USER john CREATE TOKEN TYPE REST WITH TTL '1m' REFRESH; +``` + +Verify with: + +```questdb-sql +SHOW USER john; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | true | +| JWK Token | false | +| REST Token | false | + +## See also + +- [DROP TOKEN](/docs/query/sql/acl/alter-user-drop-token/) diff --git a/documentation/query/sql/acl/alter-user-disable.md b/documentation/query/sql/acl/alter-user-disable.md new file mode 100644 index 000000000..166ed9756 --- /dev/null +++ b/documentation/query/sql/acl/alter-user-disable.md @@ -0,0 +1,46 @@ +--- +title: ALTER USER DISABLE reference +sidebar_label: DISABLE +description: + "ALTER USER DISABLE turns off a user account without deleting it. Applies to + RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... DISABLE` turns off a user account without deleting it. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName DISABLE; +``` + +## Description + +A disabled user keeps its permissions, group memberships and tokens; it simply +cannot authenticate until it is enabled again. Existing sessions are not the +subject of this statement, so disable is not a way to evict a connected user. + +To remove a user permanently instead, use +[`DROP USER`](/docs/query/sql/acl/drop-user/). + +## Examples + +```questdb-sql +ALTER USER john DISABLE; +``` + +Verify with [`SHOW USERS`](/docs/query/sql/show/#show-users), which reports +`false` in its `enabled` column. + +## See also + +- [ALTER USER ENABLE](/docs/query/sql/acl/alter-user-enable/) +- [DROP USER](/docs/query/sql/acl/drop-user/) diff --git a/documentation/query/sql/acl/alter-user-drop-token.md b/documentation/query/sql/acl/alter-user-drop-token.md new file mode 100644 index 000000000..ac6674e0e --- /dev/null +++ b/documentation/query/sql/acl/alter-user-drop-token.md @@ -0,0 +1,63 @@ +--- +title: ALTER USER DROP TOKEN reference +sidebar_label: DROP TOKEN +description: + "ALTER USER DROP TOKEN removes a JWK or one or all REST API tokens from a user + account. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... DROP TOKEN` removes a JSON Web Key or a REST API token from a +user account. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName DROP TOKEN TYPE + { JWK | REST [token] }; +``` + +## Description + +- `ALTER USER username DROP TOKEN TYPE JWK` removes the JSON Web Key from the + user account. +- `ALTER USER username DROP TOKEN TYPE REST token` removes that REST token from + the user account. +- `ALTER USER username DROP TOKEN TYPE REST` with no token removes **all** of the + user's REST tokens. + +## Examples + +```questdb-sql title="Remove the JSON Web Key" +ALTER USER john DROP TOKEN TYPE JWK; +``` + +```questdb-sql title="Remove REST API tokens" +-- drop a single REST API token +ALTER USER john DROP TOKEN TYPE REST 'qt1cNK6s2t79f76GmTBN9k7XTWm5wwOtF7C0UBxiHGPn44'; +-- drop all REST API tokens for the given user +ALTER USER john DROP TOKEN TYPE REST; +``` + +Verify with: + +```questdb-sql +SHOW USER john; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | true | +| JWK Token | false | +| REST Token | false | + +## See also + +- [CREATE TOKEN](/docs/query/sql/acl/alter-user-create-token/) diff --git a/documentation/query/sql/acl/alter-user-enable.md b/documentation/query/sql/acl/alter-user-enable.md new file mode 100644 index 000000000..85ec70576 --- /dev/null +++ b/documentation/query/sql/acl/alter-user-enable.md @@ -0,0 +1,46 @@ +--- +title: ALTER USER ENABLE reference +sidebar_label: ENABLE +description: + "ALTER USER ENABLE turns a disabled user account back on. Applies to RBAC in + QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... ENABLE` turns a disabled user account back on. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName ENABLE; +``` + +## Description + +The user can authenticate again, with the permissions, group memberships and +tokens it had before it was disabled. Nothing is restored or re-granted, because +nothing was removed. + +A user is enabled when created, so this is only needed after +[`DISABLE`](/docs/query/sql/acl/alter-user-disable/). + +## Examples + +```questdb-sql +ALTER USER john ENABLE; +``` + +Verify with [`SHOW USERS`](/docs/query/sql/show/#show-users), which reports +`true` in its `enabled` column. + +## See also + +- [ALTER USER DISABLE](/docs/query/sql/acl/alter-user-disable/) +- [CREATE USER](/docs/query/sql/acl/create-user/) diff --git a/documentation/query/sql/acl/alter-user-set-memory-limit.md b/documentation/query/sql/acl/alter-user-set-memory-limit.md new file mode 100644 index 000000000..493cc535c --- /dev/null +++ b/documentation/query/sql/acl/alter-user-set-memory-limit.md @@ -0,0 +1,65 @@ +--- +title: ALTER USER SET MEMORY LIMIT reference +sidebar_label: SET MEMORY LIMIT +description: + "ALTER USER SET MEMORY LIMIT caps the native memory each of a user's queries + may allocate. Applies to RBAC in QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... SET MEMORY LIMIT` caps the native memory each of the user's +queries may allocate. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName SET MEMORY LIMIT { size | UNLIMITED }; +``` + +## Description + +- `ALTER USER username SET MEMORY LIMIT size` caps the native memory each of 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` clears the user's own limit. A + group limit or the workload limit (`cairo.query.memory.limit.bytes`) then + applies. `SET MEMORY LIMIT 0` does the same. + +The limit applies to the user's queries on both the primary and replicas. +Setting it requires the `SET MEMORY LIMIT` permission. + +The built-in admin and external (SSO/OIDC) users cannot be given a limit; the +statement is rejected for both. An external user inherits a limit from its +groups instead. + +A limit set here takes priority over the user's groups and over the +[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#cairoquerymemorylimitbytes) +workload limit; see [memory limits](/docs/security/rbac/#memory-limits) for how +limits resolve. + +This is a per-query ceiling. To bound what a whole workload may hold at once +instead, use a +[resource group](/docs/query/sql/acl/alter-user-set-resource-group/). + +## Examples + +```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; +``` + +Use [`SHOW USERS`](/docs/query/sql/show/#show-users) to inspect the user's own or +inherited group limit in the `memory_limit` column. + +## See also + +- [Memory limits](/docs/security/rbac/#memory-limits) diff --git a/documentation/query/sql/acl/alter-user-set-resource-group.md b/documentation/query/sql/acl/alter-user-set-resource-group.md new file mode 100644 index 000000000..5c54afb47 --- /dev/null +++ b/documentation/query/sql/acl/alter-user-set-resource-group.md @@ -0,0 +1,68 @@ +--- +title: ALTER USER SET RESOURCE GROUP reference +sidebar_label: SET RESOURCE GROUP +description: + "ALTER USER SET RESOURCE GROUP places a user's queries under a resource group, + overriding any ACL group mapping. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER USER ... SET RESOURCE GROUP` places the user's queries under a +[resource group](/docs/concepts/resource-groups/), which governs their +admission, CPU share and memory budget. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName SET RESOURCE GROUP resourceGroupName; +``` + +## Description + +A direct mapping on the user beats any mapping it would inherit from an ACL +group, whatever that group's priority. That is how you make one person an +exception without touching the groups. + +`MAPPING PRIORITY` is **not** accepted here, because a user has exactly one +direct mapping and there is nothing to break a tie between. It belongs to +[`ALTER GROUP SET RESOURCE GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/). + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +queries that start after the change rather than one already running. + +## Examples + +```questdb-sql +ALTER USER john SET RESOURCE GROUP reporting; +``` + +Verify with [`SHOW USERS`](/docs/query/sql/show/#show-users), which reports the +direct mapping in its `resource_group` column: + +```questdb-sql +SELECT name, resource_group FROM (SHOW USERS); +``` + +| name | resource_group | +| ----- | -------------- | +| admin | null | +| john | reporting | + +To see the group a session actually resolved to, which may come from an ACL +group rather than a direct mapping, use +[`current_resource_group()`](/docs/query/functions/meta/#current_resource_group). + +## See also + +- [ALTER USER UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-user-unset-resource-group/) +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [Resource groups](/docs/concepts/resource-groups/) diff --git a/documentation/query/sql/acl/alter-user-unset-resource-group.md b/documentation/query/sql/acl/alter-user-unset-resource-group.md new file mode 100644 index 000000000..e7388de12 --- /dev/null +++ b/documentation/query/sql/acl/alter-user-unset-resource-group.md @@ -0,0 +1,66 @@ +--- +title: ALTER USER UNSET RESOURCE GROUP reference +sidebar_label: UNSET RESOURCE GROUP +description: + "ALTER USER UNSET RESOURCE GROUP removes a user's direct resource group + mapping. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`ALTER USER ... UNSET RESOURCE GROUP` removes the user's direct +[resource group](/docs/concepts/resource-groups/) mapping. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName UNSET RESOURCE GROUP; +``` + +## Description + +Removing the direct mapping does not leave the user ungoverned. Its queries fall +back to the highest-priority mapping among its ACL groups, or to `DEFAULT` when +it belongs to none that are mapped. + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +queries that start after the change rather than one already running. + +Unmapping is also the prerequisite for dropping a resource group: +[`DROP RESOURCE GROUP`](/docs/query/sql/acl/drop-resource-group/) is refused +while any principal is still mapped to it. + +## Examples + +```questdb-sql +ALTER USER john UNSET RESOURCE GROUP; +``` + +`SHOW USERS` then reports `null` in the `resource_group` column: + +```questdb-sql +SELECT name, resource_group FROM (SHOW USERS); +``` + +| name | resource_group | +| ----- | -------------- | +| admin | null | +| john | null | + +`null` means no direct mapping, not that the user is ungoverned. Use +[`current_resource_group()`](/docs/query/functions/meta/#current_resource_group) +from the user's own session to see the group its queries actually resolve to. + +## See also + +- [ALTER USER SET RESOURCE GROUP](/docs/query/sql/acl/alter-user-set-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) +- [Resource groups](/docs/concepts/resource-groups/) diff --git a/documentation/query/sql/acl/alter-user-with-no-password.md b/documentation/query/sql/acl/alter-user-with-no-password.md new file mode 100644 index 000000000..3bf930d7f --- /dev/null +++ b/documentation/query/sql/acl/alter-user-with-no-password.md @@ -0,0 +1,56 @@ +--- +title: ALTER USER WITH NO PASSWORD reference +sidebar_label: WITH NO PASSWORD +description: + "ALTER USER WITH NO PASSWORD removes a user's password. Applies to RBAC in + QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... WITH NO PASSWORD` removes a user's password. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName WITH NO PASSWORD; +``` + +## Description + +This is the only way to clear a password. `WITH PASSWORD ''` does not work, +because empty passwords are rejected. + +Removing the password does not disable the user. If it still holds a JWK or REST +token it can continue to authenticate with that; to stop access entirely, use +[`DISABLE`](/docs/query/sql/acl/alter-user-disable/) or drop its tokens with +[`DROP TOKEN`](/docs/query/sql/acl/alter-user-drop-token/). + +## Examples + +```questdb-sql +ALTER USER john WITH NO PASSWORD; +``` + +Verify with: + +```questdb-sql +SHOW USER john; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | false | +| JWK Token | false | +| REST Token | false | + +## See also + +- [ALTER USER WITH PASSWORD](/docs/query/sql/acl/alter-user-with-password/) +- [ALTER USER DISABLE](/docs/query/sql/acl/alter-user-disable/) diff --git a/documentation/query/sql/acl/alter-user-with-password.md b/documentation/query/sql/acl/alter-user-with-password.md new file mode 100644 index 000000000..7bf424ad1 --- /dev/null +++ b/documentation/query/sql/acl/alter-user-with-password.md @@ -0,0 +1,52 @@ +--- +title: ALTER USER WITH PASSWORD reference +sidebar_label: WITH PASSWORD +description: + "ALTER USER WITH PASSWORD sets a user's password. Applies to RBAC in QuestDB + Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + RBAC provides fine-grained database permissions management. + + +`ALTER USER ... WITH PASSWORD` sets a user's password. + +--- + +## Syntax + +```questdb-sql +ALTER USER userName WITH PASSWORD password; +``` + +## Description + +Setting a password replaces any existing one. Empty passwords are rejected, so +`WITH PASSWORD ''` cannot be used to clear one; use +[`WITH NO PASSWORD`](/docs/query/sql/acl/alter-user-with-no-password/) instead. + +## Examples + +```questdb-sql +ALTER USER john WITH PASSWORD '1m@re@lh@cker'; +``` + +Verify with: + +```questdb-sql +SHOW USER john; +``` + +| auth_type | enabled | +| ---------- | ------- | +| Password | true | +| JWK Token | false | +| REST Token | false | + +## See also + +- [ALTER USER WITH NO PASSWORD](/docs/query/sql/acl/alter-user-with-no-password/) +- [CREATE USER](/docs/query/sql/acl/create-user/) diff --git a/documentation/query/sql/acl/alter-user.md b/documentation/query/sql/acl/alter-user.md deleted file mode 100644 index 828980e37..000000000 --- a/documentation/query/sql/acl/alter-user.md +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: ALTER USER reference -sidebar_label: ALTER USER -description: - "ALTER USER enables or disables a user, manages passwords and tokens, and sets - a per-user query memory limit. Applies to RBAC in QuestDB Enterprise." ---- - -import { EnterpriseNote } from "@site/src/components/EnterpriseNote" - - - RBAC provides fine-grained database permissions management. - - -`ALTER USER` modifies user 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="Enable / disable" -ALTER USER userName { ENABLE | DISABLE }; -``` - -```questdb-sql title="Set or remove password" -ALTER USER userName WITH { PASSWORD password | NO PASSWORD }; -``` - -```questdb-sql title="Create token" -ALTER USER userName CREATE TOKEN TYPE - { JWK | REST WITH TTL timeUnit [REFRESH] }; -``` - -```questdb-sql title="Drop token" -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. -- `ALTER USER username DISABLE` - disables user account. -- `ALTER USER username WITH PASSWORD password` - sets password for the user - account. -- `ALTER USER username WITH NO PASSWORD` - removes password for the user - account. -- `ALTER USER username CREATE TOKEN TYPE JWK` - adds Json Web Key to user - account. Returns public key (x, y) and private key. The private key is not - stored in QuestDB. -- `ALTER USER username DROP TOKEN TYPE JWK` - removes Json Web Key from user - account. -- `ALTER USER username CREATE TOKEN TYPE REST WITH TTL timeUnit REFRESH` - adds - 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 each of - 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` - clears the user's own - limit. A group limit or the workload limit (`cairo.query.memory.limit.bytes`) - then applies. `SET MEMORY LIMIT 0` does the same. - -The limit applies to the user's queries on both the primary and replicas. -Setting it requires the `SET MEMORY LIMIT` permission. The built-in admin and -external (SSO/OIDC) users cannot be given a limit; the statement is rejected for -both. An external user inherits a limit from its groups instead. A set limit -takes priority over the user's groups and over the -[`cairo.query.memory.limit.bytes`](/docs/configuration/cairo-engine/#cairoquerymemorylimitbytes) -workload limit; see [memory limits](/docs/security/rbac/#memory-limits) for how -limits resolve. - -## Examples - -### Enable user - -```questdb-sql -ALTER USER john ENABLE; -``` - -### Disable user - -```questdb-sql -ALTER USER john DISABLE; -``` - -### Set password - -```questdb-sql -ALTER USER john WITH PASSWORD '1m@re@lh@cker'; -``` - -### Remove password - -```questdb-sql -ALTER USER john WITH NO PASSWORD; -``` - -Removing user's password is not possible with `WITH PASSWORD ''` because it -rejects empty passwords. - -### Add Json Web Key - -```questdb-sql -ALTER USER john CREATE TOKEN TYPE JWK; -``` - -### Remove Json Web Key - -```questdb-sql -ALTER USER john DROP TOKEN TYPE JWK; -``` - -Result of commands above can be verified with `SHOW USER`, e.g. - -```questdb-sql -SHOW USER john; -``` - -| auth_type | enabled | -| ---------- | ------- | -| Password | true | -| JWK Token | false | -| REST Token | false | - -### Add REST API token - -```questdb-sql --- generate a token with no TTL refresh -ALTER USER john CREATE TOKEN TYPE REST WITH TTL '1m'; --- generate a token with TTL refresh -ALTER USER john CREATE TOKEN TYPE REST WITH TTL '1m' REFRESH; -``` - -Here, the TTL (Time-to-Live) value should contain an integer and a unit, e.g. -`1m`. The supported units are: - -- `s` - second -- `m` - minute -- `h` - hour -- `d` - day - -The minimal allowed TTL value is 1 minute, the maximum value is 10 years (10 \* -365 days). - -The REFRESH modifier is optional. When the REFRESH modifier is specified, the -token's expiration timestamp will be refreshed on each successful -authentication. - -:::note - -When replication is used, the token will not be refreshed on successful -authentication on replicas, but only on the primary node. This makes tokens with -the REFRESH modifier meaningful for use on the primary node only. - -::: - -### Remove REST API token - -```questdb-sql --- drop single REST API token -ALTER USER john DROP TOKEN TYPE REST 'qt1cNK6s2t79f76GmTBN9k7XTWm5wwOtF7C0UBxiHGPn44'; --- drop all REST API tokens for the given user -ALTER USER john DROP TOKEN TYPE REST; -``` - -Result of commands above can be verified with `SHOW USER`, e.g. - -```questdb-sql -SHOW USER john; -``` - -| auth_type | enabled | -| ---------- | ------- | -| 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; -``` - -Use [`SHOW USERS`](/docs/query/sql/show/#show-users) to inspect the user's own -or inherited group limit in the `memory_limit` column. diff --git a/documentation/query/sql/acl/create-group.md b/documentation/query/sql/acl/create-group.md index 838bdfcaf..d4ac0f00a 100644 --- a/documentation/query/sql/acl/create-group.md +++ b/documentation/query/sql/acl/create-group.md @@ -38,15 +38,17 @@ OIDC or LDAP group to the new group in one statement, so members of the external group inherit its permissions on login. The group and the mapping are created atomically. `WITH EXTERNAL ALIAS` cannot be combined with `IF NOT EXISTS`. To map or unmap an existing group, use -[`ALTER GROUP`](/docs/query/sql/acl/alter-group/). For the external group -mapping flow, see the +[`ALTER GROUP ... WITH EXTERNAL ALIAS`](/docs/query/sql/acl/alter-group-with-external-alias/) +or +[`DROP EXTERNAL ALIAS`](/docs/query/sql/acl/alter-group-drop-external-alias/). +For the external group mapping flow, see the [OpenID Connect (OIDC) integration](/docs/security/oidc/#mapping-user-permissions) guide. `CREATE GROUP` cannot set a memory limit: a new group has none, and `SHOW GROUPS` reports `null` in its `memory_limit` column. To cap the native memory each query from the group's members may allocate, use -[`ALTER GROUP ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-group/#set-memory-limit) +[`ALTER GROUP ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-group-set-memory-limit/) after creating the group. The chosen name must be unique across all users (including the built-in admin), @@ -75,7 +77,11 @@ SHOW GROUPS; that yields: -| name | external_alias | memory_limit | -| -------- | --------------------------------------- | ------------ | -| admins | | null | -| analysts | CN=Analysts,OU=Users,DC=example,DC=com | null | +| name | external_alias | memory_limit | resource_group | resource_group_priority | +| -------- | -------------------------------------- | ------------ | -------------- | ----------------------- | +| admins | | null | null | null | +| analysts | CN=Analysts,OU=Users,DC=example,DC=com | null | null | null | + +`resource_group` and `resource_group_priority` are `null` until the group is +mapped with +[`ALTER GROUP ... SET RESOURCE GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/). diff --git a/documentation/query/sql/acl/create-resource-group.md b/documentation/query/sql/acl/create-resource-group.md new file mode 100644 index 000000000..fd237c524 --- /dev/null +++ b/documentation/query/sql/acl/create-resource-group.md @@ -0,0 +1,111 @@ +--- +title: CREATE RESOURCE GROUP reference +sidebar_label: CREATE RESOURCE GROUP +description: + "CREATE RESOURCE GROUP creates a query workload policy setting admission, CPU + weight and memory limits. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`CREATE RESOURCE GROUP` creates a query workload policy. + +For what the limits actually guarantee, see +[resource groups](/docs/concepts/resource-groups/). + +--- + +## Syntax + +```questdb-sql +CREATE RESOURCE GROUP [IF NOT EXISTS] groupName + [WITH ( parameter = value [, parameter = value ...] )]; +``` + +## Parameters + +Every parameter is optional. An unset parameter falls back to the instance +default, which is not always "unlimited": + +| Parameter | Accepted values | Unset behaviour | +| -------------------- | --------------------------------------------------------------------- | ---------------- | +| `cpu_weight` | integer, 1 to 10000 | 100 | +| `max_active_queries` | integer, 1 or more | unlimited | +| `max_queued_queries` | integer, 0 or more | unlimited | +| `queue_timeout` | whole milliseconds, or a duration such as `'15s'` or `'2m'` | 30 seconds | +| `memory_limit` | a byte size such as `'8G'`, or `0` or `UNLIMITED` for no group ceiling | no group ceiling | + +## Description + +`CREATE RESOURCE GROUP` adds a policy with no principals mapped to it. Creating +the group alone changes nothing: map a user, ACL group or service account to it +with [`ALTER USER`](/docs/query/sql/acl/alter-user-set-resource-group/), +[`ALTER GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/) or +[`ALTER SERVICE ACCOUNT`](/docs/query/sql/acl/alter-service-account-set-resource-group/) before it +governs anything. + +The name must be unique across resource groups. If it is already taken the +statement fails, unless `IF NOT EXISTS` is included. + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission. + +Creating a second resource group engages CPU scheduling for the whole instance. +While `DEFAULT` is the only group there is no CPU slicing at all, so weights +have no effect until a second group exists. See +[when scheduling engages](/docs/concepts/resource-groups/#when-scheduling-engages). + +A group ceiling only ever lowers what a query may use. `memory_limit` is capped +by `resource.groups.process.memory.limit.bytes`, and it never raises a limit set +on the principal or by `cairo.query.memory.limit.bytes`. + +## Examples + +```questdb-sql title="A policy with no limits of its own" +CREATE RESOURCE GROUP analytics; +``` + +```questdb-sql title="Half the default CPU share, four concurrent queries, 2 GiB" +CREATE RESOURCE GROUP reporting WITH ( + cpu_weight = 50, + max_active_queries = 4, + memory_limit = '2G' +); +``` + +```questdb-sql title="A background workload that yields to everything else" +CREATE RESOURCE GROUP IF NOT EXISTS exports WITH ( + cpu_weight = 10, + max_active_queries = 1 +); +``` + +Verify with [`resource_groups()`](/docs/query/functions/meta/#resource_groups): + +```questdb-sql +SELECT name, cpu_weight, max_active_queries, memory_limit_bytes +FROM resource_groups() +ORDER BY name; +``` + +| name | cpu_weight | max_active_queries | memory_limit_bytes | +| --------- | ---------- | ------------------ | ------------------ | +| DEFAULT | 100 | null | 0 | +| analytics | 100 | null | 0 | +| exports | 10 | 1 | 0 | +| reporting | 50 | 4 | 2147483648 | + +`max_active_queries` is `null` when the group sets no admission limit, and +`memory_limit_bytes` is `0` when it sets no memory ceiling. `cpu_weight` always +reports an effective value, so a group that sets none shows `100`. + +## See also + +- [ALTER RESOURCE GROUP](/docs/query/sql/acl/alter-resource-group/) +- [DROP RESOURCE GROUP](/docs/query/sql/acl/drop-resource-group/) +- [Configure and use resource groups](/docs/operations/resource-groups/) diff --git a/documentation/query/sql/acl/create-service-account.md b/documentation/query/sql/acl/create-service-account.md index ce9230ec9..97651a2ea 100644 --- a/documentation/query/sql/acl/create-service-account.md +++ b/documentation/query/sql/acl/create-service-account.md @@ -32,7 +32,7 @@ CREATE SERVICE ACCOUNT [IF NOT EXISTS] accountName [OWNED BY ownerName]; `CREATE SERVICE ACCOUNT` cannot set a memory limit. To cap the native memory each of the service account's queries may allocate, use -[`ALTER SERVICE ACCOUNT ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-service-account/#set-memory-limit) +[`ALTER SERVICE ACCOUNT ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-service-account-set-memory-limit/) after creating it. Service accounts do not inherit group limits. The chosen name must be unique across all users (including the built-in admin), diff --git a/documentation/query/sql/acl/create-user.md b/documentation/query/sql/acl/create-user.md index 65457ccb0..354374435 100644 --- a/documentation/query/sql/acl/create-user.md +++ b/documentation/query/sql/acl/create-user.md @@ -33,9 +33,9 @@ also be set for the user. `CREATE USER` cannot set a memory limit. To cap the native memory each of the user's queries may allocate, set a limit on the user with -[`ALTER USER ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-user/#set-memory-limit), +[`ALTER USER ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-user-set-memory-limit/), or on one of its groups with -[`ALTER GROUP ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-group/#set-memory-limit). +[`ALTER GROUP ... SET MEMORY LIMIT`](/docs/query/sql/acl/alter-group-set-memory-limit/). See [memory limits](/docs/security/rbac/#memory-limits) for how the two interact. @@ -67,7 +67,7 @@ CREATE USER IF NOT EXISTS john WITH NO PASSWORD; ``` If you want to update the user's password unconditionally, you can use the -[ALTER USER](/docs/query/sql/acl/alter-user/#set-password) command. +[ALTER USER](/docs/query/sql/acl/alter-user-with-password/) command. ## Examples diff --git a/documentation/query/sql/acl/drop-resource-group.md b/documentation/query/sql/acl/drop-resource-group.md new file mode 100644 index 000000000..677606b19 --- /dev/null +++ b/documentation/query/sql/acl/drop-resource-group.md @@ -0,0 +1,89 @@ +--- +title: DROP RESOURCE GROUP reference +sidebar_label: DROP RESOURCE GROUP +description: + "DROP RESOURCE GROUP removes a query workload policy once no principal is + mapped to it. Applies to QuestDB Enterprise." +--- + +import { EnterpriseNote } from "@site/src/components/EnterpriseNote" + + + Resource groups isolate competing query workloads inside a single QuestDB + instance. + + +`DROP RESOURCE GROUP` removes a query workload policy. + +For what the limits actually guarantee, see +[resource groups](/docs/concepts/resource-groups/). + +--- + +## Syntax + +```questdb-sql +DROP RESOURCE GROUP [IF EXISTS] groupName; +``` + +## Description + +The drop is refused while any principal is still mapped to the group. Unmap them +first with `UNSET RESOURCE GROUP` on each user, ACL group or service account. +The error names one of the blocking principals: + +``` +Resource Group is assigned to an ACL entity [entity=analyst] +``` + +Without `IF EXISTS`, dropping a group that does not exist raises an error. + +The statement requires the +[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission. + +`DEFAULT` cannot be dropped. `DROP RESOURCE GROUP DEFAULT` fails with +`built-in Resource Group cannot be dropped`. + +### What happens to queries still using it + +Once no principal is mapped, the group can be dropped even while its queries are +still running. It disappears from +[`resource_groups()`](/docs/query/functions/meta/#resource_groups) immediately, +but running and queued queries, including suspended cursors, carry on under the +settings the group had, and their memory still counts towards the process +budget. + +Recreating a group with the same name produces a new group with fresh counters. +Queries still running under the dropped group do not move to it. Map principals +to the new group to place their subsequent queries under it. + +Dropping the last group other than `DEFAULT` disengages CPU scheduling for the +instance once those queries finish. See +[when scheduling engages](/docs/concepts/resource-groups/#when-scheduling-engages). + +## Examples + +```questdb-sql title="Unmap the principals, then drop" +ALTER USER analyst UNSET RESOURCE GROUP; +ALTER GROUP analysts UNSET RESOURCE GROUP; + +DROP RESOURCE GROUP reporting; +``` + +```questdb-sql title="Drop only if present" +DROP RESOURCE GROUP IF EXISTS reporting; +``` + +To find every principal mapped to a group before dropping it: + +```questdb-sql title="List the principals blocking a drop" +SELECT name FROM (SHOW USERS) WHERE resource_group = 'reporting'; +SELECT name FROM (SHOW GROUPS) WHERE resource_group = 'reporting'; +SELECT name FROM (SHOW SERVICE ACCOUNTS) WHERE resource_group = 'reporting'; +``` + +## See also + +- [CREATE RESOURCE GROUP](/docs/query/sql/acl/create-resource-group/) +- [ALTER RESOURCE GROUP](/docs/query/sql/acl/alter-resource-group/) +- [Configure and use resource groups](/docs/operations/resource-groups/) diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index eb21445f8..38163957d 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -371,7 +371,7 @@ SHOW GROUPS; GiB), `null` when none is set. `external_alias` is empty when the group is not mapped to an external group. `resource_group` and `resource_group_priority` are the group's -[resource group](/docs/operations/resource-groups/#mapping-principals) mapping, +[resource group](/docs/query/sql/acl/alter-group-set-resource-group/) mapping, `null` when the group is not mapped. See [memory limits](/docs/security/rbac/#memory-limits). @@ -557,7 +557,7 @@ SHOW SERVICE ACCOUNTS; `memory_limit` is the account's own query memory limit in bytes (`268435456` is 256 MiB) and `resource_group` its -[resource group](/docs/operations/resource-groups/#mapping-principals) mapping, +[resource group](/docs/query/sql/acl/alter-service-account-set-resource-group/) mapping, each `null` when not set. Filtering by a user or group instead lists the service accounts that principal @@ -639,7 +639,7 @@ reports the effective limit and includes it. In `SHOW GROUPS` and neither inherits one. See [memory limits](/docs/security/rbac/#memory-limits). `resource_group` is the user's direct -[resource group](/docs/operations/resource-groups/#mapping-principals) mapping; +[resource group](/docs/query/sql/acl/alter-user-set-resource-group/) mapping; a user mapped only through an ACL group shows `null` here. :::note diff --git a/documentation/query/sql/switch-role.md b/documentation/query/sql/switch-role.md index 3c9e3b002..9b764b08d 100644 --- a/documentation/query/sql/switch-role.md +++ b/documentation/query/sql/switch-role.md @@ -133,6 +133,11 @@ session that submitted it. The outcome is visible through `SWITCH STATUS`, `GET /lifecycle`, and the server log. See [Refusals and the torn state](/docs/high-availability/failover/#refusals-and-the-torn-state). +With [resource groups](/docs/concepts/resource-groups/) enabled, a promotion is +also refused when the node has not yet received the resource group catalog. The +server log names `RESOURCE_GROUP_CATALOG_UNAVAILABLE` with the reason. See +[Replication and catalog lag](/docs/concepts/resource-groups/#behaviour-under-failure-and-on-replicas). + ## Examples A planned switchover runs on two instances, in this order: diff --git a/documentation/security/oidc.mdx b/documentation/security/oidc.mdx index 51e5d01ab..fb10a470f 100644 --- a/documentation/security/oidc.mdx +++ b/documentation/security/oidc.mdx @@ -655,7 +655,7 @@ ALTER GROUP groupName DROP EXTERNAL ALIAS 'CN=TestGroup1,OU=DC Users,DC=ad,DC=qu ``` See [`CREATE GROUP`](/docs/query/sql/acl/create-group/) and -[`ALTER GROUP`](/docs/query/sql/acl/alter-group/) for the full syntax. +[`ALTER GROUP`](/docs/query/sql/acl/alter-group-with-external-alias/) for the full syntax. External users cannot be given a query memory limit directly; `ALTER USER ... SET MEMORY LIMIT` is rejected for them. Set the limit on the diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index 62dd03447..e5e1841da 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -601,9 +601,10 @@ default. Per-principal limits are available since QuestDB Enterprise 4.0.2; the server-wide [workload limits](/docs/configuration/cairo-engine/#memory-limits) they override are available since QuestDB 10.0.0. -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/): +Set a limit with +[`ALTER USER`](/docs/query/sql/acl/alter-user-set-memory-limit/), +[`ALTER GROUP`](/docs/query/sql/acl/alter-group-set-memory-limit/), or +[`ALTER SERVICE ACCOUNT`](/docs/query/sql/acl/alter-service-account-set-memory-limit/): ```questdb-sql ALTER USER john SET MEMORY LIMIT 512M; @@ -845,7 +846,7 @@ SELECT * FROM all_permissions(); | SET TABLE TYPE | Database | Table | Change table type | | SETTINGS | Database | Change instance settings in Web Console | | SNAPSHOT | Database | Create snapshots | -| SQL ENGINE ADMIN | Database | List/cancel running queries | +| SQL ENGINE ADMIN | Database | List/cancel running queries, manage and map resource groups | | SWITCH ROLE | Database | Switch the replication role, read SWITCH STATUS | | SYSTEM ADMIN | Database | System functions (reload_tls, etc.) | | TRUNCATE TABLE | Database | Table | Truncate tables | @@ -900,9 +901,32 @@ replication role, is an ordinary grantable permission. ## SQL commands reference - [ADD USER](/docs/query/sql/acl/add-user/) -- [ALTER GROUP](/docs/query/sql/acl/alter-group/) -- [ALTER SERVICE ACCOUNT](/docs/query/sql/acl/alter-service-account/) -- [ALTER USER](/docs/query/sql/acl/alter-user/) +- ALTER GROUP + - [DROP EXTERNAL ALIAS](/docs/query/sql/acl/alter-group-drop-external-alias/) + - [SET MEMORY LIMIT](/docs/query/sql/acl/alter-group-set-memory-limit/) + - [SET RESOURCE GROUP](/docs/query/sql/acl/alter-group-set-resource-group/) + - [UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-group-unset-resource-group/) + - [WITH EXTERNAL ALIAS](/docs/query/sql/acl/alter-group-with-external-alias/) +- ALTER SERVICE ACCOUNT + - [CREATE TOKEN](/docs/query/sql/acl/alter-service-account-create-token/) + - [DISABLE](/docs/query/sql/acl/alter-service-account-disable/) + - [DROP TOKEN](/docs/query/sql/acl/alter-service-account-drop-token/) + - [ENABLE](/docs/query/sql/acl/alter-service-account-enable/) + - [SET MEMORY LIMIT](/docs/query/sql/acl/alter-service-account-set-memory-limit/) + - [SET RESOURCE GROUP](/docs/query/sql/acl/alter-service-account-set-resource-group/) + - [UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-service-account-unset-resource-group/) + - [WITH NO PASSWORD](/docs/query/sql/acl/alter-service-account-with-no-password/) + - [WITH PASSWORD](/docs/query/sql/acl/alter-service-account-with-password/) +- ALTER USER + - [CREATE TOKEN](/docs/query/sql/acl/alter-user-create-token/) + - [DISABLE](/docs/query/sql/acl/alter-user-disable/) + - [DROP TOKEN](/docs/query/sql/acl/alter-user-drop-token/) + - [ENABLE](/docs/query/sql/acl/alter-user-enable/) + - [SET MEMORY LIMIT](/docs/query/sql/acl/alter-user-set-memory-limit/) + - [SET RESOURCE GROUP](/docs/query/sql/acl/alter-user-set-resource-group/) + - [UNSET RESOURCE GROUP](/docs/query/sql/acl/alter-user-unset-resource-group/) + - [WITH NO PASSWORD](/docs/query/sql/acl/alter-user-with-no-password/) + - [WITH PASSWORD](/docs/query/sql/acl/alter-user-with-password/) - [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/) diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 7728fea13..986ff6a8d 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -288,13 +288,37 @@ module.exports = { label: "ALTER", items: [ { - id: "query/sql/acl/alter-group", - type: "doc", + type: "category", + label: "ALTER GROUP", + // Ordered by the label the sidebar renders, not the doc id + items: [ + "query/sql/acl/alter-group-drop-external-alias", // DROP EXTERNAL ALIAS + "query/sql/acl/alter-group-set-memory-limit", // SET MEMORY LIMIT + "query/sql/acl/alter-group-set-resource-group", // SET RESOURCE GROUP + "query/sql/acl/alter-group-unset-resource-group", // UNSET RESOURCE GROUP + "query/sql/acl/alter-group-with-external-alias", // WITH EXTERNAL ALIAS + ], }, { - id: "query/sql/acl/alter-service-account", + id: "query/sql/acl/alter-resource-group", type: "doc", }, + { + type: "category", + label: "ALTER SERVICE ACCOUNT", + // Ordered by the label the sidebar renders, not the doc id + items: [ + "query/sql/acl/alter-service-account-create-token", // CREATE TOKEN + "query/sql/acl/alter-service-account-disable", // DISABLE + "query/sql/acl/alter-service-account-drop-token", // DROP TOKEN + "query/sql/acl/alter-service-account-enable", // ENABLE + "query/sql/acl/alter-service-account-set-memory-limit", // SET MEMORY LIMIT + "query/sql/acl/alter-service-account-set-resource-group", // SET RESOURCE GROUP + "query/sql/acl/alter-service-account-unset-resource-group", // UNSET RESOURCE GROUP + "query/sql/acl/alter-service-account-with-no-password", // WITH NO PASSWORD + "query/sql/acl/alter-service-account-with-password", // WITH PASSWORD + ], + }, { type: "category", label: "ALTER TABLE", @@ -352,8 +376,20 @@ module.exports = { ], }, { - id: "query/sql/acl/alter-user", - type: "doc", + type: "category", + label: "ALTER USER", + // Ordered by the label the sidebar renders, not the doc id + items: [ + "query/sql/acl/alter-user-create-token", // CREATE TOKEN + "query/sql/acl/alter-user-disable", // DISABLE + "query/sql/acl/alter-user-drop-token", // DROP TOKEN + "query/sql/acl/alter-user-enable", // ENABLE + "query/sql/acl/alter-user-set-memory-limit", // SET MEMORY LIMIT + "query/sql/acl/alter-user-set-resource-group", // SET RESOURCE GROUP + "query/sql/acl/alter-user-unset-resource-group", // UNSET RESOURCE GROUP + "query/sql/acl/alter-user-with-no-password", // WITH NO PASSWORD + "query/sql/acl/alter-user-with-password", // WITH PASSWORD + ], }, "query/sql/alter-view", ], @@ -374,6 +410,10 @@ module.exports = { }, "query/sql/create-live-view", "query/sql/create-mat-view", + { + id: "query/sql/acl/create-resource-group", + type: "doc", + }, { id: "query/sql/acl/create-service-account", type: "doc", @@ -396,6 +436,10 @@ module.exports = { }, "query/sql/drop-live-view", "query/sql/drop-mat-view", + { + id: "query/sql/acl/drop-resource-group", + type: "doc", + }, { id: "query/sql/acl/drop-service-account", type: "doc", From 85432d8db2a75cfc61ad6f017956c61de0bb4fe7 Mon Sep 17 00:00:00 2001 From: javier Date: Wed, 16 Sep 2026 19:17:36 +0200 Subject: [PATCH 10/11] docs: reword the common scenarios pointer on the resource groups concept page --- documentation/concepts/resource-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/concepts/resource-groups.md b/documentation/concepts/resource-groups.md index 0b3fece60..0cf301d0c 100644 --- a/documentation/concepts/resource-groups.md +++ b/documentation/concepts/resource-groups.md @@ -19,7 +19,7 @@ consume while their queries run. One instance typically serves several workloads at once: dashboards that must answer in milliseconds, an ad-hoc analyst, and a nightly report that scans a year of data. When these workloads compete without resource controls, the report can increase dashboard latency. -[Common scenarios](/docs/operations/resource-groups/#common-scenarios) works +[Common scenarios](/docs/operations/resource-groups/#common-scenarios) walks through that case and five others as policies you can copy. Creating one takes two statements: From c4161c7f2372c5cf6204df7b1d51b694a94d686e Mon Sep 17 00:00:00 2001 From: victor Date: Tue, 22 Sep 2026 11:28:40 +0800 Subject: [PATCH 11/11] use RESOURCE GROUP ADMIN --- documentation/changelog.mdx | 2 +- documentation/operations/resource-groups.md | 8 ++++---- documentation/query/functions/meta.md | 8 ++++---- .../query/sql/acl/alter-group-set-resource-group.md | 2 +- .../query/sql/acl/alter-group-unset-resource-group.md | 2 +- documentation/query/sql/acl/alter-resource-group.md | 2 +- .../sql/acl/alter-service-account-set-resource-group.md | 2 +- .../sql/acl/alter-service-account-unset-resource-group.md | 2 +- .../query/sql/acl/alter-user-set-resource-group.md | 2 +- .../query/sql/acl/alter-user-unset-resource-group.md | 2 +- documentation/query/sql/acl/create-resource-group.md | 2 +- documentation/query/sql/acl/drop-resource-group.md | 2 +- documentation/security/rbac.md | 3 ++- 13 files changed, 20 insertions(+), 19 deletions(-) diff --git a/documentation/changelog.mdx b/documentation/changelog.mdx index c3e923206..f8ca8c90b 100644 --- a/documentation/changelog.mdx +++ b/documentation/changelog.mdx @@ -29,12 +29,12 @@ This page tracks significant updates to the QuestDB documentation. ### Reference - Added [`resource_groups()`](/docs/query/functions/meta/#resource_groups), which returns each group's policy alongside its live admission, CPU and memory counters, and [`current_resource_group()`](/docs/query/functions/meta/#current_resource_group), which reports the group a session's queries run in. `current_resource_group()` returns `DEFAULT` for an unmapped principal and `null` only when a query was never admitted to a group +- Added the [`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, which gates creating, altering and dropping resource groups, mapping principals to them, and reading `resource_groups()`. `SQL ENGINE ADMIN` keeps its existing scope of listing and cancelling queries - Added the [resource group metrics](/docs/operations/logging-metrics/#resource-group-metrics): nine Prometheus series per group labelled with `resource_group`, plus five instance-wide series for activation, catalog health, managed dispatch and scheduler degradation - Added the Fiber mode properties that resource groups depend on: [`http.worker.fiber.enabled`](/docs/configuration/http-server/#httpworkerfiberenabled), [`pg.worker.fiber.enabled`](/docs/configuration/postgres-wire-protocol/#pgworkerfiberenabled), [`shared.network.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharednetworkworkerfiberenabled), [`shared.query.worker.fiber.enabled`](/docs/configuration/shared-workers/#sharedqueryworkerfiberenabled) and [`mat.view.refresh.worker.fiber.enabled`](/docs/configuration/materialized-views/#matviewrefreshworkerfiberenabled), all defaulting to `true` and none reloadable ### Updated -- [SQL ENGINE ADMIN](/docs/security/rbac/#permissions) - Now also gates creating, altering and dropping resource groups, mapping principals to them, and reading `resource_groups()` - [query.timeout](/docs/configuration/cairo-engine/#querytimeout) - The clock includes time spent queued for resource group admission, so a query can time out before it starts. Over PGWire each `Execute` message restarts it - [Replication](/docs/high-availability/overview/#resource-groups-in-a-replicated-cluster) and [SWITCH ROLE](/docs/query/sql/switch-role/) - Resource group policies replicate through the WAL pipeline while enforcement stays local, so a lagging replica applies the policy it has, and a replica that has not received the catalog cannot be promoted - [query_activity()](/docs/query/functions/meta/#query_activity) - Documented the `is_wal`, `memory_used`, and `memory_limit` columns, and added a `resource_group` column showing which group each running query was admitted to. `memory_limit` is now capped by the group budget as well, so a principal with no limit of its own still reports one when its group sets a ceiling diff --git a/documentation/operations/resource-groups.md b/documentation/operations/resource-groups.md index 806575042..46410f604 100644 --- a/documentation/operations/resource-groups.md +++ b/documentation/operations/resource-groups.md @@ -27,10 +27,10 @@ siblings. but mapping statements require it, since mappings attach to ACL principals. - The worker pools that execute SQL must run in Fiber mode, which is the default. -- The [`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission for group - management, mappings and instance-wide inspection. Ordinary users need no - permission to call `current_resource_group()` and check their own query's - group. +- The [`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission for + group management, mappings and instance-wide inspection through + `resource_groups()`. Ordinary users need no permission to call + `current_resource_group()` and check their own query's group. Mapping a principal to a resource group grants it nothing by itself. The user still needs `HTTP` or `PGWIRE` to connect and `SELECT` on the tables it queries, diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index dfa141760..0ac2f6f59 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -98,7 +98,7 @@ to. Run it from a client session to confirm which group that session's queries land in, which is the quickest way to check that a principal mapping took effect. Any authenticated session can call it, with no permission of its own, unlike [`resource_groups()`](#resource_groups) which requires -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions). An ordinary user can +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions). An ordinary user can therefore check their own group but not read anyone else's policy. **Arguments:** @@ -642,9 +642,9 @@ is doing right now. Groups stay visible when `resource.groups.enabled` is `false`; only their counters sit at zero. Calling it requires the database-level -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, the same -permission that gates managing groups and listing or cancelling running queries. -A principal without it gets `Access denied for [SQL ENGINE ADMIN]`. +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, the same +permission that gates creating, altering and mapping groups. A principal without +it gets `Access denied for [RESOURCE GROUP ADMIN]`. **Arguments:** diff --git a/documentation/query/sql/acl/alter-group-set-resource-group.md b/documentation/query/sql/acl/alter-group-set-resource-group.md index eb7ab2e41..fb5eaf70d 100644 --- a/documentation/query/sql/acl/alter-group-set-resource-group.md +++ b/documentation/query/sql/acl/alter-group-set-resource-group.md @@ -29,7 +29,7 @@ ALTER GROUP groupName SET RESOURCE GROUP resourceGroupName ## Description Mapping an ACL group is how you cover a team without naming each member. It -requires the [`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, +requires the [`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and affects queries that start after the change rather than one already running. This is workload mapping: it decides how much of the instance the group's diff --git a/documentation/query/sql/acl/alter-group-unset-resource-group.md b/documentation/query/sql/acl/alter-group-unset-resource-group.md index 40d671936..d3b267b77 100644 --- a/documentation/query/sql/acl/alter-group-unset-resource-group.md +++ b/documentation/query/sql/acl/alter-group-unset-resource-group.md @@ -36,7 +36,7 @@ The priority is removed with the mapping; there is no way to clear one while keeping the other. The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and affects queries that start after the change rather than one already running. Unmapping is also the prerequisite for dropping a resource group: diff --git a/documentation/query/sql/acl/alter-resource-group.md b/documentation/query/sql/acl/alter-resource-group.md index 65ab40062..a659915e1 100644 --- a/documentation/query/sql/acl/alter-resource-group.md +++ b/documentation/query/sql/acl/alter-resource-group.md @@ -49,7 +49,7 @@ which is not always "unlimited": `RESET (cpu_weight)` returns the group to a weight of 100, and `RESET (queue_timeout)` returns it to 30 seconds. The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission. +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission. `DEFAULT` can be altered but not renamed. `ALTER RESOURCE GROUP DEFAULT RENAME TO` fails with `built-in Resource Group cannot be renamed`. diff --git a/documentation/query/sql/acl/alter-service-account-set-resource-group.md b/documentation/query/sql/acl/alter-service-account-set-resource-group.md index fc8a3c81b..b5312cdb7 100644 --- a/documentation/query/sql/acl/alter-service-account-set-resource-group.md +++ b/documentation/query/sql/acl/alter-service-account-set-resource-group.md @@ -36,7 +36,7 @@ exactly one mapping and there is nothing to break a tie between. It belongs to [`ALTER GROUP SET RESOURCE GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/). The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and affects queries that start after the change rather than one already running. This governs the queries the account runs. It does not throttle ingestion, which diff --git a/documentation/query/sql/acl/alter-service-account-unset-resource-group.md b/documentation/query/sql/acl/alter-service-account-unset-resource-group.md index 186103d80..38098b860 100644 --- a/documentation/query/sql/acl/alter-service-account-unset-resource-group.md +++ b/documentation/query/sql/acl/alter-service-account-unset-resource-group.md @@ -31,7 +31,7 @@ mapping to fall back to, because a service account cannot belong to a group. Unsetting therefore leaves it governed only by `DEFAULT`'s policy. The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and affects queries that start after the change rather than one already running. Unmapping is also the prerequisite for dropping a resource group: diff --git a/documentation/query/sql/acl/alter-user-set-resource-group.md b/documentation/query/sql/acl/alter-user-set-resource-group.md index 5c54afb47..a7627b114 100644 --- a/documentation/query/sql/acl/alter-user-set-resource-group.md +++ b/documentation/query/sql/acl/alter-user-set-resource-group.md @@ -36,7 +36,7 @@ direct mapping and there is nothing to break a tie between. It belongs to [`ALTER GROUP SET RESOURCE GROUP`](/docs/query/sql/acl/alter-group-set-resource-group/). The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and affects queries that start after the change rather than one already running. ## Examples diff --git a/documentation/query/sql/acl/alter-user-unset-resource-group.md b/documentation/query/sql/acl/alter-user-unset-resource-group.md index e7388de12..99452b3a6 100644 --- a/documentation/query/sql/acl/alter-user-unset-resource-group.md +++ b/documentation/query/sql/acl/alter-user-unset-resource-group.md @@ -31,7 +31,7 @@ back to the highest-priority mapping among its ACL groups, or to `DEFAULT` when it belongs to none that are mapped. The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission, and affects +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission, and affects queries that start after the change rather than one already running. Unmapping is also the prerequisite for dropping a resource group: diff --git a/documentation/query/sql/acl/create-resource-group.md b/documentation/query/sql/acl/create-resource-group.md index fd237c524..877fc04f3 100644 --- a/documentation/query/sql/acl/create-resource-group.md +++ b/documentation/query/sql/acl/create-resource-group.md @@ -53,7 +53,7 @@ The name must be unique across resource groups. If it is already taken the statement fails, unless `IF NOT EXISTS` is included. The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission. +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission. Creating a second resource group engages CPU scheduling for the whole instance. While `DEFAULT` is the only group there is no CPU slicing at all, so weights diff --git a/documentation/query/sql/acl/drop-resource-group.md b/documentation/query/sql/acl/drop-resource-group.md index 677606b19..c898a3dbd 100644 --- a/documentation/query/sql/acl/drop-resource-group.md +++ b/documentation/query/sql/acl/drop-resource-group.md @@ -39,7 +39,7 @@ Resource Group is assigned to an ACL entity [entity=analyst] Without `IF EXISTS`, dropping a group that does not exist raises an error. The statement requires the -[`SQL ENGINE ADMIN`](/docs/security/rbac/#permissions) permission. +[`RESOURCE GROUP ADMIN`](/docs/security/rbac/#permissions) permission. `DEFAULT` cannot be dropped. `DROP RESOURCE GROUP DEFAULT` fails with `built-in Resource Group cannot be dropped`. diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index e5e1841da..17b226a67 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -839,6 +839,7 @@ SELECT * FROM all_permissions(); | REMOVE STORAGE POLICY | Database | Table | Remove storage policies | | RENAME COLUMN | Database | Table | Column | Rename columns | | RENAME TABLE | Database | Table | Rename tables | +| RESOURCE GROUP ADMIN | Database | Manage and map resource groups, read `resource_groups()` | | RESUME WAL | Database | Table | Resume WAL processing | | SELECT | Database | Table | Column | Read data | | SET STORAGE POLICY | Database | Table | Set storage policies | @@ -846,7 +847,7 @@ SELECT * FROM all_permissions(); | SET TABLE TYPE | Database | Table | Change table type | | SETTINGS | Database | Change instance settings in Web Console | | SNAPSHOT | Database | Create snapshots | -| SQL ENGINE ADMIN | Database | List/cancel running queries, manage and map resource groups | +| SQL ENGINE ADMIN | Database | List/cancel running queries | | SWITCH ROLE | Database | Switch the replication role, read SWITCH STATUS | | SYSTEM ADMIN | Database | System functions (reload_tls, etc.) | | TRUNCATE TABLE | Database | Table | Truncate tables |