Operations & security¶
fdyno is the data plane only. It does not terminate TLS, implement IAM, or run a management console. Run it inside a trusted boundary and configure it through environment variables. It exposes health, readiness, and metrics endpoints for orchestration, and can run opt-in background maintenance workers.
Configuration¶
| Variable | Default | Purpose |
|---|---|---|
DYNODB_LISTEN_ADDR |
:8000 |
Data-plane listen address. |
FDB_CLUSTER_FILE |
FoundationDB default | Path to the cluster file. |
DYNODB_CREDENTIALS |
dev defaults | SigV4 keyId:secret pairs (comma-separated). When set, these replace the built-in dev credentials. |
DYNODB_PPROF_ADDR |
unset (disabled) | Private address for pprof, if needed. |
DYNODB_ACCESS_LOG |
unset (disabled) | When set, log one line per request (method, action, status, duration). |
DYNODB_READ_HEADER_TIMEOUT |
10s |
Slow-header (Slowloris) defense. |
DYNODB_READ_TIMEOUT |
60s |
Maximum time to read a request. |
DYNODB_WRITE_TIMEOUT |
120s |
Maximum time to write a response. |
DYNODB_IDLE_TIMEOUT |
120s |
Keep-alive idle timeout. |
Health checks and readiness¶
fdyno separates liveness from readiness so an orchestrator can tell "the process is up" apart from "the process can serve traffic".
| Endpoint | Checks FoundationDB | Use for |
|---|---|---|
GET /, /healthz, /livez |
No | Liveness probes. Returns 200 whenever the process is running; never touches FoundationDB. |
GET /readyz, /health/ready |
Yes | Readiness probes / load-balancer health. Performs a lightweight FoundationDB round-trip (reads the cluster read version within a short timeout) and returns 200 when reachable, 503 when not. |
Keeping liveness static is deliberate: a transient FoundationDB blip takes an
instance out of the load-balancer pool (via /readyz) instead of restarting the
process. Point liveness probes at /livez and readiness probes at /readyz.
Observability¶
GET /metricsreturns a JSON document: process uptime,requests_total,errors_total, and per-action request counts and average latencies. When background workers are enabled it also reports, per worker, whether this instance holds the worker lease, how many times it has run, how many items it has processed, and how long since its last run. Counters are process-local and reset on restart.- Access log (
DYNODB_ACCESS_LOG) emits one structured line per request for debugging and audit, complementing the aggregate/metricscounters. - pprof is served only when
DYNODB_PPROF_ADDRis set, on a separate listener (see Hardening).
Background maintenance workers¶
These workers are opt-in — each is disabled unless its interval variable is set — and are safe to run across many instances. Each takes a lease stored in FoundationDB, so with multiple instances exactly one runs a given worker at a time (no redundant full-table scans), and each persists its scan cursor in FoundationDB so a restart resumes rather than rescanning from the start. Their transactions run at FoundationDB's batch priority, so under contention they defer to foreground client requests and never steal read/write throughput from the data plane.
| Variable | Worker | Purpose |
|---|---|---|
DYNODB_TTL_SWEEP_INTERVAL |
TTL sweeper | Reclaims storage for expired items that are never read again. Read paths already hide and lazily delete expired items on access, so this only affects items that are never touched. DYNODB_TTL_SWEEP_MAX_SCAN bounds items scanned per pass. |
DYNODB_CDC_TRIM_INTERVAL |
Change-stream trimmer | Trims change-stream (CDC) records older than DYNODB_CDC_RETENTION (default 24h). DYNODB_CDC_TRIM_MAX_SCAN bounds items scanned per pass. |
DYNODB_TXN_TOKEN_GC_INTERVAL |
Idempotency-token GC | Reclaims expired TransactWriteItems idempotency tokens. DYNODB_TXN_TOKEN_GC_MAX_SCAN bounds items scanned per pass. |
Authentication¶
fdyno verifies AWS SigV4 signatures on every request against a map from access key ID to secret.
The built-in credentials are not secret
The default local, alternator, and cassandra credentials exist for
development and conformance testing. Any deployment that keeps them is
effectively unauthenticated. Set your own, which replaces the defaults:
Serving on a non-loopback address without DYNODB_CREDENTIALS set logs a
startup warning, because the well-known dev keys would otherwise remain valid.
fdyno does not implement authorization: there are no IAM policies and no resource-policy enforcement. Any caller with a valid signature can access any table. Treat the network boundary as your authorization boundary.
Hardening¶
- pprof is disabled by default. It is served only when
DYNODB_PPROF_ADDRis set, on a separate listener and mux. Bind it to a private address such as127.0.0.1:6060and never expose it publicly. - HTTP timeouts are set by default to bound connection lifetime and defend against slow-client attacks; all are tunable via the variables above.
- Request size is capped at 16 MB (DynamoDB's maximum request size),
including after decompression; larger requests are rejected with
413before the body is buffered, so a malformed or compressed-bomb payload cannot exhaust memory. - TLS is not terminated by fdyno; run it behind a reverse proxy or load balancer that terminates TLS.
- CORS: fdyno mirrors DynamoDB and returns
Access-Control-Allow-Origin: *for requests carrying anOriginheader. It never setsAccess-Control-Allow-Credentials, and every request still requires a valid SigV4 signature, so this alone does not grant cross-origin access to data.
Deployment shape¶
flowchart LR
C["Clients"] --> LB["TLS terminator<br/>(reverse proxy / LB)"]
LB --> F1["fdyno"]
LB --> F2["fdyno"]
LB --> F3["fdyno"]
F1 --> FDB[("FoundationDB cluster")]
F2 --> FDB
F3 --> FDB
Because fdyno holds no durable state, you can run as many instances as you like behind a load balancer; they all share one consistent view through FoundationDB. Scale the FoundationDB cluster for storage and throughput. Background workers stay correct at any instance count because they coordinate through FoundationDB leases.
Capacity and limits¶
fdyno reproduces DynamoDB's API, not its provisioning model. Operators should understand where the two intentionally differ:
- No request throttling. fdyno does not impose request-unit or throughput
caps and never raises
ProvisionedThroughputExceededExceptionorThrottlingException. It scales with the FoundationDB cluster; backpressure surfaces only if FoundationDB itself is overloaded. - At-rest encryption is a property of the FoundationDB cluster. Configure encryption there (FoundationDB 7.x supports native encryption); fdyno does not add an encryption layer of its own.
- Transaction limits. A single write is bounded by FoundationDB's per-transaction
limits (10 MB, 5 s). A
BatchWriteItem/TransactWriteItemswhose items plus index and stream entries exceed those limits is rejected with aValidationExceptionso the client can split the request.
Durability¶
All durable state lives in FoundationDB, so operational durability is FoundationDB's durability. A fdyno process can be restarted or replaced at any time without data loss. TTL configuration also survives a restart, so item expiry continues correctly. For point-in-time and on-demand backups see Backup & restore. See also Transactions & consistency.
Throughput and latency¶
The numbers below come from cmd/perfbench against a single fdyno process backed by
a single-node FoundationDB cluster using the in-memory storage engine, on an Apple
M3 Max (14 cores), over loopback. Items are about 200 bytes with a composite key.
These are local single-node figures that show the relative cost of each operation; a
multi-node FoundationDB cluster on SSD storage behaves differently.
Eight concurrent clients:
| Operation | Throughput | p50 | p99 |
|---|---|---|---|
GetItem |
4,659 ops/s | 1.7 ms | 2.3 ms |
Query (10 items) |
3,415 ops/s | 2.3 ms | 3.0 ms |
UpdateItem |
925 ops/s | 8.0 ms | 16 ms |
PutItem |
916 ops/s | 8.0 ms | 16 ms |
A read is served by a single FoundationDB read. Write latency is dominated by
FoundationDB's commit, which is why PutItem and UpdateItem sit near 8 ms at p50
on this setup.
Reporting security issues¶
Please report vulnerabilities privately through the repository's GitHub Security advisories rather than a public issue.