celld-operator

Observability

Two planes, both per-tenant by construction: celld's built-in tracing (spans and logs, written to the tenant's own bucket prefix or your OTLP collector) and the operator's Prometheus metrics (capacity and health, derived from each pod's /state).

Traces and logs

One field enables celld's telemetry for the fleet:

telemetry:
  enabled: true       # sets CELLD_OTEL=1 (the WorkerApp default)
  retention: 30d      # bucket sink only; "none" defers to bucket lifecycle rules
  # sink: otlp        # or send OTLP/HTTP to a collector instead:
  # otlpEndpoint: http://otel-collector.monitoring.svc:4318
SinkBehavior
bucket (default)Parquet traces and logs under the fleet bucket's telemetry/ prefix, partitioned by node and hour. DuckDB queries them directly, with no services to run. celld deletes files older than retention.
otlpOTLP/HTTP protobuf to the collector at otlpEndpoint (sets OTEL_EXPORTER_OTLP_ENDPOINT). Setting otlpEndpoint without a sink selects otlp automatically. Every fleet reports as its own service: the operator sets OTEL_SERVICE_NAME to the WorkerApp name, so a shared collector does not flatten all fleets into celld's default celld service.

What celld emits:

Under load, telemetry sheds before requests do, and celld counts what it sheds. The trace schema is v0-unstable; column names can change before a stable release.

Querying the bucket sink

INSTALL httpfs; LOAD httpfs;
CREATE SECRET r2 (TYPE s3, KEY_ID '…', SECRET '…',
                  ENDPOINT 'ACCOUNT.r2.cloudflarestorage.com', URL_STYLE 'path');
SELECT name, count(*), avg(duration_us)/1000 AS avg_ms
FROM 's3://platform-cells/apps/chat/telemetry/traces/*/*/*/*/*/*.parquet'
GROUP BY name ORDER BY 2 DESC;

celld flushes a Parquet file every 5 minutes by default (or sooner at 5 MiB of buffered events), which bounds how fresh these queries can be; the operator exposes no field for that interval, so treat the bucket sink as minutes-behind rather than live. Run the hourly compaction job or queries grow slow within hours. The job is a DuckDB COPY that rewrites one past hour of small files into one zstd Parquet; run it on a maintenance node, because celld does not compact its own files.

Fleet metrics

celld has no metrics endpoint yet, so the operator polls each pod's internal /state (from the leader only, every --state-poll-interval, default 15s) and exports Prometheus series per WorkerApp and per pod:

MetricMeaning
celld_resident_cellsOccupied (resident) cells per pod
celld_resident_cell_utilizationoccupied / maxResidentCells (0..1), the primary capacity signal
celld_restoringCold activations in flight
celld_evictingCells being evicted
celld_shedding1 while pressure-shedding; the hard out-of-capacity signal
celld_state_up1 if /state answered the last poll

The same series drive autoscaling, dashboards, and alerting. Request rates and latencies are derived from spans (or from your gateway's metrics) until upstream ships a native metrics signal.

At-a-glance status

$ kubectl get workerapps -A
NAMESPACE     NAME    PHASE        APP                READY   RESTORING   AGE
tenant-acme   chat    Ready        7b21e0c4a9d3f508   3       0           4d
tenant-beta   docs    RollingOut   sha-11aa22   2       1

PHASE, the rolled-out version, ready pods, and fleet-wide restoring count are printer columns on the CRD; conditions carry the detail. See the WorkerApp status reference.