Skip to content

feat: multi-cell support — trigger once, run in every cell - #28

Draft
rogercampos wants to merge 1 commit into
mainfrom
multi-cell-support
Draft

feat: multi-cell support — trigger once, run in every cell#28
rogercampos wants to merge 1 commit into
mainfrom
multi-cell-support

Conversation

@rogercampos

@rogercampos rogercampos commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What

DataDrip can now coordinate the same backfill or script across several independent deployments ("cells"): a developer triggers it once, from any cell's UI, and DataDrip creates and executes an independent copy of the run in every cell — with the UI honestly reflecting that results differ per cell.

Fully opt-in and backwards compatible: with DataDrip.current_cell_id / cell_transport unset (the defaults), behavior is exactly as today. New columns are nullable, the new table starts empty, and the Cell API rejects everything until tokens are configured — hosts can bump the gem and run the migrations with zero behavior change, then enable multi-cell purely via configuration.

How it works

developer's browser ──▶ Coordinator cell (local run + 1 CellDispatch per target cell)
                             │  CellDispatcherJob: POST create   ─▶ Remote cell's Cell API
                             │  show-page poll:    GET status    ─▶ creates/reports its own
                             │  header actions:    POST stop / DELETE   local run
  • Runs are correlated by group_uuid; creation is idempotent per (group_uuid, cell_id) (unique index + find-or-return), so dispatch retries and duplicate deliveries are safe.
  • Execution stays strictly cell-local — each cell's Dripper/ScriptRunner, queue, and data. Only control traffic crosses cells.

Changes

Config & transport (lib/)

  • DataDrip.current_cell_id, cell_ids, cell_transport, cell_api_tokens, cell_ui_url — plain values or callables; DataDrip.multi_cell? gates every new code path.
  • DataDrip::CellTransport::Http — dependency-free HTTPS/JSON transport (configurable url/query/headers, network errors wrapped so callers can tell "cell said no" from "cell unreachable"); any object with the same call contract can replace it.
  • DataDrip::CellClient — thin wrapper that knows the Cell API paths and always sends the intended target_cell_id.

Cell API (new second engine: DataDrip::CellApi::Engine)

  • Separate mountable so hosts can keep the human UI behind their staff gate while the machine-to-machine API sits outside it (bearer token only, constant-time compare, rotation via token array).
  • Target-cell echo check → 421 Misdirected Request if the routing layer delivered to the wrong cell.
  • POST /v1/backfill_runs & /v1/script_runs (idempotent create), GET /v1/groups/:group_uuid (status snapshot; script snapshots carry the full log output — the coordinator never stores it), stop / retry_failed_batches / delete re-applying the existing owner-only and history-preservation rules.

Dispatch

  • GroupCreator saves the local run, freezes one payload per CellDispatch, and enqueues one CellDispatcherJob per cell.
  • Job failure semantics: 4xx (validation, class not deployed yet) → marked failed, waits for the Retry dispatch button; 5xx / network errors → marked failed and re-raised so the host's queue retry policy applies.

UI

  • "Where to run" on both new-run forms: All cells / Only this cell / Choose cells (current cell always included).
  • Run lists: N cells badge on coordinator runs, from <cell> badge on dispatched runs.
  • Show pages: per-cell cards with live status, progress, errors, failed-batch retry, stop, script output, and optional deep links into the other cell's UI; unreachable cells render as such and never block the page. Polling continues while any remote leg is active.
  • Group actions fan out: stop-all, delete (legs that already ran are kept as history in their own cell, and the flash says so).

Identity

  • backfiller_name snapshot now also on script runs; remote cells store the coordinator's backfiller_id verbatim (ids assumed globally unique) without requiring a local record — belongs_to is now optional with a local-existence validation for local-origin runs only.

Schema & generators

  • group_uuid / cell_id / origin / origin_cell_id on both run tables (+ unique (group_uuid, cell_id)), new data_drip_cell_dispatches table.
  • rails generate data_drip:add_multi_cell for existing installs; install templates updated for fresh ones.

Testing

  • 378 examples, 0 failures (91 new): transport (webmock, incl. timeouts/refusals), Cell API request specs (auth, rotation, echo check, idempotency, validation, stop/retry/delete rules), dispatcher job failure matrix, GroupCreator, status fetcher (incl. shared-deadline timeout), model validations, and controller specs for targeting, per-cell cards, updates payload, retry-dispatch, and stop/delete fan-out.
  • Rubocop clean; compiled Tailwind CSS regenerated.

🤖 Generated with Claude Code

DataDrip can now coordinate the same backfill or script across several
independent deployments ("cells"). The cell whose UI created the run is
the coordinator: it saves its own local run, records one CellDispatch
per target cell, and a background job delivers each one to that cell's
new machine-to-machine Cell API, where an independent copy of the run
is created and executes on that cell's own queue against its own data.

Everything is opt-in: with DataDrip.current_cell_id / cell_transport
unset (the defaults), behavior is exactly as before. The new columns
are nullable and the Cell API rejects all requests until tokens are
configured, so hosts can upgrade and migrate with zero change.

- Config: current_cell_id, cell_ids, cell_transport, cell_api_tokens,
  cell_ui_url (values or callables), plus a built-in HTTPS transport
  (DataDrip::CellTransport::Http) and client (DataDrip::CellClient).
- Cell API: separate engine (DataDrip::CellApi::Engine) so hosts can
  mount it outside their staff/admin gate; bearer-token auth with
  constant-time compare, target-cell echo check (421 on misrouting),
  idempotent creation per (group_uuid, cell_id) backed by a unique
  index, group status snapshots, and stop / retry / delete endpoints
  that re-apply the owner-only and history-preservation rules.
- Dispatch: GroupCreator freezes the payload per dispatch;
  CellDispatcherJob marks failures visibly (422 waits for a human via
  "Retry dispatch"; 5xx and network errors also raise so the queue
  retries).
- UI: "Where to run" targeting on the new-run forms, origin/cell badges
  in the lists, per-cell cards on the show pages with live status,
  progress, errors and script output fetched from each cell on every
  poll (unreachable cells render as such and never block), fan-out of
  stop/delete, and per-cell retry of failed batches.
- Identity: runs snapshot backfiller_name (now also on script runs);
  remote cells store the coordinator's backfiller_id verbatim without
  requiring a local record (ids are assumed globally unique).
- Schema: group_uuid/cell_id/origin/origin_cell_id on both run tables,
  data_drip_cell_dispatches, a data_drip:add_multi_cell upgrade
  generator, and updated install templates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant