diff --git a/documentation/architecture/storage-engine.md b/documentation/architecture/storage-engine.md index 5f2335618..ada358aef 100644 --- a/documentation/architecture/storage-engine.md +++ b/documentation/architecture/storage-engine.md @@ -55,9 +55,10 @@ Older partitions (any partition other than the most recent one) can be converted Partitions in Parquet format remain fully available for queries. Users don't need to know whether a partition is in QuestDB binary format or Parquet format. All the data types available in QuestDB can be converted to Parquet. -When using QuestDB Enterprise, tables can be configured to convert to Parquet automatically and to send the Parquet -files to object storage (Amazon S3, Microsoft Blob Storage, Google Cloud Storage, NFS...). This can help reduce the -cost of storing historical data while keeping it fully available for queries. +When using QuestDB Enterprise, tables can be configured to convert to Parquet automatically using +[storage policies](/docs/concepts/storage-policy/). This can help reduce local disk usage +while keeping historical data fully available for queries. Support for automatic upload +of Parquet files to object storage will be added in a future release. diff --git a/documentation/concepts/materialized-views.md b/documentation/concepts/materialized-views.md index 4ae5c248d..92dd94bb7 100644 --- a/documentation/concepts/materialized-views.md +++ b/documentation/concepts/materialized-views.md @@ -336,6 +336,16 @@ CREATE MATERIALIZED VIEW trades_hourly AS ( The view's TTL is independent of the base table's TTL. +:::note + +In QuestDB Enterprise, TTL is superseded by +[storage policies](/docs/concepts/storage-policy/). Use +[`STORAGE POLICY(...)`](/docs/query/sql/alter-mat-view-set-storage-policy/) on +a materialized view instead of `TTL` for graduated lifecycle management +(convert to Parquet, then drop). + +::: + ### Initial refresh When created, materialized views start an **asynchronous full refresh**: @@ -592,6 +602,9 @@ the replica's view was not fully up-to-date. Sets the time limit for incremental refresh on a materialized view - [`ALTER MATERIALIZED VIEW SET TTL`](/docs/query/sql/alter-mat-view-set-ttl/): Sets the time-to-live (TTL) period on a materialized view + - [`ALTER MATERIALIZED VIEW SET STORAGE POLICY`](/docs/query/sql/alter-mat-view-set-storage-policy/): + Attaches a [storage policy](/docs/concepts/storage-policy/) to a + materialized view (QuestDB Enterprise) - **Configuration** - [Materialized views configs](/docs/configuration/overview/#materialized-views): diff --git a/documentation/concepts/partitions.md b/documentation/concepts/partitions.md index decbef1f6..6aed44d68 100644 --- a/documentation/concepts/partitions.md +++ b/documentation/concepts/partitions.md @@ -189,3 +189,5 @@ partition as a single unit. - [DETACH PARTITION](/docs/query/sql/alter-table-detach-partition/) — Move to cold storage - [ATTACH PARTITION](/docs/query/sql/alter-table-attach-partition/) — Restore detached data - [TTL](/docs/concepts/ttl/) — Automatic partition cleanup by age +- [Storage Policy](/docs/concepts/storage-policy/) — Graduated partition + lifecycle (convert to Parquet, then drop) in QuestDB Enterprise diff --git a/documentation/concepts/storage-policy.md b/documentation/concepts/storage-policy.md new file mode 100644 index 000000000..aaa27a490 --- /dev/null +++ b/documentation/concepts/storage-policy.md @@ -0,0 +1,363 @@ +--- +title: Storage Policy +sidebar_label: Storage Policy +description: Automate partition lifecycle in QuestDB Enterprise - convert to Parquet locally and drop old data on a schedule. +--- + +:::note + +Storage policies are available in **QuestDB Enterprise** only. + +::: + +A storage policy automates the lifecycle of table partitions. It defines when +partitions are converted to Parquet, when native data is removed, and when local +copies are dropped. This replaces the need for manual partition management or +external scheduling. + +:::info + +Storage policies currently operate **locally only**. Parquet files are not +automatically uploaded to object storage, and the `DROP REMOTE` clause is +reserved syntax — it is rejected at SQL parse time with +`'DROP REMOTE' is not supported yet`. Accordingly, the `drop_remote` +column in the [`storage_policies`](/docs/query/functions/meta/#storage_policies) +view is always blank in the current release; it is kept for forward +compatibility. Object storage integration will be added in a future release. + +::: + +## Requirements + +Storage policies require: + +- A [designated timestamp](/docs/concepts/designated-timestamp/) column +- [Partitioning](/docs/concepts/partitions/) enabled +- QuestDB Enterprise + +## How it works + +A storage policy consists of up to four TTL settings. Each setting controls a +stage in the partition lifecycle: + +| Setting | Description | +|---------|-------------| +| `TO PARQUET` | Convert the partition from native binary format to Parquet | +| `DROP NATIVE` | Remove native binary files, keeping only the local Parquet copy | +| `DROP LOCAL` | Remove all local data (both native and Parquet) | +| `DROP REMOTE` | _Reserved._ Will remove the Parquet file from object storage when remote upload is supported | + +All settings are optional. Use only the ones relevant to your use case. All TTL +values must be **positive**; `0` is rejected. + +### Partition lifecycle + +As time passes, each partition progresses through the stages defined by the +policy: + +```text + TO PARQUET DROP NATIVE DROP LOCAL + [Native] ──────────┬──────────────────┬──────────────────┬─────── + │ │ │ + ▼ ▼ ▼ + Native + Parquet Parquet only Data removed + (local) (local) +``` + +### TTL evaluation + +Storage policy TTLs follow the same evaluation rules as +[TTL](/docs/concepts/ttl/). A partition becomes eligible for a lifecycle action +when its **entire time range** falls outside the TTL window: + +```text +eligible when: partition_end_time < reference_time - TTL +``` + +**This rule is applied independently for each stage's TTL.** A partition can +be eligible for `TO PARQUET` long before it is eligible for `DROP NATIVE`, +`DROP LOCAL`, or (one day) `DROP REMOTE`. Each stage uses its own `TTL` in the +formula above; the stages share only the reference time and the ordering +constraint `TO PARQUET <= DROP NATIVE <= DROP LOCAL <= DROP REMOTE`. + +The reference time is `min(wall_clock_time, latest_timestamp)` by default — +the same formula used by TTL. The +[`cairo.ttl.use.wall.clock`](/docs/concepts/ttl/#restore-legacy-behavior) +setting applies to storage policies as well: setting it to `false` removes +the wall-clock cap for both TTL and storage policy evaluation. See +[TTL § Reference time](/docs/concepts/ttl/#reference-time) for the rationale +and the data-loss hazard of disabling the cap. + +QuestDB checks storage policies periodically (every 15 minutes by default) and +processes eligible partitions automatically. + +## Storage policy vs TTL + +Storage policies replace [TTL](/docs/concepts/ttl/) in QuestDB Enterprise. If +you are already familiar with TTL, this comparison is the fastest way in: + +| | TTL | Storage Policy | +|---|-----|----------------| +| **Availability** | Open source | Enterprise only | +| **Action** | Drops partitions entirely | Graduated lifecycle (convert, then drop) | +| **Parquet conversion** | No | Yes (automatic local conversion) | +| **Granularity** | Single retention window | Up to four independent TTL stages | + +In QuestDB Enterprise, `CREATE TABLE ... TTL` and `ALTER TABLE SET TTL` are +deprecated. Use storage policies instead: + +```questdb-sql +-- Instead of: +-- ALTER TABLE trades SET TTL 30 DAYS; + +-- Use: +ALTER TABLE trades SET STORAGE POLICY(DROP LOCAL 30d); +``` + +:::note + +If a table already has a TTL set, you must clear it with +`ALTER TABLE SET TTL 0` before setting a storage policy. `SET TTL 0` is the +only `SET TTL` value Enterprise accepts; any non-zero value is rejected with +`TTL settings are deprecated, please, create a storage policy instead`. + +::: + +## Setting a storage policy + +### At table creation + +```questdb-sql +CREATE TABLE trades ( + ts TIMESTAMP, + symbol SYMBOL, + price DOUBLE +) TIMESTAMP(ts) PARTITION BY DAY + STORAGE POLICY(TO PARQUET 3d, DROP NATIVE 10d, DROP LOCAL 1M) + WAL; +``` + +### On existing tables + +```questdb-sql +ALTER TABLE trades SET STORAGE POLICY( + TO PARQUET 3 DAYS, + DROP NATIVE 10 DAYS, + DROP LOCAL 1 MONTH +); +``` + +Only the specified settings are changed. Omitted settings remain unchanged. + +### On materialized views + +```questdb-sql +CREATE MATERIALIZED VIEW hourly_trades AS ( + SELECT ts, symbol, sum(price) total + FROM trades + SAMPLE BY 1h +) PARTITION BY DAY + STORAGE POLICY(TO PARQUET 7d, DROP NATIVE 14d); +``` + +```questdb-sql +ALTER MATERIALIZED VIEW hourly_trades SET STORAGE POLICY(TO PARQUET 7d); +``` + +For full syntax details, see +[ALTER TABLE SET STORAGE POLICY](/docs/query/sql/alter-table-set-storage-policy/). + +## TTL duration format + +Storage policy TTLs accept the same duration formats as +[TTL](/docs/concepts/ttl/): + +| Unit | Long form | Short form | +|------|-----------|------------| +| Hours | `1 HOUR` / `2 HOURS` | `1h` | +| Days | `1 DAY` / `3 DAYS` | `1d` / `3d` | +| Weeks | `1 WEEK` / `2 WEEKS` | `1W` / `2W` | +| Months | `1 MONTH` / `6 MONTHS` | `1M` / `6M` | +| Years | `1 YEAR` / `2 YEARS` | `1Y` / `2Y` | + +### Ordering constraint + +TTL values must be in ascending order: + +```text +TO PARQUET <= DROP NATIVE <= DROP LOCAL <= DROP REMOTE +``` + +For example, you cannot drop native files before the Parquet conversion +completes. All TTL values must be positive — `0` is rejected. + +## Disabling and enabling + +Temporarily suspend a storage policy without removing it: + +```questdb-sql +ALTER TABLE trades DISABLE STORAGE POLICY; +``` + +Re-enable it later: + +```questdb-sql +ALTER TABLE trades ENABLE STORAGE POLICY; +``` + +Both `ENABLE` and `DISABLE` require a policy to exist on the table; the +statement returns an error otherwise. + +## Removing a storage policy + +To permanently remove a storage policy from a table: + +```questdb-sql +ALTER TABLE trades DROP STORAGE POLICY; +``` + +## Checking storage policies + +Query the `storage_policies` system view to see all active policies: + +```questdb-sql +SELECT * FROM storage_policies; +``` + +| table_dir_name | to_parquet | drop_native | drop_local | drop_remote | status | last_updated | +|----------------|-----------|-------------|------------|-------------|--------|--------------| +| trades~12 | 72h | 240h | 1m | | A | 2025-01-15T10:30:00.000000Z | + +- TTL values are rendered in just two units: `h` for hours and `m` for + **months**. Hour-, day-, and week-based durations are normalized to hours + when stored, so a `3 DAYS` TTL appears as `72h` and `1 WEEK` appears as + `168h`. Month-based durations keep the lowercase `m` suffix — **`1m` in + this view means one month, not one minute**; QuestDB's duration shorthand + has no unit for minutes +- Status `A` means active; `D` means disabled (see + [Disabling and enabling](#disabling-and-enabling)) +- Unset stages appear blank. `drop_remote` is **always blank in the current + release** because `DROP REMOTE` is rejected at SQL parse time with + `'DROP REMOTE' is not supported yet`; the column is kept for forward + compatibility + +For the full column reference and types, see +[`storage_policies`](/docs/query/functions/meta/#storage_policies). + +## Replication + +Storage policy definitions are persisted in WAL-backed system tables, so the +policy itself is replicated to every instance in the cluster. Enforcement runs +**independently on each instance** — Parquet files are produced locally and +are not replicated. + +This means the primary and its replicas can temporarily disagree on which +partitions have been converted to Parquet or dropped, depending on when each +node's storage policy [check interval](#configuration) last fired. The state +converges as each instance processes its own queue. See +[Replication overview](/docs/high-availability/overview/#storage-policies-in-a-replicated-cluster) +for details. + +## Configuration + +Storage policy behavior can be tuned in `server.conf`. Time-based properties +accept values with unit suffixes (e.g., `15m`, `30s`, `1h`) or raw microsecond +values: + +| Property | Default | Description | +|----------|---------|-------------| +| `storage.policy.check.interval` | `15m` (15 min) | How often QuestDB scans for partitions to process | +| `storage.policy.retry.interval` | `1m` (1 min) | Retry interval for failed tasks | +| `storage.policy.max.reschedule.count` | `20` | Maximum retries before abandoning a task | +| `storage.policy.writer.wait.timeout` | `30s` (30 sec) | Timeout for acquiring the table writer | +| `storage.policy.worker.count` | `2` | Number of storage policy worker threads (0 disables the feature) | +| `storage.policy.worker.affinity` | `-1` (no affinity) | CPU affinity for each worker thread (comma-separated list) | +| `storage.policy.worker.sleep.timeout` | `100ms` | Sleep duration when worker has no tasks | + +## Permissions + +Storage policy operations require specific permissions in QuestDB Enterprise: + +| Operation | Required permission | +|-----------|-------------------| +| `SET STORAGE POLICY` | `SET STORAGE POLICY` | +| `DROP STORAGE POLICY` | `REMOVE STORAGE POLICY` | +| `ENABLE STORAGE POLICY` | `ENABLE STORAGE POLICY` | +| `DISABLE STORAGE POLICY` | `DISABLE STORAGE POLICY` | + +Grant permissions using standard RBAC syntax: + +```questdb-sql +GRANT SET STORAGE POLICY ON trades TO analyst; +GRANT REMOVE STORAGE POLICY ON trades TO admin; +``` + +## End-to-end example + +A complete lifecycle on a single table, from creation through verification, +modification, inspection of the generated DDL, temporary suspension, and +permanent removal: + +```questdb-sql title="1. Create the table with a storage policy" +CREATE TABLE trades ( + ts TIMESTAMP, + symbol SYMBOL, + price DOUBLE +) TIMESTAMP(ts) PARTITION BY DAY + STORAGE POLICY(TO PARQUET 3d, DROP NATIVE 10d, DROP LOCAL 1M) + WAL; +``` + +```questdb-sql title="2. Verify via the system view" +SELECT table_dir_name, to_parquet, drop_native, drop_local, status +FROM storage_policies +WHERE table_dir_name LIKE 'trades%'; +``` + +| table_dir_name | to_parquet | drop_native | drop_local | status | +|----------------|------------|-------------|------------|--------| +| trades~12 | 72h | 240h | 1m | A | + +```questdb-sql title="3. Modify one stage (others remain unchanged)" +ALTER TABLE trades SET STORAGE POLICY(TO PARQUET 1d); +``` + +```questdb-sql title="4. Inspect the current DDL" +SHOW CREATE TABLE trades; +``` + +```text +CREATE TABLE 'trades' ( + ts TIMESTAMP, + symbol SYMBOL CAPACITY 256 CACHE, + price DOUBLE +) timestamp(ts) PARTITION BY DAY +STORAGE POLICY(TO PARQUET 1 DAY, DROP NATIVE 10 DAYS, DROP LOCAL 1 MONTH) WAL; +``` + +```questdb-sql title="5. Temporarily suspend the policy (e.g. during a backfill)" +ALTER TABLE trades DISABLE STORAGE POLICY; +-- status in storage_policies changes to 'D' +``` + +```questdb-sql title="6. Re-enable and, later, drop it for good" +ALTER TABLE trades ENABLE STORAGE POLICY; +ALTER TABLE trades DROP STORAGE POLICY; +-- row disappears from storage_policies +``` + +## Guidelines + +| Use case | Suggested policy | Rationale | +|----------|-----------------|-----------| +| Real-time metrics | `TO PARQUET 1d, DROP NATIVE 7d, DROP LOCAL 30d` | Keep recent data fast, drop old data automatically | +| Trading data | `TO PARQUET 7d, DROP NATIVE 30d` | Keep Parquet locally for long-term queries | +| IoT telemetry | `TO PARQUET 1d, DROP NATIVE 3d, DROP LOCAL 90d` | High volume, convert early to save disk; keep a brief native overlap for in-flight queries before dropping the native files | +| Aggregated views | `TO PARQUET 30d` | Low volume, keep locally in Parquet | + +**Tips:** + +- Start with `TO PARQUET` and `DROP NATIVE` to reduce local disk usage while + keeping data queryable in Parquet format +- Use `DROP LOCAL` with care as it permanently removes data from the local disk +- TTL values should be significantly larger than the partition interval diff --git a/documentation/concepts/ttl.md b/documentation/concepts/ttl.md index d4db9c510..d7b9e34ab 100644 --- a/documentation/concepts/ttl.md +++ b/documentation/concepts/ttl.md @@ -8,6 +8,20 @@ TTL (Time To Live) automatically drops old partitions based on data age. Set a retention period, and QuestDB removes partitions that fall entirely outside that window - no cron jobs or manual cleanup required. +:::caution + +**QuestDB Enterprise: TTL is superseded by +[Storage Policy](/docs/concepts/storage-policy/).** Enterprise rejects any +non-zero `SET TTL` with +`TTL settings are deprecated, please, create a storage policy instead`. +Storage policies extend TTL with graduated lifecycle management (convert to +Parquet, then drop) and are the recommended retention primitive for Enterprise +users. The rest of this page describes TTL behavior on QuestDB Open Source +(and the `SET TTL 0` case on Enterprise, used to clear an older TTL before +attaching a storage policy). + +::: + import Screenshot from "@theme/Screenshot" +## Storage policy + +:::note + +Storage policy is [Enterprise](/enterprise/) only. + +::: + +Storage policies automate partition lifecycle management, including local +deletion and cold storage offloading. + +For details, see the +[storage policy concept](/docs/concepts/storage-policy/) page. + + + ## Logging & Metrics The following settings are available in `server.conf`: diff --git a/documentation/getting-started/enterprise-quick-start.md b/documentation/getting-started/enterprise-quick-start.md index 03655aa13..1e1f64483 100644 --- a/documentation/getting-started/enterprise-quick-start.md +++ b/documentation/getting-started/enterprise-quick-start.md @@ -30,7 +30,8 @@ inform your own unique choices. [6. Query data, PostgreSQL query](#6-query-data-postgresql-query)\ [7. Setup replication](#7-setup-replication)\ [8. Enable compression](#8-enable-compression)\ -[9. Double-check kernel limits](#9-double-check-kernel-limits)\ +[9. Configure a storage policy](#9-configure-a-storage-policy)\ +[10. Double-check kernel limits](#10-double-check-kernel-limits)\ [Next steps](#next-steps)\ [FAQ](#faq) @@ -467,7 +468,95 @@ of Kubernetes is supported. For more on storage and compression, see [Enable compression with ZFS](/docs/deployment/compression-zfs/). -## 9. Double-check kernel limits +## 9. Configure a storage policy + +[Storage policies](/docs/concepts/storage-policy/) are the Enterprise primitive +for automated partition retention. A policy converts older partitions to +Parquet, drops the native binary files, and eventually drops the Parquet files +too — on a schedule you define. This supersedes plain TTL in Enterprise, where +`ALTER TABLE SET TTL` with a non-zero value is rejected. + +### Migrating from TTL when upgrading from OSS + +Tables and materialized views that were created in OSS keep their existing +`TTL` setting after you upgrade to Enterprise — no data is lost at upgrade +time. However, Enterprise rejects any **new** `TTL` changes (both +`CREATE ... TTL` and `ALTER ... SET TTL `) with: + +``` +TTL settings are deprecated, please, create a storage policy instead +``` + +To move a legacy table or materialized view from `TTL` to a storage policy: + +1. **Clear the existing TTL** by setting it to `0`. This is the only `SET TTL` + value Enterprise accepts, and it is required before a storage policy can be + attached: + + ```questdb-sql title="Clear the legacy TTL" + ALTER TABLE trades SET TTL 0; + -- or, for a materialized view: + ALTER MATERIALIZED VIEW trades_hourly SET TTL 0; + ``` + +2. **Attach a storage policy** that reproduces — and ideally extends — the + retention the TTL used to provide. A policy lets you keep data in Parquet + after you would previously have dropped it, so `DROP LOCAL` (or + `DROP NATIVE` if you don't want Parquet at all) is the stage that replaces + the old TTL horizon: + + ```questdb-sql title="Replace a 1-month TTL with an equivalent policy" + ALTER TABLE trades SET STORAGE POLICY( + TO PARQUET 3 DAYS, + DROP NATIVE 10 DAYS, + DROP LOCAL 1 MONTH + ); + ``` + + If you want the policy to behave exactly like the old TTL (delete the + partition outright after the same interval), use a single-stage policy — + for example `STORAGE POLICY(DROP NATIVE 1 MONTH)` to match `TTL 1 MONTH`. + +Do this for every table and materialized view you want to keep managed +automatically. Tables without a storage policy retain their data indefinitely +once their legacy TTL has been cleared. + +### Creating new tables with a storage policy + +Attach a policy at table creation: + +```questdb-sql title="Web Console - Create a table with a storage policy" +CREATE TABLE trades ( + ts TIMESTAMP, + symbol SYMBOL, + price DOUBLE +) TIMESTAMP(ts) PARTITION BY DAY + STORAGE POLICY(TO PARQUET 3d, DROP NATIVE 10d, DROP LOCAL 1M) + WAL; +``` + +Or attach a policy to an existing table: + +```questdb-sql title="Web Console - Set a storage policy on an existing table" +ALTER TABLE trades SET STORAGE POLICY( + TO PARQUET 3 DAYS, + DROP NATIVE 10 DAYS, + DROP LOCAL 1 MONTH +); +``` + +Check active policies via the `storage_policies` system view: + +```questdb-sql +SELECT * FROM storage_policies; +``` + +For the full concept, including the stage model, timing, and replication +behavior, see +[Storage Policy](/docs/concepts/storage-policy/) and +[ALTER TABLE SET STORAGE POLICY](/docs/query/sql/alter-table-set-storage-policy/). + +## 10. Double-check kernel limits QuestDB works together with your server operating system to achieve maximum performance. Prior to putting your server under heavy loads, consider checking diff --git a/documentation/getting-started/migrate-to-enterprise.md b/documentation/getting-started/migrate-to-enterprise.md index 04087663b..7056619af 100644 --- a/documentation/getting-started/migrate-to-enterprise.md +++ b/documentation/getting-started/migrate-to-enterprise.md @@ -16,8 +16,9 @@ stays in place and works immediately. - **Role-based access control (RBAC)** with users, groups, and permissions - **Single Sign-On (SSO)** via OpenID Connect - **Database replication** for high availability -- **Multi-tier storage** with seamless object storage integration -- **Automated backup and recovery** for data protection +- **[Storage policies](/docs/concepts/storage-policy/)** for automated partition + lifecycle management (convert to Parquet, then drop on a schedule) +- **Automated [backup and recovery](/docs/operations/backup/)** for data protection ## Upgrade steps diff --git a/documentation/high-availability/overview.md b/documentation/high-availability/overview.md index 14bf49bfd..c36f38bde 100644 --- a/documentation/high-availability/overview.md +++ b/documentation/high-availability/overview.md @@ -79,6 +79,28 @@ cases. Tables without timestamps (typically used for reference/lookup data) are not replicated automatically and should be populated separately on each instance. +## Storage policies in a replicated cluster + +[Storage policy](/docs/concepts/storage-policy/) definitions are stored in +WAL-backed system tables, so the policy itself — the `TO PARQUET`, +`DROP NATIVE`, and `DROP LOCAL` TTLs and the active/disabled status — is +replicated to every instance through the same WAL pipeline as user data. + +Enforcement, however, runs **independently on each instance**. Parquet files +are produced locally and are not replicated; each node's storage policy job +schedules its own `PARQUET_CONVERSION`, `PARQUET_COMMIT`, and `DROP_LOCAL` +work against its local data. As a result: + +- At any given moment a partition may be in different states across the + primary and its replicas (e.g. already converted on the primary but still + native on a replica that hasn't yet hit its check interval). +- These differences are temporary. Once each instance's check job runs and + processes the partition, the cluster converges to the same logical state. +- Tuning the [check interval](/docs/concepts/storage-policy/#configuration) + (`storage.policy.check.interval`) or worker count + (`storage.policy.worker.count`) per instance lets you trade conversion + latency against background load on that node. + ## Bring Your Own Cloud (BYOC) QuestDB Enterprise can be self-managed or operated by QuestDB's team under the diff --git a/documentation/operations/backup.md b/documentation/operations/backup.md index 08bfe029f..012592d4e 100644 --- a/documentation/operations/backup.md +++ b/documentation/operations/backup.md @@ -332,6 +332,31 @@ the object store. Backups are stored under `backup//`. To find your instance name, see [Backup instance name](#backup-instance-name). +### Interaction with storage policies + +[Storage policies](/docs/concepts/storage-policy/) operate locally — they +convert partitions to Parquet in place and then drop native (and eventually +local Parquet) files on a schedule. Backups capture whatever is on local disk +at the time the backup runs: + +- Partitions still in native format are backed up as native files. +- Partitions that have been converted to Parquet (via the `TO PARQUET` stage, + after `DROP NATIVE` has fired) are backed up as Parquet files. +- Once `DROP LOCAL` fires and removes a partition from local disk, subsequent + backups will no longer contain that partition — restoring an earlier backup + is the only way to recover it. + +Plan retention (`backup.cleanup.keep.latest.n`) with your storage policy's +`DROP LOCAL` TTL in mind: a partition is only recoverable from a backup that +was taken **before** `DROP LOCAL` removed it from disk. If you need to keep +historical partitions available for restore, make sure your oldest retained +backup predates the earliest `DROP LOCAL` fire. + +Storage policies run per-instance, so primaries and replicas may disagree on +which partitions are native vs. Parquet at any given moment. Typically, +backing up the primary is sufficient (see the bullet on +primary/replica backups below). + ### Limitations - **Database-wide only**: Backup captures the entire database. You cannot diff --git a/documentation/operations/data-retention.md b/documentation/operations/data-retention.md index 18dcfb4c9..7d69a978f 100644 --- a/documentation/operations/data-retention.md +++ b/documentation/operations/data-retention.md @@ -10,11 +10,17 @@ over time. If stale data is no longer required, users can delete old data from QuestDB to either save disk space or adhere to a data retention policy. This is achieved in QuestDB by removing data partitions from a table. -QuestDB offers two approaches for data retention: - -- **Automatic**: Use [Time To Live (TTL)](/docs/concepts/ttl/) to automatically - drop partitions when data ages beyond a specified threshold. This is the - simplest approach for most use cases. +QuestDB offers three approaches for data retention: + +- **TTL** _(automatic, open source)_: Use + [Time To Live (TTL)](/docs/concepts/ttl/) to automatically drop partitions when + data ages beyond a specified threshold. This is the simplest approach and is + available in both open source and Enterprise editions. +- **Storage policy** _(automatic, Enterprise only)_: Use a + [storage policy](/docs/concepts/storage-policy/) to automate the partition + lifecycle — convert to Parquet locally and drop old data on a schedule. This is + the recommended approach for QuestDB Enterprise users who need graduated data + tiering beyond simple deletion. - **Manual**: Use `DROP PARTITION` commands as described on this page for explicit control over which partitions to remove and when. diff --git a/documentation/query/export-parquet.md b/documentation/query/export-parquet.md index 989ed9bcc..fc5c7c3d2 100644 --- a/documentation/query/export-parquet.md +++ b/documentation/query/export-parquet.md @@ -18,6 +18,17 @@ All methods compress with `lz4_raw` by default. See [Data Compression](#data-com To read and query Parquet files, see the [`read_parquet` function](/docs/query/functions/parquet/). +:::tip + +In QuestDB Enterprise, in-place Parquet conversion can be **automated** via +[storage policies](/docs/concepts/storage-policy/). A storage policy runs the +conversion on a schedule (e.g., convert to Parquet after 3 days), and can also +drop the native files and, later, the Parquet files. The manual `ALTER TABLE +CONVERT PARTITION TO PARQUET` approach described below remains available on +both OSS and Enterprise. + +::: + ## Export via REST The `/exp` REST API endpoint executes a query and streams the result as a Parquet file directly to the client. This is a synchronous operation — the HTTP response completes when the file is fully transferred. diff --git a/documentation/query/functions/meta.md b/documentation/query/functions/meta.md index 535b9b21a..dae212033 100644 --- a/documentation/query/functions/meta.md +++ b/documentation/query/functions/meta.md @@ -285,6 +285,59 @@ Edit `server.conf` and run `reload_config`: SELECT reload_config(); ``` +## storage_policies + +:::note + +Storage policies — and the `storage_policies` view — are available in +**QuestDB Enterprise** only. + +::: + +`storage_policies` is a system view that lists every +[storage policy](/docs/concepts/storage-policy/) currently attached to a table +or materialized view. Query it like any other table: + +```questdb-sql +SELECT * FROM storage_policies; +``` + +**Columns:** + +| Column | Type | Description | +|--------|------|-------------| +| `table_dir_name` | _STRING_ | Directory name of the table or materialized view the policy is attached to. Matches the `table_dir_name` column in [`tables()`](#tables) / [`materialized_views`](#materialized_views). | +| `to_parquet` | _STRING_ | TTL for the `TO PARQUET` stage (e.g. `72h`, `1m`). Blank when the stage is not configured. | +| `drop_native` | _STRING_ | TTL for the `DROP NATIVE` stage. Blank when the stage is not configured. | +| `drop_local` | _STRING_ | TTL for the `DROP LOCAL` stage. Blank when the stage is not configured. | +| `drop_remote` | _STRING_ | Reserved — always blank in the current release. The `DROP REMOTE` clause is rejected at SQL parse time with `'DROP REMOTE' is not supported yet`. The column is kept for forward compatibility. | +| `status` | _CHAR_ | Policy status. `A` = active (the policy is being enforced), `D` = disabled (via [`ALTER TABLE DISABLE STORAGE POLICY`](/docs/query/sql/alter-table-set-storage-policy/)). | +| `last_updated` | _TIMESTAMP_ | Timestamp of the most recent change to the policy definition (not the last time partitions were processed). | + +**Notes on TTL formatting:** + +- TTL values are rendered in just two units: `h` for hours and `m` for + **months**. Durations written in the DDL as days, weeks, or years are + normalized to hours when stored (e.g., `3 DAYS` → `72h`, `1 WEEK` → + `168h`). Month-based durations are stored and rendered with the lowercase + `m` suffix — despite the visual collision with "minute", `m` in this view + is **months**, and QuestDB's duration shorthand has no unit for minutes. + +**Example:** + +```questdb-sql title="List all storage policies" +SELECT * FROM storage_policies; +``` + +| table_dir_name | to_parquet | drop_native | drop_local | drop_remote | status | last_updated | +|----------------|------------|-------------|------------|-------------|--------|--------------| +| trades~12 | 72h | 240h | 1m | | A | 2025-01-15T10:30:00.000000Z | +| metrics~18 | 168h | | | | D | 2025-01-14T09:15:42.000000Z | + +The first row is a policy with three active stages (3-day Parquet conversion, +10-day native drop, 1-month local drop) and is currently enforced. The second +row has only the `TO PARQUET` stage set and has been temporarily disabled. + ## table_columns `table_columns('tableName')` returns the schema of a table or a materialized @@ -385,6 +438,19 @@ 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 produced alongside the native files. Set by either + [manual Parquet conversion](/docs/query/export-parquet/#in-place-conversion) + (`ALTER TABLE ... CONVERT PARTITION TO PARQUET`) or by a + [storage policy](/docs/concepts/storage-policy/)'s `TO PARQUET` stage + (Enterprise). The partition is still served from native storage until it is + switched to Parquet-only format +- `isParquet` - _BOOLEAN_, true if the partition is stored in Parquet format + (native files have been replaced). Set the same way as + `hasParquetGenerated` — either manually or by a storage policy's `DROP + NATIVE` stage +- `parquetFileSize` - _LONG_, size in bytes of the partition's `data.parquet` + file when `hasParquetGenerated` or `isParquet` is true; `-1` otherwise **Examples:** @@ -403,12 +469,12 @@ CREATE TABLE my_table AS ( table_partitions('my_table'); ``` -| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | -| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | -| 0 | WEEK | 2022-W52 | 2023-01-01 00:36:00.0 | 2023-01-01 23:24:00.0 | 39 | 98304 | 96.0 KiB | false | false | true | false | false | -| 1 | WEEK | 2023-W01 | 2023-01-02 00:00:00.0 | 2023-01-08 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | -| 2 | WEEK | 2023-W02 | 2023-01-09 00:00:00.0 | 2023-01-15 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | -| 3 | WEEK | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101 | 83902464 | 80.0 MiB | false | true | true | false | false | +| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | hasParquetGenerated | isParquet | parquetFileSize | +| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | ------------------- | --------- | --------------- | +| 0 | WEEK | 2022-W52 | 2023-01-01 00:36:00.0 | 2023-01-01 23:24:00.0 | 39 | 98304 | 96.0 KiB | false | false | true | false | false | false | false | -1 | +| 1 | WEEK | 2023-W01 | 2023-01-02 00:00:00.0 | 2023-01-08 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | false | false | -1 | +| 2 | WEEK | 2023-W02 | 2023-01-09 00:00:00.0 | 2023-01-15 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | false | false | -1 | +| 3 | WEEK | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101 | 83902464 | 80.0 MiB | false | true | true | false | false | false | false | -1 | ```questdb-sql title="Get size of a table in disk" SELECT size_pretty(sum(diskSize)) FROM table_partitions('my_table'); @@ -422,9 +488,9 @@ SELECT size_pretty(sum(diskSize)) FROM table_partitions('my_table'); SELECT * FROM table_partitions('my_table') WHERE active = true; ``` -| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | -| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | -| 3 | WEEK | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101 | 83902464 | 80.0 MiB | false | true | true | false | false | +| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | hasParquetGenerated | isParquet | parquetFileSize | +| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | ------------------- | --------- | --------------- | +| 3 | WEEK | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101 | 83902464 | 80.0 MiB | false | true | true | false | false | false | false | -1 | ## table_storage diff --git a/documentation/query/sql/alter-mat-view-set-storage-policy.md b/documentation/query/sql/alter-mat-view-set-storage-policy.md new file mode 100644 index 000000000..8628d23a1 --- /dev/null +++ b/documentation/query/sql/alter-mat-view-set-storage-policy.md @@ -0,0 +1,162 @@ +--- +title: ALTER MATERIALIZED VIEW SET STORAGE POLICY +sidebar_label: SET STORAGE POLICY +description: + ALTER MATERIALIZED VIEW SET STORAGE POLICY SQL keyword reference documentation. +--- + +Sets, modifies, enables, disables, or removes a storage policy on a materialized +view. + +:::note + +Storage policies are available in **QuestDB Enterprise** only. + +::: + +Refer to the [Storage Policy](/docs/concepts/storage-policy/) concept guide for +a full overview. + +## Syntax + +### Set or modify a storage policy + +```questdb-sql +ALTER MATERIALIZED VIEW view_name SET STORAGE POLICY( + [TO PARQUET ttl,] + [DROP NATIVE ttl,] + [DROP LOCAL ttl,] + [DROP REMOTE ttl] +); +``` + +Only the specified settings are changed. Omitted settings retain their current +values. + +### Enable or disable a storage policy + +```questdb-sql +ALTER MATERIALIZED VIEW view_name ENABLE STORAGE POLICY; +ALTER MATERIALIZED VIEW view_name DISABLE STORAGE POLICY; +``` + +Disabling a policy suspends processing without removing the policy definition. + +### Remove a storage policy + +```questdb-sql +ALTER MATERIALIZED VIEW view_name DROP STORAGE POLICY; +``` + +This permanently removes the storage policy from the materialized view. + +## Description + +A storage policy defines up to four TTL-based stages that control how partitions +transition from native format to Parquet and eventually get removed: + +| Setting | Effect | +|---------|--------| +| `TO PARQUET ` | Convert partition from native format to Parquet locally | +| `DROP NATIVE ` | Remove native binary files, keeping only the local Parquet copy | +| `DROP LOCAL ` | Remove all local copies of the partition | +| `DROP REMOTE ` | _Reserved._ Will remove the partition from object storage when remote upload is supported | + +:::info + +`DROP REMOTE` is reserved syntax. It is rejected at SQL parse time with +`'DROP REMOTE' is not supported yet`. Automatic upload of Parquet files to +object storage is not currently supported — storage policies operate locally +only. Because the clause cannot take effect, the `drop_remote` column in the +[`storage_policies`](/docs/query/functions/meta/#storage_policies) view is +always blank in the current release. + +::: + +### TTL format + +Follow each setting with a duration value using one of these formats: + +- Long form: `3 DAYS`, `1 MONTH`, `2 YEARS` +- Short form: `3d`, `1M`, `2Y` + +Supported units: `HOUR`/`h`, `DAY`/`d`, `WEEK`/`W`, `MONTH`/`M`, `YEAR`/`Y`. +Both singular and plural forms are accepted. + +### Constraints + +- TTL values must be in ascending order: + `TO PARQUET <= DROP NATIVE <= DROP LOCAL <= DROP REMOTE` +- All TTL values must be positive — `0` is rejected +- Each setting can only appear once per statement +- The materialized view must have a designated timestamp and partitioning enabled +- If the materialized view has a TTL set, clear it with + `ALTER MATERIALIZED VIEW SET TTL 0` before setting a storage policy. Any + non-zero `SET TTL` value is rejected in Enterprise with + `TTL settings are deprecated, please, create a storage policy instead` +- `ENABLE` and `DISABLE` require a policy to exist on the view; both return an + error otherwise + +### Permissions + +Each operation requires a specific permission: + +| SQL command | Required permission | +|-------------|-------------------| +| `SET STORAGE POLICY` | `SET STORAGE POLICY` | +| `DROP STORAGE POLICY` | `REMOVE STORAGE POLICY` | +| `ENABLE STORAGE POLICY` | `ENABLE STORAGE POLICY` | +| `DISABLE STORAGE POLICY` | `DISABLE STORAGE POLICY` | + +## Examples + +Set a storage policy with multiple stages: + +```questdb-sql +ALTER MATERIALIZED VIEW trades_hourly SET STORAGE POLICY( + TO PARQUET 7 DAYS, + DROP NATIVE 14 DAYS, + DROP LOCAL 1 MONTH +); +``` + +Update only the Parquet conversion threshold: + +```questdb-sql +ALTER MATERIALIZED VIEW trades_hourly SET STORAGE POLICY(TO PARQUET 14d); +``` + +Temporarily suspend a policy: + +```questdb-sql +ALTER MATERIALIZED VIEW trades_hourly DISABLE STORAGE POLICY; +``` + +Re-enable it: + +```questdb-sql +ALTER MATERIALIZED VIEW trades_hourly ENABLE STORAGE POLICY; +``` + +Remove a policy entirely: + +```questdb-sql +ALTER MATERIALIZED VIEW trades_hourly DROP STORAGE POLICY; +``` + +Check active policies: + +```questdb-sql +SELECT * FROM storage_policies; +``` + +## See also + +- [Storage Policy concept](/docs/concepts/storage-policy/) +- [ALTER TABLE SET STORAGE POLICY](/docs/query/sql/alter-table-set-storage-policy/) +- [CREATE MATERIALIZED VIEW](/docs/query/sql/create-mat-view/) +- [ALTER MATERIALIZED VIEW SET TTL](/docs/query/sql/alter-mat-view-set-ttl/) +- [`storage_policies`](/docs/query/functions/meta/#storage_policies) — system + view listing active policies +- [RBAC permissions](/docs/security/rbac/#permissions) — `SET`, `REMOVE`, + `ENABLE`, and `DISABLE STORAGE POLICY` permissions diff --git a/documentation/query/sql/alter-mat-view-set-ttl.md b/documentation/query/sql/alter-mat-view-set-ttl.md index 6a103788a..143eff9ed 100644 --- a/documentation/query/sql/alter-mat-view-set-ttl.md +++ b/documentation/query/sql/alter-mat-view-set-ttl.md @@ -8,6 +8,18 @@ description: Sets the [time-to-live](/docs/concepts/ttl/) (TTL) period on a materialized view, automatically dropping partitions older than the specified duration. +:::caution + +**QuestDB Enterprise: TTL is deprecated.** Enterprise rejects any non-zero +`SET TTL` with +`TTL settings are deprecated, please, create a storage policy instead`. Use +[`ALTER MATERIALIZED VIEW SET STORAGE POLICY`](/docs/query/sql/alter-mat-view-set-storage-policy/) +instead. `SET TTL 0` is still accepted, for clearing a pre-existing TTL before +attaching a storage policy. See [Storage Policy](/docs/concepts/storage-policy/) +for the Enterprise replacement. + +::: + ## Syntax ``` diff --git a/documentation/query/sql/alter-table-set-storage-policy.md b/documentation/query/sql/alter-table-set-storage-policy.md new file mode 100644 index 000000000..32e7571f1 --- /dev/null +++ b/documentation/query/sql/alter-table-set-storage-policy.md @@ -0,0 +1,182 @@ +--- +title: ALTER TABLE SET STORAGE POLICY +sidebar_label: SET STORAGE POLICY +description: ALTER TABLE SET STORAGE POLICY SQL keyword reference documentation. +--- + +Sets, modifies, enables, disables, or removes a storage policy on a table. For +the equivalent operations on materialized views, see +[ALTER MATERIALIZED VIEW SET STORAGE POLICY](/docs/query/sql/alter-mat-view-set-storage-policy/). + +:::note + +Storage policies are available in **QuestDB Enterprise** only. + +::: + +Refer to the [Storage Policy](/docs/concepts/storage-policy/) concept guide for +a full overview. + +## Syntax + +### Set or modify a storage policy + +```questdb-sql +ALTER TABLE table_name SET STORAGE POLICY( + [TO PARQUET ttl,] + [DROP NATIVE ttl,] + [DROP LOCAL ttl,] + [DROP REMOTE ttl] +); +``` + +Only the specified settings are changed. Omitted settings retain their current +values. + +### Enable or disable a storage policy + +```questdb-sql +ALTER TABLE table_name ENABLE STORAGE POLICY; +ALTER TABLE table_name DISABLE STORAGE POLICY; +``` + +Disabling a policy suspends processing without removing the policy definition. + +### Remove a storage policy + +```questdb-sql +ALTER TABLE table_name DROP STORAGE POLICY; +``` + +This permanently removes the storage policy from the table. + +## Description + +A storage policy defines up to four TTL-based stages that control how partitions +transition from native format to Parquet and eventually get removed: + +| Setting | Effect | +|---------|--------| +| `TO PARQUET ` | Convert partition from native format to Parquet locally | +| `DROP NATIVE ` | Remove native binary files, keeping only the local Parquet copy | +| `DROP LOCAL ` | Remove all local copies of the partition | +| `DROP REMOTE ` | _Reserved._ Will remove the partition from object storage when remote upload is supported | + +:::info + +`DROP REMOTE` is reserved syntax. It is rejected at SQL parse time with +`'DROP REMOTE' is not supported yet`. Automatic upload of Parquet files to +object storage is not currently supported — storage policies operate locally +only. Because the clause cannot take effect, the `drop_remote` column in the +[`storage_policies`](/docs/query/functions/meta/#storage_policies) view is +always blank in the current release. + +::: + +### TTL format + +Follow each setting with a duration value using one of these formats: + +- Long form: `3 DAYS`, `1 MONTH`, `2 YEARS` +- Short form: `3d`, `1M`, `2Y` + +Supported units: `HOUR`/`h`, `DAY`/`d`, `WEEK`/`W`, `MONTH`/`M`, `YEAR`/`Y`. +Both singular and plural forms are accepted. + +### Constraints + +- TTL values must be in ascending order: + `TO PARQUET <= DROP NATIVE <= DROP LOCAL <= DROP REMOTE` +- All TTL values must be positive — `0` is rejected +- Each setting can only appear once per statement +- The table must have a designated timestamp and partitioning enabled +- If the table has a TTL set, clear it with `ALTER TABLE SET TTL 0` before + setting a storage policy. Any non-zero `SET TTL` value is rejected in + Enterprise with `TTL settings are deprecated, please, create a storage policy + instead` +- `ENABLE` and `DISABLE` require a policy to exist on the table; both return an + error otherwise + +### Permissions + +Each operation requires a specific permission: + +| SQL command | Required permission | +|-------------|-------------------| +| `SET STORAGE POLICY` | `SET STORAGE POLICY` | +| `DROP STORAGE POLICY` | `REMOVE STORAGE POLICY` | +| `ENABLE STORAGE POLICY` | `ENABLE STORAGE POLICY` | +| `DISABLE STORAGE POLICY` | `DISABLE STORAGE POLICY` | + +## Examples + +Set a storage policy with all three currently-supported stages: + +```questdb-sql +ALTER TABLE sensor_data SET STORAGE POLICY( + TO PARQUET 3 DAYS, + DROP NATIVE 10 DAYS, + DROP LOCAL 1 MONTH +); +``` + +Update only the Parquet conversion threshold: + +```questdb-sql +ALTER TABLE sensor_data SET STORAGE POLICY(TO PARQUET 7d); +``` + +Temporarily suspend a policy: + +```questdb-sql +ALTER TABLE sensor_data DISABLE STORAGE POLICY; +``` + +Re-enable it: + +```questdb-sql +ALTER TABLE sensor_data ENABLE STORAGE POLICY; +``` + +Remove a policy entirely: + +```questdb-sql +ALTER TABLE sensor_data DROP STORAGE POLICY; +``` + +Check active policies: + +```questdb-sql +SELECT * FROM storage_policies; +``` + +The storage policy also appears in `SHOW CREATE TABLE` output: + +```questdb-sql +SHOW CREATE TABLE sensor_data; +``` + +```text +CREATE TABLE 'sensor_data' ( + ts TIMESTAMP, + value DOUBLE +) timestamp(ts) PARTITION BY DAY +STORAGE POLICY(TO PARQUET 3 DAYS, DROP NATIVE 10 DAYS, DROP LOCAL 1 MONTH) WAL; +``` + +Stages that are not set are omitted from the output. + +## See also + +- [Storage Policy concept](/docs/concepts/storage-policy/) +- [ALTER MATERIALIZED VIEW SET STORAGE POLICY](/docs/query/sql/alter-mat-view-set-storage-policy/) +- [CREATE TABLE](/docs/query/sql/create-table/) — `STORAGE POLICY` clause at + table creation +- [ALTER TABLE SET TTL](/docs/query/sql/alter-table-set-ttl/) — the TTL + feature storage policies supersede in Enterprise +- [`storage_policies`](/docs/query/functions/meta/#storage_policies) — system + view listing active policies +- [`SHOW CREATE TABLE`](/docs/query/sql/show/#show-create-table) — displays + the attached `STORAGE POLICY` clause +- [RBAC permissions](/docs/security/rbac/#permissions) — `SET`, `REMOVE`, + `ENABLE`, and `DISABLE STORAGE POLICY` permissions diff --git a/documentation/query/sql/alter-table-set-ttl.md b/documentation/query/sql/alter-table-set-ttl.md index 70bb861e8..06b8d4309 100644 --- a/documentation/query/sql/alter-table-set-ttl.md +++ b/documentation/query/sql/alter-table-set-ttl.md @@ -6,6 +6,18 @@ description: ALTER TABLE SET TTL SQL keyword reference documentation. Sets the time-to-live (TTL) period on a table. +:::caution + +**QuestDB Enterprise: TTL is deprecated.** Enterprise rejects any non-zero +`SET TTL` with +`TTL settings are deprecated, please, create a storage policy instead`. Use +[`ALTER TABLE SET STORAGE POLICY`](/docs/query/sql/alter-table-set-storage-policy/) +instead. `SET TTL 0` is still accepted, for clearing a pre-existing TTL before +attaching a storage policy. See [Storage Policy](/docs/concepts/storage-policy/) +for the Enterprise replacement. + +::: + Refer to the [section on TTL](/docs/concepts/ttl/) for a conceptual overview. ## Syntax diff --git a/documentation/query/sql/create-mat-view.md b/documentation/query/sql/create-mat-view.md index a3e7fc378..4d1444671 100644 --- a/documentation/query/sql/create-mat-view.md +++ b/documentation/query/sql/create-mat-view.md @@ -20,13 +20,17 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] viewName [ PERIOD ( SAMPLE BY INTERVAL ) ] ] AS [ ( ] query [ ) ] [ TIMESTAMP ( columnRef ) ] -[ PARTITION BY ( YEAR | MONTH | WEEK | DAY | HOUR ) [ TTL n timeUnit ] ] +[ PARTITION BY ( YEAR | MONTH | WEEK | DAY | HOUR ) + [ TTL n timeUnit + | STORAGE POLICY ( policyStage [, policyStage ...] ) ] ] [ OWNED BY ownerName ] ``` Where: - `interval`: Duration like `1m`, `10m`, `1h`, `1d` - `timeUnit`: `HOURS | DAYS | WEEKS | MONTHS | YEARS` +- `policyStage`: `TO PARQUET duration | DROP NATIVE duration | DROP LOCAL duration | DROP REMOTE duration` + (Enterprise only; all stages optional; durations must be positive and in ascending order) - `query`: Must contain `SAMPLE BY` or time-based `GROUP BY` ## Parameters @@ -42,6 +46,7 @@ Where: | `TIMESTAMP` | Designate timestamp column for the view | | `PARTITION BY` | Partitioning unit for view storage | | `TTL` | Retention period for view data | +| `STORAGE POLICY` | Partition lifecycle automation (Enterprise) — mutually exclusive with `TTL` | | `OWNED BY` | Assign ownership (Enterprise) | ## Rules and defaults @@ -277,6 +282,45 @@ Time units: `HOURS`, `DAYS`, `WEEKS`, `MONTHS`, `YEARS` The view's TTL is independent of the base table's TTL. See [TTL documentation](/docs/concepts/ttl/) for details. +:::note + +In QuestDB Enterprise, `TTL` is deprecated — `CREATE MATERIALIZED VIEW ... TTL` +is rejected with `TTL settings are deprecated, please, create a storage policy +instead`. Use `STORAGE POLICY` instead. If a legacy materialized view has a TTL +set, clear it with `ALTER MATERIALIZED VIEW SET TTL 0` before setting a storage +policy. + +::: + +## Storage Policy + +:::note + +Storage policies are available in **QuestDB Enterprise** only. + +::: + +A [storage policy](/docs/concepts/storage-policy/) automates the partition +lifecycle by defining when partitions are converted to Parquet locally, when +native data is removed, and when local copies are dropped. Place the +`STORAGE POLICY(...)` clause after `PARTITION BY`: + +```questdb-sql title="With storage policy (Enterprise)" +CREATE MATERIALIZED VIEW trades_hourly AS ( + SELECT timestamp, symbol, avg(price) AS avg_price FROM trades SAMPLE BY 1h +) PARTITION BY DAY + STORAGE POLICY(TO PARQUET 7d, DROP NATIVE 14d); +``` + +A storage policy supports up to four settings: `TO PARQUET`, `DROP NATIVE`, +`DROP LOCAL`, and `DROP REMOTE`. All are optional, all TTL values must be +positive, and they must be in ascending order. `DROP REMOTE` is reserved +syntax and is currently rejected at SQL parse time with +`'DROP REMOTE' is not supported yet`. + +To modify a storage policy after creation, see +[ALTER MATERIALIZED VIEW SET STORAGE POLICY](/docs/query/sql/alter-mat-view-set-storage-policy/). + ## Complete example Putting it all together: @@ -390,6 +434,7 @@ GRANT DROP MATERIALIZED VIEW ON trades_hourly TO user1; | `base table does not exist` | Referenced table doesn't exist | | `query is not supported` | Query doesn't meet constraints (missing SAMPLE BY, uses FILL, etc.) | | `permission denied` | Missing required permission (Enterprise) | +| `TTL settings are deprecated, please, create a storage policy instead` | `TTL` clause used in QuestDB Enterprise — use `STORAGE POLICY` instead | ## See also @@ -397,4 +442,5 @@ GRANT DROP MATERIALIZED VIEW ON trades_hourly TO user1; - [REFRESH MATERIALIZED VIEW](/docs/query/sql/refresh-mat-view/) - [DROP MATERIALIZED VIEW](/docs/query/sql/drop-mat-view/) - [ALTER MATERIALIZED VIEW SET REFRESH](/docs/query/sql/alter-mat-view-set-refresh/) +- [ALTER MATERIALIZED VIEW SET STORAGE POLICY](/docs/query/sql/alter-mat-view-set-storage-policy/) - [ALTER MATERIALIZED VIEW SET TTL](/docs/query/sql/alter-mat-view-set-ttl/) diff --git a/documentation/query/sql/create-table.md b/documentation/query/sql/create-table.md index 45542f777..d54074ce5 100644 --- a/documentation/query/sql/create-table.md +++ b/documentation/query/sql/create-table.md @@ -21,6 +21,7 @@ The first two modes accept the same set of optional clauses: - [`TIMESTAMP`](#designated-timestamp) - designated timestamp column - [`PARTITION BY`](#partitioning) - partition unit and WAL mode - [`TTL`](#time-to-live-ttl) - time-to-live for partitions +- [`STORAGE POLICY`](#storage-policy) - partition lifecycle automation (Enterprise) - [`DEDUP`](#deduplication) - deduplication keys (can also be set later with [`ALTER TABLE DEDUP ENABLE`](/docs/query/sql/alter-table-enable-deduplication/)) - [`WITH`](#with-table-parameter) - table parameters @@ -36,7 +37,8 @@ TABLE [IF NOT EXISTS] tableName [TIMESTAMP (columnName) [PARTITION BY { NONE | YEAR | MONTH | DAY | HOUR } [BYPASS WAL | WAL] - [TTL n { HOUR[S] | DAY[S] | WEEK[S] | MONTH[S] | YEAR[S] }]]] + [ TTL n { HOUR[S] | DAY[S] | WEEK[S] | MONTH[S] | YEAR[S] } + | STORAGE POLICY ( policyStage [, policyStage ...] ) ]]] [DEDUP UPSERT KEYS (columnName [, columnName ...])] [WITH tableParameter] [IN VOLUME 'alias'] @@ -52,13 +54,24 @@ TABLE [IF NOT EXISTS] tableName [TIMESTAMP (columnName) [PARTITION BY { NONE | YEAR | MONTH | DAY | HOUR } [BYPASS WAL | WAL] - [TTL n { HOUR[S] | DAY[S] | WEEK[S] | MONTH[S] | YEAR[S] }]]] + [ TTL n { HOUR[S] | DAY[S] | WEEK[S] | MONTH[S] | YEAR[S] } + | STORAGE POLICY ( policyStage [, policyStage ...] ) ]]] [DEDUP UPSERT KEYS (columnName [, columnName ...])] [WITH tableParameter] [IN VOLUME 'alias'] [OWNED BY ownerName]; ``` +Where `policyStage` is one of: + +``` +TO PARQUET duration | DROP NATIVE duration | DROP LOCAL duration | DROP REMOTE duration +``` + +Stages are Enterprise-only, all optional, and their durations must be positive +and in ascending order. `TTL` and `STORAGE POLICY` are mutually exclusive — see +[Storage Policy](#storage-policy). + ```questdb-sql title="Create from another table's structure (CREATE TABLE LIKE)" CREATE TABLE tableName (LIKE sourceTableName); ``` @@ -241,6 +254,49 @@ information on the behavior of this feature. ::: +:::note + +In QuestDB Enterprise, `TTL` is deprecated — `CREATE TABLE ... TTL` is +rejected with `TTL settings are deprecated, please, create a storage policy +instead`. Use `STORAGE POLICY` instead. If a legacy table has a TTL set, clear +it with `ALTER TABLE SET TTL 0` before setting a storage policy. + +::: + +## Storage Policy + +:::note + +Storage policies are available in **QuestDB Enterprise** only. + +::: + +A [storage policy](/docs/concepts/storage-policy/) automates the partition +lifecycle by defining when partitions are converted to Parquet locally, when +native data is removed, and when local copies are dropped. Place the +`STORAGE POLICY(...)` clause after `PARTITION BY`: + +```questdb-sql title="With storage policy (Enterprise)" +CREATE TABLE trades ( + timestamp TIMESTAMP, + symbol SYMBOL, + price DOUBLE, + amount DOUBLE +) TIMESTAMP(timestamp) +PARTITION BY DAY +STORAGE POLICY(TO PARQUET 3d, DROP NATIVE 10d, DROP LOCAL 1M) +WAL; +``` + +A storage policy supports up to four settings: `TO PARQUET`, `DROP NATIVE`, +`DROP LOCAL`, and `DROP REMOTE`. All are optional, all TTL values must be +positive, and they must be in ascending order. `DROP REMOTE` is reserved +syntax and is currently rejected at SQL parse time with +`'DROP REMOTE' is not supported yet`. + +To modify a storage policy after table creation, see +[ALTER TABLE SET STORAGE POLICY](/docs/query/sql/alter-table-set-storage-policy/). + ## Deduplication When [Deduplication](/docs/concepts/deduplication) is enabled, QuestDB only diff --git a/documentation/query/sql/show.md b/documentation/query/sql/show.md index 0d94883dd..6976ce129 100644 --- a/documentation/query/sql/show.md +++ b/documentation/query/sql/show.md @@ -117,6 +117,29 @@ CREATE TABLE sensors ( ) timestamp(ts) PARTITION BY DAY BYPASS WAL; ``` +#### Storage policy clause + +When a [storage policy](/docs/concepts/storage-policy/) is attached to a table +(Enterprise only), the policy renders as a `STORAGE POLICY(...)` clause in the +`SHOW CREATE TABLE` output: + +```questdb-sql +SHOW CREATE TABLE sensor_data; +``` + +```text +CREATE TABLE 'sensor_data' ( + ts TIMESTAMP, + value DOUBLE +) timestamp(ts) PARTITION BY DAY +STORAGE POLICY(TO PARQUET 3 DAYS, DROP NATIVE 10 DAYS, DROP LOCAL 1 MONTH) WAL; +``` + +Stages that are not configured on the policy are omitted from the clause. A +disabled policy (`ALTER TABLE ... DISABLE STORAGE POLICY`) still renders — the +disabled state is not part of the DDL. See +[ALTER TABLE SET STORAGE POLICY](/docs/query/sql/alter-table-set-storage-policy/). + #### Enterprise variant [QuestDB Enterprise](/enterprise/) will include an additional `OWNED BY` clause populated with the current user. @@ -159,12 +182,16 @@ including any `DECLARE` parameters if the view is parameterized. SHOW PARTITIONS FROM my_table; ``` -| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | -| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | -| 0 | WEEK | 2022-W52 | 2023-01-01 00:36:00.0 | 2023-01-01 23:24:00.0 | 39 | 98304 | 96.0 KiB | false | false | true | false | false | -| 1 | WEEK | 2023-W01 | 2023-01-02 00:00:00.0 | 2023-01-08 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | -| 2 | WEEK | 2023-W02 | 2023-01-09 00:00:00.0 | 2023-01-15 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | -| 3 | WEEK | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101 | 83902464 | 80.0 MiB | false | true | true | false | false | +| index | partitionBy | name | minTimestamp | maxTimestamp | numRows | diskSize | diskSizeHuman | readOnly | active | attached | detached | attachable | hasParquetGenerated | isParquet | parquetFileSize | +| ----- | ----------- | -------- | --------------------- | --------------------- | ------- | -------- | ------------- | -------- | ------ | -------- | -------- | ---------- | ------------------- | --------- | --------------- | +| 0 | WEEK | 2022-W52 | 2023-01-01 00:36:00.0 | 2023-01-01 23:24:00.0 | 39 | 98304 | 96.0 KiB | false | false | true | false | false | false | false | -1 | +| 1 | WEEK | 2023-W01 | 2023-01-02 00:00:00.0 | 2023-01-08 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | false | false | -1 | +| 2 | WEEK | 2023-W02 | 2023-01-09 00:00:00.0 | 2023-01-15 23:24:00.0 | 280 | 98304 | 96.0 KiB | false | false | true | false | false | false | false | -1 | +| 3 | WEEK | 2023-W03 | 2023-01-16 00:00:00.0 | 2023-01-18 12:00:00.0 | 101 | 83902464 | 80.0 MiB | false | true | true | false | false | false | false | -1 | + +See [`table_partitions()`](/docs/query/functions/meta/#table_partitions) for the +full column list, including `hasParquetGenerated`, `isParquet`, and +`parquetFileSize`. ### SHOW PARAMETERS diff --git a/documentation/security/rbac.md b/documentation/security/rbac.md index 09101e276..b82e3eff7 100644 --- a/documentation/security/rbac.md +++ b/documentation/security/rbac.md @@ -571,18 +571,22 @@ SELECT * FROM all_permissions(); | DEDUP ENABLE | Database | Table | Enable deduplication | | DEDUP DISABLE | Database | Table | Disable deduplication | | DETACH PARTITION | Database | Table | Detach partitions | +| DISABLE STORAGE POLICY | Database | Table | Disable storage policies | | DROP COLUMN | Database | Table | Column | Drop columns | | DROP INDEX | Database | Table | Column | Drop indexes | | DROP PARTITION | Database | Table | Drop partitions | | DROP TABLE | Database | Table | Drop tables | | DROP MATERIALIZED VIEW | Database | Table | Drop materialized views | +| ENABLE STORAGE POLICY | Database | Table | Enable storage policies | | INSERT | Database | Table | Insert data | | REFRESH MATERIALIZED VIEW | Database | Table | Refresh materialized views | | REINDEX | Database | Table | Column | Reindex columns | +| REMOVE STORAGE POLICY | Database | Table | Remove storage policies | | RENAME COLUMN | Database | Table | Column | Rename columns | | RENAME TABLE | Database | Table | Rename tables | | RESUME WAL | Database | Table | Resume WAL processing | | SELECT | Database | Table | Column | Read data | +| SET STORAGE POLICY | Database | Table | Set storage policies | | SET TABLE PARAM | Database | Table | Set table parameters | | SET TABLE TYPE | Database | Table | Change table type | | SETTINGS | Database | Change instance settings in Web Console | diff --git a/documentation/sidebars.js b/documentation/sidebars.js index bf61c7620..bf99c48ad 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -287,6 +287,7 @@ module.exports = { "query/sql/alter-table-rename-column", "query/sql/alter-table-resume-wal", "query/sql/alter-table-set-param", + "query/sql/alter-table-set-storage-policy", "query/sql/alter-table-set-ttl", "query/sql/alter-table-set-type", "query/sql/alter-table-squash-partitions", @@ -308,6 +309,7 @@ module.exports = { "query/sql/alter-mat-view-resume-wal", "query/sql/alter-mat-view-set-refresh", "query/sql/alter-mat-view-set-refresh-limit", + "query/sql/alter-mat-view-set-storage-policy", "query/sql/alter-mat-view-set-ttl", ], }, @@ -532,6 +534,7 @@ module.exports = { }, "concepts/deduplication", "concepts/ttl", + "concepts/storage-policy", "concepts/write-ahead-log", ], },