05 Stream the analytical copy — instructor notes
Hold the line on the module claiming no measured improvement, spend the initial load on the engine and ordering-key decision, and catch the pipe that was pointed at RDS.
Budget 30 to 45 minutes. It is the easiest module to facilitate and the easiest one to mis-frame: no downtime, no cutover, no runbook, and — deliberately — no measured improvement. The whole module is one console flow plus one genuine design decision, and the design decision is where the time should go.
Open by making the module's argument yourself
Have every participant load the dashboard before they build anything, and then say the uncomfortable half out loud: every panel renders, the data is current, and nothing got better. Managed Postgres bought transactional headroom, which module 03 cited and did not measure. It did not turn a row store into a column store. The eight panels are still scanning tens of millions of rows through a heap, still competing with the checkout path for the same buffers and the same disk.
Somebody will want to re-run the benchmark here. That instinct is correct and one module early; say so explicitly rather than deflecting, because the reason matters — nothing about how the panels execute has changed yet, so there is nothing to measure. A number taken here would be noise with a label on it.
The one thing to forbid: quoting the hand-run ClickHouse query from Step 5 as a comparison against module 01's baseline. It is a single interactive query with no concurrent dashboard sessions and no writer contending, against a baseline that was eight concurrent readers under 200 orders a second. It will look spectacular and it is a different experiment. Module 06 measures it properly, and that is the only form of the number worth carrying out of the room.
What to demo, what to let them do
- Demo the ClickPipes creation flow on your screen, slowly, and stop on the table-definition
step. That screen is the module's actual content and it is the one everybody clicks Next past.
Show the proposed engine and the proposed ordering key per table, and do not advance until the
room has argued about
order_items. - Demo the source selection deliberately. Point at Managed Postgres in the source list and say why it is not RDS: pointing it at RDS works, it is the shorter workshop from module 01's road not taken, and it leaves the write path on the slower engine. A participant who picks RDS here ends the day with a coherent-looking system and an incoherent story.
- Let them create their own service, user and pipe. Three things must match what module 06
expects and they are worth saying as a list: the ClickHouse database is named
shop, the user isworkshopwithSELECTonshop.*, andCH_HOSTis captured as a bare hostname with no scheme and no port. - Let them verify from ClickHouse rather than from the console.
system.partsfor the per-table row counts, then theUPDATE-and-find-it-with-FINALcheck for CDC liveness. A console page reporting green is not evidence that a row arrived.
The engine and ordering key discussion
Run this while the initial load progresses — PROVISIONAL: 12 to 20 minutes for 60,520,000 seeded rows — so the wait costs nothing. Four beats:
- Why
ReplacingMergeTreeat all. ClickHouse has no in-placeUPDATE, so a CDC stream of PostgresUPDATEandDELETEstatements arrives as inserts of new versions and something has to decide which version wins at read time. That something is the engine, keyed on theORDER BYtuple, resolved during background merges. - Why
order_itemsgets it too, despite being append-only. This is the row that surprises people and the one worth arguing. The engine is a property of the destination table; the append-only-ness is a property of today's writer. The firstUPDATEorDELETEanyone issues upstream — a correction, an erasure request, a backfill — has no representation in a plainMergeTree, which then keeps both versions forever and silently double-counts. - The correctness constraint on the ordering key.
ReplacingMergeTreededuplicates within theORDER BYtuple, so the tuple must uniquely identify a source row. If it does not, two versions of the same row land in different tuples, never collapse, and every aggregate over the table double-counts. That is not a tuning consideration; it is the contract. - The tension, which is the real lesson. Every panel filters on
placed_at, and a table ordered byorder_item_idhas a sparse index that is useless for that predicate. Leading withplaced_atis safe only ifplaced_atnever changes for a given row — true fororder_items.placed_atin this schema, false fororders.updated_at, and emphatically false fororders.status.
Then have each participant state, per table, what they chose and why, including the ClickPipes defaults they deliberately kept. "I took the default" is a fine answer recorded as a decision and a poor one recorded as an absence — the difference matters when the table is a customer's and the ordering key cannot be changed later without a rebuild.
Questions worth asking
- "Why is this pipe pointed at Managed Postgres and not at RDS? What would still work if it were pointed at RDS, and what would you no longer have?"
- "
order_itemsis append-only. DefendReplacingMergeTreefor it anyway." Then: "who upstream could issue theUPDATEthat breaks the other choice?" - "Is
FINALa keyword you would put in a dashboard panel?" No — it resolves duplicates on every read. It is the right tool for verifying one row by hand and the wrong one for a panel, and the panels get away without it because they aggregate over windows large enough that a handful of unmerged duplicates cannot move the answer. A workload that needs exactness on mutable rows is a design conversation, not a keyword. - "This pipe created a second replication slot. On which machine, and what are you now responsible for?" On Managed Postgres — and everything module 03 taught about an abandoned slot applies to it, which is why module 07's teardown deletes the pipe before the instance.
- "The ClickHouse counts trail the Postgres counts. When is that fine and when is it a problem?" Fine when the gap is small and stable; a problem when it grows.
Common failures
- The pipe was pointed at RDS. It works, the data arrives, and nothing errors. Catch it by asking to see the source on the pipe's overview page, not by waiting for a symptom. The fix is to delete the pipe and recreate it against Managed Postgres; the slot it created on RDS goes with it, and that slot is exactly the one module 07 checks for.
- The ClickHouse database is
default, notshop. Module 06'ssql/06_pg_clickhouse.sqlpasses the same value asdbnameand as the schema forIMPORT FOREIGN SCHEMA, so a mismatch surfaces there as an import that creates nothing. Fix it here, not there. CH_HOSTcaptured with a scheme or a port.https://…or…:8443in the foreign server'shostoption produces a connection failure in module 06 that reads like a credentials problem. The learner page is explicit: bare hostname.- The
workshopuser was never grantedSELECT. The user mapping is created successfully and every foreign-table query then fails on the ClickHouse side. Have them run oneSELECTasworkshopbefore leaving this module. - The service is in a different region from the pipe's source. It works and adds latency to every CDC batch and every module 06 panel. Region discipline was module 00's job; this is where it surfaces.
- A participant pauses the pipe to "have a look at something". Same disk-fill timer as module 03, on Managed Postgres this time. Resume it, and use the moment: the lesson generalizes across both replication tools, which is the point.
End checkpoint
- A ClickHouse service exists in the same region, with a
shopdatabase and aworkshopuser holdingSELECT. - A Postgres CDC ClickPipe runs from Managed Postgres — verified on the pipe's own page, not assumed — over the four tables.
- The initial load has completed and
system.partsshows all four tables populated. - An
UPDATEmade on Managed Postgres appears in ClickHouse within seconds viaFINAL. - The pipe's slot on Managed Postgres reads
active = twith small, stable lag. - Every participant can state, per table, the engine and ordering key they chose and why.
Nobody should leave this module with a benchmark number. If somebody has one, make sure they know what it is not.
04 Cut over — instructor notes
Run the window as a timed exercise, let the sequence step fail before it is fixed, and stop a participant improvising the runbook they spent module 02 writing.
06 Route the OLAP workload — instructor notes
Read the extension file with the room, insist on all three proofs, manage a pushdown hunt whose answer is version-dependent, and keep the final benchmark comparable.