Zum Inhalt springen
Deutsch

Die vollständige reference.conf

Das ist die eingebaute Konfiguration des Frameworks, exakt so, wie sie in src/config/reference.ts ausgeliefert wird. Es ist die vollständige Menge der First-Party-Settings: was hier nicht steht, hat keine HOCON-Form, und was hier steht, wird von irgendetwas gelesen (sonst schlägt ein Test fehl — siehe Keine toten Keys unten).

Jeder gezeigte Wert ist der bereits geltende Default, du musst also nie das Ganze kopieren. Schreib nur das in deine application.conf, was du ändern willst:

# application.conf — alles andere behält die Defaults unten
actor-ts {
system.name = "billing"
sharding.passivation-idle = 2 minutes
}

Was die einzelnen Keys tun, steht in Konfiguration — diese Seite ist die vollständige Liste, jene die Erklärung.

actor-ts {
system {
name = "default"
# How long terminate() lets the actors under /user finish the work they
# already have before the stop cascade starts. The wait ends as soon as
# the tree goes quiet, so an idle system pays a tick rather than the
# budget; what is still queued when the budget runs out is dead-lettered
# exactly as it was before. Keep it under
# coordinated-shutdown.default-phase-timeout — the last phase awaits
# terminate(), so a drain as long as the phase leaves no room to stop
# anything. 0 disables draining and restores the pre-#663 behaviour.
#
# A throttled or suspended mailbox is never waited on: neither drains at
# a rate a shutdown can wait for.
shutdown-drain-timeout = 2s
}
logger {
# System log level. Gates BEFORE the per-sink min-levels below, so a
# sink asking for "debug" while this says "info" receives nothing —
# lower this first, then narrow per sink.
level = "info" # debug | info | warn | error | off
# Grace period terminate() gives the active logger to flush and close
# its sinks before whenTerminated() resolves. Bounds the whole logger,
# so it applies to a custom one too — any logger with a close().
close-timeout = 3s
# Multi-sink pipeline. Every sink ships disabled; enabling at least one
# replaces the default single ConsoleLogger with a MultiSinkLogger over
# the enabled set. An explicit logger (or logSinks) passed to
# ActorSystem.create replaces this whole block rather than merging with
# it -- a sink belongs to one construction world or the other.
#
# The Sentry sink has no block here: it needs your own @sentry/node
# import, which a config file cannot hold, so it is wired in code.
# See docs -> Observe -> Logging -> Platform integrations.
sinks {
console {
enabled = false
min-level = "info" # debug | info | warn | error | off
format = "text" # text = human-readable, json = one NDJSON object per record
stream = "auto" # auto | stdout | stderr; auto = console.* for text, stdout for json
}
file {
enabled = false
min-level = "info"
format = "text"
directory = "logs" # created if missing
prefix = "log" # file name is <prefix>-<yyyy-MM-dd>-<HH-mm-ss>.<extension>
extension = "txt"
# Rolling over always opens a NEW stamped file — the active file is
# never renamed, which Windows forbids while it is open anyway.
max-file-bytes = 64M # roll when the file would pass this; 0 = never
rotate-interval = "daily" # off | hourly | daily — roll on the clock boundary
# Retention only ever deletes files matching this sink's own prefix,
# extension and timestamp shape — never anything else in the directory.
max-files = 14 # keep this many rotated files; 0 = keep all
max-age = 14d # delete rotated files older than this; 0 = keep all
compress-rotated = false # gzip each rotated file to <name>.gz
delivery {
max-batch-size = 500
flush-interval = 1s
queue-capacity = 10000
overflow = "drop-new" # drop-new | drop-head
}
}
# Graylog. Native rather than via OTLP because Graylog's
# OpenTelemetry input speaks gRPC only, which the otlp sink does not.
gelf {
enabled = false
min-level = "info"
protocol = "udp" # udp | tcp | http
host = "127.0.0.1" # udp/tcp
port = 12201 # udp/tcp
url = "" # http only, e.g. "http://graylog:12201/gelf"
# The GELF "host" field. Empty = the OS hostname, else the system name.
host-name = ""
compression = "gzip" # udp only: none | gzip (server auto-detects)
max-chunk-bytes = 1420 # udp only: datagram size before chunking
request-timeout = 10s # http only
# TLS for tcp is code-only: those fields carry the key material
# itself, not a path to it.
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
# OpenTelemetry logs over HTTP with a JSON body. One endpoint format
# reaches Loki 3+, Parseable, SigNoz, Datadog, Axiom, Honeycomb, New
# Relic and every OTel Collector — start here before a native sink.
otlp {
enabled = false
min-level = "info"
url = "http://localhost:4318/v1/logs"
# service.name on the OTLP resource; defaults to the system name.
service-name = ""
scope-name = "actor-ts"
gzip = false
request-timeout = 10s
# Request headers (API keys, tenant ids) are code-only: a config
# file is the wrong home for a credential.
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
# Grafana Loki's native push API. Loki 3+ also accepts OTLP at
# /otlp/v1/logs, so the otlp sink reaches it too; this one exists for
# direct push and explicit control over the label set.
loki {
enabled = false
min-level = "info"
url = "" # base URL, e.g. "http://loki:3100"
tenant-id = "" # X-Scope-OrgID, for multi-tenant Loki
format = "text" # text | json — how each log line is rendered
# Labels are Loki's INDEX. Keep them static and few: every distinct
# combination is a separate stream, and a per-record value here
# (an actor path, a request id) multiplies streams without bound.
# Everything variable rides as structured metadata instead.
labels {
service = "" # empty = the actor system's name
}
structured-metadata = true
request-timeout = 10s
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
# Parseable's REST ingestion. Its OTLP endpoint works too — this
# sink sends a flatter record and skips OTLP semantics.
parseable {
enabled = false
min-level = "info"
url = "" # base URL, e.g. "https://parseable.internal"
stream = "" # target dataset; created on first use
# Basic auth OR an API key, never both. Prefer a substitution
# (${?PARSEABLE_API_KEY}) over writing a secret in here.
username = ""
password = ""
api-key = ""
request-timeout = 10s
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
# Seq, over CLEF — newline-delimited JSON with @-prefixed reserved
# keys. Close enough to the framework's own NDJSON that there is
# almost nothing to translate.
seq {
enabled = false
min-level = "info"
url = "" # base URL, e.g. "http://seq:5341"
api-key = "" # X-Seq-ApiKey; prefer ${?SEQ_API_KEY}
request-timeout = 10s
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
# Splunk's HTTP Event Collector.
splunk {
enabled = false
min-level = "info"
url = "" # HEC base URL, e.g. "https://splunk:8088"
token = "" # HEC token; prefer ${?SPLUNK_HEC_TOKEN}
index = "" # empty = the token's default index
source = "actor-ts"
sourcetype = "_json"
host-name = "" # empty = the actor system's name
request-timeout = 10s
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
# RFC 5424 syslog — the integration that needs no vendor: rsyslog,
# syslog-ng, journald's forwarder, Papertrail and a long tail of
# appliances all speak it.
syslog {
enabled = false
min-level = "info"
transport = "udp" # udp | tcp | tls
host = "127.0.0.1"
port = 514
facility = 16 # 0-23; 16 = local0, the range for applications
app-name = "" # empty = the actor system's name
host-name = "" # empty = the OS hostname
# tcp/tls framing. octet-counting (RFC 6587) is the only one that
# survives a message containing a newline — a stack trace always does.
framing = "octet-counting" # octet-counting | lf
# TLS material is code-only: those fields carry the key itself.
delivery {
max-batch-size = 100
flush-interval = 2s
queue-capacity = 10000
}
}
}
}
actor {
# User messages ONE actor handles per dispatcher turn before it yields.
# Distinct from dispatcher.throughput below, which bounds how many queued
# units — each belonging to a different actor — a ThroughputDispatcher
# drains per tick. This is the knob that amortises the scheduling round
# trip, because a cell may only ever have one unit queued at a time.
# 1 restores the pre-#409 message-at-a-time interleaving; raising it trades
# fairness for throughput, since nothing else on the event loop runs until
# the actor yields. Override per actor with ActorOptions.withThroughput().
throughput = 16
}
dispatcher {
# hybrid | immediate | microtask | throughput
#
# hybrid wakes actors on the microtask queue and spends every 64th unit on
# a macrotask so timers and I/O still run; immediate is the previous
# default, one setImmediate per turn — fair, and ~2.4 us a hop that a
# request/response actor cannot amortise. microtask is the unbounded form
# and starves the event loop under a sustained volley; it is kept for
# measurement, not for production.
default = "hybrid"
# Queued units a ThroughputDispatcher drains per tick, ACROSS actors —
# see actor.throughput above for the per-actor batch.
throughput = 16
}
# Bounded record of the messages the system could not deliver, inspectable
# and replayable through system.deadLetterQueue. Undeliverable messages are
# published on the event stream either way -- this decides whether anything
# KEEPS them, which by default nothing does.
dead-letters {
# One axis, ordered by how much of the letter is kept:
# off = publish only, keep nothing (the behaviour before #433)
# metrics = count them and keep no payload, for the alert without the
# evidence locker (retaining a message is a data-protection
# decision; observing a rate is not)
# memory = keep in a bounded ring, lost with the process
# persistent = additionally write to the configured journal, so the queue
# is still there after a restart
store = "off"
# Letters held before the oldest is evicted. The queue is a diagnostic
# ring: an unbounded one turns a delivery outage into an out-of-memory.
# Ignored by the off and metrics stores, which hold no letters.
max-entries = 1000
# Age letters out after this long. 0 disables ageing and leaves
# max-entries as the only bound.
retention = 1h
# How often one letter may be replayed before it is quarantined. A
# replayed message that dead-letters again comes back as the SAME entry
# with a higher count, so a poison message cannot be retried forever.
max-replays = 3
# Journal stream the persistent store writes to. Empty = derive it from
# the system name, so two systems sharing a journal keep separate queues.
persistence-id = ""
}
cluster {
gossip-interval = 1s
seed-retry-interval = 3s
weakly-up-after = 0s # 0 disables auto weakly-up promotion
# Caps on the local member map. max-frame-bytes bounds ONE gossip frame;
# these bound what a sequence of well-formed frames can accumulate, since
# gossip is what introduces addresses in the first place. 0 disables
# either. max-tombstones is the load-bearing one: a tombstone carries no
# liveness, so nothing but the TTL below ever reclaims it.
max-members = 1000
max-tombstones = 10000
tombstone {
time-to-live = 24h
prune-interval = 5m
min-retention = 0s # 0 = derive from failure-detector down-after
}
failure-detector {
heartbeat-interval = 500ms
unreachable-after = 2s
down-after = 5s # measured from the last heartbeat, so > unreachable-after
}
# Stable-observation bootstrap: poll discovery until the contact-point set
# has been unchanged for stable-margin, then let the lowest-addressed node
# -- and only it -- form a cluster if no peer promoted it within
# self-election-grace. Opt-in: bootstrapCluster reads this block only when
# its stableObservation option is set. required-contact-points is the one
# knob worth changing: 1 keeps single-node development working, but only a
# value matching the expected replica count catches discovery that is
# stably wrong rather than merely slow.
bootstrap {
stable-margin = 5s
poll-interval = 1s
max-wait = 60s
required-contact-points = 1
self-election-grace = 10s
# Fewest up members -- self included -- before bootstrapCluster's
# awaitReady (and Cluster.awaitReady / isReady) counts the cluster as
# ready. 1 keeps single-node development working; a deployment states
# its replica count here, for the same reason as
# required-contact-points.
minimum-members = 1
# await-ready ships no value on purpose -- a key that is always present
# could not express "unset", and unset is what selects the computed
# default: self-election-grace + 5s behind stable observation, 5s
# otherwise (#1086). Set a duration to pin the budget regardless:
#
# await-ready = 30s
}
# Cluster-wide publish/subscribe (DistributedPubSub). The caps bound what
# one mediator can be made to hold -- by local subscribers and by a peer's
# gossiped topic claims alike. A Subscribe over a cap is answered with
# SubscribeRejected, never silently dropped.
pub-sub {
gossip-interval = 1s
max-subscribers-per-topic = 10000
max-topics = 10000
max-remote-nodes-per-topic = 1000
# A publish that reached no subscriber goes to system.deadLetters, so a
# mistyped topic is observable instead of silent. off = discard it.
send-to-dead-letters-when-no-subscribers = on
}
# Cluster-wide service registry (Receptionist). Subscribers are watched,
# so a stopped one is dropped; the caps bound the ones that are still alive.
# The total counts key/subscriber pairs, not distinct subscribers — one
# subscriber on three keys spends three of it.
receptionist {
gossip-interval = 1s
max-subscribers-per-key = 1000
max-subscriptions-total = 10000
}
}
# Cluster-wide replicated CRDT store (DistributedData). Top-level rather
# than under cluster.* because the module is -- the cluster is a positional
# argument to start(), not a tunable. Both caps bound quorum requests
# (updateAsync + getAsync); 0 disables either. What they buy is a bound on
# the unsettled set itself: every entry holds a promise, a timer and a
# target set until its deadline passes, so refusing past the cap turns what
# would be a timeout storm into immediate, attributable rejections.
#
# max-gossip-bytes bounds one outbound gossip frame instead; a larger store
# is pushed a slice per tick. It is clamped down to remote.max-frame-bytes,
# because a frame past that cap is rejected on its length prefix and costs
# the whole peer association -- heartbeats included. 0 removes the budget
# (the clamp still applies).
distributed-data {
gossip-interval = 1s
max-pending-quorum-requests = 1000
max-quorum-timeout = 30s
max-gossip-bytes = 1M
}
remote {
# Bind address of this node. Cluster.join reads these when its options
# leave host/port unset, so a deployment can move the address into config.
#
# host is the interface to BIND, and the wildcard below is the right
# default for it. It is not an identity: what peers dial is
# advertised-host, which falls back to host only when host is not a
# wildcard, then to CLUSTER_HOST / POD_IP / HOSTNAME, then to 127.0.0.1.
# advertised-host ships no value on purpose -- a key that is always
# present could not express "unset", and unset is what makes that
# fallback chain reachable. Set it wherever the bound interface and the
# dialable address differ, which in Kubernetes is every pod:
#
# advertised-host = ${?POD_IP}
#
# Leaving it at the wildcard in a multi-node deployment is the failure it
# exists to prevent: every node advertises the identical
# system@0.0.0.0:2552, each reads the others' announcements as claims
# about itself, and every member map ends up holding one entry.
tcp {
host = "0.0.0.0"
port = 2552
}
tls {
# Read but NOT honoured: the transport the cluster builds for itself is
# always plaintext. Setting this to true only buys a startup WARN that
# says so — encrypting the wire is issue #941.
enabled = false
}
max-frame-bytes = 16M # per-frame wire cap; lower it on semi-trusted networks
}
http {
backend = "fastify" # fastify | express | hono
# In-flight drain window for unbind() before connections are forced.
# 0 keeps the historical behaviour (force immediately); raise it if you
# want in-flight requests to finish on shutdown.
shutdown-grace-period = 0ms
# Outbound HttpClient defaults — the system's shared client and any
# newClient(...) that leaves a field unset. Leaf names match the
# HttpClientOptions fields (camelCase) and are validated on read (a bad
# value throws OptionsError). A request may still override each of them
# per call; these are the floor the fleet inherits without a code change.
client {
maxResponseBytes = 8M # buffered response body ceiling
defaultTimeoutMs = 30s # deadline for a call that names none
redirect = "follow" # follow | error | manual
maxRedirects = 5 # hops a followed chain may take
}
# Server-side defaults for websocket() routes (per-connection policy).
# Leaf names match the WebsocketRouteOptions fields (camelCase); a route
# may override any of them, and the resolved values are validated
# (OptionsError on a bad value).
websocket {
maxFrameBytes = 1M # inbound frame size cap
onOversizeFrame = "close" # close | drop
onInvalidMessage = "close" # close | drop | hook
maxBufferedBytes = 4M # outbound buffer cap before backpressure
onBackpressure = "drop" # drop | close
# maxConnections is unlimited by default; set a positive integer to cap.
# Inbound frames held while the connection actor starts; past either cap
# the socket is closed with 1013 instead of buffered without bound.
maxPreAttachFrames = 256
maxPreAttachBytes = 4M
# How long an admitted upgrade waits for its connection actor before the
# socket is closed and its maxConnections slot released. Infinity to
# disable (code only — HOCON has no Infinity literal).
acceptTimeoutMs = 10s
}
}
cache {
# Defaults for the built-in in-memory cache (the "default" cache, and any
# cache whose plugin resolves to actor-ts.cache.in-memory). Leaf names
# match the InMemoryCacheOptions fields (camelCase) and are validated on
# read — a bad value throws OptionsError.
in-memory {
maxEntries = 10000 # LRU cap on entries (Infinity/unbounded only settable in code)
cleanupMs = 60000 # background expired-entry sweep interval, ms (0 disables the sweep)
}
# Per-instance overrides live under the cache's own name and win over the
# block above, so one consumer can be sized for its own key space:
#
# actor-ts.cache.idempotency.in-memory.maxEntries = 200000
#
# The name is the application's, so these paths cannot be listed here.
# actor-ts.cache.<name>.plugin selects the backend the same way.
#
# prefixQuotas splits ONE instance between the consumers writing into it,
# so a flood of keys under one prefix evicts only that prefix's entries
# (#607). Each quota is a cap and a reservation; they must sum to at most
# that instance's maxEntries. Off by default — an undivided map is the
# behaviour every release before it had. Quote the prefixes, they contain
# a colon:
#
# actor-ts.cache.shared.in-memory {
# maxEntries = 10000
# prefixQuotas { "rsp:" = 7000, "idem:" = 2000, "rl:" = 1000 }
# }
#
# A per-name table replaces the global one rather than merging with it.
}
persistence {
journal {
plugin = "actor-ts.persistence.journal.in-memory"
}
snapshot-store {
plugin = "actor-ts.persistence.snapshot-store.in-memory"
}
}
sharding {
number-of-shards = 64
rebalance-interval = 2s
hand-off-timeout = 10s
remember-entities = false
passivation-idle = 5m # idle window before an entity passivates; 0 disables the sweep
# shard-passivation-idle -- how long a shard may stand empty before it
# stops as well. Deliberately left unset rather than given a value:
# unset, it follows passivation-idle, which is what "the shard goes
# when its entities do" needs. Set it (0ms disables) to decouple them.
max-entities = 0 # 0 = no per-node cap
}
worker-cluster {
workers = "auto" # "auto" uses navigator.hardwareConcurrency
restart-policy = "on-failure" # always | on-failure | never
}
coordinated-shutdown {
default-phase-timeout = 5s
terminate-actor-system = true
exit-process = false # call process.exit(0) once the pipeline completes
# Framework components register their own teardown in the pipeline: the
# HTTP server unbinds in service-unbind, broker actors close their
# connections in service-stop, a joined cluster leaves in cluster-leave,
# DevTools detaches with the rest of the service layer. Set false to keep
# the phases and register everything yourself -- for an embedder that owns
# the lifecycle of the resources it handed the system.
auto-register-tasks = true
}
}

Zwei Familien von Settings haben oben keinen Eintrag, beide mit Absicht.

Plugin-Subtrees. Journals, Snapshot Stores, Caches und Broker lesen ihre Verbindungs-Settings aus ihrem eigenen Subtree — actor-ts.persistence.journal.postgres, actor-ts.io.broker.kafka, actor-ts.cache.redis und so weiter. Sie sind optionale Peers; Defaults für sie auszuliefern hieße, eine Konfiguration für ein Paket auszuliefern, das du vielleicht gar nicht installiert hast. Jedes ist auf seiner eigenen Seite dokumentiert.

Alles, was kein Wert ist. Der Entity-Actor, Message-Extraktoren, Allocation Strategies, Leases, Transports, Downing Provider, eigene Backends — eine Config-Datei kann keine Klasse und keine Closure ausdrücken, also bleiben die im Code und laufen über den jeweiligen XOptions-Builder.

Drei Schichten, höchste zuerst:

  1. Explizite Optionen im Code — ClusterOptions.create().withPort(2552), ActorSystem.create('billing').
  2. Deine application.conf (oder ein inline config-Objekt).
  3. Diese Datei.

Pro Feld, nicht pro Block: setzt du eine Failure-Detector-Schwelle im Code, kommen die anderen beiden weiterhin aus deiner Config-Datei. Ein undefined im Code bedeutet „nicht gesetzt” und fällt durch — es löscht die darunterliegende Schicht nicht.

Eine ganze Weile lang dokumentierte diese Datei rund fünfundzwanzig Settings, die im Framework nie jemand gelesen hat. Du konntest actor-ts.sharding.passivation-idle = 2 minutes setzen, bekamst keine Passivierung und keine Warnung — der Wert wurde nicht abgelehnt, er wurde nie angeschaut (Issue #653).

Das ist jetzt ein Build-Fehler. Ein Test läuft über jedes Blatt oben und prüft, dass es sowohl von ConfigKeys aus erreichbar ist — der typisierten Liste der Pfade, die das Framework kennt — als auch im Quellcode referenziert wird.

Heute ist kein Key ausgenommen. Die Liste der bewussten Ausnahmen gibt es weiterhin, und jeder Eintrag darauf muss die Issue-Nummer nennen, die ihn wieder entfernt — sie ist nur leer. remote.tls.enabled war der letzte Eintrag, und #591 hat sie geleert, indem der Key einen Leser bekam: er entscheidet jetzt, ob der Node beim Start warnt, dass die Cluster-Verbindung Klartext ist. Beachte, wo das die Latte legt — gelesen, nicht umgesetzt. Die Verbindung zu verschlüsseln ist weiterhin #941; was dieser Test ausschließt, ist ein Key, den niemand auch nur anschaut.