Execution
The Read Fleet
Distribute analytical reads across disposable workers over published artifacts — one brain, many muscles.The read fleet lets one Postgres — the brain — offload analytical queries to lightweight worker processes on other machines — the muscles — without replicating Postgres, without a distributed query planner, and without ever risking a wrong answer.
The trick is that RVBBIT's acceleration layer is already immutable files governed by a catalog. Publish those files to shared object storage and any machine can serve scans over them; the brain keeps the heap (source of truth), the catalog, and the router. Workers hold no state worth mourning: kill one mid-query and the router falls back to local execution — the query gets slower, never wrong.
Think of it as a lakehouse turned upside down: instead of starting with files on S3 and bolting on a database to keep them consistent, you start with the database and publish the files outward.
The rules that keep it simple#
- Whole queries only. A query is routed to exactly one place. If its tables span placements, the brain serves it. There is no distributed join executor, and there never will be.
- Fail open. A dead or unreachable worker means a warning in the log and local execution — no user-visible error. Removing a live worker from the fleet is an anticlimax by design.
- Workers earn queries by passing probes. The dispatcher only considers registered, enabled endpoints whose last health probe succeeded.
- Declared staleness. Workers read published generations. Freshness is bounded by publication cadence — the same stale-is-slow-not-wrong contract as the rest of the acceleration layer.
1 · Configure a publish store#
-- Non-secret config lives in the catalog; credentials NEVER do.
SELECT rvbbit.set_publish_store('s3://my-bucket/fleet');
-- Verify: canary write / head / delete with timings.
SELECT rvbbit.publish_store_doctor();
Credentials come from the server environment (AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, optional AWS_ENDPOINT) — an
env file or compose environment: block on the Postgres container. Google
Cloud Storage works through its S3-interoperability endpoint: create HMAC keys
for a service account and set AWS_ENDPOINT=https://storage.googleapis.com.
2 · Publish artifacts#
Once a store is configured, every compaction publishes its new row groups automatically — keeping the local files (the brain never depends on the network for its own reads). Backfill or re-publish by hand:
SELECT rvbbit.publish_row_groups('my_table'); -- backfill one table
SELECT * FROM rvbbit.publish_state; -- water level per table
SELECT rvbbit.republish(); -- after changing the store URL
Per-table opt-out (for data that must not leave the box):
INSERT INTO rvbbit.publish_policy (table_oid, enabled)
VALUES ('sensitive_table'::regclass::oid, false);
3 · Start a worker#
A worker is the rvbbit-duck engine from the standard image, listening on
TCP. It holds no Postgres credentials beyond a read-only catalog DSN, and
object-store credentials scoped to reading artifacts:
docker run -d --name rvbbit-duck-worker --restart unless-stopped \
-p 9464:9464 \
-v /etc/ssl/certs:/etc/ssl/certs:ro \
-e RVBBIT_SIDECAR_REMOTE=1 \
-e RVBBIT_ENGINE_TOKEN='<shared-token>' \
-e AWS_ACCESS_KEY_ID=... -e AWS_SECRET_ACCESS_KEY=... \
-e AWS_ENDPOINT=https://storage.googleapis.com \
--entrypoint /usr/local/bin/rvbbit-duck \
ghcr.io/ryrobes/rvbbit-postgres:latest \
--serve-tcp 0.0.0.0:9464 --workers 4 \
--dsn "host=<brain-host> port=5432 user=... dbname=..." --engine duck
Notes:
RVBBIT_ENGINE_TOKENis fail-closed: the worker refuses to start without it, and every request must carry a matching token. Run workers on a private network (VPC, WireGuard); TLS between brain and worker is on the roadmap (Arrow Flight + mTLS).- The
/etc/ssl/certsmount supplies CA certificates for object-store TLS (needed on current images). RVBBIT_SIDECAR_REMOTE=1makes the worker resolve row groups via their published URLs instead of brain-local paths.
4 · Register it with the brain#
SELECT rvbbit.fleet_add('cpu-1', '10.0.0.19:9464');
SELECT rvbbit.fleet_probe('cpu-1'); -- health check with teeth (see below)
SELECT * FROM rvbbit.fleet; -- the registry, at a glance
The brain needs the same RVBBIT_ENGINE_TOKEN in its environment — it
authenticates to workers, never the reverse.
fleet_probe is not a TCP ping: it asks the worker to prewarm, which proves
the transport, the token, the worker's DSN back to the catalog, and that the
published artifacts are visible from that machine. A failed probe removes the
node from the dispatch rotation until a later probe succeeds:
SELECT * FROM rvbbit.fleet_doctor(); -- probe every enabled worker
SELECT rvbbit.fleet_set_enabled('cpu-1', false); -- drain by hand
SELECT rvbbit.fleet_remove('cpu-1');
5 · Routing#
With a healthy registered worker, engine-eligible queries dispatch to it automatically. To pin a session (testing, or forcing a specific node):
SET rvbbit.duck_fleet_endpoint = '10.0.0.19:9464'; -- session-level pin
SET rvbbit.route_force_candidate = 'duck_vector'; -- force the engine too
Every decision and execution records which node served it in
route_decisions.node / route_executions.node (NULL = the brain's local
engines):
SELECT coalesce(node, 'local') AS node, count(*)
FROM rvbbit.route_decisions
WHERE decided_at > now() - interval '1 hour'
GROUP BY 1;
In Data Rabbit, the Fleet window (System folder) is the live topology — click a worker to probe it — and the Adaptive Routing window grows per-node filter pills so you can see exactly what ran where.
Disk lifecycle: the local cache escape hatch#
Published tables can release their local copies — reads transparently shift to the published objects (slower, never wrong), reclaiming the brain's disk:
SELECT rvbbit.evict_local('big_archive_table'); -- verify, flip reads, reclaim
Eviction verifies the published object byte-for-byte before releasing
anything, and the local file goes through the same deferred-unlink grace
window as every other artifact (reap_grace_minutes in rvbbit.settings —
size it beyond your longest remote query).
Reference#
| Surface | Purpose |
|---|---|
rvbbit.set_publish_store(url, enabled) |
Configure the shared store (non-secret half) |
rvbbit.publish_store_doctor() |
Canary write/head/delete with timings |
rvbbit.publish_row_groups(rel) |
Publish a table's unpublished row groups |
rvbbit.republish(rel?) |
Re-home artifacts after a store change |
rvbbit.evict_local(rel) |
Release local copies of published artifacts |
rvbbit.publish_state |
View: published / evicted per table |
rvbbit.publish_policy |
Per-table publication opt-out |
rvbbit.fleet_add / fleet_remove / fleet_set_enabled |
Registry management |
rvbbit.fleet_probe(name) / rvbbit.fleet_doctor() |
Health checks (recorded on the registry row) |
rvbbit.fleet |
View: the registry with probe state |
rvbbit.duck_fleet_endpoint (GUC) |
Session-level endpoint pin |
route_decisions.node / route_executions.node |
Which node served each query |
rvbbit-duck --serve-tcp HOST:PORT |
Run a fleet worker |
RVBBIT_ENGINE_TOKEN |
Shared auth token (worker fail-closed; brain env) |
RVBBIT_SIDECAR_REMOTE=1 |
Worker resolves published URLs, not local paths |
What this is not#
There is no consensus protocol, no shard rebalancing, no replica lag to reason about, and no distributed transaction anywhere in this design. The brain remains a single ordinary Postgres with ordinary Postgres HA options. The fleet scales reads over immutable data — which, in an accelerated RVBBIT warehouse, is most of the expensive work — and protects the writer's buffer cache and I/O from analytical bursts while doing it.