Operations
strata_server reads environment variables at startup. The default
configuration is for local development.
Environment variables
Section titled “Environment variables”STRATA_TOKEN(default:dev-strata-token) sets the shared secret. Applications send this value as?token=. Set a different value for every deployment.STRATA_DATA_DIR(default:data) sets the directory for DETS persistence.STRATA_STATIC_DIR(default: unset) sets the root for static HTTP assets.just examplesets this variable. When it is unset, requests outside/adminand the WebSocket path return HTTP 404.STRATA_ADMIN_DIR(default:priv/admin) sets the directory for the built admin page.STRATA_RATE_LIMIT_MESSAGES_PER_SECOND(default:20) sets the sustained number of decoded non-join messages for each socket.0removes the rate limit.STRATA_RATE_LIMIT_MESSAGES_BURST(default:20) sets the burst bucket capacity (not an extra allowance).0uses the sustained rate.STRATA_RATE_LIMIT_JOINS_PER_SECOND(default:10) sets the sustained join rate per socket.0removes the join limit.STRATA_RATE_LIMIT_JOINS_BURST(default:20) sets the join bucket capacity.0uses the sustained join rate.STRATA_ALLOWED_ORIGINS(default: unset) sets a comma-separated list of exact WebSocket origins. An empty list allows every origin.STRATA_MAX_DOCUMENT_BYTES(default:16777216, or 16 MiB) limits growth of each encoded document, including causal metadata.0disables this budget. A negative value fails store startup.
Environment variables do not set the listen port or WebSocket path. An
embedded server can change port and websocket_path in StrataConfig.
Single-node deployment
Section titled “Single-node deployment”Run one Strata server against each STRATA_DATA_DIR. One store actor owns
the instance's documents, deletion links, and tombstones. Other instances
do not replicate or merge that state. Do not point multiple servers at the
same data directory, including on a shared volume.
Route all clients that share documents to the same instance. Per-client sticky sessions are insufficient if two collaborators reach different instances. A reconnect must return to the instance that owns the data. Strata does not offer automatic cross-node failover, partition recovery, or zero-downtime rolling upgrades. For a planned replacement, stop writers, complete shutdown, and start the replacement with the same stored data only after the previous owner has stopped.
You can partition independent workspaces across independent deployments
through application routing. Give each deployment its own data directory.
Keep a parent and its owned descendants together because create and
delete require one store owner. References to another instance do not
provide cross-instance sync.
One WebSocket can join multiple documents. An upstream doc_id query
parameter does not make Strata route each join to another server. If your
application selects an instance by workspace or document group, all joins
on that connection must belong there. The application must use separate
connections for other instances and arrange data migration before changing
the routing assignment. This is application-managed partitioning, not the
cluster support deferred in
#6.
Endpoints
Section titled “Endpoints”| Path | Purpose |
|---|---|
/socket/websocket?token=… | Application WebSocket endpoint |
/admin?token=… | Read-only admin page |
/* | Static assets from STRATA_STATIC_DIR, if set |
Non-local deployment precautions
Section titled “Non-local deployment precautions”- Set
STRATA_TOKENto a strong shared secret. An application with this token can read and write every document. The token also gives access to the admin page. - Set
STRATA_ALLOWED_ORIGINSto the exact origins that can open a WebSocket. Separate multiple origins with commas. - Set
STRATA_DATA_DIRto a persistent directory. Back up this directory. The storage format can change before 1.0. - Set
STRATA_STATIC_DIRonly if Strata must serve application assets. - Set
STRATA_ADMIN_DIRto the compiled admin directory. Protect the token because the admin page uses the same shared secret. - Set message and join rate limits for the expected workload.
- Put a reverse proxy in front of Strata. Configure TLS and per-IP rate limits at the proxy.
Strata passes these settings to beryl's separate per-socket message and join
buckets. The existing message defaults remain 20/20; join defaults are 10/20.
An excess join receives rate_limited. Beryl drops excess normal messages.
These limits do not limit raw undecodable frames. Strata does not apply a limit for
each IP address.
See Security Model for token and origin limits. See Durability and Recovery for backup instructions.
Strata has no health endpoint, HTTP metrics endpoint, or structured log format. Monitor the process and the listening port. Keep enough free disk space for the DETS files. Test backup and restore procedures before you depend on the stored data.
An invalid numeric rate-limit value uses the default. Check startup configuration before you expose the server.
Document growth budget
Section titled “Document growth budget”The store checks the encoded merged state before accepting apply,
create, or delete. If a child or parent exceeds its budget, the entire
operation is rejected with document_limit_exceeded; no partial child,
parent, or tombstone change is adopted or published. This also applies to
correlated requests. No extra encoding pass is needed for admission.
Recovery does not delete documents that exceed a newly lowered budget. Each such document may remain the same size or shrink, but may not grow. Once below the configured budget, the normal limit applies. Zero restores unlimited admission for deployments that explicitly accept that risk.
Use document byte_size in admin status to detect approaching limits. This
budget includes user values and retained metadata, not just visible keys.
It does not cap total document count, total store memory, transient decode
allocations, or permanent document-ID tombstones. The wire frame limit is
separate. See Metadata Growth for the v1 limit
and migration options.
Snapshot sizing
Section titled “Snapshot sizing”Each join and reconnect transfers a full document in one text frame. Large documents increase serialization, transfer, and client decode/merge work. Concurrent joins repeat that work. Strata has no chunk threshold or partial-snapshot resume; see protocol limits.
Choose STRATA_MAX_DOCUMENT_BYTES below the lowest message-size limit in
your deployed transport path, with room for the document ID and JSON
envelope. The default 16 MiB budget is not a tested capacity promise for
your proxy, browser, or Erlang client. It also does not bound multi-document
create frames, which contain both child and parent state.
Use admin document byte_size as a state-size signal, not the complete
frame size. Test full joins and reconnects with representative documents,
retained metadata, clients, and concurrent connections through your actual
proxy. If a large snapshot fails, inspect client errors and proxy limits
before repeated retries. Lowering the budget prevents new growth but does
not shrink existing documents. Plan smaller application documents or an
explicit document replacement where needed.
Shared-store measurements
Section titled “Shared-store measurements”store.operational_data and the operational field on read-only admin_status
frames expose bounded measurements. The existing admin page ignores this
additional field. No admin mutation or new authorization path is added.
| Measurement | Boundary |
|---|---|
queue_us | Enqueue timestamp to the start of the owner handler. Periodic flush uses its scheduled due time. |
service_us | Start of the owner handler through completion and reply dispatch. |
round_trip_us | Public store call entry to response receipt or timeout, including caller scheduling. Periodic flush has no caller round trip. |
admin_collection | Full status collection, including summaries, counts, presence reads, operational-data reads, and JSON construction. Its queue is zero; it runs outside the store owner. |
deleted_documents | Number of live documents removed by a successful delete. A repeated tombstone delete records zero. |
Snapshot, apply, create, delete, summary, flush, and close have separate statistics. Counts distinguish owner success/failure from caller timeout. A timeout can later be followed by successful owner completion. An accepted mutation count is not proof that a checkpoint contains it.
Checkpoint progress records completed attempts, successes, failures, the accepted-mutation count included by the last successful checkpoint, and the last completion time/result. Flush and close service distributions measure persistence cost. This is checkpoint completion progress, not byte-by-byte DETS write progress or an fsync guarantee.
Counts and extrema cover the current owner lifetime. Each distribution keeps only its latest 128 samples; p50/p95/p99 describe that window, not the whole deployment. Measurements retain no document IDs or payloads and reset after owner replacement. Monotonic microsecond timestamps are local to the BEAM node and are not wall-clock timestamps.
These reads share the store mailbox. A blocked owner can delay or time out a metrics read too; this is not an independent health monitor. Callers send one small follow-up timing message per store call. Recording those samples also costs owner time. Admin snapshots include earlier completed collections, not the collection currently being encoded. Transport serialization and network latency are outside the store timing boundary.
store.close_with_observations returns final data, including successful close
latency. Ordinary close keeps its existing result type and discards this
snapshot. Forced termination does not promise final measurements.
Repeatable mixed load
Section titled “Repeatable mixed load”Run just benchmark-store. It creates an isolated temporary store, seeds 64
documents with 16 KiB values, then runs four concurrent workers for 200 rounds
each. Workers mix hot and private document writes, snapshots, two-document
subtree deletes, explicit flushes, and full admin collection. The final JSON
reports workload parameters, elapsed time, per-class latency distributions,
checkpoint progress, and deletion sizes. It includes seed work in the
lifetime counts. The temporary store is removed after a successful run.
Repeat the run on the intended BEAM version and storage volume. Record the machine, VM scheduler settings, storage, document sizes, and sample-window limit with results. Change one workload dimension at a time when comparing loads. This local scenario is not a production capacity result.
Keep or partition the owner
Section titled “Keep or partition the owner”Keep the global owner while measured tail latency, recovery time, and checkpoint loss windows meet the application's requirements. First compare queue and service distributions by class. Reduce excessive admin polling or checkpoint frequency only if the durability requirement permits it.
Investigate partitioning when repeated representative runs show that slow work for one workload causes another to miss its latency requirement, or when the global owner reaches measured capacity. Compare against a baseline without that slow workload; mailbox length alone is insufficient evidence. Measure the observation cost as part of that comparison.
An ownership-subtree or workspace partition must retain atomic parent-link, child-state, and tombstone changes. A document partition needs an explicit cross-owner protocol for create/delete. Any proposal must also preserve recovery, publication, and rejection semantics. These measurements describe shared work; they do not prove a production bottleneck or justify one actor per document.
Recovery and shutdown
Section titled “Recovery and shutdown”strata_server.start(config) returns a running Server. Its RestForOne
supervisor starts the store, socket-count tracker, presence, channel runtime,
admin collector, and HTTP listener in that order. An abnormal store exit
restarts those dependents. The store and tracker use stable named handles;
new channel callbacks and the admin collector reach the replacement owner.
Existing sockets disconnect and must rejoin for a recovered snapshot.
The supervisor permits two restarts in five seconds. More failures stop the tree. Use an external service manager to restart the application, and inspect the storage error before repeated attempts. PubSub uses a node-local shared scope; it does not own document state or retain a store process reference.
An embedded host must call strata_server.shutdown(server) for an intentional
stop. This stops the HTTP listener, admin collector, channels, presence, and
tracker before it calls store.close. A successful close ends the supervision
tree. A storage error returns StoreCloseFailed; the service stays stopped
and the store remains available for a shutdown retry. External callers of
document_store(server) must stop writes before calling shutdown.
Supervisor-driven termination also attempts the store's final close, with a
10-second worker shutdown bound. Failure appears in the process error/log
path. An untrappable kill, VM crash, or host loss cannot run that close.
The default run entry point waits for the supervision tree; it does not
install an OS signal handler. Hosts that need a confirmed close on SIGTERM
must connect their signal lifecycle to shutdown, not assume a signal proves
a flush.