Skip to main content

Runtime Topology: YU15-eod-risk-extract

The inherited cluster tier is unchanged in shape: three Aeron Cluster members behind a stateless gateway tier. This state adds a producer alongside them, plus the two dependencies it needs; a NATS broker with JetStream and a database holding the published closing prices.

Entrypoints​

EntrypointProcessPurpose
ClusterNodeMainorder-matcher-cluster StatefulSetHosts the clustered service. Serves /health and /ready; readiness now reports the consensus-log position.
ClusterGatewayMaincluster-gateway DeploymentInherited unchanged: REST and FIX ingress, forwarded through the cluster client.
RiskExtractMainrisk-extract DeploymentThe EOD producer. Same image, different main.
nats-server --jetstreamnats DeploymentTrigger stream, cut transport, announcement transport.
mariadbeod-price-db DeploymentThe state's own schema, from the database-init-sql ConfigMap. Read-only from the producer's view; written by trade-processor.
trade-processortrade-processor DeploymentConsumes the ADR-048 trade bridge's /trades and persists Trade + Position; produces and publishes the closing-price version.
price-publisherprice-publisher DeploymentMarket data for equities and the listed option chain. Options are derived from their underlying's tick, never walked independently.
position-serviceposition-service DeploymentMarks every account against the published version and emits eod.pnl.done; the extract's real trigger.

Components​

ComponentRoleState
Sequenced markerNames the consensus sequence the extract is cut atNone; mutates nothing
RiskExtractCutRenders the canonical cut on every memberPure function of replicated state
RiskExtractCutPublisherLeader-side cut β†’ NATS bridgeBounded SPSC queue, daemon thread
RiskExtractCsvRenders the delivered fixturePure function of cut + reference data
RiskExtractMainOrchestrates one EOD batchDurable consumer cursor only
RiskExtractGcsSinkImmutable object deliveryNone
Object sinkHolds fixtures and their cutsWrite-once, keyed by the stamp

Networking​

PathTransportNotes
producer β†’ membersAeron Cluster client, UDP 21800–22200The producer's pod label must appear in the cluster NetworkPolicy ingress allowlist; without it the client silently cannot reach any member.
leader β†’ producerNATS risk.extract.cutOne message per extract, self-counting rows.
producer ↔ NATSTCP 4222Trigger consumption and delivery announcement.
producer β†’ price DBTCP 3306Read-only, one query per extract.
producer β†’ object sinkFilesystem, or HTTPS to storage.googleapis.comWrite-once either way.

Startup / Health Order​

  1. NATS becomes ready; the producer creates the EOD stream if position-service has not already.
  2. The price database becomes ready. The producer does not connect to it until a batch fires.
  3. Cluster members form and elect; readiness gates each member on its consensus-log position relative to its peers.
  4. The gateway connects; /ready turns 200 once its cluster session is live.
  5. The producer connects to NATS (retrying until it is there) and subscribes to its durable trigger. It holds no cluster session while idle; one is opened per batch.

Ordering between the producer and everything else does not matter: it retries NATS, ensures the stream idempotently, and connects to the cluster and the database only when a batch actually fires.

Degraded Behavior​

ConditionBehavior
NATS unavailable at producer startProducer retries indefinitely, logging each attempt. It does not exit; a batch producer that dies on a cold dependency simply is not there when the batch fires.
Cluster unreachable when a batch firesThe marker ack times out, the extract fails, the trigger stays unacked, and JetStream redelivers. Nothing partial is written or announced.
RISK_EXTRACT_NATS_URL unset on membersThe marker still sequences and every member still renders and hashes the cut; nothing is published, so the producer times out waiting for it and reports the missing configuration in the failure.
Cut lost or truncated in flightA lost message is a timeout; a truncated one fails the declared row-count check. Neither can pass as a complete portfolio.
Trading occurs during the buildThe witness marker lands beyond N + 1, the producer refuses to emit, and the trigger is redelivered.
A security has neither a published close nor a trade at NThe whole extract aborts. No zero-filled or omitted row is ever delivered.
An account holds a position but has no counterparty mappingThe whole extract aborts.
Price database unavailableThe extract fails and is redelivered. With RISK_EXTRACT_JDBC_URL deliberately unset, every row instead marks from the cluster's last trade and says so per row.
Object already exists at the keyThe write is refused; on a filesystem by CREATE_NEW, on GCS by if-generation-match: 0. A redelivered trigger cannot replace a fixture already scored against.
A contract's underlying is absent from the feedThe contract is skipped rather than quoted off a fabricated price. It is then unpriced at EOD, and the fail-safe halts any account holding it; loudly, rather than marking it wrong.
An option fill reaches trade-processorPersists with its full OCC symbol. Before the widened columns this failed with Data too long for column 'security' and the fill was lost from SQL while remaining booked in the cluster.
A member restarts during the EOD windowIt replays, re-renders the identical cut for any marker in the replayed range, and rejoins the Service on its consensus-log position; it does not have to wait for trading to resume.
Leader changes between the two markersThe producer's session follows the new leader; if the session is lost the marker ack times out and the extract is retried rather than emitted against a partial view.