What DevOps actually is
DevOps is often reduced to "a set of tools", but it started as a fix for a people problem: developers were rewarded for shipping change, operations teams were rewarded for stability, and releases became rare, huge and scary. DevOps puts both goals on one team and makes change small, frequent and automated, so that each release is boring.
The work runs as a continuous loop:
- Plan: a small, well-defined change (a ticket, not a quarter-long project).
- Code: on a branch, under version control.
- Build and Test: automatically, on every pull request (Continuous Integration).
- Release and Deploy: through an automated pipeline, environment by environment (Continuous Delivery).
- Operate and Monitor: watch the system in production, alert on problems, and feed what you learn back into Plan.
Underneath sit a few cultural principles: shared ownership ("you build it, you run it"), automation of anything done twice, measuring outcomes rather than effort, and blameless learning from incidents. If you want the mechanics of the Build-to-Deploy stages for SQL, read our companion guide on CI/CD for data pipelines.
What DataOps adds
Copying DevOps practices onto a data team gets you a long way, but it misses one thing. A software system's behaviour is determined by its code. A data pipeline's output is determined by its code and the data flowing through it, and the data changes every day without asking permission:
- The CRM team adds a new region code, and your
CASEstatement silently drops those rows. - A source file arrives half-empty, the load "succeeds", and the dashboard shows a 90% revenue drop.
- An upstream column changes from
INTtoVARCHAR, and joins quietly stop matching.
None of these involve a code change, so CI never sees them. DataOps therefore extends DevOps from "test the code when it changes" to "test the data every time it moves", and treats data like a product with owners, consumers and service levels.
DevOps vs DataOps side by side
| Aspect | DevOps | DataOps |
|---|---|---|
| What ships | Application code and infrastructure | SQL, models, pipelines, reports, and the data they produce |
| What can break production | Mostly code and config changes | Code changes and upstream data changes |
| Testing | Unit, integration, end-to-end tests at build time | Build-time tests plus data tests on every scheduled run |
| Monitoring | Uptime, latency, error rates | Freshness, volume, schema, quality, lineage |
| Promise to users | SLA on availability ("99.9% uptime") | SLA on data ("sales mart refreshed by 7 AM, reconciled to finance") |
| Who's involved | Developers, ops/SRE | Data engineers, analysts, BI developers, data owners in the business |
| Rollback | Redeploy the previous version | Redeploy code and repair or reload affected data |
The six DataOps practices
- Everything as code. SQL, dbt models, orchestration DAGs, warehouse objects, roles and grants, even the warehouse infrastructure itself (Terraform), live in Git and change only through reviewed pull requests.
- Separate, reproducible environments. Dev, Test and Prod, created from code, with production-like (cloned or masked) data so tests mean something.
- CI/CD for every change. Lint, build and test on every PR; promote the same artifact through environments automatically.
- Automated data testing on every run. Uniqueness, not-null, accepted values, referential integrity and reconciliation checks run as part of the pipeline, and a critical failure stops bad data reaching the dashboard.
- Observability, SLAs and alerting. Every run is logged; freshness and volume are compared to expectations; owners are alerted before the business notices.
- Blameless incident reviews. When a number is wrong, write down the timeline, the root cause, and the test or check that will catch it next time. Then add it.
Data observability and SLAs in SQL
Data observability is usually described as five signals: freshness (is it up to date?), volume (did the expected rows arrive?), schema (did columns change?), quality (do values pass the rules?) and lineage (what else is affected?). Commercial tools automate this, but you can get the core of it with a run-log table and SQL your team already understands:
-- Every orchestrated job writes one row per run
CREATE TABLE ops.pipeline_runs (
pipeline VARCHAR,
run_at TIMESTAMP,
status VARCHAR, -- SUCCESS / FAILED
rows_loaded INTEGER
);
-- Freshness SLA: hours since the last SUCCESSFUL run vs the promise
SELECT r.pipeline,
MAX(r.run_at) AS last_success,
DATEDIFF('hour', MAX(r.run_at), CURRENT_TIMESTAMP) AS hours_stale,
s.max_hours_stale,
CASE WHEN DATEDIFF('hour', MAX(r.run_at), CURRENT_TIMESTAMP) > s.max_hours_stale
THEN 'BREACHED' ELSE 'OK' END AS sla_status
FROM ops.pipeline_runs r
JOIN ops.pipeline_sla s ON s.pipeline = r.pipeline
WHERE r.status = 'SUCCESS'
GROUP BY r.pipeline, s.max_hours_stale;
-- Volume anomaly: today's load vs the trailing 7-run average
SELECT pipeline, run_at, rows_loaded,
AVG(rows_loaded) OVER (PARTITION BY pipeline ORDER BY run_at
ROWS BETWEEN 7 PRECEDING AND 1 PRECEDING) AS avg_prev_7
FROM ops.pipeline_runs
WHERE status = 'SUCCESS'
QUALIFY rows_loaded < 0.5 * avg_prev_7; -- flag loads below half of normal
Schedule these checks right after the pipeline runs and route any rows they return to the owning team's alert channel. A "successful" load of 312 rows when 10,000 is normal is exactly the failure that status-only monitoring misses. For the quality signal, reuse the test patterns in our data quality validation guide.
Measuring delivery: DORA metrics in SQL
The DevOps Research and Assessment (DORA) program popularized four key metrics that separate high-performing delivery teams from the rest. Two measure speed, two measure stability, and the point is that good teams improve both together:
- Deployment frequency: how often changes reach production.
- Lead time for changes: time from commit to running in production.
- Change failure rate: share of deployments that cause an incident or need a fix.
- Time to restore service: how long it takes to recover when one does.
If your CI/CD tool writes a row per deployment (most can, via a final pipeline step), all four are a single query:
SELECT
DATE_TRUNC('month', deployed_at) AS month,
COUNT(*) AS deployment_frequency,
ROUND(AVG(DATEDIFF('minute', committed_at, deployed_at)) / 60.0, 1) AS avg_lead_time_hours,
ROUND(100.0 * SUM(CASE WHEN caused_incident THEN 1 ELSE 0 END) / COUNT(*), 1) AS change_failure_rate_pct,
ROUND(AVG(CASE WHEN caused_incident
THEN DATEDIFF('minute', deployed_at, restored_at) END), 0) AS avg_minutes_to_restore
FROM ops.deployments
GROUP BY 1
ORDER BY 1;
Put this on a small Power BI page next to your pipeline health checks and you have a DataOps scorecard. The trend matters more than the absolute numbers: fewer, bigger deployments with a rising failure rate is the pattern DevOps was invented to fix.
The tooling map
You don't need all of these, and tools change faster than practices. Use the map to see which job each tool does:
| Job | Common options |
|---|---|
| Version control & review | GitHub, GitLab, Azure Repos, Bitbucket |
| CI/CD pipelines | GitHub Actions, Azure DevOps Pipelines, GitLab CI, Jenkins |
| Transformation as code | dbt, SQLMesh, stored procedures in Git |
| Database migrations | Flyway, Liquibase, schemachange |
| Orchestration | Airflow, Dagster, Prefect, Azure Data Factory, Snowflake Tasks |
| Data testing | dbt tests, Great Expectations, Soda, plain SQL checks |
| Observability & lineage | Dedicated data observability platforms, dbt docs, warehouse-native lineage, or SQL over your run logs |
| Infrastructure as code | Terraform, Pulumi |
| BI release management | Power BI / Fabric deployment pipelines and Git integration |
A 30-day plan to start
Most of the value of DataOps comes from a few habits, not a platform purchase. For a small team:
- Week 1, Git: move every SQL script, model and pipeline definition into one repository. Turn on branch protection: no direct pushes to
main, one approval required. - Week 2, tests: pick your five most important tables. Add uniqueness, not-null and one reconciliation check to each, and run them after every load.
- Week 3, run log & SLAs: log every pipeline run (status + row count), agree a freshness SLA for your top three dashboards with their owners, and alert when it's breached.
- Week 4, CI: add a pull-request pipeline that lints SQL and builds and tests changed models in a CI schema. Start logging deployments so you can track DORA metrics from day one.
Common mistakes
- Buying a tool before changing the habits: an observability platform can't help a team that still edits production by hand.
- Monitoring job status only: "SUCCESS" with 3% of normal row volume is still an incident.
- Testing in CI but never at run time, missing every failure caused by upstream data rather than code.
- No named owner per dataset, so alerts fire into a shared channel and nobody acts.
- Treating incidents as individual blame, which teaches people to hide problems instead of adding the test that prevents them.
Key takeaways
- DevOps is a culture of small, frequent, automated, reliable change, with CI/CD as its engine.
- DataOps applies DevOps to data and adds continuous testing and monitoring of the data itself, because data breaks without code changes.
- Observability means watching freshness, volume, schema, quality and lineage on every run, against SLAs the business agreed to.
- DORA metrics (deployment frequency, lead time, change failure rate, time to restore) can be computed with one SQL query over a deployment log.
- Start with Git, a handful of data tests, a run log with SLAs, and CI on pull requests. Tools come later.