Skip to main content

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 availableWhat 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 latencyWrites are correct and durable now; OLTP write-latency tuning is ongoing
Cold storage tiering removes local files only at a restartA 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 availableWhat to use today
Incremental materialized view refresh, and REFRESH MATERIALIZED VIEW CONCURRENTLYFull REFRESH MATERIALIZED VIEW; the CONCURRENTLY form is refused
PostgreSQL's non-blocking, two-phase CONCURRENTLY index buildCREATE 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 / SPLITRange, 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 nodeSuch 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 SELECTMake 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 CUBEAggregate 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_ndimsarray_dims, which names the subscript range of each dimension
pg_advisory_lock and its family with a key computed from a column or an expressionA 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 databasesRun 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 statementKeep 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 availableWhat to use today
tsvector / tsquery as a built-in typeFull-text search through the built-in fts and bm25 packages
Range typesA pair of columns for the lower and upper bound
EXCLUDE constraintsA UNIQUE constraint or an application-side check
XMLJSONB 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 rulesThe 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 digitsNUMERIC(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 availableWhat to use today
Overloading: two functions or procedures of one name with different argument typesThe second CREATE is refused, naming the first. Give the variants distinct names, or redefine one with CREATE OR REPLACE

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 availableWhat 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 exhaustedWrites 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 boundaryThe 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 availableWhat 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 groupsThe 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 stagedCommit 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 availableWhat to use today
pg_create_restore_point(name) inside an expression, such as SELECT pg_create_restore_point('p')::textCall 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 backupTake 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.