Repository navigation
Conversation
Run every statement through clickhouse-format (24.8), the layout SHOW CREATE and production's DDL use, so the per-table commits that follow show only real changes. Comments are kept. No statement changes: formatting the old and new files gives identical output.
Move each statement next to the others of its table, tables by name, ALTERs after their CREATE. Only the order changes.
Add the columns production has: top_{dns,tcp,tls}_rule_id, probe_id and
analysis_rules_version, and its minmax indexes on measurement_start_time,
probe_cc and probe_asn.
Like every table from here on, it's now created as ooni.<table> with IF NOT
EXISTS, as production reports it.
Replicated at /clickhouse/{cluster}/tables/ooni/asnmeta on every replica,
as ooni/data#190 made it (and OOPRIV-181 restored on data1).
No change but the explicit index_granularity production reports.
Production's path has no /{shard} suffix. A replica created with the old path
wouldn't join production's table.
Production doesn't have it. Only the legacy citizenlab updater used it, and ooni/data's updater (ooni/data#190) swaps citizenlab without it.
Production has none of the 17 *_obs_w_sum, *_w_sum, *_s_pos, *_s_neg, last_ts and current_mean columns, and no partitioning. It has current_state, and block_type in the CREATE instead of a later ALTER. float is now spelled Float32, the same type.
Production's table is a plain ReplacingMergeTree(ts), not replicated or partitioned, with *_current_state instead of *_obs_w_sum and *_w_sum. The *_last_change and *_last_ts columns are data1's, the only replica with rows.
No change but the explicit index_granularity production reports.
anomaly, confirmed and msm_failure are LowCardinality(String) (ooni/backend#1279), input has a bloom filter index (ooni/backend#1292), and is_verified is in the CREATE. The ALTERs that added and then dropped nym, zkp_request, age_range and msm_range are gone. data1 and data3's definition: data2 alone also has deduplicate_merge_projection_mode = 'rebuild'.
No change but the explicit index_granularity production reports.
An EmbeddedRocksDB table, so it isn't replicated: production has it on data1 and data2 only, and ON CLUSTER creates it, empty, on every node.
An EmbeddedRocksDB table, not replicated, like fingerprints_dns.
No change but IF NOT EXISTS.
A local MergeTree. data1's definition, the only replica with rows and with failed_fastpath.
Replicated at production's path, msmt_feedback_new.
Replicated at production's path, obs_web_repl.
A Join table, local to each node.
Its inner table keeps production's Keeper path, so a new replica joins the existing one. Production has the view on data1 and data3.
A header with the dump date, which replica's definition is used where they differ, and what's left out: the updaters' *_tmp tables, msmt_feedback_old, session_expunge (unused since ooni/backend#972) and the leftover inner table of the dropped counters_test_list view. Also CREATE DATABASE, which the ooni. names need on a new cluster.
| `input` String, | ||
| `msmt_cnt` UInt64 | ||
| ) | ||
| ENGINE = ReplicatedSummingMergeTree('/clickhouse/{cluster}/tables/ooni/.inner_id.6a72ceec-7e0e-4b65-a3ab-da2809202114/{shard}', '{replica}') |
There was a problem hiding this comment.
This keeper path is needed for a rebuilt replica added to the cluster, but using an explicit target table name has other benefits than just being more readable:
Explicit target tables (… TO ooni.counters_asn_test_list_data) are still worth doing, for other reasons:
- The definition reads plainly, with no generated name.
- The view can be dropped and recreated, for example to change its query, without losing its rows.
- The target can be altered or backfilled directly.
- Backups and restores name the table directly instead of going through the view.
ClickHouse fills in {database} when it creates a table, so in production
the paths are unchanged: /clickhouse/{cluster}/tables/ooni/<table>/{shard}.
A second database on the same cluster, e.g. a scratch or CI one, now gets
paths of its own instead of joining production's replicas.
counters_asn_test_list's path keeps production's name for its hidden table,
.inner_id.6a72ceec-…, with a comment saying why: a replica added later joins
production's table whatever UUID its view gets. With {table} there, it
would start a separate, empty path unless its CREATE copied the view's UUID.
Table names are unqualified, so clickhouse-client --database decides where the file loads, and CREATE DATABASE moves to the header's instructions: it has to exist on every node first, or the nodes without it fail with UNKNOWN_DATABASE while the others create the table. Tested on a two-node 24.8.6.70 cluster with Keeper. The node that receives an ON CLUSTER query qualifies its names with the client's database before passing it on, so every node uses that database, not its own default. Loaded into ooni, this file gives exactly the definitions and Keeper paths of the previous commit's file on both nodes; loaded again into ooni_test on the same cluster, it shares no path with ooni, and its view reads ooni_test's tables.
| `notes` String | ||
| ) | ||
| ORDER BY (ts, type, probe_cc, probe_asn, uid); | ||
| ENGINE = EmbeddedRocksDB |
There was a problem hiding this comment.
We should be very careful about this, since clickhouse does not handle replication for it natively and it requires manually synching the data between all replicates. Alternatively since we now have a cluster setup we could change this to a different engine type.
There was a problem hiding this comment.
yes - we have a backup job that does serialize to/from this type but I want to move to replicated types instead.
While a materialized view exists without its target table, every insert into the view's source table fails: for counters_asn_test_list, fastpath's inserts. Tested on 24.8. It matters once #521's follow-up gives counters_asn_test_list an explicit target table.
Sync
scripts/cluster-migration/schema.sqlwith production's tables. The intent is to use it as a single source of truth for both CI and production deploys. This PR makes it an exact copy of production; the changes that make it reusable come in follow-ups (below).How to review
Commit by commit. The first two only reformat (with
clickhouse-format, the layout production's DDL uses) and sort by table: formatting the old and new files gives identical statements. Each later commit changes, adds or removes one table, and its message says what differs from main and why. Worth a careful look:citizenlab: production's Keeper path has no/{shard}. A replica created from main's path wouldn't join production's table.citizenlab_flip: removed; production doesn't have it.event_detector_cusums: production's is a plainReplacingMergeTree(ts), neither replicated nor partitioned.fastpath: the #1279 and #1292 changes are in theCREATE; the ALTERs that added and then dropped four columns are gone.Where the definitions come from
Dumped from data1, data2 and data3 on 2026-10-20, after asnmeta was made replicated again on data1 (OOPRIV-181). Loaded into a one-node
oonidata_clusteron ClickHouse 24.8.6.70, the file creates all 21 tables and views as production reports them, Keeper paths included. The header says which replica's definition is used where they differ, and what's left out. Main's column comments are kept.Nothing here runs against production: it's a change to the file only.
Follow-ups
scripts/cluster-migration/migrations/, applied once, instead of ALTERs appended to schema.sql, with a CI check that schema.sql equals the baseline plus every migration.clickhouse-client --databasedecides (e.g. for CI), and{database}in every Keeper path, so a scratch database can't join production's replicas. Tested on a two-node 24.8 cluster: the node that receives anON CLUSTERquery qualifies unqualified names with its own database before passing it on, and this schema made generic that way loads intoooniwith exactly the definitions and Keeper paths it has here, and into a second database on the same cluster with no shared path.counters_asn_test_listinstead of its.inner_idtable.