Architecture
Runtime layout — adapter crates, caches, streaming spine, and the planned scale topology.
Runtime Flow
flowchart LR
upstream["Live upstream pollers<br/>(built-in + external)"] --> ingestor
static["Bundled datasets"] --> adapter
rss["RSS proxy & service APIs"] --> adapter
ingestor["feed-ingestor<br/>(poll + normalize)"] --> spine["Zenoh spine<br/>(planned phase 3)"]
ingestor --> hot["Dragonfly hot cache"]
hot --> adapter["feed-api<br/>(HTTP + gRPC)"]
spine --> adapter
adapter --> trailbase["TrailBase<br/>(local SQLite cache)"]
adapter --> questdb["QuestDB<br/>(history sink)"]
adapter --> http["HTTP / GeoJSON / MVT"]
adapter --> grpc["gRPC (incl. StreamLayer)"]
http --> client["Map client / operator UI"]
grpc --> clientThe deployed adapter today serves from in-process snapshots and lazy-loaded
static JSON. The planned scale split moves the polling layer into a sibling
feed-ingestor crate and fans deltas through Zenoh; the read-API crate
(feed-api) keeps the HTTP/gRPC surface stable across that move.
Crates
| Crate | Role |
|---|---|
feed-api | HTTP + gRPC read API, operator control plane, alerts, vessels, analysis, satellites. Where every route in the OpenAPI lives. |
feed-ingestor | (in-flight) Polling sources, normalization, ingest into Dragonfly + Zenoh. Owns the satellite/cameras pollers moving out of feed-api. |
feed-core | Shared types, schemas, contract glue. |
feed-store | Persistence helpers (TrailBase, QuestDB sinks/rollups). |
Ports
| Service | Port | Purpose |
|---|---|---|
feed-api HTTP | 8080 (HTTP_BIND) | Catalog, layers, datasets, operator, vessels, analysis, satellites, docs, Scalar, Farsight SPA. |
feed-api gRPC | 50051 (GRPC_BIND) | Read API + StreamLayer push. |
trailbase | 4000 (host 4001) | Local SQLite cache + admin UI. |
| Dragonfly | env (DRAGONFLY_URL) | Hot snapshot cache (m0f.2, landed). |
| Zenoh spine | env (ZENOH_URL) | Delta fan-out (m0f.4, landed; HTTP fan-out endpoints planned). |
| QuestDB ILP | env (QUESTDB_ILP_URL) | Track-history sink + rollup jobs. |
| Envoy edge | k8s ingress | TLS + routing at farsight.r2d2.office.ilab.zone. |
Cache semantics
- Static datasets are shipped with the image — durable by construction.
- Live snapshots live in process memory and (when
DRAGONFLY_URLis set) in Dragonfly hot cache. Recovery on restart is sub-second. - TrailBase holds the localized SQLite mirror of every catalog dataset + generated geometry — see TrailBase Cache.
- QuestDB holds track history and rollups (
adsb_track_history, per-vessel windows) used by analysis endpoints; no-op withoutQUESTDB_ILP_URL.
Streaming reality check
The OpenAPI advertises HTTP streaming (/stream/{layer}, /ws/{layer})
and vector tiles (/tiles/{layer}/{z}/{x}/{y}.mvt) as x-status: planned.
Neither is in the deployed image today. The gRPC StreamLayer RPC is
the only push-style surface that works end-to-end right now.
See Tiles & Streaming for what to use in the meantime.
Deployment
The production deployment runs in the office k8s cluster (Helm overlay
values.r2d2.yaml) with Envoy at the ingress and a Longhorn PVC mounted at
/data for the local cache. The Scalar reference and Fumadocs site are
served by the same adapter binary on the same port — no separate doc service.