The engines
Eleven engines. One substrate.
“One store” is a claim worth being precise about. This is not one engine that does several things loosely, and it is not eleven services behind a façade. It is eleven access paths over one substrate, one transaction and one language. Seven of them change how a record is reached. The other four change something else, which is why they are also the four that are not shorthand for a table and some clauses: a series changes what the store will answer with, a queue changes who may reach a record and until when, a topic makes a message unchangeable once written and numbers it at commit, and a vault changes what is stored — its declared fields are sealed before the record is written, so the index, the change feed, the replication log and every backup carry ciphertext.
The whole set
None of these is a plug-in, a sidecar or a second process. Every one of them writes through the same transaction.
| Engine | Holds | Asked with | Served by |
|---|---|---|---|
| Documents | Objects, arrays, nested trees | CREATE · SELECT · UPDATE · DELETE | Identity, a value index, or a scan |
| Graph | Its own records, and the edges between them | RELATE, -> <- traversal, bounded by DEPTH | The edge table's own index, or adjacency beside the node |
| Full-text | Terms, produced by an analyzer | MATCHES (PREFIX, FUZZY, INFIX), search::score; DEFINE SEARCH across tables; ORDER BY FUSE to rank with meaning or place | A SEARCH index |
| Vectors | Arrays of numbers — a store of them, or a field of a record, of a declared width | vector::euclidean and friends, in ORDER BY; APPROXIMATE to ask the index | A VECTOR proximity graph |
| Geometry | Shapes on the sphere — a store of places, or a field of a record | Eight geo:: predicates, a distance to any shape, an area, geo::cell | A SPATIAL cell index |
| Key-value | One value per key — optionally expiring, in a space that may hold at most n | GET · SET · DEL · KEYS · EXPIRE · TTL · INCR · SET … IF | The key itself |
| Files | Bytes, in chunks, under a ceiling the bucket may declare | PUT · READ | The bucket's metadata records |
| Time-series | Events ordered by when they happened, answered only within a declared retention | DEFINE SERIES … RETAIN … TIME at · FILL · LATEST BY · ASOF JOIN · DEFINE ROLLUP | The event time in the key, so a late event lands in place and the floor is a position |
| Queues | Ordinary records, handed out one holder at a time under a hold that lapses | DEFINE QUEUE … TIMEOUT · CLAIM · RELEASE | The deadline the claim writes, which is a field of the record |
| Topics | Messages that are never changed, at dense positions decided at commit | DEFINE TOPIC · CREATE · READ FROM … FOR CONSUMER · DEFINE GROUP · ACK · NACK | The position, each reader's position, and each group's messages in flight, kept in the store |
| Vault | Declared fields marked SECRET, sealed one key per record | DEFINE VAULT, REVEAL, ADD RECIPIENT | The record's own data key, not an index |
Which one you want
The table above says what each engine is. This one starts from the question you arrived with.
| What you are trying to do | Reach for | Why that one |
|---|---|---|
| Store an object and read it back by id | Documents, no index | Identity is already the fastest path there is — a seek, not a search |
| Find records by the value of a field | Documents, a value index | An index changes what the read costs, never what it answers |
| Find records by a word inside text | Full-text | A value index matches the whole field; MATCHES matches terms |
| Rank text results by how well they match | Full-text, search::score | Scoring needs a collection to measure a word's rarity against |
| Rank several fields of several tables as one | Full-text, DEFINE SEARCH | One analyzer and one set of statistics, each table's grants kept |
| Find what is near something in space | Geometry | Metres on the sphere, and predicates that are exact at the boundary |
| Find what is similar to something | Vectors | Similarity is distance between arrays of numbers, not a filter |
| Follow relationships between records | Graph | An edge is a record with its own index, so it carries data and queries like a row |
| Walk a graph, where the cost that matters is the hop | Graph | Adjacency sits beside the node, so a hop does not grow with the number of neighbours |
| Keep one value under one key, fast | Key-value | No document tree to walk, and the key is the address |
| Keep bytes that are not a document at all | Files | Chunked storage, with the metadata as ordinary records |
| Group events by hour, day or week | Time-series | The window is anchored to the epoch, so every process asking for 1h agrees where it starts |
| Keep hourly totals as the raw events arrive | Time-series, DEFINE ROLLUP | Kept in the transaction that writes the event, so the totals and the events never disagree |
| Keep only the last thirty days, and have reads say so | Time-series, DEFINE SERIES … RETAIN 30d | The floor governs the answer; removing the bytes is a separate act, so a lagging pass costs storage and never correctness |
| Hand a piece of work to exactly one worker at a time | Queues | The hold lapses on its own, so a worker that dies returns its work without anything having to notice |
| A session that times out, a rate-limit window, a lock that frees itself | Key-value, EXPIRE · INCR · SET … IF ABSENT | The expiry is in the key's version, so every node and every restart agrees on it |
| Publish events several parts of the application read, each at its own pace | Topics | A reader's position commits with what it writes here, so an effect in this store happens once per message |
| Rank by the words typed and by what they meant | Full-text with ORDER BY FUSE | Fused by place in each order, never by adding a score to a distance |
| Keep a credential the database itself cannot read back | Vault | The field is sealed before the record is encoded, so every copy downstream carries ciphertext |
Declare the engine, or reach it from a table
When an engine is what you are storing, declare the store as one. That is the route in: one word, and the parts that have to agree with each other already do.
DEFINE GRAPH social;
CREATE social:1 = { name: 'ada', team: 'core' };
DEFINE EDGE knows IN social FROM social TO social;
DEFINE VECTOR embeddings DIMENSION 768 DISTANCE cosine;
CREATE embeddings:'intro' = { vector: [0.1, 0.2, 0.3], label: 'intro' };
DEFINE GEO places;
CREATE places:'paris' = { geometry: geometry { type: 'Point', coordinates: [2.35, 48.85] } };No table is declared first, because the word declares one. A declared engine you cannot write a record into would be a label rather than a store.
The other route stays, and for most records it is still the right one: a vector, a shape or an edge as a field of a table that exists for its own reasons. A chunk with an embedding is still a chunk. Tables you already have join a graph with IN social rather than being replaced by it.
Two things those tables are not saying
These are not alternatives
One record can carry a text field, a vector, a shape and an edge at once, and one transaction writes all of them. The question is never which engine is this database but which access path answers this read — and a single statement may use several.
Only one trades exactness for speed
An index changes what a read costs, never what it answers. That holds for every index here with one exception — the vector index, which cannot promise it, and so the statement has to ask for approximation out loud.
SELECT * FROM points ORDER BY vector::euclidean(at, [0.1, 0.0]) LIMIT 2;
-- the exact scan it always was
SELECT * FROM points ORDER BY vector::euclidean(at, [0.1, 0.0]) LIMIT 2 APPROXIMATE;
-- now the proximity graph, and now it may be wrongWhich matters more than it looks: the ground truth you measure your own recall against comes from the same store as the answer, rather than from a second system you would have to trust.