Current Limitations
ScramDB runs transactions, analytics, and AI on one live copy of your data. The core SQL surface, the distributed serializable cluster, point-in-time recovery, database branching, GPU acceleration, and the polyglot UDF runtime all ship today. This page lists what is not available yet, and what to use in its place.
We keep this list honest. If a feature is here, it is genuinely not ready. If a feature is not on this page, assume it works.
Storage and I/O
ScramDB ships an io_uring storage backend as an opt-in alternative to the default file backend. Select it with backend = "io_uring" in the [storage.io] configuration table. Leaving backend unset (or set to "auto", the default) still runs on the file backend.
| Not yet available | What to use today |
|---|---|
| NVMe passthrough (raw-namespace I/O) | The default file backend, or the opt-in io_uring backend for a faster I/O path. backend = "nvme_passthrough" is accepted only with nvme_device naming a present, usable device, and even then the server's storage still runs on the file backend underneath, with a warning; there is no raw-namespace I/O path yet |
| Fully tuned single-row write latency | Writes are correct and durable now; OLTP write-latency tuning is ongoing |
| Cold storage tiering removes local files only at a restart | A copied segment's local file leaves local disk when the server next starts, not while it runs; a segment read back stays local until it is untouched again; copies nothing refers to are not deleted from the store. See Storage Tiering |
GPU metrics on the Prometheus /metrics endpoint | [gpu] metrics_enabled has no observable effect yet. The rest of /metrics works; see Observability |
SQL features
| Not yet available | What to use today |
|---|---|
Incremental materialized view refresh, and REFRESH MATERIALIZED VIEW CONCURRENTLY | Full REFRESH MATERIALIZED VIEW; the CONCURRENTLY form is refused |
PostgreSQL's non-blocking, two-phase CONCURRENTLY index build | CREATE INDEX CONCURRENTLY is accepted and builds the index; the CONCURRENTLY flag isn't consulted, so the build takes the same lock a plain CREATE INDEX would, rather than running lock-free in two phases |
Partition MERGE / SPLIT | Range, list, and hash partitioning with CREATE TABLE ... PARTITION BY, plus ATTACH / DETACH PARTITION |
Streaming an INSERT ... SELECT whose query sorts, groups or aggregates, combines set operations or a WITH query that is not inlined, reads a partitioned table by its parent, or joins through an outer join whose kept side the plan builds or a join build larger than the execution memory; or whose target has an INSERT trigger, a foreign key or a RETURNING list; or that runs on a cluster node | Such a statement holds its query's whole result before it writes it, so past the execution memory it can be refused. Raise [storage] execution_memory_bytes, or split it into several INSERT ... SELECT statements over key ranges. Every other INSERT ... SELECT streams its rows into the table at any size |
pg_notify(...) inside a larger expression, or beside other output columns of its SELECT | Make pg_notify(channel, payload) the only output column of its SELECT (any FROM, WHERE, GROUP BY or ORDER BY works, one notification per row), or use NOTIFY channel, 'payload'; see LISTEN, UNLISTEN and NOTIFY |
The ?| and ?& JSONB operators | ? per key combined with AND / OR. A GIN index answers @>, <@ and ? on JSONB |
A GIN index answering the array operators @>, <@ and && | The operators themselves answer, by reading the table; an array column's GIN index is built and kept current, but no query uses it yet |
SQL/JSON path array slices ([a to b]), last, arithmetic inside a path, and item methods (.size(), .type(), .datetime() and the rest) | The rest of the path language works, in lax and strict mode, filters (?(...)) and variables included; see Functions. Take a slice or a size with the JSONB functions outside the path |
A NUMERIC column in a GROUPING SETS, ROLLUP or CUBE, when it is the grouping key itself, or the query also has HAVING, a DISTINCT aggregate, or a statistical aggregate (STDDEV, VARIANCE, and their variants) | COUNT, SUM, AVG, MIN and MAX over a NUMERIC value column, grouped by an integer or text key with no HAVING or DISTINCT, still work; move a HAVING condition into a wrapping query's WHERE clause |
A DISTINCT aggregate (SUM(DISTINCT x)) inside GROUPING SETS, ROLLUP or CUBE | Aggregate over a SELECT DISTINCT subquery instead |
An aggregate's own ORDER BY inside a window function (array_agg(x ORDER BY y) OVER (...)) | Order the window itself over the whole partition (array_agg(x) OVER (PARTITION BY p ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)), or aggregate in a subquery |
array_ndims | array_dims, which names the subscript range of each dimension |
pg_advisory_lock and its family with a key computed from a column or an expression | A literal integer key or a bind parameter, in a statement of its own |
On a connection to a database other than the default one: CREATE VIEW, CREATE SCHEMA, COMMENT ON, VACUUM, and GRANT/REVOKE on schemas, sequences or databases | Run them on a connection to the default database; see Statements on a non-default database connection |
| An expression nested past 1024 levels, or past 100,000 expression nodes, in one statement | Keep nesting to what a hand-written query needs; the statement is refused up front with SQLSTATE 54001 rather than risking a stack overflow. See Error Codes |
Data types
| Not yet available | What to use today |
|---|---|
tsvector / tsquery as a built-in type | Full-text search through the built-in fts and bm25 packages |
| Range types | A pair of columns for the lower and upper bound |
EXCLUDE constraints | A UNIQUE constraint or an application-side check |
XML | JSONB for structured documents |
Refusing a TEXT value written to a JSONB column (PostgreSQL's SQLSTATE 42804) | The value is read by the JSONB input and stored when it is a valid document (SQLSTATE 22P02 when it is not); write ::jsonb to keep such a statement portable |
| Collations that order text by a locale's rules | The collations that exist (default, C and POSIX) all order text by its bytes; COLLATE with any of them is accepted and stored on the column |
NUMERIC values beyond 38 significant digits | NUMERIC(p,s) up to 38 digits. A DECLARATION above 38 (NUMERIC(210,10), common in ported schemas) is accepted with its bound clamped to 38, because a declared precision is a maximum constraint on values rather than a storage promise. A VALUE that genuinely needs more than 38 digits is rejected at insert, never silently truncated or wrapped. |
Programmability
| Not yet available | What to use today |
|---|---|
| Overloading: two functions or procedures of one name with different argument types | The second CREATE is refused, naming the first. Give the variants distinct names, or redefine one with CREATE OR REPLACE |
Vector search
An anode/manode index (see Vector Search) is
an approximate index: it promises no recall number, and we don't claim one here either.
These are the honest edges of that approximation, not a bug list.
| Not yet available | What to use today |
|---|---|
A quantized coarse estimator for *_l1_ops indexes (the 4-bit sweep and 1-bit estimate the other metrics use) | *_l1_ops still indexes and searches correctly, ranking directly on the exact binary16 plane; expect it to visit more candidates per search than an *_l2_ops or *_cosine_ops index at the same recall |
An ANN index over a SPARSEVEC column past vector.sparsevec_index_max_dims (2,000 by default) | Raise the setting with ALTER SYSTEM SET before CREATE INDEX: a SPARSEVEC index stores each row densified inside the arena, so a wider column costs more arena memory per row, not more disk |
| Guaranteed index-accelerated search for every row once an index's memory budget is exhausted | Writes still succeed; the rows an arena has no room for enter the index's tail state and are served by an exact scan merged into every search until the budget grows and a background regrow drains them. scram_vector_indexes.unindexed_rows and the scramdb_vector_index_unindexed_rows metric (see Observability) show it happening, never silently |
A proven-optimal exact_tier_ratio or selectivity_boundary filter-tier boundary | The shipped defaults (4 and 0.55) are measured priors, not a universal optimum; EXPLAIN names the tier chosen and the selectivity estimate that chose it, and both boundaries are live settings you can retune for your own predicate selectivity (see Tuning: Vector Indexes) |
Clustering
| Not yet available | What to use today |
|---|---|
A branch at a past time (scram.branch_at), or a cluster restore, to a point after a COPY into a distributed table committed, when the COPY began staging before the oldest base the archive keeps for one of its shard groups | The statement stops with a message naming the oldest base. Choose a point before that COPY committed, or keep the archive long enough ([storage.wal.archive] retention) to hold the whole of your longest load; a COPY staged after the base is restored and branched whole |
Reading, inside the same transaction block, the rows a COPY into a distributed table staged | Commit the COPY first. Inside the block a query does not see the rows it copied, and an INSERT of a key the block copied replaces the copied row at COMMIT instead of failing with 23505 |
Backup and restore
| Not yet available | What to use today |
|---|---|
pg_create_restore_point(name) inside an expression, such as SELECT pg_create_restore_point('p')::text | Call it on its own, SELECT pg_create_restore_point('p') (optionally AS lsn, or pg_catalog.-qualified); its answer is already the point's position as pg_lsn text |
A pg_lsn column type: pg_create_restore_point answers the position as text in pg_lsn form (0/16B3748) | Read the column as text; the value is PostgreSQL's own rendering of the position |
A point-in-time restore bringing back a database created, functions installed from a UDF package, or a transaction prepared (PREPARE TRANSACTION) after the base backup | Take a new base backup after the change; a restore from it holds the change |
Operations
ScramDB has no built-in connection pooler. Point your application's own connection pool (built into most PostgreSQL drivers and frameworks) at ScramDB, and keep the number of concurrent connections sensible.