WEB / WEB-18.03
NoSQL and key-value stores
Distinguish storage models in the NoSQL family; read a public note key/value, version, and lifetime contract in a selected local adapter.
1. Core · Mechanism
Read every scenario without motion
Value found
- Public read intent
The application prepares a read for public:note:tea. This example’s store is authoritative only for its own memory state; the key prefix establishes neither identity nor permission.
- Application
- Public note read requested
- Local adapter
- No result
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Key mapping
The adapter uses the selected public key; its prefix implements neither authorization nor data validation.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Lifetime decision
Selected logical clock=49 is below expiresAt=50. The store finds the value; that lookup outcome is not yet the application’s JSON-shape decision.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Clock=49 < expiry=50; value found
- Result decision
- No application decision
- Lookup result
The adapter returns found with a JSON string for public:note:tea containing id=tea, title=Tea note and version=1. Parsing and shape validation are the next local step.
- Application
- Public note read requested
- Local adapter
- Found JSON string
- Selected KV owner
- Clock=49 < expiry=50; value found
- Result decision
- No application decision
- Result processing
The application parses the JSON string, validates the id/title/version shape and creates an independent copy. Changing that copy’s fields sends no store-write operation.
- Application
- Public note read requested
- Local adapter
- Found JSON string
- Selected KV owner
- Clock=49 < expiry=50; value found
- Result decision
- Parsed copy; shape valid
- Application choice
The application uses its selected result; persistence, replica visibility, and permissions do not follow from this lookup.
- Application
- Tea note; version=1
- Local adapter
- Found JSON string
- Selected KV owner
- Clock=49 < expiry=50; value found
- Result decision
- Parsed copy; shape valid
Lifetime ended
- Public read intent
The application prepares a read for public:note:tea. This example’s store is authoritative only for its own memory state; the key prefix establishes neither identity nor permission.
- Application
- Public note read requested
- Local adapter
- No result
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Key mapping
The adapter uses the selected public key; its prefix implements neither authorization nor data validation.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Lifetime decision
Logical clock=50 reaches expiresAt=50; equality produces missing in this profile. The store retains no value for lookup, but the application has not received the missing result yet.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Clock=50 reaches expiry=50; missing
- Result decision
- No application decision
- Lookup result
The adapter returns missing with fixtureReason=expired in this teaching record. A general product API is not assumed to know this cause for every miss; there is no JSON value.
- Application
- Public note read requested
- Local adapter
- Missing; fixture reason expired
- Selected KV owner
- Clock=50 reaches expiry=50; missing
- Result decision
- No application decision
- Result processing
Missing provides no note to parse and this branch creates no default note. A cache fallback read from another authoritative owner requires a separate contract and is not performed in this scene.
- Application
- Public note read requested
- Local adapter
- Missing; fixture reason expired
- Selected KV owner
- Clock=50 reaches expiry=50; missing
- Result decision
- No parsed note; no default
- Application choice
The application uses its selected result; persistence, replica visibility, and permissions do not follow from this lookup.
- Application
- Missing received; no note; no fallback shown
- Local adapter
- Missing; fixture reason expired
- Selected KV owner
- Clock=50 reaches expiry=50; missing
- Result decision
- No parsed note; no default
Stale revision
- Conditional update ready
The application carries expectedVersion=1 from an earlier read and requests a conditional update to public:note:tea. That old expectation does not establish the current store version; no update is accepted yet.
- Application
- Conditional update ready; expectedVersion=1
- Local adapter
- No result
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Key mapping
The adapter uses the selected public key; its prefix implements neither authorization nor data validation.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- New revision
An intervening update has produced id=tea, title=Coffee note and version=2 in the store. The application retains expectedVersion=1; its earlier read does not make that current owner state known.
- Application
- Expected version 1
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Current Coffee note; version=2
- Result decision
- No application decision
- Condition check
In one serialized decision, the owner compares expectedVersion=1 with currentVersion=2 and finds a mismatch. This local conditional-update decision is not a distributed lock or collection transaction.
- Application
- Expected version 1
- Local adapter
- Compare expected=1/current=2
- Selected KV owner
- Current Coffee note; version=2
- Result decision
- No application decision
- Reject stale
The mismatch withholds the update, preserving afterValue as Coffee note/version=2. A separate read can compare that final value; rejection alone does not mean an entire transaction was rolled back.
- Application
- Expected version 1
- Local adapter
- Compare expected=1/current=2
- Selected KV owner
- Coffee note; version=2 preserved
- Result decision
- No application decision
- Conflict result
The application sees that its stale decision was not applied; this does not test a product whole-transaction rollback.
- Application
- Update denied; expectedVersion=1
- Local adapter
- Compare expected=1/current=2
- Selected KV owner
- Coffee note; version=2 preserved
- Result decision
- Conflict; no overwrite
Store unavailable
- Public read intent
The application prepares a read for public:note:tea. This example’s store is authoritative only for its own memory state; the key prefix establishes neither identity nor permission.
- Application
- Public note read requested
- Local adapter
- No result
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Key mapping
The adapter uses the selected public key; its prefix implements neither authorization nor data validation.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Stored JSON; selected lifetime
- Result decision
- No application decision
- Unavailable
The selected fixture injects availability=false; this does not establish that the note is absent or expired.
- Application
- Public note read requested
- Local adapter
- Key selected: public:note:tea
- Selected KV owner
- Injected availability=false
- Result decision
- No application decision
- Separate error
The adapter returns an unavailable error; this contract contains no fallback collapsing it into missing.
- Application
- Public note read requested
- Local adapter
- Error; no value/miss
- Selected KV owner
- Injected availability=false
- Result decision
- No application decision
- Stop decision
The application creates neither authoritative empty state nor a default note; only the bounded failure outcome is shown.
- Application
- Public note read requested
- Local adapter
- Error; no value/miss
- Selected KV owner
- Injected availability=false
- Result decision
- Stop decision; no default
ACTORS · STATE · DATA FLOW
NoSQL is not one database behavior
NoSQL groups several data models outside relational tables; documents and key-value storage are our two examples. One product query language, validation, transactions, and consistency do not automatically apply to another. MongoDB collections can contain documents with different fields, yet the application still needs meaningful field contracts. MongoDB supports schema validation; embedding or referencing depends on access patterns and relationship ownership.
In a key-value model the application selects a key and the store returns its value. public:note:tea is a lookup name for a public synthetic note; its prefix does not implement authentication or authorization. Our local adapter stores a JSON string, which the application parses and validates as id/title/version. Receiving a string does not prove that its JSON has the expected shape. Redis GET distinguishes an existing string, a missing key, and a wrong-type error; this product contract is not universal NoSQL behavior.
Choose a model by identifying fields read together, changes that must be atomic together, and the authoritative owner. MongoDB single-document writes are atomic; an operation changing many documents is not automatically atomic as a whole. Multi-document transactions also exist. Redis processes an EXEC group serially, but does not provide SQL-style rollback for command errors. Our native adapter executes neither product: it illustrates serialized access within one selected process.
Lifetime, revision, and availability differ
The fixture assigns expiresAt=50 in logical clock units. At 49 the value exists; at 50 it becomes missing at the expiry boundary. This is not a wall-clock timer test. Eviction removes keys before expiry according to memory pressure and policy. Redis maxmemory policies can evict keys, while noeviction can instead reject new writes; not every policy deletes data. A key might never have existed either. A plain missing result cannot establish its cause.
TTL is a store lifecycle contract, not an arbitrary document field. Ordinary Redis SET replacement clears an existing TTL; retention needs an explicit option such as KEEPTTL. EXPIRE establishes a timeout. Replacement and explicit eviction are separate local fixture events. GET success is not a persistence guarantee: Redis RDB, AOF, and disabled persistence have different loss boundaries. Our memory-only adapter stores nothing beyond its process and claims neither restart survival nor crash durability.
An application that read version 1 may encounter owner version 2 before updating. The selected adapter compares expected and current revisions in one serialized decision and changes nothing on mismatch. This local contract does not test distributed atomicity; Redis WATCH/EXEC has its own optimistic condition semantics. An unavailable store raises a separate error: converting it into a missing key and empty authoritative state changes application decisions incorrectly. Replica visibility, consistency levels, and retries require a separately selected product and topology.
2. Red · Trust & Impact
Read every scenario without motion
Version stale
- The KV owner and encoding were selected
public:note:tea is the key for a public note stored as a JSON string. The prefix is data mapping, not read or write permission for an actor.
- KV operation context
- Key: public:note:tea
- Owner/version gate
- Version check pending
- Application state boundary
- State unchanged
- Expected and current versions were separated
The stale record has expectedVersion=1 and currentVersion=2. An old caller expectation does not make the current Coffee note in the store non-authoritative.
- KV operation context
- Expected=1
- Owner/version gate
- Current=2
- Application state boundary
- Decision pending
- The serialized-owner gate rejected the update
accepted=false is a selected single-owner decision. It compares the current version and withholds the stale update without claiming every separate read/write is atomic.
- KV operation context
- Version mismatch known
- Owner/version gate
- Accepted=false
- Application state boundary
- Update withheld
- The after-value was preserved
afterValue retains id=tea, title=Coffee note and version=2. The negative fixture shows preservation of current data, not only a denial label.
- KV operation context
- After: Coffee note
- Owner/version gate
- After version=2
- Application state boundary
- No stale overwrite
- Product atomicity was not expanded incorrectly
MongoDB single-document atomicity does not automatically make a whole updateMany atomic. Redis EXEC is not the same contract as SQL rollback; operation scope remains explicit.
- KV operation context
- Single-owner scope
- Owner/version gate
- Product scopes differ
- Application state boundary
- No collection-wide claim
- No persistence outcome was added
selectedModel uses persistence=none. State preservation is known for the memory adapter, not replica visibility, disk durability or crash recovery.
- KV operation context
- Memory state preserved
- Owner/version gate
- Persistence=none
- Application state boundary
- Durability unestablished
Unavailable
- The unavailable record was selected
unavailable.status=error. This is not the same contract result as a lookup missing through expiry or eviction.
- KV operation context
- Status: error
- Owner/version gate
- Fallback decision pending
- Application state boundary
- Default not created
- Conversion to missing was rejected
treatedAsMissing=false remains. Treating unavailable owner state as an empty note can create incorrect new state while authority is unknown.
- KV operation context
- Error ≠ missing
- Owner/version gate
- Authoritative value UNKNOWN
- Application state boundary
- No guessed empty note
- The default-creation boundary stayed closed
createdDefaultNote=false. An adapter error is not routed into a successfully observed missing path, so state-changing fallback does not begin.
- KV operation context
- Unavailable known
- Owner/version gate
- Default creation=false
- Application state boundary
- No state-changing fallback
- Cache and authority were separated
A memory-cache miss may need a read from another owner. This fixture says authoritativeForFixtureOnly=true without defining a real database fallback owner.
- KV operation context
- Fixture owner only
- Owner/version gate
- External fallback unspecified
- Application state boundary
- No invented source value
- A success reply did not become persistence
Even successful Redis GET does not prove persistence. An error here supplies still less state evidence; outage, missing data and durability are separate questions.
- KV operation context
- State unknown on error
- Owner/version gate
- Durability separate
- Application state boundary
- No clean default state
- The safe result was reported correctly
The known result is no conversion of error to missing and no default-note creation. This branch cannot establish whether data exists, expired or was deleted.
- KV operation context
- No false missing
- Owner/version gate
- No default mutation
- Application state boundary
- Data existence UNKNOWN
ASSUMPTION · BOUNDARY · CONTROL
A missing result does not explain ownership
When a controlled public-note lookup returns missing, identify the owner and the errors the adapter distinguishes. An expired cache entry and an authoritative record never written can require different product decisions. A memory-only cache miss may need a database fallback; collapsing an authoritative-store outage into missing can produce false default state. These scenarios use only authored logical lifetime and an injected unavailable event, not Redis telemetry.
Key naming organizes data, while a public prefix alone grants no permission. Validating id, title and version against the expected JSON shape is separate. Treating store success as sufficient application semantics can lead to decisions over the wrong note or revision. Field/schema checks and data ownership are reviewed together rather than inferred from a string lookup result.
Attach atomicity to an operation boundary
A local expected-revision rejection proves only one serialized owner decision. Excluding a state change between separate read and write operations requires another assumption. Extending MongoDB single-document atomicity to a collection, or Redis serialized EXEC processing to SQL rollback, is likewise incorrect. The impact can be partially changed related data or a stale decision overwriting newer state. Our harmless version mismatch preserves the selected state.
Do not equate an accepted reply with persistence or replica visibility. An update accepted in process memory can be lost after a crash; TTL, eviction, and persistence policies are different mechanisms. Review expects an explicit operation scope, failure outcome, and application fallback decision. This fixture measures no cluster, replication lag, concurrent clients, or durability; drawing those capabilities into the model would also create false evidence.
3. Blue · Secure development & defense
Read every scenario without motion
TTL and eviction
- The logical-clock boundary was read
At hit.clock=49 and expiresAt=50, the value is found. This logical-clock result is not a production Redis timing or eviction-policy measurement.
- Value/lifetime observation
- Clock=49
- Adapter contract review
- Expires at=50
- Evidence conclusion
- Status=found
- The expiry-equality outcome was matched
At expiry.clock=50, status=missing with fixtureReason=expired. The boundary differs from the normal hit; equality is not read as still found.
- Value/lifetime observation
- Clock=50
- Adapter contract review
- Status=missing
- Evidence conclusion
- Reason=expired
- Pre-expiry eviction was kept separate
At eviction.clock=49, status=missing. The reason is an explicit authored eviction event, so it differs from expiry at clock=50.
- Value/lifetime observation
- Eviction at=49
- Adapter contract review
- Missing before expiry
- Evidence conclusion
- Cause differs
- Runtime reasons were not generalized
fixtureReason is this example’s observation field. A real adapter need not distinguish expiry, eviction and never-written causes for every missing result.
- Value/lifetime observation
- Fixture cause known
- Adapter contract review
- Product cause may be unknown
- Evidence conclusion
- No invented telemetry
- The replacement-TTL contract was read
replacement has oldExpiresAt=50, newExpiresAt=null and ttlPreserved=false. This replacement choice clears the TTL; preserving the old TTL is not assumed.
- Value/lifetime observation
- Old TTL boundary=50
- Adapter contract review
- New expiry=null
- Evidence conclusion
- TTL preserved=false
- Lifetime and persistence were separated
Expiry, eviction and replacement are different lifetime mechanisms. None alone proves disk restore, replication or data survival after a process crash.
- Value/lifetime observation
- Lifetime rules known
- Adapter contract review
- Persistence separate
- Evidence conclusion
- No restore guarantee
Copy and selector
- The found-value encoding was reviewed
The store keeps a JSON string; hit.value is parsed data with id=tea, title=Tea note and version=1. Store lookup and shape validation are different operations.
- Value/lifetime observation
- Stored: JSON string
- Adapter contract review
- Returned: parsed data
- Evidence conclusion
- Copy review pending
- The application-copy boundary was read
applicationMutationChangesStore=false declares that the returned object is separate from store state. Changing a local field does not automatically update the store.
- Value/lifetime observation
- Returned copy independent
- Adapter contract review
- Store mutation=false
- Evidence conclusion
- No shared-object assumption
- The error-selector condition was defined
The selector counts observations with status=error and treatedAsMissing=true. The normal error record has treatedAsMissing=false, giving expected=0.
- Value/lifetime observation
- Error/missing conversion condition
- Adapter contract review
- Normal expected=0
- Evidence conclusion
- Negative policy retained
- The positive comparison was bounded
positiveExpected=1 is only an inconsistent observation comparison. It is not reported as an actual product event converting error into missing.
- Value/lifetime observation
- Comparison expected=1
- Adapter contract review
- Fixture only
- Evidence conclusion
- No actual incident claim
- The missing-observation outcome was separated
With a missing record or treatedAsMissing field, count=0 cannot prove correct error handling. Adapter outcome and application fallback need coverage.
- Value/lifetime observation
- Coverage may be missing
- Adapter contract review
- Handling UNKNOWN
- Evidence conclusion
- No clean selector conclusion
- Copy, policy and product scope were separated
The parsed copy and finite decision belong to the memory adapter, with productsExecuted=[]. Actual Redis/MongoDB behavior and namespace authorization need separate evidence.
- Value/lifetime observation
- Local copy behavior known
- Adapter contract review
- Products not executed
- Evidence conclusion
- Permission separate
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Connect adapter contracts to application decisions
Define found/value, missing, and unavailable/error outcomes at the application boundary. Validate the value schema after the store operation as a separate local step. The fixture returns a parsed copy so application mutation cannot silently change stored data. Key mapping is selected only for a public synthetic note. Real identity, authorization, namespace isolation, and presentation require separate design.
If missing triggers a fallback, state which owner is read and what is authoritative on errors. Here unavailable does not create an empty note: a state-changing decision stops. The contract does not authorize treating a cache as a database. Separate expiry and eviction observations help debugging, but actual adapters may not know every cause. Our missing-reason field is authored fixture observability, not a runtime field supplied by every Redis client.
Verify lifecycle policy independently
Use a selected logical clock to check immediately before and at expiry; test explicit pre-expiry eviction separately for the same value. Record whether replacement preserves or clears lifetime. Production Redis policies depend on current documentation and workload; this lesson recommends no memory-limit or fsync tuning value. Do not report the native Python adapter as Redis or MongoDB execution.
Independently read the current revision and verify preservation after a stale update. The local operation is one owner decision; other products have their own conditional-write, transaction and consistency contracts. If persistence is required, consider process memory, snapshots, append-only logs and restore separately. An accepted memory update does not imply preserved data after a crash or visibility at a replica.
NOW APPLY IT TO THE CASE
Why are missing, unavailable and stale versions different decisions?
In the public-note memory adapter, errors create no default note and stale versions preserve current state. TTL, eviction, replacement and parsed copies are reviewed through separate fields.
Compare the cached case representation with its authoritative data owner. Keep stored value, revision, transaction and cache-copy completion separate.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Public note key/version/lifetime ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"selectedModel": {
"runtime": "own Python in-memory adapter, serialized owner access",
"productsExecuted": [],
"key": "public:note:tea",
"valueEncoding": "JSON string",
"authoritativeForFixtureOnly": true,
"persistence": "none"
},
"hit": {
"clock": 49,
"expiresAt": 50,
"status": "found",
"value": {
"id": "tea",
"title": "Tea note",
"version": 1
},
"applicationMutationChangesStore": false
},
"expiry": {
"clock": 50,
"status": "missing",
"fixtureReason": "expired"
},
"eviction": {
"clock": 49,
"status": "missing",
"fixtureReason": "explicit authored eviction event, not measured Redis policy"
},
"stale": {
"expectedVersion": 1,
"currentVersion": 2,
"accepted": false,
"afterValue": {
"id": "tea",
"title": "Coffee note",
"version": 2
},
"decisionScope": "selected serialized single owner"
},
"unavailable": {
"status": "error",
"treatedAsMissing": false,
"createdDefaultNote": false
},
"replacement": {
"oldExpiresAt": 50,
"newExpiresAt": null,
"ttlPreserved": false,
"contract": "selected replacement clears TTL; Redis SET reference only"
},
"productScopes": {
"mongoSingleDocumentAtomic": true,
"mongoUpdateManyWholeAutomaticallyAtomic": false,
"redisExecIsSqlRollback": false,
"redisGetSuccessProvesPersistence": false
},
"selector": {
"language": "XPath",
"xml": "<audit><kv status=\"error\" treatedAsMissing=\"false\"/></audit>",
"xpath": "count(/audit/kv[@status='error' and @treatedAsMissing='true'])",
"expected": 0,
"positiveFixtureXml": "<audit><kv status=\"error\" treatedAsMissing=\"true\"/></audit>",
"positiveExpected": 1,
"scope": "Only authored protocol-decision observations, not product log field names or a live HTTP capture.",
"missingFieldBehavior": "Missing observation is insufficient evidence; zero matches is not proof of correct runtime behavior."
}
}- selectedModel
- Local adapter scope, not product runtime.
- hit/expiry/eviction
- Logical clock and different missing conditions.
- stale
- Expected revision and preserved current value.
- unavailable/replacement
- Error semantics and selected lifetime replacement contract.
- selector
- Inert XPath counting synthetic missing/error decisions.
Public synthetic note; the own Python adapter is memory-only. No Redis/MongoDB, cluster, timer, or durability execution is claimed. Missing reason is a fixture field.
Two reality checks
NoSQL needs no schema or transactions.
Every model needs a data contract; selected products can provide validation and different atomicity/transaction scopes.
A missing key proves an available store and empty authoritative state.
Missing has multiple causes; unavailable is a separate error. Cache absence does not prove an absent authoritative record.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
The adapter returned an error. Should it be treated as missing and create a default note?
- Yes; every unsuccessful GET proves the key is absent.
- No; treatedAsMissing=false and no default creation.
Read the explained answer
No; treatedAsMissing=false and no default creation. — An error does not reveal the data owner state; silently converting it to a missing branch is incorrect.
Does the selected replacement preserve TTL, and was actual Redis executed?
Selected lesson artifact fields; teaching example
- selectedModel
- {"runtime":"own Python in-memory adapter, serialized owner access","productsExecuted":[],"key":"public:note:tea","valueEncoding":"JSON string","authoritativeForFixtureOnly":true,"persistence":"none"}
- replacement
- {"oldExpiresAt":50,"newExpiresAt":null,"ttlPreserved":false,"contract":"selected replacement clears TTL; Redis SET reference only"}
- TTL is cleared; productsExecuted is empty.
- Successful Redis GET guarantees persistence and preserved TTL.
Read the explained answer
TTL is cleared; productsExecuted is empty. — NewExpiresAt null/ttlPreserved=false is the selected rule; an owned memory adapter is not runtime evidence of the referenced product.
Why should Mongo single-document atomicity, updateMany and Redis EXEC not be equated with SQL-style rollback?
Read the explained answer
Single-document update scope does not make an entire updateMany operation automatically atomic. Redis EXEC is not equivalent to SQL rollback semantics. The selected stale-version rejection is a serialized-owner model; no actual Redis/Mongo deployment or distributed atomicity was tested.
□ I tied atomicity scope to the operation/product.
□ I did not count the memory adapter as a real-product test.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- MongoDB 9.0 Manual — Data ModelingData Modeling in MongoDB · Developing with a Flexible Data Model · Document Relationships
- MongoDB 9.0 Manual — Atomicity and TransactionsAtomicity and Transactions · Details
- Redis — GETGET command description · Return information
- Redis — SETSET command description · Optional arguments: EX/PX/EXAT/PXAT/KEEPTTL
- Redis — EXPIREEXPIRE command description · Required arguments
- Redis — TransactionsUsage · Errors inside a transaction · What about rollbacks? · Optimistic locking using check-and-set · WATCH explained
- Redis — Key evictionUsing the maxmemory configuration directive · Eviction policies
- Redis — PersistenceRedis persistence · RDB advantages · AOF advantages · How durable is the append only file?