When Can You Remove a Column Still Read by Old Code?
Review running readers, dormant workers, rollback builds and database dependencies before removing a column. A successful migration is not compatibility evidence.
A deployed replacement does not retire every reader
You can remove a column when every consumer still permitted to run is compatible with its absence, the required information has been preserved or deliberately retired, and the removal has an accepted execution and recovery plan. A new web release passing its health check proves none of those conditions for an old worker, a scheduled report or the build selected for rollback.
Consider a fictional booking service replacing a legacy free-text venue label with a validated venue identifier. Its table temporarily contains both venue_label and venue_id. Release A reads the label. Release B reads the identifier, but its initial implementation still writes the label for compatibility. A newer release C no longer names the label at all. The team wants to remove the old column after deploying C.
This is a proposed review example, not an Ampity customer migration or an executed database test. The distinct decision is when to contract a shared schema. Replacing a column's meaning, migrating existing rows and proving performance need their own acceptance evidence. Do not use a clean deployment dashboard as a substitute for any of those checks.
Describe compatibility in both directions
List what each build actually reads and writes. B may no longer need venue_label to render a page, but an INSERT or UPDATE can still name it. That build fails after removal unless its write path is also changed. An ORM can introduce dependencies through generated SQL or cached schema information even when an explicit source-code search finds no reference.
GitLab's migration guidance describes a staged column-removal procedure and explains its Rails schema-cache concern. Our operational recommendation is to inspect the relevant behavior of the actual client library, not copy GitLab's release count or helpers into an unrelated platform. Its procedure is evidence that deployed process behavior matters beyond direct reads, not a universal three-release guarantee.
For this example, schema S1 contains both fields. S2 contains only venue_id. A is incompatible with S2 because it reads the old field. B is also incompatible because it still writes it. C is only a candidate for compatibility: every relevant query and result consumer must be checked. An operation using SELECT * may change its result shape without producing an undefined-column error, while positional decoding can still produce the wrong behavior.
Keep data semantics separate. An identifier that resolves to a current venue record does not necessarily preserve the exact label historically shown on a booking. If audit or customer requirements need that historical value, its owner must approve another representation and verify it before removal. A non-null replacement field is not sufficient evidence that the old meaning is redundant.
Find consumers that can return after the rollout
Inventory web processes, background workers, scheduled jobs, migrations, reporting clients, maintenance scripts and downstream consumers. Include the code associated with queued and replayable work, not only the deployment currently accepting new requests. A dormant monthly report may produce no SQL during the observation window but still be allowed to start next week.
Retirement has two parts: the incompatible process is no longer active, and the platform prevents that incompatible build from returning through an allowed route. An image remaining available is not necessarily a problem if deployment policy excludes it; an old worker scale target that can recreate the image is. Record the actual controls and their owner instead of assuming that zero current replicas is permanent retirement.
The PostgreSQL dependency-tracking reference describes catalog dependencies and an important limit for functions whose bodies are supplied as string literals. Our operational recommendation is to inspect database objects and external consumer contracts separately. The database's dependency graph cannot certify that an application, dynamic statement or reporting integration no longer expects the field.
Inspect views, functions, triggers and extraction paths with the database maintainer. Determine how change streams and downstream schemas represent the old field under the configured system, rather than assuming a column drop updates every consumer automatically. A dependency that is not tracked by the database can still fail when invoked. A consumer list assembled only from one repository should be labeled incomplete if other teams have access.
Make rollback a supported version, not a button
Before the destructive change, A or B may still be the established recovery build. After S2, neither is compatible in this example. An operator pressing the same rollback button would exchange one incident for another. Define a specific compatible recovery build, or an approved forward-repair path, before changing the schema. Verify the replacement recovery route rather than quietly removing the old one from the runbook.
The PostgreSQL table-modification guide explains that removing a column also removes its data. Our operational recommendation is to treat schema recreation and information recovery as separate tasks. Adding back a field with the same name does not reconstruct historical labels, constraints or behavior of every dependent object. An automatic migration down method can therefore be structurally plausible without being a usable business recovery plan.
Keep one decision record for the example. C's read/write compatibility is required but not sufficient: retiring A and B, settling old work, preserving required information and approving the DDL execution plan are additional gates. If the operator cannot identify the recovery target after removal, hold the change. Compatibility approval does not authorize an arbitrary destructive migration.
S1: venue_label and venue_id present
S2: venue_id present; venue_label absent
A: reads venue_label; incompatible with S2
B: reads venue_id, writes venue_label
C: never names venue_label; review all paths
recovery target: C-compatible verified artifact
retirement: incompatible builds cannot restart
information: historical meaning disposition agreed
execution: DDL scope and stop conditions approvedThis is an illustrative compatibility record, not a runnable migration. The five expected decisions below show why evidence gaps should stop removal. They do not claim that the named builds or database versions were exercised.
| Evidence state | Removal decision | Missing or required evidence | | --- | --- | --- | | C web release is healthy; A worker still runs | Hold removal | Worker compatibility and safe retirement of A | | A is stopped but its scheduled job can recreate it | Hold removal | Enforced retirement or compatible job artifact | | B reads the replacement but still writes the old field | Hold removal | Write-path compatibility with the absent column | | C consumers are compatible; rollback still selects A | Hold removal | Verified recovery target compatible with S2 | | Consumers and recovery are compatible; required data is preserved; DDL plan is approved | Eligible for the separately authorized change | Rehearsal results, dependency review and actual execution evidence |
A quick schema change can still wait for a lock
The PostgreSQL ALTER TABLE reference specifies lock requirements and the behavior of dropping dependent objects. Our operational recommendation is to rehearse the exact statement against representative dependencies and transaction conditions. Do not infer a harmless production operation from a short migration in an empty development database.
For the referenced version, ALTER TABLE acquires an ACCESS EXCLUSIVE lock unless a subform documents otherwise. A queued schema operation can therefore matter even when its own work is brief. Agree how long the operator may wait, which observations require stopping, and who can decide what to do about blockers. Do not kill an unrelated transaction merely to make a cleanup task complete.
Review the exact dependency impact, including indexes and table constraints removed with the field. Do not treat RESTRICT as a guarantee that nothing changes: local dependencies can be removed by the documented column-drop behavior. Do not use CASCADE to bypass an unexplained dependency failure. That option can extend the change beyond the field the application team intended to retire.
The review needs privileges, timing, an approved scope, an interruption procedure and a known resulting state. An expired lock wait is not proof that a migration never committed if the client loses certainty about the operation. Inspect the actual schema and migration record before attempting another change. Keep DDL completion, application compatibility and accepted service behavior as separate recorded outcomes.
Test the absence, not just the replacement
Create an isolated test schema with the field genuinely absent, then exercise every permitted build and representative query family. Test inserts, updates, deletes, reports, result decoding and scheduled work. A test that merely sets venue_label to null still leaves the column available to name in SQL. It cannot establish compatibility with S2.
Use deliberately broken controls. Keep an old worker build in the inventory, retain B's legacy write statement, configure a recovery target that names the old field, and supply a function whose dependency is not captured by a simple catalog listing. The expected review should reject each gap. Passing only the C web path is insufficient, even if the migration itself succeeds.
AI-assisted engineering can help enumerate references and draft a compatibility matrix, but it cannot infer unseen consumers from a repository search. Generated ORM changes need the same actual-query and result-contract review as handwritten ones. Do not give an assistant permission to remove a production field because its summary says the field is unused. The accountable maintainer approves evidence and the operator executes within the authorized scope.
This review does not apply unchanged to a stopped single-client system with an explicitly accepted outage. That system may have a simpler sequence, but information disposition and recovery still matter. Self-managed installations that skip releases, independently upgraded integrations and multiple database engines need their own supported-version rules. A staged pattern is a starting point for those decisions, not proof of compatibility everywhere.
Leave an auditable retirement decision
Start with one field and a concrete consumer matrix. Record schema states, artifact identities, query families, allowed restart paths, recovery target and required information. Assign each unknown to an owner. The next useful step is an authorized rehearsal in which the old field is absent and the chosen recovery artifact is exercised, not another statement that the new deployment looks healthy.
Retain the observations and exclusions with the retirement approval. If a consumer is untested, do not label the whole fleet compatible. If recovery requires reconstructing data, record how that reconstruction will be checked and what intervening writes must be reconciled. A backup can support a recovery plan, but its existence alone does not establish a usable restoration within the business's accepted interruption window.
For the broader data transition, use the dual-run reconciliation playbook. For changes controlled by flags, read the feature-flag recovery guide. A platform modernization review can frame the supported-version boundary, while a backend systems review can examine client lifecycle and persistence contracts. Agree the actual scope and acceptance evidence before treating any of these as a production change plan.
This article presents expected review decisions only. No database migration or mixed-version application rehearsal was executed for it. Reading does not require an email address. If you choose to contact Ampity, share the field's current purpose, the consumer you cannot yet retire and the recovery build you need to preserve.