Blog

Dead code is the cheapest migration you will ever do

By Ted Ghose, Co-founder and CTO · Published · 9 minutes read

The short answer

Dead code is warehouse logic nothing uses: procedures no job or view calls, tables nothing reads, jobs that write to tables nobody queries. Find it by building a full dependency graph across code, schedulers and external callers, record the evidence for each object, have each domain owner confirm, then drop it from migration scope (not from the source). It is the cheapest object to migrate, because you do not migrate it.

The fastest way to migrate an object is not to migrate it.

Every warehouse that has run for a decade carries logic nobody uses. A procedure written for a quarterly report that was retired in 2019. Staging tables for a vendor feed switched off when the contract ended. A job that still runs at 3 a.m., still costs compute, and writes to a table that nothing has read in four years.

Nobody deletes these, because nobody is sure, and deleting the wrong one is a career event. So they stay. Then someone plans a migration, counts everything in, and prices the ghosts.

What does dead code cost a migration?

It costs three times.

Once to convert. An unreferenced object costs exactly what a live one costs: the same rules, the same review, the same reconciliation. If it is a cursor-heavy procedure or an SSIS package with a C# Script Task, it may cost more than most of the live estate.

Again to prove. Every object in scope has to be reconciled, and every one that fails has to be investigated. A dead procedure that fails reconciliation blocks the domain it sits in until someone works out that it does not matter. That investigation is often the slowest kind, because nobody remembers what the object was for.

And every month after. On a cloud platform, a job that runs is a job that is billed. Migrating dead jobs is paying to keep running something you already stopped needing.

How do you find dead code in a data warehouse?

The comment in that query is the whole lesson. Catalog dependencies miss calls made through dynamic SQL, from SQL Agent job steps, from SSIS packages, from applications and from other databases. A query like this produces candidates, not conclusions.

-- Procedures that no other object in this database references (first pass only:
-- does not see SQL Agent jobs, dynamic SQL, applications or other databases)
SELECT s.name AS schema_name, p.name AS procedure_name, p.modify_date
FROM sys.procedures AS p
JOIN sys.schemas AS s ON s.schema_id = p.schema_id
WHERE NOT EXISTS (
  SELECT 1 FROM sys.sql_expression_dependencies AS d
  WHERE d.referenced_id = p.object_id
)
ORDER BY p.modify_date;
  1. Build the dependency graph inside the warehouse. For every object, list what it reads and writes and what reads it, across tables, views, procedures, functions and packages.
  2. Add the schedulers. SQL Agent, Control-M, cron, Oozie, Snowflake tasks. A procedure called by nothing in the warehouse may be called by a scheduler outside it. A graph without scheduler definitions is wrong in the direction that hurts.
  3. Add the edges. Linked servers, applications that call procedures directly, BI tools that query tables, file exports a partner picks up. Each external dependency is listed with the object it depends on.
  4. Check activity where the platform records it. Last-modified dates, execution statistics and query history, where available and retained long enough, add evidence. They do not replace the graph; a yearly job will look dead in a 90-day window.
  5. Record the evidence per object. What it is, its complexity band, what does and does not reference it, last activity, and why it looks unused. A list without evidence will not be signed.

What are the traps?

Looks dead, butHow to catch it
Called by name inside dynamic SQLParse string literals and EXEC statements, not only catalog dependencies
Run once a year (year-end close, audit extract)Use a lookback window longer than twelve months, and ask the domain owner
Called by an application outside the warehouseInclude connection and query logs from application service accounts
Run by hand by one analystAsk; record the name and the purpose
Exists for regulatory retentionCheck with risk or compliance before dropping from scope

How do you retire dead code safely?

Dead by reference is not always dead in practice, so the rule is conservative.

The business owner of each domain confirms the list before anything leaves scope. Objects are dropped from the migration, not from the source. DDL is archived. Jobs are disabled before cutover, not deleted, so they can be re-enabled in a minute if someone calls. If an object turns out to be needed, it comes back into scope and is converted then, at the same per-tier price. Nothing is lost, and nothing is migrated on a guess.

Why clean up before the migration rather than after?

Teams often plan to tidy up once they land on the new platform. It is the wrong end, for four reasons.

  • It shrinks the price. Fewer components can move an estate into a lower size tier.
  • It shrinks the proof. Fewer objects to reconcile, fewer failures to investigate.
  • It shortens the parallel run. Dead objects cannot hold a domain hostage.
  • It produces the first honest map. In many estates, the dead-code list is the first document that has ever shown the real size of the warehouse.

Key terms

Dead code (in a warehouse)
objects such as procedures, tables, views, packages or jobs that nothing in or around the warehouse references or uses.
Dependency graph
a map of which objects read, write or call which others, including schedulers and external callers.
Unreferenced object
an object with no inbound references in the dependency graph; a candidate for retirement pending confirmation.
Drop from scope
exclude an object from the migration while leaving it, archived, in the source.

FAQ

Frequently asked questions

Start with catalog dependencies to list procedures nothing references, then add SQL Agent job steps, SSIS packages, dynamic SQL strings, application logs and other databases, because the catalog alone misses those callers.

Drop it from migration scope, not from the source. Archive the DDL, disable jobs before cutover, and keep the option to bring any object back.

It varies widely by estate and history. The only reliable figure is your own, from a full dependency graph rather than a sample.

Yes. Unreferenced objects cost the same to convert and reconcile as live ones, so removing them reduces scope, can lower the price tier and shortens the parallel run.

Because objects are dropped from scope rather than deleted, they can be brought back and converted when found, with jobs re-enabled in the meantime.

Sources

About the author

Ted Ghose is co-founder and CTO of Novuz Inc., the deterministic data warehouse modernization company based in Danville, California. He built the Novuz conversion engine and was previously a Cisco principal engineer.

Next step

The Novuz estate assessment lists every unreferenced object with its evidence and shows the price with them excluded, in three business days, for $3,000, credited against what follows. The list is yours to keep whichever method you choose.

Last reviewed:

Find out what your logic debt is costing you.

Every object counted, lineage, dead code and a fixed price in three business days. $3,000, credited.

Take the logic debt scorecard

Not sure which door? The scorecard takes ten minutes and tells you.

Scorecard