Healthcare research, via Nineleaps · 2021 - 2022

Research Study Exports

A Kubernetes job per study. Cloud SQL replica for the read, Python workers for the research tables, dbt on BigQuery for the heavy pass, then a checked package in the recipient bucket.

The problem

Each study had its own transform, and the long SQL ran on the operational Postgres. One delivery could sit on that database for half a day and block the next.

System design

  1. 01

    Study record

    The run is driven by a record, not a code change. It names the cohort, the extra forms, and whether imaging is included. An operator starts the job from that record.

  2. 02

    Per-job read replica

    The worker creates a Cloud SQL read replica, waits until replication lag is zero, and reads only that replica. The primary keeps serving the product. A matching worker deletes the replica when the job ends, including when the job is failed by hand.

  3. 03

    Extract worker

    SQL against the replica writes the source tables out as files. This is the bulk read. It does not build the research schema.

  4. 04

    Transform worker

    A second Python pass builds person, condition, drug, measurement and procedure tables. Mentions from the same visit collapse into one record. Extra forms are merged onto an existing table on a key, or appended as their own table, from the study record. Imaging sheets are written only when the record asks for them.

  5. 05

    Checks and delivery

    Schema validation and row-quality checks run before anything leaves the account. The delivery worker copies the package to the recipient bucket twice: once as a zip, once as the directory tree, and writes the bucket and key back onto the export row.

  6. 06

    Warehouse path

    The long analysis then moved off Postgres into dbt models on BigQuery. The job reads those models instead of grinding the operational database. That is the cut from about half a day to minutes. The replica remains for the extract the warehouse does not hold.

Key decisions

  • One Kubernetes job type for every study. Differences live on the study record.
  • A form is a merge or an append. It does not get its own transform class.
  • Read a replica, never the primary, and delete the replica with the job.
  • Split extract from the research schema. Collapse a visit in the transform, not in the source query.
  • Move the heavy SQL to dbt on BigQuery once the delivery shape is stable.

Outcome

New studies joined the same job. A run that had taken about half a day finished in minutes, and the primary database stopped carrying the export.

PythonKubernetesCloud SQLBigQuerydbt

Architecture flow

  1. Study record
  2. Kubernetes job
  3. Cloud SQL read replica
  4. Extract worker
  5. dbt on BigQuery
  6. Transform worker
  7. Schema and quality checks
  8. Delivery worker
  9. Replica deleted