feat: push delivery of buffered events to external ClickHouse warehouses - #8107
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds scheduled delivery of buffered S3 event objects to ClickHouse warehouses. The change introduces ClickHouse client validation and streaming insertion, S3 pending/archive/failed object handling, bounded delivery runs, connection status updates, Prometheus metrics, recurring tasks, dependency configuration, observability documentation, and unit tests covering delivery and failure scenarios. Estimated code review effort: 4 (Complex) | ~60 minutes Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8107 +/- ##
========================================
Coverage 98.70% 98.71%
========================================
Files 1528 1530 +2
Lines 60804 61195 +391
========================================
+ Hits 60016 60407 +391
Misses 788 788 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@themis-blindfold review |
⚖️ Themis review: 🔴 Hold the mergeThe delivery pipeline itself is well built — gzip passthrough, per-object vs per-connection error split, at-least-once semantics, and a genuinely thorough test suite with moto and a mocked ClickHouse boundary. Two things need fixing before this ships. The
🔴 Blockers
🟠 Majors
🧹 Nits
📝 Walkthrough
🧪 How to verify
Product take: A real capability — customers can finally get their experiment events into their own ClickHouse without building an export themselves, which is the piece that makes the external-warehouse option usable end to end. Bigger than typical work in this area. The failure-reporting gaps matter disproportionately here: a customer whose delivery is silently dropping events has no signal until they go looking for rows. Big picture: 🧭 Assumptions & unverified claims
Impeccable plumbing for the happy path; the unhappy paths are where the events go quietly missing. · reviewed at 1172924 |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
api/experimentation/services.py (2)
930-948: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not classify a budget-limited partial run as “every object rejected”.
If the first attempted object is rejected and the time budget expires before the remaining pending keys are processed,
delivered_count == 0 and rejected_countstill enters this branch. The connection is then marked errored with schema-mismatch guidance even though unattempted objects remain. Only apply this outcome when all pending objects were processed and rejected.
852-879: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftMake the insert-and-archive transition retry-safe.
If ClickHouse accepts the object but the subsequent S3 archive move fails, the task fails before
deliver_warehouse_events()returns while the object remains under the pending prefix. The next run can deliver it again, and duplicate rows are only accounted for downstream rather than prevented at the delivery boundary. Add an idempotency mechanism or per-object delivery state before relying on this retry path, and cover archive-move failure in a regression test.api/tests/unit/experimentation/test_tasks.py (1)
1036-1071: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winAssert the S3 outcome in the all-rejected test.
This test verifies the connection status and log, but not that every rejected key leaves
pending/and lands underfailed/. Add those assertions so the regression test also protects the object lifecycle.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e5e742d3-b55a-4931-8b99-cf8349858173
📒 Files selected for processing (2)
api/experimentation/services.pyapi/tests/unit/experimentation/test_tasks.py
Zaimwa9
left a comment
There was a problem hiding this comment.
Approving with couple of nits and questions
… warehouses Every 10 minutes, stream each environment's gzipped JSONEachRow event objects from the per-organisation S3 events lake into the customer's ClickHouse over HTTPS, byte for byte. Delivered objects move to archive/, objects the warehouse rejects move to failed/, and connection-level failures abort the run and surface on the connection's status.
… detail The task processor's default 60-second timeout retries a task while its original thread keeps running, so any backlog drain would race a second run for the same connection; a 9-minute timeout keeps runs inside the 10-minute tick. Failed runs now write a curated message to the user-facing status_detail instead of raw exception text.
Sibling tasks delegate their business logic to services.py; the delivery task now does too, leaving it as load-and-dispatch. This also gives future warehouse types a single seam. Under the services module's warehouse logger the events become warehouse.delivery.* to match warehouse.connection.*.
- refuse HTTP redirects: a permitted host could otherwise bounce a request, and its event payload, to an address the internal-address guard never saw - bound each run by a time budget so it finishes before the task timeout, which would otherwise abandon a still-running thread and retry alongside it - narrow the failure handler to warehouse errors, so an S3 failure fails the task instead of blaming the customer's connection - treat a run that delivers nothing and rejects everything as a failure: it points at the table's schema, and would otherwise drain into failed/ - drop insert_deduplication_token; it is inert on plain MergeTree and raises on servers that predate it - bind connection and object keys to delivery events
A rejected object means data loss for the customer's warehouse until someone inspects the failed/ prefix, so it warrants an error, not a warning.
b1ef98f to
eacbf2d
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 055b556b-b586-4aa5-af6e-37eddd3ddf98
⛔ Files ignored due to path filters (1)
api/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (9)
api/experimentation/constants.pyapi/experimentation/metrics.pyapi/experimentation/services.pyapi/experimentation/tasks.pyapi/experimentation/warehouse_delivery_service.pyapi/pyproject.tomlapi/tests/unit/experimentation/test_tasks.pyapi/tests/unit/experimentation/test_warehouse_delivery_service.pydocs/docs/deployment-self-hosting/observability/_metrics-catalogue.md
Thanks for submitting a PR! Please check the boxes below:
docs/if required so people know about the feature.Changes
Delivers buffered event objects to customers' own ClickHouse warehouses.
clickhouse-connect'sraw_insert— no decompression or parsing in the app.clickhouse-drivercannot do this: the native protocol requires the client to build columnar blocks, which its maintainer confirms is the only way data can be sent over that protocol.archive/; objects the warehouse rejects (bad data) move tofailed/and the run continues; connection-level failures (auth, network, missing table) abort the run, leave everything in place, and surface on the connection'sstatus/status_detail. A later successful run resets the status.Follow-ups
Customer-facing docs (the required
eventsDDL) are deferred until the schema-ownership question in #8150 is settled, hence the unticked docs box.How did you test this code?
flagsmith_exp.eventsDDL.