Blog
Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster

Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster

July 16, 2026
Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster
Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster

data governance. I built a tiered AI classification system, human review workflow, and the Dagster orchestration that ties it all together in production in nine days.

I work at Group 1001, an integrated, technology-driven financial services and insurance group. If you’ve watched the Indy 500®, you’ve seen one of our brands, Gainbridge℠, on the cars. Other Group 1001 companies include Delaware Life Insurance Company, Gainbridge Life Insurance Company, Clear Spring Life and Annuity Company, Clear Spring Property and Casualty Group, RVI Group, and others, together with $80B+ in assets under management.

We’re in the middle of a large migration: pulling domains that grew up as separate systems on legacy SQL Server (sales and marketing, actuarial, investment, operations, human resources) into one Snowflake account. Each arrived with its own history, schemas, naming conventions, and decades-old decisions about how to model a customer record, an actuarial table, or an HR field. Consolidated, that comes to over a million columns across 62 databases, and no two source systems name the same kind of sensitive field the same way.

As a company that includes subsidiaries that are regulated insurers, classification is not optional. Every column needs a label (SPI for Sensitive Personal Information, PII, or NON_SENSITIVE) and the matching governance tags in Snowflake, because that is what masking and access policies build on. At a million columns, the only workable version of that is automated, account-wide, and able to keep pace as new data lands.

And I was building it solo, alongside other work.

The technical challenges

Before reaching for any tool, here are the problems the system actually had to solve.

Classifying accurately at scale, affordably. Column names carry most of the signal, but not all of it, and the ambiguous cases are exactly the ones you cannot afford to get wrong. A language model can adjudicate them, but running one across a million-plus columns is slow and expensive. The engine needs tiers: cheap deterministic rules for the easy majority, and costly judgment reserved for the ambiguous minority.

A multi-stage pipeline that stays legible for one person. Discovery feeds the rules, the rules fan out to two independent classification engines, and their results merge back together. Maintained by one person who context-switches away for weeks at a time, that graph has to be readable at a glance rather than reconstructed from memory every time.

Two independent triggers, one tagging action. Tags are applied in two ways: by the scheduled run and by a human correcting the engine. These two paths share nothing (different triggers, cadence, data path), yet they have to end in the exact same tagging logic. The moment they diverge into separate code, they drift, and you are debugging two tag-writers instead of one.

Safe corrections under a polling loop. Domain experts need to fix the engine without SQL access. If the system polls a queue of pending corrections every minute and a run takes longer than a minute, ticks overlap. Get it wrong, and you either apply a correction twice or silently drop one that someone is waiting on.

Observability without building an observability stack. Solo, there was no budget to stand up logging and dashboards just to know whether a given run did what it was supposed to.

The solution

The system has two parts: a classification engine, which is mostly a Snowflake story, and the orchestration that runs it, which is where Dagster fits.

The classification engine

The engine works in three tiers.

Tier 1 is an ordered list of regex rules that run against column names, evaluated by priority so that a more specific match wins over a general one. Each rule maps a name pattern to a label, and the rules are deliberately conservative about a trap that catches naive versions of this: a name pattern that looks sensitive on a column whose data type cannot hold that kind of value. A data-type filter handles it, so a TIMESTAMP_NTZ column named LAST_CONTACT_DT is read as a metadata timestamp rather than a contact field, while the same name on a text column is treated as a real match. The list also carries EXCLUDE rules for names that look sensitive but are not, which short-circuit the check. Patterns like EMAIL, PHONE, and NAME resolve here, and between the priority ordering, type filter, and exclusions, the rules cover the majority of columns deterministically and for free.

A handful of rules are ambiguous enough that a name match alone is not trustworthy. Anything matching those gets held back rather than labeled and handed to the next tiers for a second opinion.

Tier 2 calls Snowflake’s native SYSTEM$CLASSIFY on sampled databases and maps its privacy categories onto our labels.

Tier 3 sends only the ambiguous cases to Snowflake Cortex. The prompt is written to be adversarial on purpose because the failure mode of a language model here is over-classification: it sees a name like customer_email_verified and wants to flag it, even though the column holds a boolean, not an email. The prompt pushes the other way, instructing the model to reject anything that merely describes or tracks sensitive data rather than storing it: “REJECT: the column is a flag, date, count, ID, description, audit field, or permission; it tracks the sensitive data but does not store it.” Framing the task as rejection rather than detection is what keeps the false-positive rate low. Because the rules already handle 95%+ of matches, this tier stays small and cheap, which is why the design is affordable at a million columns.

That handles the first challenge. Everything below it is orchestration, and that is what I used Dagster for.

Orchestrating it with Dagster

The pipeline shape. Dagster’s software-defined assets let the dependency graph live in the function signatures themselves:

@dg.asset
def rule_based_classification(discover_columns): ...

@dg.asset
def snowflake_classification(rule_based_classification): ...   # branch A

@dg.asset
def cortex_classification(rule_based_classification): ...      # branch B

@dg.asset
def store_classification_results(
    snowflake_classification, cortex_classification            # fan-in
): ...

Two assets name rule_based_classification as their input, so they fan out. One names both branches, so it fans in and waits for both. Dagster reads that off the signatures, runs the branches concurrently, and routes each asset’s output to its declared consumers. There are no edges to maintain and no glue code. Here is that classification spine rendered in Dagster:

The classification_job asset graph in Dagster: discover_columns feeds rule_based_classification, which fans out to cortex_classification and snowflake_classification, both feeding store_classification_results and then update_classification_run.
The classification_job asset graph in Dagster: discover_columns feeds rule_based_classification, which fans out to cortex_classification and snowflake_classification, both feeding store_classification_results and then update_classification_run.

For the legibility challenge, this is the payoff: the graph is the documentation. Coming back after weeks away, I read the signatures and know the data flow, rather than tracing edges to find where the data came from. (For comparison, an Airflow DAG describes ordering between tasks; the data handoff between them is a separate concern you wire up yourself.)

Two triggers, one tagging action. The convergence challenge was the most interesting one to solve. Two producers, the scheduled classification_job and the human-driven override_job, both need to end in one tagging_job, without either producer knowing about the consumer. Dagster’s run_status_sensor does exactly that. The downstream subscribes to the upstreams’ success; the upstreams know nothing about it:

@dg.run_status_sensor(
    monitored_jobs=[classification_job],
    run_status=dg.DagsterRunStatus.SUCCESS,
    request_job=tagging_job,
)
def tagging_sensor(context):
    return dg.RunRequest()

@dg.run_status_sensor(
    monitored_jobs=[override_job],
    run_status=dg.DagsterRunStatus.SUCCESS,
    request_job=tagging_job,
)
def tagging_after_override_sensor(context):
    return dg.RunRequest()

tagging_job has two upstream triggers and does not know which one fired. The producers have no knowledge that anything watches them. There is one tag-writer, reachable in two ways, with no coupling between the pieces.

It is worth seeing what that costs without this primitive. If classification_job called the tagging code directly, classification would have to know about tagging, and so would the override job, hard-wiring every producer to the consumer. Scheduling tagging on a timer after the others is fragile and breaks the moment a run runs long. A queue or event bus is a whole subsystem for one fan-in. Airflow gets partway there: its TriggerDagRunOperator couples in the wrong direction, since the upstream DAG has to name and fire the downstream, and its Datasets feature (which lets a downstream subscribe to data updates) is the closest analog, but reacts to data changes rather than run outcomes. The run_status_sensor puts the trigger entirely on the consumer side, in a handful of lines, reacting to success specifically.

The jobs list in Dagster showing classification_job, health_check_job, override_job, and tagging_job, each with recent successful runs.
The jobs list in Dagster showing classification_job, health_check_job, override_job, and tagging_job, each with recent successful runs.

Safe corrections under polling. Domain experts submit corrections through a small React UI whose only job is to write intent into a COLUMN_OVERRIDES table. They never touch Snowflake tags directly. A sensor turns that queue into runs, and the idempotency challenge is solved by one choice of run key:

@dg.sensor(job=override_job, minimum_interval_seconds=60)
def override_sensor(context: dg.SensorEvaluationContext):
    rows = conn.cursor().execute("""
        SELECT OVERRIDE_ID FROM COLUMN_OVERRIDES
        WHERE STATUS = 'pending' ORDER BY CREATED_AT ASC
    """).fetchall()

    if not rows:
        yield dg.SkipReason("No pending overrides")
        return

    # Sorted pending IDs as run_key: the same batch never fires twice
    run_key = "_".join(sorted(str(r[0]) for r in rows))
    yield dg.RunRequest(run_key=run_key)

The run_key is the sorted set of pending IDs, and Dagster will not launch two runs with the same key. If the sensor ticks while a batch is still in flight, it computes the identical key, and Dagster drops the duplicate. If a new correction arrives, the pending set changes, the key changes, and it becomes a fresh run that waits for the next tick. Batching, deduplication, and overlap handling all stem from that one decision. Built by hand on a cron loop, the same guarantees mean a locks table, an explicit decision about what happens when two ticks overlap, a record of which corrections have already been applied, and a test suite covering all of it. Here it is the framework’s contract plus one sorted().

Connections and observability. Every asset talks to Snowflake through a Dagster resource rather than constructing its own connection, so there is one environment-aware client defined once, injected by parameter, and swappable for a fake in tests. And each asset reports its own metadata on every run:

context.add_output_metadata({
    "match_count": matched,
    "label_distribution": dg.MetadataValue.json(dist),
})

The result

Now this system runs on a regular cadence, classifying across 62 databases and over a million columns, with a daily health check alongside it. It processes the full account inventory on each run. Corrections from domain experts land in Snowflake within about a minute of being submitted, through the same tagging path the scheduled run uses. Every run reports its own counts as metadata, so the continuous review is a glance rather than an investigation, and the asset graph serves as the living documentation of how the whole thing fits together.

What the nine days bought

The scaffolding was already in place: EKS, the Snowflake account and roles, a base rule-library structure. What I built in that window was the classification engine, the tagging pipeline, the override workflow, and the React UI.

The breakdown is the point. The classification engine, the discovery step, the rule tiers, and the two engine branches were roughly the first two days, and the hard part was the rules themselves, not the orchestration around them. Most of the rest of the week went to the override workflow and the React UI, which was mostly application and SQL work. The orchestration pieces this post is about, the sensors, the run-status chaining, and the run_key idempotency were a small fraction of the time precisely because each was a few dozen lines.

That is the real reason a solo build reached production in nine days. Dagster did not make me a faster UI developer or a better rule author. It collapsed the orchestration, coupling, idempotency, and observability work that would otherwise have sat around the actual problem, so the days went to the problem instead of the plumbing.

The opinions expressed herein are my own and not necessarily those of my employer. This communication is for informational purposes only. It is not intended to provide, and should not be interpreted as, individualized investment, legal, or tax advice.

©2026 Group 1001 IP Properties, LLC | Group 1001. All Rights Reserved

Have feedback or questions? Start a discussion in Slack or Github.

Interested in working with us? View our open roles.

Want more content like this? Follow us on LinkedIn.

Dagster Newsletter

Get updates delivered to your inbox

Latest writings

The latest news, technologies, and resources from our team.

How we use AI to get to yes (and no!) 2x faster at Dagster
Webinar

July 9, 2026

How we use AI to get to yes (and no!) 2x faster at Dagster

Learn how Dagster uses AI to build custom demos that deliver a personalized experience for every customer.

Multi-Tenancy for Modern Data Platforms
Webinar

April 13, 2026

Multi-Tenancy for Modern Data Platforms

Learn the patterns, trade-offs, and production-tested strategies for building multi-tenant data platforms with Dagster.

Deep Dive: Building a Cross-Workspace Control Plane for Databricks
Webinar

March 24, 2026

Deep Dive: Building a Cross-Workspace Control Plane for Databricks

Learn how to build a cross-workspace control plane for Databricks using Dagster — connecting multiple workspaces, dbt, and Fivetran into a single observable asset graph with zero code changes to get started.

Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster
Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster
Blog

July 16, 2026

Classifying a Million Snowflake Columns in 9 Days, Solo, with Dagster

data governance. I built a tiered AI classification system, human review workflow, and the Dagster orchestration that ties it all together in production in nine days.

Prefect is Acquiring Dagster
Prefect is Acquiring Dagster
Blog

July 13, 2026

Prefect is Acquiring Dagster

A letter from Nick Schrock, founder and creator of Dagster

Community Showcase Part 2
Community Showcase Part 2
Blog

June 30, 2026

Community Showcase Part 2

Some of the most interesting Dagster projects come from the community. This post highlights creative community-built applications.

How Magenta Telekom Built the Unsinkable Data Platform
Case study

February 25, 2026

How Magenta Telekom Built the Unsinkable Data Platform

Magenta Telekom rebuilt its data infrastructure from the ground up with Dagster, cutting developer onboarding from months to a single day and eliminating the shadow IT and manual workflows that had long slowed the business down.

Scaling FinTech: How smava achieved zero downtime with Dagster
Case study

November 25, 2025

Scaling FinTech: How smava achieved zero downtime with Dagster

smava achieved zero downtime and automated the generation of over 1,000 dbt models by migrating to Dagster's, eliminating maintenance overhead and reducing developer onboarding from weeks to 15 minutes.

Zero Incidents, Maximum Velocity: How HIVED achieved 99.9% pipeline reliability with Dagster
Case study

November 18, 2025

Zero Incidents, Maximum Velocity: How HIVED achieved 99.9% pipeline reliability with Dagster

UK logistics company HIVED achieved 99.9% pipeline reliability with zero data incidents over three years by replacing cron-based workflows with Dagster's unified orchestration platform.

Modernize Your Data Platform for the Age of AI
Guide

January 15, 2026

Modernize Your Data Platform for the Age of AI

While 75% of enterprises experiment with AI, traditional data platforms are becoming the biggest bottleneck. Learn how to build a unified control plane that enables AI-driven development, reduces pipeline failures, and cuts complexity.

Download the eBook on How to Scale Data Teams
Guide

November 5, 2025

Download the eBook on How to Scale Data Teams

From a solo data practitioner to an enterprise-wide platform, learn how to build systems that scale with clarity, reliability, and confidence.

Download the eBook Primer on How to Build Data Platforms
Guide

February 21, 2025

Download the eBook Primer on How to Build Data Platforms

Learn the fundamental concepts to build a data platform in your organization; covering common design patterns for data ingestion and transformation, data modeling strategies, and data quality tips.

AI Driven Data Engineering
Course

March 19, 2026

AI Driven Data Engineering

Learn how to build Dagster applications faster using AI-driven workflows. You'll use Dagster's AI tools and skills to scaffold pipelines, write quality code, and ship data products with confidence while still learning the fundamentals.

Dagster & ETL
Course

July 11, 2025

Dagster & ETL

Learn how to ingest data to power your assets. You’ll build custom pipelines and see how to use Embedded ETL and Dagster Components to build out your data platform.

Testing with Dagster
Course

April 21, 2025

Testing with Dagster

In this course, learn best practices for testing, including unit tests, mocks, integration tests and applying them to Dagster.