Can Botie Save a Change Without Erasing Somebody Else’s?
A technician saves an old copy after another editor has changed the pump. Keep unrelated fields intact and bind the write to the version that was actually reviewed.
14 sections · 4 questions · 1 coding challenge
The arrow keys move between sections too.
Updating an Object Required More Than Replacing Its Copy
BOTIE
Current MongoDB documentation describes atomic single-document writes and recommends including an expected current value in a write filter when concurrent updates must be coordinated. Our version check is a teaching example of that condition. The Python function exposes the rule but does not implement concurrent database access.
Sources for this history
The Saved Form Was Already Out of Date
Botie reads pump P1 at version four. While the form is open, a technician changes its status and saves version five. Botie now submits the old form with a new note. Replacing the whole document from that old copy can erase the technician’s update.
We need to be precise about what changes and what must still be true when it changes. A small field update limits what is replaced; a version condition protects the assumptions behind the edit.
This is a deterministic state-transition model. A real server must enforce the condition and write atomically; separate client-side checks are insufficient.
Step 1 of 2
The old form is no longer the current record
Both editors started from the same version.
| Event | Stored version | Consequence |
|---|---|---|
| Botie opens P1 | 4 | His edit expects version 4 |
| Technician saves first | 5 | The stored state changes |
| Botie submits expected version 4 | 5 | Reject the stale edit under this policy |
Botie opens P1
- Stored version
- 4
- Consequence
- His edit expects version 4
Technician saves first
- Stored version
- 5
- Consequence
- The stored state changes
Botie submits expected version 4
- Stored version
- 5
- Consequence
- Reject the stale edit under this policy
Step 2 of 2
A successful edit preserves unrelated content
The intended change has a narrow scope.
- 1
Check
Stored version matches expected version - 2
Set
status becomes inspect - 3
Increment
version advances by one - 4
Preserve
asset_id and notes keep their values
A write must check the state it depends on
The condition protects the reviewed state; the narrow update preserves unrelated data.
Only apply the edit to the reviewed version
Compare stored and expected versions, prepare the status change and increment, then return the original document if the comparison fails.
BOTIE
Document after the attempted edit
{
"asset_id": "P1",
"version": 5,
"status": "inspect",
"notes": [
"keep"
]
}Follow the operations
1. Current pump document
{ "asset_id": "P1", "version": 4, "status": "monitor", "notes": [ "keep" ] }2. Read a document path
4
3. Version reviewed by the editor
4
4. Compare exact scalar values
true
5. Requested status
inspect
6. Set one top-level field
{ "asset_id": "P1", "version": 4, "status": "inspect", "notes": [ "keep" ] }7. Set a constant
1
8. Increment one numeric field
{ "asset_id": "P1", "version": 5, "status": "inspect", "notes": [ "keep" ] }9. Choose using a Boolean
{ "asset_id": "P1", "version": 5, "status": "inspect", "notes": [ "keep" ] }10. Result
{ "asset_id": "P1", "version": 5, "status": "inspect", "notes": [ "keep" ] }
The browser runs an explicit model of the mechanism. It does not connect to MongoDB or Redis, simulate their query planners, or establish their concurrency guarantees.
1. Distinguish replacement from changing a field
A replacement document describes the whole new record. If it was built from an old copy, fields changed by someone else can disappear or revert. A field update describes a narrower change.
MongoDB $set can change a named field. A dotted path can target a field inside an embedded object; replacing the whole embedded object can remove its siblings. Decide which operation the user actually requested before choosing its representation.
A short update is easier to review, but its narrow scope alone does not protect every concurrency assumption.
2. Name the assumption behind this edit
Botie’s form was reviewed at version four. The edit is allowed only if the current record still has that version. Store the expected version separately from the proposed new status.
If the versions differ, return a conflict and let the user inspect the newer state. Blindly retrying the same old replacement merely repeats the unsafe assumption. Some independent increments can safely use a different atomic operation, but that is a different contract.
A conflict is information about changed state, not proof that the new status is invalid.
3. Put the condition inside the server write
MongoDB update_one accepts a filter and an update. Include identity and expected version in the filter. Set the status and increment the version in the accepted update. If no document matches, do not report success.
Reading the version in one request and writing by ID in another leaves a race between them. The server-side conditional update closes that gap for this single-document transition. Multi-document invariants need an additional design, often a supported transaction.
Our graph makes the logic visible; connecting the pieces is not a substitute for a server atomicity guarantee.
Check yourself
Questions on Parts 1 to 3
Question 1 of 2
What should the status-edit policy do?
Use the information below.
Stored version is 5. The update filter requires asset P1 and version 4.4. Inspect a real conditional update
The reference uses a local disposable MongoDB collection and PyMongo. It assumes the pump already has a version field. matched_count reports whether the filter matched a document. A zero count needs investigation: a stale version and a missing record can both fail the filter.
Do not let an untrusted client choose arbitrary update operators. This endpoint accepts one status field and constructs its own filter and update.
The worksheet below uses only the Python standard library.
Run in your own environment
Condition a status write on its stored version
This is real PyMongo syntax for one conditional update, not browser simulation code.
Install PyMongo and run a disposable local MongoDB server with an assets document {_id:"P1",version:4,status:"monitor"}. Use your own development access configuration; no credentials belong in the lesson.
from pymongo import MongoClient
collection = MongoClient("mongodb://localhost:27017").perceptron_lab_demo.assets
result = collection.update_one(
{"_id": "P1", "version": 4},
{"$set": {"status": "inspect"}, "$inc": {"version": 1}},
)
print(result.matched_count)A matched write changes status and version together. Repeating the same expected-version request no longer matches.
Condition a status write on its stored version. Documentation checked .
5. Keep the identity and unrelated fields unchanged
Our function copies the existing document, sets status and increments version only when the expected value matches. Its API does not accept replacements for asset_id or arbitrary fields. Nested notes are deep-copied so a returned object cannot accidentally modify the original fixture.
This helps distinguish the requested transition from unrelated side effects. Validation still needs a domain policy for allowed statuses in a production service. The exercise accepts nonblank text so the concurrency mechanism stays separate from a status taxonomy.
The tests should inspect preserved fields as well as the changed field.
A field assignment preserves a sibling
This local operation shows the difference between setting one field and replacing a document.
from copy import deepcopy
original = {"asset_id":"P1","status":"monitor","notes":["keep"]}
changed = deepcopy(original)
changed["status"] = "inspect"
print(changed["notes"])
print(original["status"])['keep']
monitorThe new copy retains the notes, while the original remains unchanged. No concurrent write is simulated here.
6. Separate a conflict from an unknown delivery outcome
A confirmed zero-match response tells us the conditional write did not match. A network timeout may leave the client unsure whether the server applied it. Retrying a version-conditioned update can prevent applying that same version transition twice, but the application may still need to read back or use an operation identity to explain the outcome.
Side effects outside the document, such as notifying a technician, need their own retry policy. Incrementing a version does not make a separate message send atomic with the write.
Keep the outcome of the stored transition separate from follow-up effects.
7. Replay the edit against a changed version
The first workshop edit expects and finds version four. The changed fixture stores version five but retains the expected version four. A construction that always sets status will fail this case.
Inspect the notes and identity after both attempts. Then try the accepted result again with the old expected version. The unchanged second result demonstrates the intended rejection rule, while the browser remains a single-threaded model.
Use a real database concurrency test before claiming that a deployed endpoint enforces this rule under competing writers.
Check yourself
Questions on Parts 1 to 7
Question 1 of 2
Which explanation is justified?
Use the information below.
A conditional update matches zero documents. The same filter includes both ID and expected version.Try it with Botie
Build with Botie
Write and run Python