Ready to put this into action?
Get the complete AI Integration Playbook β Practical AI implementation guide β prompt engineering, workflow automation, and ROI frameworks.
Article 112 Β· Part 11
Release, Monitor, and Roll Back a Small Application
A release is successful when the user's task works and you can recover when it stops working.
By Randy Salars Β· Published
On this page
- Define readiness in terms of behavior
- Use staging to learn before affecting users
- Run a local release-and-recovery exercise
- Monitor the user-visible failure
- Make rollback specific
- Release gradually when the application supports it
- Record recovery evidence without overstating it
- A reusable prompt
- For students: make the failed release part of the lesson
- Practice: detect a successful response with an incorrect result
A release is successful when the user's task works and you can recover when it stops working.
The deployment dashboard is green. The server responds. The team celebrates. Then a teacher opens the catalog and discovers that every title has disappeared.
Nothing crashed. The new version returned a successful response containing the wrong result.
This fictional release failure illustrates a common gap between technical availability and useful behavior. A process can be running while the application fails its central task. A release plan should connect deployment, user-journey checks, monitoring, and recovery into one reviewable process.
Define readiness in terms of behavior
Start with the acceptance criteria from the specification. For a catalog, those might include viewing the expected sample records, adding a valid title, rejecting a duplicate, and preventing cross-class access. Identify which criteria the release could affect.
Next, record the version, configuration, required data shape, and responsible person. If a release requires a database change, document whether the old application can still read and write the new shape. A rollback plan that ignores this question may restore code that no longer works with the data.
Use a short release record:
| Field | Fictional example |
|---|---|
| Candidate | Catalog release R12 |
| Intended change | Improve title sorting |
| Acceptance journey | View, add, reject duplicate, remove |
| Access check | Class A cannot read Class B records |
| Data change | None for this release |
| Recovery target | Previously verified R11 artifact |
| Stop condition | Missing records or access failure |
| Owner | Designated learning-center maintainer |
The owner is someone who can interpret a failure and take the agreed recovery action. An alert sent to an unattended inbox does not satisfy that need.
Use staging to learn before affecting users
Staging is a test environment for release behavior. It should match the production characteristics relevant to the change while using separate test data and credentials.
If a change depends on database constraints, test those constraints. If it depends on authentication, test the relevant roles. A static screenshot cannot verify either one. At the same time, copying an entire production database into a casual test environment can introduce unnecessary exposure.
List the differences that remain. A staging environment with one user cannot establish performance under a thousand concurrent users. A fake email destination can verify message preparation but cannot establish delivery through the real provider.
These differences do not make staging useless. They define what its evidence supports and where additional checks belong.
Run a local release-and-recovery exercise
The following complete Python program serves a tiny catalog response on your machine's loopback interface. It writes two release artifacts into a temporary directory, switches an active pointer, checks the served result, and restores the earlier artifact.
This is a local release-mechanism exercise. It does not deploy a public website, modify a database, or implement a full production release platform. Save it as release_lab.py and run it with Python 3.9 or later.
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import tempfile
import threading
from urllib.request import urlopen
def main():
with tempfile.TemporaryDirectory(prefix="release-lab-") as folder:
root = Path(folder)
releases = {
"v1": {"release": "v1", "titles": ["Field notebook", "Local history"]},
"v2": {"release": "v2", "titles": []},
}
for name, value in releases.items():
(root / f"{name}.json").write_text(json.dumps(value), encoding="utf-8")
def activate(name):
if name not in releases:
raise ValueError("Unknown release")
pending = root / "pending.txt"
pending.write_text(name, encoding="utf-8")
pending.replace(root / "current.txt")
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/catalog":
self.send_error(404)
return
name = (root / "current.txt").read_text(encoding="utf-8")
data = (root / f"{name}.json").read_bytes()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
def log_message(self, format, *args):
pass
activate("v1")
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
url = f"http://127.0.0.1:{server.server_port}/catalog"
def check():
with urlopen(url, timeout=3) as response:
status = response.status
data = json.load(response)
passed = data["titles"] == ["Field notebook", "Local history"]
print(data["release"], "HTTP", status, "journey", passed)
return passed
assert check()
activate("v2")
assert not check()
activate("v1")
assert check()
print("PASS: local release defect detected and previous artifact restored")
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
if __name__ == "__main__":
main()
The exercise ran successfully during preparation. Version v1 returned HTTP 200 and passed the catalog check. Version v2 also returned HTTP 200 but failed the catalog check. Restoring v1 made the same check pass again.
The release files remain unchanged while the active pointer changes. That arrangement makes the recovery target explicit. The temporary directory is removed after the server shuts down; it contains only the example's disposable artifacts.
The pointer replacement is local to one filesystem. The exercise does not prove durability during a power failure or coordinate multiple application servers. Its purpose is to make the relationship between an immutable candidate, activation, observation, and recovery visible.
Monitor the user-visible failure
A status-code monitor would have missed the deliberate defect because both versions returned HTTP 200. The content check detected the missing titles.
For a real application, combine service signals with a small number of meaningful user-journey checks. Track errors, response times, traffic, and resource pressure, but connect alerts to actionable symptoms. Google's Site Reliability Engineering chapter on monitoring explains the distinction between externally observed behavior and internal measurements.
For our catalog, useful signals might include failed saves, denied requests that unexpectedly become allowed, and a synthetic check that can no longer retrieve its known sample record. An alert saying βthe server is using memoryβ is less useful without a threshold and a consequence.
Choose thresholds from the service's needs and observed behavior. In a learning exercise, you can define βany missing fixture record stops the rollout.β Do not present that teaching threshold as a universal production standard.
Make rollback specific
βRoll back if neededβ is an intention. A usable procedure names the known working version, the activation method, the required permission, and the verification that follows.
For the local exercise, recovery means restoring the active pointer to v1 and repeating the same HTTP content check. For a deployed service, it may involve selecting a previous artifact, restoring compatible configuration, and checking caches or background workers.
Code rollback and data recovery are different operations. If the new version deleted records, returning to old code would not recreate them. If it sent messages, the old version would not unsend them. Plan those consequences before allowing the release to produce them.
Database changes often benefit from a staged compatibility approach: introduce a new field while retaining the old one, move readers and writers deliberately, and remove obsolete structures only after the recovery requirements allow it. The exact procedure depends on the database and deployment platform; verify their current documentation before executing it.
Release gradually when the application supports it
A feature flag can limit exposure to a new behavior, but only if the old path still works and the flag itself is controlled. A gradual rollout can reveal problems with a small group before broader activation.
Define what would stop expansion. For example, a synthetic study-planner feature might remain with a test group until its save journey passes and no cross-user access defect appears. If the flag changes only the interface while a background job already rewrites all records, the rollout is not actually limited in the way the screen suggests.
Keep configuration changes traceable too. A release identifier alone cannot explain behavior if someone silently changed a model, permission scope, or external endpoint after deployment.
Record recovery evidence without overstating it
A concise release note can say: βThe candidate returned an empty catalog in the local HTTP exercise. The content check detected the defect. Restoring the prior artifact restored the two expected titles. No persistent user data was involved.β
That is stronger than βrollback worksβ because it identifies the tested scope. It also gives the next maintainer a procedure to repeat.
Once the agreed checks pass, stop adding unrelated gates. The purpose of release discipline is to make useful changes dependable, not to accumulate paperwork disconnected from the application.
A reusable prompt
Prepare a release and recovery plan for [application and change]. Name the candidate, configuration, affected user journeys, data changes, owner, stop conditions, and known working recovery target. Distinguish service availability from task success. Demonstrate recovery in a disposable test setting and report exactly what that demonstration verifies.
For students: make the failed release part of the lesson
A release exercise becomes more educational when the candidate contains one deliberate defect. Students must detect the problem through an acceptance check and restore a known version without improvising edits to the active artifact.
In a team project, rotate the release operator and observer roles. The observer should be able to identify which version is active from the evidence, not from the operator's memory. Follow course rules for AI assistance and identify any generated procedure you revised after running it.
Students building portfolio projects can include the recovery record alongside screenshots. It shows that the project has been considered as a running system with failures, rather than only as a successful demonstration.
Practice: detect a successful response with an incorrect result
Run the complete exercise and explain each of its three output lines. Identify why HTTP 200 is insufficient. Change the synthetic defect to an incorrect title instead of an empty list and predict whether the existing check will catch it.
Completion check: The test environment demonstrates a failing candidate and restoration of the known result, with a clear explanation of whether data recovery was involved.
Stretch: Write a gradual-rollout plan with one explicit stop criterion and a tested way to return the exposed users to the previous behavior.
Get the AI Dispatch
Weekly insights on ai & technology β delivered to your inbox. No spam, unsubscribe any time.
Want to choose specific topics? Customize your interests