Retiring Platform Capabilities

A platform accumulates capabilities faster than it removes them. Each capability has consumers, a code path to keep correct, credentials to rotate, a runbook, and a place in somebody’s mental model. Retiring one is how a platform stays small enough to operate, and it is also the moment when the platform team is most likely to cause an outage for a team it has never spoken to. The usual failure is not a missed announcement. It is a consumer nobody knew about, a job that runs once a quarter, or a replacement that does not do quite what the old capability did.

This article gives a procedure for retiring a platform capability: discover who depends on it, support their migration, set a compatibility window, communicate through channels that reach the right people, and verify with evidence before anything is removed. The procedure is built around one idea: removal is the last step of a process whose earlier steps are reversible, and each step has a criterion that must be met before moving on.

The previous article, Measuring Platform Adoption and Developer Friction, established how to define a consumer unit, count eligible and active consumers with an explicit denominator, record workflow events, and avoid conclusions that depend only on the teams that survived. This article reuses those ideas for a harder version of the same question: not whether a capability is used, but whether anything still depends on it. Familiarity with HTTP headers, cron-style scheduling, and basic version control is assumed from general programming knowledge.

Retirement as a sequence of states

Deprecation announces that a capability is no longer recommended for new use and that consumers should prepare to move away from it. It may remain fully operational. Removal is a separate, later event, and it is a breaking change for anyone still depending on the capability. Conflating the two produces two typical failures: deprecating and never removing, which leaves the platform carrying the cost indefinitely, and announcing and removing in the same breath, which gives consumers no time to respond. The Semantic Versioning specification describes the same separation for libraries: functionality marked as deprecated should ship in a minor release with updated documentation before a later major release removes it.

For a platform capability, treat retirement as an ordered set of states, each with an entry condition and a statement of how reversible it is.

StateWhat changesEntry conditionReversible?
AnnouncedCapability is marked deprecated in documentation, catalog, and in-band signals. Behavior is unchanged.Replacement exists and is usable, or the absence of one is statedYes; withdraw the notice
Closed to new useNew consumers cannot adopt it; existing ones continueDependency inventory exists; owners notifiedYes; reopen
Degraded or restrictedOptional: scheduled brownouts, rate limits, or warnings with extra frictionMigration support is available; window has been communicatedYes; remove the restriction
DisabledCapability stops serving but configuration and data are retainedVerification criteria met or exceptions recorded with owners and datesYes if state is retained; restore is tested
RemovedCode, infrastructure, credentials, data (subject to retention), and documentation are deletedHold period after disabling has passed without a restore requestNo, except from backups

Placing a disabled state before deletion converts the most dangerous moment, a consumer discovering the removal in production, into a recoverable event. A disabled capability that is retained can be switched back on within minutes if a critical consumer surfaces. The price is that infrastructure and data stay around for the hold period, so the hold must be bounded and stated. The sections below follow the sequence: discovery feeds the inventory, migration support and the compatibility window shape the time between announcement and restriction, communication reaches the owners, and verification gates the move to disabled and then removed.

Supported lifetimes are a related idea at a different scale. A support lifecycle is a vendor’s schedule for what assistance a product version receives and for how long, and it shows how phases with distinct commitments can precede end of support. Your internal capability does not need a vendor’s phase names, but it benefits from the same discipline: say in advance which commitments apply in each state, so consumers can plan.

Dependency discovery

A system dependency is anything that needs another component’s data, behavior, availability, or decision in order to work. The dependency list for a platform capability includes more than the applications that call it. Scheduled jobs, scripts on a developer’s laptop, dashboards, exports, service accounts, configuration files in other teams’ repositories, and manual steps in a runbook are all dependencies. Hyrum’s Law gives the reason to expect surprises: with enough users of an API, observable behaviors of the system, whatever the contract promises, will be depended on by somebody. Consumers rely on field ordering, error message text, timing, and defaults that you never documented.

Sources and what each can miss

No single source is complete. Combine them and record, for each consumer found, which source revealed it. A consumer found by only one source is a sign that the other sources have gaps.

SourceWhat it revealsBlind spots
Access logs or gateway logs with client identityWho actually calls the capability, how often, and with which versionAnonymous or shared-credential traffic; callers whose period exceeds the log retention or observation window; callers behind a proxy that hides identity
Credential and entitlement inventoryWho holds a key, role, or grant for the capabilityCredentials that exist but are unused (false positives); callers that borrow someone else’s credential
Code and configuration search across repositoriesReferences to endpoints, SDK versions, template names, and config keysRepositories you cannot read; dynamically built URLs; code that is deployed but not in a searched repository
Service catalog and ownership metadataDeclared dependencies and ownersStale or never-filled declarations; undeclared dependencies
Infrastructure-as-code and pipeline definitionsResources provisioned through the capability, pipeline stages that use itResources created by hand; pipelines in other systems
Network flow or traffic dataConnections to the capability’s endpoints regardless of application loggingEncrypted payload contents; shared egress addresses that blur identity
Asking owners and runners of infrequent workMonth-end, quarter-end, annual, and manual dependenciesDepends on people knowing and answering; rotating staff forget

A service catalog helps only if its dependency fields are used and maintained, which is one reason to treat catalog accuracy as an output of the discovery exercise, not an input. Where recorded lineage or catalog data exist, treat a missing edge as unknown, not as proof of no dependency.

The observation-window problem, shown with a simulation

Usage logs only show consumers that called during the period you looked at. Consumers whose schedule is longer than your window are invisible. The simulation below uses an invented population of seven consumers calling a legacy endpoint over a 120-day log: three daily callers, one weekly report, a job that runs every 30 days, a job that runs every 90 days, and an ad hoc script that calls on about 3% of days. The schedules are assumptions chosen to illustrate the effect.

# synthetic consumer population calling a legacy endpoint (illustrative; invented here)
consumers={
 "svc-orders":   ("daily",   0),
 "svc-billing":  ("daily",   0),
 "cron-nightly": ("daily",   0),
 "svc-reports":  ("weekly",  3),
 "job-month-end":("monthly", 10),   # runs once every 30 days
 "job-quarterly":("quarterly",60),  # once every 90 days
 "team-x-script":("adhoc",   0),
}
# ... calls(kind, offset) returns the days on which each consumer calls, over a 120-day log ...
last_day=DAYS-1
for window in (7,28,35,95):
    seen=sorted(c for c,ds in log.items() if any(d>last_day-window for d in ds))
    print(f"observation window {window:3d} d: {len(seen)}/{len(consumers)} consumers seen; missed:",
          sorted(set(consumers)-set(seen)))

Output of the full script, executed with Python 3.12 and a fixed random seed:

observation window   7 d: 5/7 consumers seen; missed: ['job-month-end', 'job-quarterly']
observation window  28 d: 6/7 consumers seen; missed: ['job-quarterly']
observation window  35 d: 6/7 consumers seen; missed: ['job-quarterly']
observation window  95 d: 7/7 consumers seen; missed: []

A 7-day window finds five of seven consumers and misses the month-end job and the quarterly job. In this run the ad hoc script happened to call within the last 7 days, which is luck and not detection; with a different seed it could be missed too. A 28-day window finds the month-end job, because a 30-day period guarantees at least one call within any 30 days but not within 28, and in this run its last call fell inside the window. It still misses the quarterly job, and so does a 35-day window. Only a window longer than the longest period, here 95 days against a 90-day job, finds everyone. The general rule is that the observation window must be longer than the longest period of any legitimate consumer, and you do not know that period in advance. Ask owners of infrequent work what their schedule is, and prefer a window of at least a full business cycle (including quarter-end or year-end where those apply) before declaring the inventory complete.

Two further problems affect any log-based inventory. First, anonymous calls. If a share of traffic arrives without a client identifier, or with a shared credential, you can count the calls but not identify the owner. Treat that as a finding: begin requiring identification, with a warning period, before the retirement window starts, so that the later notices can reach someone. Second, the measurement itself can fail. A log pipeline that drops events for a week produces a week of apparent zero usage. Check the volume of the log source over the observation period before treating silence as evidence.

The dependency register

Record what you find in a register with one row per consumer and these fields: the consumer unit, the owner (a team or person who can act), how it was discovered, what it uses (which operations, fields, or behaviors), its call pattern including the longest known period, its criticality, its migration status, and the date of the last confirmation. The register is the working document for every later step: it drives who gets notified, which migrations need help, which consumers must be confirmed before a restriction, and which remain as exceptions. Dependencies also run in the other direction: the replacement depends on its own schema, accounts, and availability, and a change to any of those after cutover is a change to the new path as well.

Migration support

Telling consumers to migrate is not the same as making migration possible. The platform team’s job is to lower the cost of moving until the remaining cost sits with decisions only the consumer can make. Start by establishing what the replacement actually does.

Map capability, not just endpoints

List each behavior the old capability provided, including behaviors consumers relied on without promise. For each, record whether the replacement provides it, provides it differently, or deliberately does not. A table of operation-to-operation correspondence is not enough; check semantics such as defaults, ordering, error responses, limits, latency, and idempotency. The breaking change entry describes how a change in what a field means can be quiet: consumers that substitute a default for a missing field keep running and compute something different. The same applies to the migration itself. Where the replacement deliberately drops a behavior, say so in the migration guide and give consumers a path, or state that none exists.

Reduce the work for the consumer

  • Migration guide with a worked example. Show the before and after for the most common usage, then list the differences that need judgment. A guide that only lists differences leaves the common case to be rediscovered by every team.
  • Automated changes. Where consumers use an SDK, a template, or a configuration format, a script or codemod that opens a change request in the consumer’s repository converts the mechanical part. The consumer still reviews and merges, since only they can judge whether the behavior is right for their service.
  • Compatibility layers. An adapter or shim that accepts the old interface and calls the new one lets consumers move later. This pattern is described as expand and contract: add the new form, migrate consumers, then remove the old form, designing each intermediate state to be releasable. A shim has costs: it is code the platform maintains, it may not reproduce every behavior, and it creates a new dependency on itself. Give the shim its own end date and treat it as part of the retirement, not a way to avoid one.
  • Parallel running. Where outputs can be compared, run the old and new paths side by side and report differences to the consumer before they cut over.
  • Direct help. Office hours, a named contact, and migration work done by the platform team for consumers who lack capacity. Prioritize help by criticality and by effort: a consumer with a one-line change needs a notice; a consumer with a data migration needs a plan.

Plan the return path

Migrating a consumer is a change like any other and can go wrong. A prepared migration fallback returns the consumer to the source after switchover, and it needs preparation before the switch, since problems can emerge days or weeks later when a business cycle first exercises affected functionality. A backup is not the same as a fallback: it supplies recoverable data and not a complete procedure. For a capability that holds state, decide before migration what happens to writes made on the new path if the consumer must go back. Rollback of a deployment restores a retained version; it does not reconcile data written in the meantime.

Who does the work

There are three common arrangements, and the right one depends on how many consumers there are and how uniform their usage is. If there are few consumers with varied usage, platform engineers working alongside each team produces better migrations and better knowledge of undocumented dependencies, at the cost of platform team time. If there are many consumers with uniform usage, automated changes plus self-service guidance scale better, and the platform team handles exceptions. If consumers are other organizations or external customers, the platform team cannot do the work and must give more time and clearer tooling instead. Whatever the arrangement, record the decision per consumer in the register.

Compatibility windows

A compatibility window is the period during which the old capability keeps working while consumers migrate. It starts when a usable replacement exists and consumers have been told, and it ends at the first restriction or removal. Counting the window from the announcement alone penalizes consumers when the replacement arrives late. The deprecation glossary entry recommends counting the migration window from the date the replacement becomes usable.

What published policies do

Public projects publish their windows, which gives reference points. The Kubernetes deprecation policy says GA API versions may be marked deprecated but must not be removed within a major version of Kubernetes. For beta API versions, deprecation may occur no more than 9 months or 3 minor releases after introduction (whichever is longer), and the version stops being served 9 months or 3 minor releases after deprecation (whichever is longer). Alpha versions may be removed in any release without prior deprecation. It also requires that API objects round-trip between versions without information loss, which is what makes migration possible without data loss. Google’s API improvement proposal on versioning and deprecation recommends 180 days for removing deprecated items in beta channels and says deprecated elements must not graduate from alpha to beta or beta to stable. SemVer asks only that a minor release carrying the deprecation precede the major release that removes it.

These are policies for public interfaces with external consumers, and the numbers are choices of those projects, not universal constants. They show the structure of a good policy: windows differ by maturity level, are stated in advance, and are counted in a unit the consumer can plan around, such as releases or months. An internal window can be shorter than a public one when the consumer set is known and reachable, and should be longer when consumers release rarely or when work is seasonal.

Sizing a window for your own capability

There is no formula that produces the correct number, but the inputs are clear. The window must cover at least the release cadence of the slowest legitimate consumer, since a team that ships quarterly cannot migrate and verify in six weeks. It must span the longest period of any scheduled dependency, so that every periodic job runs at least once on the new path and fails visibly if it will fail. It must allow for a business freeze, such as a year-end period when teams cannot change production. And it must leave time for the platform team to respond to what migration reveals, such as a behavior the replacement lacks. State the window as dates in the announcement, record the reasoning in the register, and publish the extension policy: who may request an extension, how much, and what evidence is needed. A policy without an extension path gets informal extensions, and each informal extension teaches consumers that dates are negotiable.

In-band signals

Documentation alone relies on consumers reading it. HTTP offers machine-readable signals that clients and monitoring can act on. RFC 9745 defines the Deprecation response header, whose value is a date in the structured-field format, written as an at-sign followed by Unix seconds. The RFC says the act of deprecation does not change the behavior of the resource; it informs clients that the resource will be or has been deprecated. RFC 8594 defines the Sunset header carrying an HTTP-date after which the resource is expected to become unresponsive, which clients should treat as a hint, since it is not guaranteed the resource will be available until that time and not after. RFC 9745 requires that the Sunset timestamp not be earlier than the Deprecation timestamp. Each RFC also defines a Link relation (deprecation and sunset, respectively) for pointing to documentation. RFC 8594 is an Informational RFC, not a standard.

The script below serves a deprecated path with these headers from a local server and reads them with a client. The dates are invented, and the client is given a fixed “now” of 2026-06-01 so the days remaining can be computed.

import threading, json, datetime as dt
from http.server import BaseHTTPRequestHandler, HTTPServer
from email.utils import format_datetime, parsedate_to_datetime
import urllib.request

DEPRECATED_AT=dt.datetime(2026,3,1,tzinfo=dt.timezone.utc)
SUNSET_AT=dt.datetime(2026,9,1,tzinfo=dt.timezone.utc)

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith("/v1/"):
            self.send_response(200)
            self.send_header("Deprecation", f"@{int(DEPRECATED_AT.timestamp())}")
            self.send_header("Sunset", format_datetime(SUNSET_AT, usegmt=True))
            self.send_header("Link", '<https://platform.example.test/migrate-v2>; rel="deprecation"; type="text/html"')
            body=b'{"queues": []}'
        else:
            self.send_response(200); body=b'{"queues": [], "version": 2}'
        self.send_header("Content-Type","application/json"); self.send_header("Content-Length",str(len(body)))
        self.end_headers(); self.wfile.write(body)

def check(path, now):   # client side: read the signals, do not change behavior
    r=urllib.request.urlopen(f"http://127.0.0.1:{port}{path}")
    dep=r.headers.get("Deprecation"); sun=r.headers.get("Sunset")
    out={"status":r.status,"deprecation":dep,"sunset":sun,"link":r.headers.get("Link")}
    if dep:
        dts=dt.datetime.fromtimestamp(int(dep[1:]),dt.timezone.utc)   # "@<unix seconds>"
        sts=parsedate_to_datetime(sun) if sun else None
        out["deprecated_now"]=dts<=now
        out["days_to_sunset"]=(sts-now).days if sts else None
        out["sunset_not_before_deprecation"]=(sts>=dts) if sts else None
    return out
{
 "status": 200,
 "deprecation": "@1772323200",
 "sunset": "Tue, 01 Sep 2026 00:00:00 GMT",
 "link": "<https://platform.example.test/migrate-v2>; rel=\"deprecation\"; type=\"text/html\"",
 "deprecated_now": true,
 "days_to_sunset": 92,
 "sunset_not_before_deprecation": true
}
{"status": 200, "deprecation": null, "sunset": null, "link": null}

The first response says the resource has been deprecated since 1 March 2026 (the Unix value 1772323200), will sunset on 1 September 2026, and the client computes 92 days remaining; the second path, the replacement, carries no signal. The behavior of the response is unchanged, which is the point of a deprecation signal and also its limit: no client has to read these headers, and many will not. Use them as one channel among several. Their real value is that they can be consumed by tooling, such as a client library that logs a warning, a gateway that counts requests carrying the signal, or a dashboard of who is still calling deprecated resources. Kubernetes does something related in-band: since v1.19, requests to deprecated API endpoints return a Warning header and add an annotation to audit events, and the API server exposes a gauge metric of deprecated API requests (per its documentation), so operators can see who is still calling.

Communications

Retirement communication succeeds when the person who can act on it reads it in time. The dependency register tells you who that is. A general announcement posted to a platform channel reaches the people who already follow the platform, which tends to be the consumers least likely to be surprised.

Write for the recipient who has to do something

A notice needs to answer the questions a recipient will ask, in this order: what is changing, what do I have to do, by when, how, and what happens if I do nothing. A usable message contains the capability name and the replacement, the dates (announcement, closure to new use, first restriction if any, disable, removal) in absolute form, a link to the migration guide, the specific evidence that this recipient is affected (for example, the calls your logs show from their service account in the last 30 days), and a way to get help or ask for an extension. Specificity is what separates the notice that gets action from the one that gets ignored. Saying “your service account X called operation Y 412 times last week” gives the owner something to verify. The call counts in a real notice would come from your logs; the sentence above is an example of format only.

Channels, sequence, and escalation

  • Direct messages to owners from the register are the primary channel. Send them to the team’s contact address or on-call channel, and ask for an acknowledgment.
  • In-band signals (headers, SDK warnings, CLI messages, portal banners) reach the code and people that actually use the capability, including ones the register missed.
  • Catalog and documentation updates mark the capability deprecated where new consumers look for it, which is also what closes it to new use.
  • Broad announcements serve as the record and reach unknown consumers, but should not be the only channel.
  • Reminders on a schedule tied to the dates: for example at announcement, at the midpoint, a fixed interval before the first restriction, and before removal. The intervals are a choice to make for each retirement; there is no tested optimum.

Track acknowledgments and migration status per consumer. Escalation should be defined before it is needed: a consumer who has not acknowledged by the midpoint gets a second contact through a different path (a manager or the owning organization’s reliability contact), and a consumer who cannot be identified at all is a risk to record and handle in the verification step. Communicate changes of plan as loudly as the original plan; an extension granted to one consumer should be visible as a policy applied consistently, not as a private exception.

The limits of communication

Some consumers will not read notices, owners change, and services outlive their teams. That is why communication is paired with discovery before and verification after. It also argues against long silent periods: a notice sent at announcement and again at removal does not reach a team that reorganized in between. Reminders and in-band signals keep the message current.

Verification and retirement criteria

The decision to disable and then remove a capability should be made against criteria written before the window opens. Without them, the decision defaults to the date on the calendar or to the loudest objection.

Criteria to define in advance

  • Usage. Calls to the old capability, by identified consumer, have fallen to zero or to a list of accepted exceptions. Define the measurement: the log source, its completeness check, the observation window (longer than the longest known period), and how anonymous traffic is handled.
  • Register status. Every consumer in the register is marked migrated, accepted as an exception with an owner and date, or confirmed as no longer needing it. Rows with no confirmation are not ignorable.
  • Replacement health. The replacement has served the migrated consumers through at least one full cycle of their periodic work, so a quarterly job that moved has actually run on it.
  • Restore capability. A tested procedure exists to re-enable the capability after it is disabled, with the configuration and data retained.
  • Retention and compliance. Data retained or deleted in accordance with the applicable retention requirements, which may require keeping some data after the capability is gone.
  • Cleanup list. The set of things that count as removed: code, infrastructure, DNS records, credentials and roles, scheduled jobs, dashboards, alerts, documentation, catalog entries, and the cost line. Credentials left behind are a security issue in addition to clutter.

Staged restriction and what brownouts reveal

Between closed-to-new-use and disabled, a platform can degrade the capability on purpose to make dependencies visible. A brownout is a scheduled, announced, short interruption. A consumer that fails during it, and complains, has been discovered. The method is useful and it has a specific blind spot: it reveals only consumers whose calls happen during the interruption. The simulation below uses an invented schedule for five consumers and three brownout plans; its output is shown after the code.

# consumer: (period_days, offset_day, hours_of_day_when_it_calls)   -- invented schedule
C={"svc-orders":(1,0,[0.5*h for h in range(48)]),   # continuous: every 30 min
   "cron-nightly":(1,0,[2.0]),
   "svc-reports":(7,2,[9.0]),
   "job-month-end":(30,10,[23.0]),
   "job-quarterly":(90,60,[4.0])}
# scenarios: one 1h outage (10:00-11:00) on day 95; the same window daily on days 90-99;
# and three 2h outages (22:00-24:00) on days 94, 96, 98
one 1h brownout on day 95                        -> revealed: ['svc-orders']; not revealed: ['cron-nightly', 'job-month-end', 'job-quarterly', 'svc-reports']
1h daily brownouts days 90-99                    -> revealed: ['svc-orders']; not revealed: ['cron-nightly', 'job-month-end', 'job-quarterly', 'svc-reports']
3 brownouts, 2h, days 94,96,98 at 22:00-24:00    -> revealed: ['svc-orders']; not revealed: ['cron-nightly', 'job-month-end', 'job-quarterly', 'svc-reports']

Only the continuous caller (every 30 minutes) is revealed by any of the plans. The nightly job calls at 02:00, outside the windows. The weekly report calls on one day at 09:00. The month-end job’s next call falls on day 100 (outside the days tested), and the quarterly job is far from the window. The three plans differ in duration and timing, yet none of them touches a periodic consumer, because each consumer’s schedule is a small set of moments. These results follow from the schedule assumed; they illustrate the mechanism, not the hit rate you would see in practice. To use brownouts well, schedule them to cover the times that periodic work is expected, announce them so consumers can observe the failure, and do not treat the absence of complaints as proof of absence. The brownout is a way to flush out additional consumers, not a replacement for the inventory.

Informal “scream tests”, turning something off and waiting to see who complains, deserve caution. They are quick, and they transfer the cost of discovery to the consumer at a moment of the consumer’s choosing, possibly during an incident. If you use a disable step as a final check, make it announced, time-boxed, reversible within minutes, and applied only after the inventory and brownout steps.

When usage is not zero on the date

The hardest moment is the removal date arriving with usage still above zero. Waiting for zero usually means waiting forever. Decide the rule beforehand and find out why usage remains before applying it, since a consumer who never saw the notice, a critical report whose migration is still being validated, and a job nobody owns any more call for different answers. The options are: disable after a final notice and keep the capability recoverable; grant a short, named exception with a new date, an owner, and a reason; or, if the old capability has a security or correctness problem, proceed on time regardless and provide emergency migration support. Record each exception in the register with the approving person. Repeated unannounced delays undermine confidence in the plan, so an extension is announced to everyone, with the reason.

Evidence record and the end state

Keep a short record for each retirement: the final register, the observation window and the completeness check of the log source, the notices sent and acknowledgments received, the brownouts run and what they revealed, the exceptions and their approvals, the disable date and the hold period, and the cleanup checklist with each item confirmed. The record serves as proof that the decision was made on evidence and as a template for the next retirement. After removal, keep watching the capability’s old endpoints and identifiers for a period, since late callers appear as errors that the platform team should triage and route.

Failure modes

  • The rare consumer. A quarterly or annual job fails when the capability is gone. Mitigation: observation windows longer than the longest period, asking owners of infrequent work, and a disabled state before deletion.
  • Silent measurement failure. A log gap reads as zero usage. Mitigation: check source volume and completeness over the window.
  • Anonymous or shared credentials. Callers cannot be notified. Mitigation: require identification before the window starts, and treat unattributed traffic as a blocking finding.
  • Replacement mismatch. The new capability lacks a behavior consumers relied on. Mitigation: map behaviors, run parallel comparisons, and write down deliberate omissions.
  • Permanent shim. The compatibility layer outlives the capability it replaced. Mitigation: give it an end date and an owner at creation.
  • Notice reaches the wrong person. The register owner has left or the contact is a mailbox nobody reads. Mitigation: confirm owners at each step and escalate by a second path.
  • Extension creep. Informal extensions teach consumers that dates move. Mitigation: a published extension policy applied consistently.
  • Data loss at deletion. Required data or evidence is deleted with the capability. Mitigation: retention check in the criteria, and a hold period before deletion.
  • Brownout becomes an incident. A planned interruption hits a consumer during a critical period. Mitigation: announce, avoid freeze periods, and keep the interruption easy to cancel.
  • Platform capacity. Migration support requests exceed the team’s capacity. Mitigation: prioritize by criticality, automate the mechanical part, and publish the window with realistic dates before committing.

A worked sequence for a hypothetical v1 endpoint

The following sequence is a hypothetical illustration, not a record of a real retirement. A platform team plans to retire a v1 provisioning endpoint in favor of v2. The dates are placeholders that show ordering, and the lengths are assumptions that each team would size using the rules above.

  1. Prepare. The replacement is available, its behavior map shows two intentional differences, and a migration guide and a codemod exist. The team builds the dependency register from gateway logs (observed over a window longer than the longest known period), credential inventory, repository search, and conversations with owners of month-end and quarter-end work.
  2. Announce. Direct notices go to each owner in the register with their observed usage. The catalog and documentation mark v1 deprecated. The v1 responses start carrying Deprecation and Sunset headers and a link to the guide. New consumers are blocked from v1.
  3. Support. Office hours run through the window. The team opens codemod-generated change requests for consumers that agree. Anonymous callers are asked to identify themselves; unidentified traffic is tracked as a separate line.
  4. Restrict. Near the end of the window, announced brownouts run at times that cover the schedules in the register, and reminders go to consumers still marked unmigrated. The team reviews remaining usage by consumer weekly.
  5. Decide. At the gate, the team checks the criteria: usage by identified consumers, register status, replacement health through a periodic cycle, restore procedure tested. Remaining consumers are either migrated, granted a named exception, or escalated.
  6. Disable. v1 stops serving with configuration and data retained, and the restore path is ready. The team watches errors and late contacts through the hold period.
  7. Remove. After the hold period passes with no unresolved restore request, the team deletes code, infrastructure, credentials, and documentation per the cleanup list, updates the catalog, and files the evidence record.

At each step the previous step’s evidence decides whether to continue. If discovery turns up a dependency late, the window is extended in a visible way, and nothing is deleted before the evidence supports it. The usage data and event definitions used here come from the same instrumentation as the adoption measures of the previous article; the following article, Instrumenting Services with Metrics, Logs, and Traces, covers how to generate that telemetry reliably, including the identity attributes that make a caller attributable.

References


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.