Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions documentation/concepts/deep-dive/sql-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,9 @@ QuestDB exposes a few SQL-level features for operating tables at scale:

- **[TTL](/docs/concepts/ttl/)**. Per-table retention that drops whole
partitions older than the configured horizon.
- **[Storage policy](/docs/concepts/storage-policy/)** (Enterprise). Moves
old partitions to Parquet on object storage while keeping them queryable.
- **[Storage policy](/docs/concepts/storage-policy/)** (Enterprise). Converts
old partitions to Parquet locally, keeping them queryable, and can drop local
copies on a schedule.
- **[RBAC](/docs/security/rbac/)** (Enterprise). Users, groups, and service
accounts with granular permissions over tables and operations.
- **[Backup / CHECKPOINT](/docs/query/sql/checkpoint/)**. Filesystem-level
Expand Down
99 changes: 55 additions & 44 deletions documentation/concepts/storage-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ Converting a partition to Parquet removes its native files and serves reads dire

:::info

Storage policies currently operate **locally only**. Parquet files are not
automatically uploaded to object storage, so the `TO REMOTE` and `DROP REMOTE`
clauses are reserved syntax — they are rejected at SQL parse time with
`'TO REMOTE' is not supported yet` and `'DROP REMOTE' is not supported yet`.
Accordingly, the `to_remote` and `drop_remote` columns in the
[`storage_policies`](/docs/query/functions/meta/#storage_policies) view are
always blank in the current release; they are kept for forward compatibility.
Storage policies currently operate **locally only**. `TO PARQUET` and
`DROP LOCAL` are the enforced stages. `TO REMOTE` is accepted and stored but
not yet enforced, so setting it has no effect for now: no upload to object
storage happens yet. `DROP REMOTE` is not yet supported and is rejected at SQL
parse time with `'DROP REMOTE' is not supported yet`. In the
[`storage_policies`](/docs/query/functions/meta/#storage_policies) view the
`drop_remote` column is therefore always `0h`, and `to_remote` reads `0h`
unless you set a `TO REMOTE` value (which is stored but has no effect yet).
Object storage integration will be added in a future release.

:::
Expand All @@ -43,9 +44,9 @@ stage in the partition lifecycle:
| Setting | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `TO PARQUET` | Convert the partition from native binary format to Parquet. The native files are removed and reads are served from the Parquet file |
| `TO REMOTE` | _Reserved._ Will upload the Parquet file to object storage when remote upload is supported |
| `TO REMOTE` | Accepted and stored but **not yet enforced**; no upload happens yet. Reserved for future object storage upload |
| `DROP LOCAL` | Remove all local data (native or Parquet) |
| `DROP REMOTE` | _Reserved._ Will remove the Parquet file from object storage when remote upload is supported |
| `DROP REMOTE` | _Not yet supported._ Rejected at parse time with `'DROP REMOTE' is not supported yet`. Reserved for future object storage removal |

All settings are optional. Use only the ones relevant to your use case. All TTL
values must be **positive**; `0` is rejected.
Expand Down Expand Up @@ -75,9 +76,9 @@ 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 LOCAL` (or,
one day, the reserved `TO REMOTE` and `DROP REMOTE` stages). Each stage uses
its own `TTL` in the formula above; the stages share only the reference time
and the [ordering constraints](#ordering-constraint).
once remote upload is enforced, the `TO REMOTE` and `DROP REMOTE` stages).
Each stage uses its own `TTL` in the formula above; the stages share only the
reference time and the [ordering constraints](#ordering-constraint).

The reference time is `min(wall_clock_time, latest_timestamp)` by default —
the same formula used by TTL. The
Expand All @@ -87,7 +88,7 @@ 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
QuestDB checks storage policies periodically (every 5 minutes by default) and
processes eligible partitions automatically.

## Storage policy vs TTL
Expand All @@ -102,8 +103,10 @@ you are already familiar with TTL, this comparison is the fastest way in:
| **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:
In QuestDB Enterprise, use storage policies instead of TTL. On a regular table,
`ALTER TABLE SET TTL` with a non-zero value is rejected, while
`CREATE TABLE ... TTL` is accepted only for backward compatibility and is
translated into a `STORAGE POLICY(DROP LOCAL ...)`:

```questdb-sql
-- Instead of:
Expand All @@ -115,10 +118,12 @@ 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`.
A table can carry a TTL only if it was created in QuestDB Open Source and later
upgraded to Enterprise. Clear that legacy TTL with `ALTER TABLE SET TTL 0`
before setting a storage policy; otherwise `SET STORAGE POLICY` is rejected with
`Cannot set storage policy, please, remove TTL settings`. On Enterprise tables
`SET TTL 0` is the only accepted `SET TTL` value; any non-zero value is rejected
with `TTL is not supported on Enterprise tables; use a storage policy instead`.

:::

Expand All @@ -145,7 +150,8 @@ ALTER TABLE trades SET STORAGE POLICY(
);
```

Only the specified settings are changed. Omitted settings remain unchanged.
`SET STORAGE POLICY` replaces the policy in full: every stage you omit is
cleared, not preserved. To keep a stage, restate it in the same statement.

For full syntax details, see
[ALTER TABLE SET STORAGE POLICY](/docs/query/sql/alter-table-set-storage-policy/).
Expand Down Expand Up @@ -173,11 +179,11 @@ TO PARQUET <= DROP LOCAL
TO REMOTE <= DROP LOCAL <= DROP REMOTE
```

`TO PARQUET` and `TO REMOTE` are **independent** neither has to precede the
other. If `TO REMOTE` fires before `TO PARQUET`, both the native and Parquet
copies are written locally and reads continue to be served from the native
format until `TO PARQUET` removes the native files. All TTL values must be
positive — `0` is rejected.
`TO PARQUET` and `TO REMOTE` are **independent**: neither has to precede the
other. Once remote upload is enforced, a `TO REMOTE` that runs before
`TO PARQUET` would keep both the native and Parquet copies on local disk, with
reads served from the native format until `TO PARQUET` removes the native
files. All TTL values must be positive; `0` is rejected.

## Disabling and enabling

Expand Down Expand Up @@ -206,29 +212,27 @@ ALTER TABLE trades DROP STORAGE POLICY;

## Checking storage policies

Query the `storage_policies` system view to see all active policies:
Query the `storage_policies` system view to see all policies and their status:

```questdb-sql
SELECT * FROM storage_policies;
```

| table_dir_name | to_parquet | to_remote | drop_local | drop_remote | status | last_updated |
| -------------- | ---------- | --------- | ---------- | ----------- | ------ | --------------------------- |
| trades~12 | 72h | | 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
| trades~12 | 72h | 0h | 1m | 0h | A | 2025-01-15T10:30:00.000000Z |

- TTL values are rendered in two units: `h` for hours and `m` for **months**.
Hour-, day-, and week-based durations are stored as hours, so a `3 DAYS` TTL
appears as `72h` and `1 WEEK` as `168h`. Month- and year-based durations are
stored as months, so `1 MONTH` appears as `1m` and `1 YEAR` as `12m`. In this
view **`m` means months, not minutes**; 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. `to_remote` and `drop_remote` are **always blank
in the current release** because `TO REMOTE` and `DROP REMOTE` are rejected
at SQL parse time with `'TO REMOTE' is not supported yet` and
`'DROP REMOTE' is not supported yet`; the columns are kept for forward
compatibility
- An unset stage renders as `0h`, not blank. Because `DROP REMOTE` is rejected
at parse time, `drop_remote` is always `0h`; `to_remote` reads `0h` unless a
`TO REMOTE` value is set (stored, but not yet enforced)

For the full column reference and types, see
[`storage_policies`](/docs/query/functions/meta/#storage_policies).
Expand All @@ -250,19 +254,22 @@ 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
accept values with unit suffixes (e.g., `5m`, `1h`, `100ms`) or raw microsecond
values:

| Property | Default | Description |
| ------------------------------------- | ------------------ | ---------------------------------------------------------------- |
| `storage.policy.check.interval` | `15m` (15 min) | How often QuestDB scans for partitions to process |
| `storage.policy.check.interval` | `5m` (5 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.count` | `4` | 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 |

See
[Storage policy configuration](/docs/configuration/storage-policy/) for the
complete list, including the remaining worker-pool tuning properties.

## Permissions

Storage policy operations require specific permissions in QuestDB Enterprise:
Expand Down Expand Up @@ -307,7 +314,7 @@ WHERE table_dir_name LIKE 'trades%';
| -------------- | ---------- | ---------- | ------ |
| trades~12 | 72h | 1m | A |

```questdb-sql title="3. Modify one stage (others remain unchanged)"
```questdb-sql title="3. Replace the policy (omitted stages are cleared)"
ALTER TABLE trades SET STORAGE POLICY(TO PARQUET 1d);
```

Expand All @@ -324,6 +331,10 @@ CREATE TABLE 'trades' (
STORAGE POLICY(TO PARQUET 1 DAY) WAL;
```

The `DROP LOCAL 1 MONTH` stage from step 1 is gone: step 3 restated only
`TO PARQUET`, and `SET STORAGE POLICY` replaces the whole policy rather than
merging into it.

```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'
Expand Down
15 changes: 9 additions & 6 deletions documentation/concepts/ttl.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ window - no cron jobs or manual cleanup required.
**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`.
`TTL is not supported on Enterprise tables; use 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).
users. Materialized views are the exception: they continue to use `TTL` for
retention on Enterprise (set via
[`ALTER MATERIALIZED VIEW SET TTL`](/docs/query/sql/alter-mat-view-set-ttl/)).
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).

:::

Expand Down Expand Up @@ -166,6 +169,6 @@ ALTER TABLE trades SET TTL 0h;
- TTL should be significantly larger than your partition interval
- For manual control instead of automatic TTL, see
[Data Retention](/docs/operations/data-retention/)
- For graduated lifecycle management (convert to Parquet, offload to object
storage, then drop), see [Storage Policy](/docs/concepts/storage-policy/)
- For graduated lifecycle management (convert to Parquet locally, then drop on
a schedule), see [Storage Policy](/docs/concepts/storage-policy/)
(Enterprise)
38 changes: 30 additions & 8 deletions documentation/configuration/storage-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ Storage policy is [Enterprise](/enterprise/) only.

:::

Storage policies automate partition lifecycle management, including local
deletion and cold storage offloading. These settings control the scan interval,
retry behavior, and worker threads for the storage policy engine.
Storage policies automate partition lifecycle management: they convert older
partitions to Parquet locally and drop local copies on a schedule. These
settings control the scan interval, retry behavior, and worker threads for the
storage policy engine.

For details, see the
[storage policy concept](/docs/concepts/storage-policy/) page.

## storage.policy.check.interval

- **Default**: `15m`
- **Default**: `5m`
- **Reloadable**: no

How often QuestDB scans for partitions to process.
Expand Down Expand Up @@ -47,21 +48,42 @@ CPU affinity for each storage policy worker thread (comma-separated list).

## storage.policy.worker.count

- **Default**: `2`
- **Default**: `4`
- **Reloadable**: no

Number of storage policy worker threads. Setting to `0` disables the feature.

## storage.policy.worker.haltOnError

- **Default**: `false`
- **Reloadable**: no

Whether a storage policy worker thread halts when it hits an unhandled error.

## storage.policy.worker.nap.threshold

- **Default**: `100`
- **Reloadable**: no

Number of idle worker-loop iterations before a storage policy worker naps.

## storage.policy.worker.sleep.threshold

- **Default**: `500`
- **Reloadable**: no

Number of idle worker-loop iterations before a storage policy worker sleeps.

## storage.policy.worker.sleep.timeout

- **Default**: `100ms`
- **Reloadable**: no

Sleep duration when a storage policy worker has no tasks to process.

## storage.policy.writer.wait.timeout
## storage.policy.worker.yield.threshold

- **Default**: `30s`
- **Default**: `10`
- **Reloadable**: no

Timeout for acquiring the table writer during storage policy operations.
Number of idle worker-loop iterations before a storage policy worker yields.
12 changes: 6 additions & 6 deletions documentation/getting-started/enterprise-quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,16 +479,16 @@ too — on a schedule you define. This supersedes plain TTL in Enterprise, where
### Migrating from TTL when upgrading from OSS

Tables 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 on tables (both `CREATE TABLE ... TTL` and
`ALTER TABLE SET TTL <non-zero>`) with:
upgrade to Enterprise — no data is lost at upgrade time. On an existing table,
`ALTER TABLE SET TTL` with a non-zero value is then rejected with:

```
TTL settings are deprecated, please, create a storage policy instead
TTL is not supported on Enterprise tables; use a storage policy instead
```

Materialized views are not affected: they continue to use `TTL` for retention
in Enterprise.
(`CREATE TABLE ... TTL` is still accepted for backward compatibility: Enterprise
translates it into a `STORAGE POLICY(DROP LOCAL ...)`.) Materialized views are
not affected: they continue to use `TTL` for retention in Enterprise.

To move a legacy table from `TTL` to a storage policy:

Expand Down
Loading
Loading