Timescale + pgvector in one image
Why the pg17 image bundles both — and what we avoid by not running two databases.

DEINO runs on one Postgres. Not one Postgres for the ledger and another for vectors and another for time-series — one. The pg17 Timescale image bundles pgvector, and that single fact is why we do not run three databases.
DEINO runs on one Postgres. Not one Postgres for the ledger and another for vectors and another for time-series — one. The pg17 Timescale image bundles pgvector, and that single fact is why we do not run three databases.
One image, three jobs
The Timescale pg17 image is a Postgres that has TimescaleDB (for hypertables and time-series) and pgvector (for vector embeddings) in one process. DEINO uses it for everything: the Ledger (accounts, keys, events), the job and whisper queue, the World Monitor geo cache, the simulation rows, and the embedding store. One pool, one connection string, one migration story. The image bundles the extensions so we do not run a separate vector database and a separate time-series store.

What we avoid
Running a separate vector store (a dedicated pgvector instance, or a different vector database) means a second connection pool, a second set of migrations, a second failure mode, and a second consistency boundary. A query that joins ledger rows to embeddings across two databases is a distributed query, and distributed queries are where honesty goes to die. Keeping everything in one Postgres means the join is a local join, the transaction is a local transaction, and the backup is one backup. The cost is one process that does more; the benefit is one system that is coherent.
A query that joins ledger rows to embeddings across two databases is a distributed query, and distributed queries are where honesty goes to die. One Postgres, one join, one transaction.
Hypertables and vectors in the same pool
Timescale hypertables serve the time-series workloads — events, signals, the geo cache as it grows. pgvector serves the embedding workload — similarity search over node embeddings. They share the same pool, the same connection budget, the same WAL. The discipline is that the pool is sized for the combined load, and the migrations are ordered so that an extension is present before a table that uses it is created. One migration story, one set of .sqlx queries, one offline cache committed to the repo.
- One Postgres (Timescale pg17) for ledger, time-series, vectors, cache, and queue — one pool, one backup.
- Hypertables for time-series; pgvector for embeddings — same process, same WAL.
- No distributed joins — a ledger-to-embedding join is a local join, not a cross-database query.
Why not PostGIS
World Monitor routes on geohash prefixes, computed in deino-core::geo, not on PostGIS geometry. That is a deliberate non-dependency: PostGIS is a heavy extension, and the routing we need — which tile does this point belong to, which tiles are in this viewport — is a string prefix operation on a geohash, not a spatial query. Keeping PostGIS out keeps the image smaller and the dependency surface narrower. The geo logic lives in application code (deino-core), where it is typed and tested, not in an extension the database owns. That is the same discipline as one-database: the fewer moving parts, the more coherent the system.
The fewer databases, the fewer distributed queries. The fewer extensions, the narrower the dependency surface. One Postgres with the right extensions is the coherent choice — not the ambitious one.
— database architecture note
What it is not
One Postgres is not a claim that one database scales to anything. It is a claim that for the POC, and for a long way past it, one well-tuned Postgres with the right extensions is simpler and more honest than three coordinated stores. If and when a workload outgrows it, the split is a future decision made with evidence, not a present assumption made on faith. We are pre-revenue and building the POC; the one-database posture is built, and the .sqlx cache is committed so CI builds offline.