Skip to content

Commit 8e25112

Browse files
authored
Merge pull request #89 from usherlabs/feat/archive-sequence-column
Add per-run seq column to strategy_data archive tables
2 parents e5cab5b + fa42488 commit 8e25112

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

schema/clickhouse/strategy_data.sql

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,23 @@
1010
-- envelope source = "hb_runtime".
1111
--
1212
-- NO TTL: replay-critical strategy history, retained indefinitely.
13+
--
14+
-- Delivery contract: the producer is AT-MOST-ONCE over a bounded in-memory
15+
-- queue. It drops the oldest row when the queue is full and discards a batch
16+
-- after two failed POST attempts, so rows can be lost silently. To make loss
17+
-- distinguishable from "the event never happened", every row carries `seq`: a
18+
-- monotonic counter starting at 1, scoped to one (controller_id, run_id) and
19+
-- shared across ALL tables in this database. Gap detection is therefore a UNION
20+
-- of these tables filtered by run_id, looking for holes in `seq`; the producer's
21+
-- heartbeat carries dropped_rows/failed_batches as the aggregate counterpart.
22+
-- A hole proves an allocated row was lost. The converse does not hold: paths
23+
-- that skip emission entirely (e.g. blocked Layer12 ticks bypassing the archive
24+
-- tap) never allocate a `seq`, so their absence leaves no hole.
25+
--
26+
-- Adding a column here is a POISON PILL if it ships after the producer. Inserts
27+
-- use JSONEachRow, so a row carrying a column ClickHouse does not have fails the
28+
-- whole per-table batch; the forwarder returns non-2xx and the producer drops it.
29+
-- Deploy the forwarder (which applies this file at startup) BEFORE the producer.
1330

1431
CREATE DATABASE IF NOT EXISTS strategy_data;
1532

@@ -28,6 +45,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.policy_evaluation_events
2845
trading_pair LowCardinality(String),
2946
market_id String,
3047
run_id String,
48+
-- Per-run gap-detection counter; see the delivery contract at the top.
49+
seq UInt64,
3150

3251
policy_epoch String,
3352
fidelity LowCardinality(String),
@@ -61,6 +80,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.strategy_policy_snapshots
6180
trading_pair LowCardinality(String),
6281
market_id String,
6382
run_id String,
83+
-- Per-run gap-detection counter; see the delivery contract at the top.
84+
seq UInt64,
6485

6586
snapshot_reason LowCardinality(String),
6687
policy_epoch String,
@@ -88,6 +109,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.market_identity
88109
trading_pair LowCardinality(String),
89110
market_id String,
90111
run_id String,
112+
-- Per-run gap-detection counter; see the delivery contract at the top.
113+
seq UInt64,
91114

92115
snapshot_reason LowCardinality(String),
93116
source_hash String,
@@ -115,6 +138,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.symbol_mapping
115138
trading_pair LowCardinality(String),
116139
market_id String,
117140
run_id String,
141+
-- Per-run gap-detection counter; see the delivery contract at the top.
142+
seq UInt64,
118143

119144
snapshot_reason LowCardinality(String),
120145
source_hash String,
@@ -140,6 +165,8 @@ CREATE TABLE IF NOT EXISTS strategy_data.inventory_settlement_events
140165
trading_pair LowCardinality(String),
141166
market_id String,
142167
run_id String,
168+
-- Per-run gap-detection counter; see the delivery contract at the top.
169+
seq UInt64,
143170

144171
event_kind LowCardinality(String),
145172
token LowCardinality(String),
@@ -151,3 +178,20 @@ CREATE TABLE IF NOT EXISTS strategy_data.inventory_settlement_events
151178
ENGINE = MergeTree
152179
PARTITION BY toYYYYMM(fromUnixTimestamp64Milli(event_time_ms))
153180
ORDER BY (controller_id, trading_pair, event_time_ms);
181+
182+
-- Backfill the gap-detection counter onto already-created tables. Existing rows
183+
-- keep seq = 0; only runs that start after the producer ships allocate real values.
184+
ALTER TABLE strategy_data.policy_evaluation_events
185+
ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
186+
187+
ALTER TABLE strategy_data.strategy_policy_snapshots
188+
ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
189+
190+
ALTER TABLE strategy_data.market_identity
191+
ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
192+
193+
ALTER TABLE strategy_data.symbol_mapping
194+
ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;
195+
196+
ALTER TABLE strategy_data.inventory_settlement_events
197+
ADD COLUMN IF NOT EXISTS seq UInt64 AFTER run_id;

test/fixtures/archive_forwarder_envelope.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
"exchange": "binance",
1414
"fidelity": "hb_runtime_policy_clock",
1515
"lag_ms": 12,
16-
"source_cursor": "block:12345680:log:3",
1716
"market_id": "arb-usdc-015",
1817
"payload_json": "{\"execution_role\":\"dex_lead\",\"mid_price\":\"1.0234\"}",
1918
"policy_epoch": "42",
2019
"run_id": "run-2026-07-02T00-00-00Z",
2120
"schema_version": "1",
21+
"seq": 1,
2222
"source": "hb_runtime",
23+
"source_cursor": "block:12345680:log:3",
2324
"trading_pair": "ARB-USDC"
2425
},
2526
"table": "strategy_data.policy_evaluation_events"
@@ -39,6 +40,7 @@
3940
"policy_epoch": "42",
4041
"run_id": "run-2026-07-02T00-00-00Z",
4142
"schema_version": "1",
43+
"seq": 2,
4244
"snapshot_reason": "content_change",
4345
"source": "hb_runtime",
4446
"source_hash": "1ed025b4ae41b0865651beb829e16eb62119ecb867c5148858fbe4bfe35e547e",
@@ -62,6 +64,7 @@
6264
"reservation_id": "resv-0001",
6365
"run_id": "run-2026-07-02T00-00-00Z",
6466
"schema_version": "1",
67+
"seq": 3,
6568
"source": "hb_runtime",
6669
"token": "ARB",
6770
"trading_pair": "ARB-USDC",

0 commit comments

Comments
 (0)