hamsa software
← All articles
Engineering7 min read

The switch that outlived its recovery plan

A reader/index compatibility matrix shows why a release flag can remain operable after the old route stops being a valid fallback.

A curved span connects two rectangular supports, suggesting a recovery route that depends on compatibility at both ends.

The old reader depends on compatible data

The switch can work perfectly and still leave search broken. Turn off the release flag and requests return to V1, exactly as configured; the new index records, however, now use a shape V1 cannot read. The visible control still exists after its recovery promise has expired. That is why a rollback plan needs to name the stored states its chosen route can handle, and revisit that claim when writers start producing something new.

A feature flag selects behaviour. It does not automatically undo the data, messages or external effects produced while another behaviour was selected. That distinction is easy to miss because the switch is visible and the accumulated state often is not. A release review can demonstrate both branches before rollout, then stop testing the old branch while new writes gradually invalidate the original rollback claim. The flag remains in the dashboard after its meaning has changed.

The invented search transition separates two choices. V1 reads schema one with a top-level title. V2 reads that and schema two, where title lives inside display and tokens have been added. One release flag selects the reader; another decision selects the shape written to the index. This is not a model of a real search engine or flag provider. Its small size lets us see how a single Boolean description could conceal two changes that stop being reversible at different times.

Trace the compatibility before trusting the recovery control

Initially V1 reads schema one and writers produce schema one. During reader rollout, V2 can be selected while writers continue producing schema one. Turning the reader flag off remains compatible in that state. Later, writers begin producing schema-two-only records. V2 can read them; V1 cannot. At that point the same flag transition no longer restores a working old route. It simply directs traffic to code that rejects current index content.

The four local reader/record combinations make the dependency visible. V1 returns the schema-one title and rejects schema two; V2 returns both titles. Flipping the flag over old schema-one records would therefore pass twice, reassuring the team about a condition the new writer is about to remove. Include a schema-two-only record before calling V1 a fallback. The test needs the state the service will accumulate after rollout, rather than only the state available on the day the flag was first introduced.

One response is to delay schema-two-only writes until rollback to V1 is no longer required. Another is to keep a schema-one index maintained alongside the new one. A compatibility reader or a tested transformation may be possible too. Each has a cost. Dual indexes need coherent updates and operational checks; a transformation can lose information; delaying the writer may postpone the very feature that motivated the change. The matrix makes those costs discussable before an incident turns them into improvised repair.

Rebuilding the old index from canonical data may offer recovery, but then the rebuild is part of the procedure, with its own duration and freshness limits. Verify that V1 can still use what the source supplies, rebuild the compatible state and only then select it. The flag does none of that prerequisite work. It is an immediate fallback only while a suitable landing state already exists; otherwise the operation described as switch off has become a small migration of its own.

Flag off selects V1; schema 2 does not become schema 1

Executed local shape-reader model over two invented index records; no production deployment or flag service was tested.

Scroll sideways to see all columns.

Reader / rollout stageSchema 1Schema 2Flag-off recovery
V1 readerReads titleRejectsOnly with schema 1
V2 readerReads titleReads nested titleDepends on selected V1 state
V2 rollout; writer keeps schema 1CompatibleNot yet writtenCompatible
Schema 2-only writes beginOld rows possibleCurrent writesV1 no longer sufficient
Rebuild old index firstSeparate procedureRequires canonical sourceNot an instant flag flip
The release flag routes reads; writer shape is a separate decision. Unavailable configuration defaulting to false can be unsafe once false routes to incompatible V1.

Name the kind of switch being kept

Pete Hodgson's feature-toggle article distinguishes categories including release, experiment, operational and permissioning toggles, with different lifetimes and evaluation needs.[1] That is a useful reason not to delete every old flag on a calendar deadline. A temporary release control and a long-lived entitlement may share an implementation mechanism while serving different promises. The entitlement can still be needed years later; the unfinished release branch can quietly impose maintenance work after its purpose has ended.

Give this release flag an owner and an exit condition that says what must actually become true: accepted rollout evidence, no intended cohort still needing V1, a recorded recovery arrangement and permission to remove compatibility machinery. A hoped-for deletion date cannot supply those facts. A review date is still valuable, especially when the condition remains unmet. It gives the owner a reason to decide whether the plan is late, has changed or has quietly stopped making sense.

An operational kill switch can legitimately remain if it disables expensive optional behaviour without requiring incompatible old state. That is a different design from routing back to V1. A system might stop offering advanced suggestions while preserving basic search through one supported reader, for example. Keeping that control could be valuable even after the release flag is removed. Reusing one flag name for both purposes makes it harder for an operator to know whether turning it off reduces load, changes results or revives obsolete code.

Permissioning deserves its own boundary too. A product entitlement controls the experience available to a user; security-sensitive access still needs enforcement at the actual operation and data boundaries. The existence of a long-lived feature toggle does not by itself establish those controls. In the release inventory, state the operational meaning and the source of the decision clearly enough that a future maintainer does not confuse a rollout convenience with an enduring authorisation policy.

Long-lived switches can serve different purposes

The article distinguishes the search release flag from other legitimate controls.

Scroll sideways to see all columns.

PurposeExpected roleRelevant maintenance
ReleaseMove cohorts between V1 and V2Retirement and current data compatibility.
Operational kill switchDisable costly optional behaviourDefine which supported function remains.
EntitlementControl an enduring product experienceKeep security enforcement at operations and data boundaries.
A shared implementation mechanism does not make the lifetimes, default behaviour or recovery promises interchangeable.

A transition can be correct for one request and wrong for a journey

Decide when the flag is evaluated. A search results page might use V2 while a later export request uses V1 if the decision changes between them. That may be acceptable when both implement a compatible public contract. It may be confusing if the visible result and export use incompatible identifiers or different query semantics. The question is which relationship the product promises across the journey, rather than whether every individual request read a valid Boolean.

Stable cohort assignment can keep a person’s experience consistent during an experiment, but cannot make V1 read a schema-two record. Decide separately where the flag is evaluated, which default applies when configuration is unavailable and how that decision is recorded. A freely supplied client value must not choose a privileged route. These conditions affect the individual request as well as the rollout: the operator needs to know which reader actually handled an export when its result differs from the search page the person just saw.

false routes to V1 here. After schema-two-only writes, an unavailable-configuration default of false therefore selects an incompatible reader. A value once described as conservative can become the riskier choice as state changes around it. Test missing configuration and stale cached values against the data the service supports now. Keeping old code in the repository is not enough to keep the old recovery option alive.

This is also why removing a flag involves more than deleting an entry from a management service. The application may retain a default, a cached value, an old conditional branch, test permutations and runbook instructions. Clean up those references as one reviewed change or a clearly sequenced plan. A deleted server-side setting with an old false default still in code can accidentally resurrect the path the team intended to retire. The removal procedure should state which route remains and how that is verified.

Retire a claim as deliberately as the code

For a tiny reversible presentation change, a static release or a straightforward redeploy may be simpler than introducing a permanent runtime control. A flag earns its upkeep when independent exposure, experiment or operational response is valuable enough to justify the extra states. The search example shows why the upkeep includes current compatibility evidence, not just a place to store a value. Both branches are software somebody remains responsible for until one is actually removed.

A short record can tie the flag’s purpose, owner, audience and evaluation point to compatible data states, default behaviour and its retirement condition. Include the recovery route that will survive removal. In this example, beginning schema-two-only writes is the moment to reconsider V1 as a fallback. The instructions can then say what recovery really entails: change readers, rebuild an index or use another supported route. The operator should not have to discover the missing step after turning off a switch that did exactly what it was told.

Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.

See the Payment Operations Pilot

References & further reading

Primary sources behind this article. Living documentation may change.

  1. Pete Hodgson: feature-toggle categories and carrying cost Published 9 October 2017
Hamsa Jama

Practical engineering. A human perspective.

More about Hamsa Software and the person behind it. Editorial notes.

A few more things
worth a read.

Back to the reading room
Engineering7 min read

What does £240 belong to?

A readable table preserves relationships between values, row labels, column headings and actions across different ways of reading.

Engineering7 min read

When two calculations agree on the wrong answer

A shadow comparison exposes differences. Independent rule examples explain whether those differences are bugs, corrections or changed assumptions.