Install
openclaw skills install bookforge-offline-concurrency-strategy-selectorUse when designing concurrency control for long-running edits where a business transaction spans multiple system transactions — user opens a record, edits for minutes or hours, then saves. Selects between optimistic locking (version column, collision detection at commit) vs pessimistic locking (record check-out, conflict prevention at load time) and decides whether to add Coarse-Grained Lock (aggregate-root lock for multi-object edits) and Implicit Lock (framework-enforced locking to prevent gaps). Handles: lost update prevention, concurrent edit collision detection, offline lock strategy, long-running transaction concurrency, version column design, lock table design, lock timeout policy, aggregate lock, editing concurrency, lock type selection (exclusive-read vs exclusive-write vs read-write). Diagnoses mis-configurations: DB-level locks held across user think-time, implicit-lock gaps, optimistic/pessimistic mixing on overlapping data, timestamp-based versioning pitfalls.
openclaw skills install bookforge-offline-concurrency-strategy-selectorThis skill applies whenever a business transaction spans multiple system transactions — the user loads a record in one HTTP request, works with it (seconds to hours), and saves in a later request. During that gap, the database cannot protect against another user modifying the same data. You need application-level concurrency control.
Use this skill when designing new editing workflows, diagnosing "lost update" bugs, or auditing existing lock strategies. It selects among four patterns from Fowler's Patterns of Enterprise Application Architecture Ch 16 and routes to child implementation skills when needed.
Do not use if the entire user workflow fits in a single request/transaction — standard database isolation levels are sufficient and far simpler.
Gather the following before proceeding. If working from a codebase, grep for session state, version columns, and lock tables. Otherwise, ask the user directly.
Required:
Observable from codebase:
SELECT FOR UPDATE or similar → potential long-lock anti-pattern to flagSufficiency check: If collision frequency and rework cost are both unknown, use the heuristic: small internal team + low data overlap → default Optimistic. High-value financial/legal records with multiple editors → investigate Pessimistic.
Check: does the editing workflow span multiple system transactions?
WHY: All four patterns add complexity and infrastructure. If a single system transaction suffices, the added complexity is pure overhead. The book is explicit: "If you can make all your business transactions fit into a system transaction … then do that."
If offline concurrency applies, proceed. If not, recommend appropriate isolation level and stop.
Evaluate:
| Signal | Toward Optimistic | Toward Pessimistic |
|---|---|---|
| Collision frequency | Rare | Frequent |
| Rework cost if conflict | Low (few fields, quick redo) | High (hours of work, complex re-entry) |
| Edit duration | Short (seconds to a few minutes) | Long (30+ minutes, multi-step forms) |
| Concurrency need | High (many users, maximize throughput) | Lower (can accept serialized access) |
| Implementation budget | Lower | Higher (lock manager, timeouts, protocol) |
Decision:
version integer column. On save, include WHERE version = :loaded_version in UPDATE/DELETE. If row count = 0 → collision → rollback and surface error with who-modified-and-when.WHY: Optimistic concurrency trades late failure (discovered at commit) for better throughput and simpler implementation. Pessimistic concurrency trades reduced concurrency for early failure (user knows immediately the record is locked). The correct choice is a UX and domain decision as much as a technical one — it shapes the entire user experience.
Fowler's default: "Consider [Optimistic] as the default approach to business transaction conflict management in any system you build. The pessimistic version works well as a complement."
If Pessimistic was chosen in Step 2, pick the lock type:
WHY: The lock type directly controls system concurrency. Exclusive read locks are severe — they serialize ALL access. Most enterprise systems need only exclusive write locks. Read/write locks are a compromise but require careful implementation and are harder for domain experts to reason about.
Examine the editing workflow:
If YES → add Coarse-Grained Lock (aggregate-root-level lock / cluster lock):
Version object/row that all aggregate members point to (same instance, not equal value). Incrementing the shared version locks the entire group atomically.If NO (objects are independently lockable) → skip Coarse-Grained Lock.
WHY: Without Coarse-Grained Lock, per-object locking requires all code paths to know and enumerate every member of the group. This breaks down as the group grows and introduces subtle bugs when a developer forgets to lock one member. Fowler: "locking either the asset or the lease ought to result in the lease and all of its assets being locked."
Examine the codebase (or planned architecture):
If YES → add Implicit Lock (framework-enforced lock):
LockingMapper decorator wraps find() to always acquire read lock before loading; validates write lock is held before update()/delete()releaseAllLocks(sessionId) automaticallyIf the codebase is tiny or the locking scheme is minimal → may skip, but Fowler: "the risk of a single forgotten lock is too great" in most enterprise applications.
WHY: Offline concurrency bugs are extremely hard to reproduce and test. A single missed acquireLock() call defeats the entire scheme. "Generally, if an item might be locked anywhere it must be locked everywhere."
Check for these failure modes in the current or proposed design:
SELECT FOR UPDATE held open for the duration of a multi-request editing session → DB connection held for minutes/hours, destroys scalability. Flag and replace with application-level lock table.modified_at timestamp as the version marker → unreliable across servers, clock skew causes false-positives and missed conflicts. Replace with an integer version counter.WHY: Each anti-pattern either silently defeats the locking scheme (invisible gaps) or creates a different operational disaster (DB bottleneck, deadlock in clustered nodes). These are the most common failure modes in production concurrency implementations.
Output the artifact (see Outputs section).
## Concurrency Decision Record: [Feature/Entity Name]
**Decision date:** YYYY-MM-DD
**Applicable entities:** [list]
### 1. Primary Strategy
**Choice:** Optimistic Offline Lock / Pessimistic Offline Lock / Both (hybrid)
**Rationale:** [collision frequency assessment] × [rework cost assessment] → [reasoning]
### 2. Lock Type (if Pessimistic)
**Choice:** Exclusive write / Exclusive read / Read-write
**Rationale:** [why this lock type fits the access pattern]
### 3. Aggregate Locking
**Coarse-Grained Lock:** Required / Not required
**Scope:** [which objects share the lock]
**Implementation:** Shared version object / Root lock
### 4. Framework Enforcement
**Implicit Lock:** Required / Not required
**Integration point:** Abstract mapper supertype / Mapper decorator / ORM hook / Session listener
### 5. Infrastructure Requirements
- [ ] Version column: `ALTER TABLE <table> ADD COLUMN version INTEGER NOT NULL DEFAULT 0`
- [ ] Lock table: `CREATE TABLE app_lock (lockable_id BIGINT PRIMARY KEY, owner_id VARCHAR(255), acquired_at TIMESTAMP)`
- [ ] Lock timeout policy: [N minutes; auto-release on session invalidation]
- [ ] modifiedBy + modified columns for error messages
### 6. UX Specification
- **On Optimistic collision:** [show error with "Modified by [user] at [time]. Please reload and re-apply your changes."]
- **On Pessimistic lock unavailable:** [show "This record is currently being edited by [user]. Please try again later."]
- **On lock timeout:** [show "Your editing session has expired. Please reload the record."]
### 7. Anti-Pattern Warnings
[List any flagged issues from Step 6]
### 8. Child Implementation Skills Needed
- [ ] optimistic-offline-lock-implementer (version column + mapper mechanics)
- [ ] pessimistic-offline-lock-implementer (lock manager + protocol)
1. The offline concurrency problem is a domain problem, not just a technical one. Collision frequency, rework cost, and lock granularity require domain expert input — not just a DBA. Which records are high-contention? How much time does a user typically spend on a workflow? What's the cost of losing 30 minutes of insurance underwriting? These answers drive the technical choice.
2. Optimistic is the default; Pessimistic is the exception. Optimistic Offline Lock is easier to implement, has no lock infrastructure to maintain, and gives better concurrency. Pessimistic introduces lock managers, timeout policies, session listeners, and deadlock-avoidance concerns. Only adopt Pessimistic when the Optimistic failure mode (late collision discovery) is genuinely unacceptable.
3. The version counter must be an integer, not a timestamp.
System clocks are unreliable, especially across multiple servers. A monotonically incrementing integer column provides deterministic conflict detection. Including modifiedBy and modified columns alongside the version enables user-facing error messages ("Modified by Alice at 2:34pm") but should not replace the integer version in the WHERE clause.
4. Never hold a database lock across user think-time.
SELECT FOR UPDATE and similar database-native locks hold a DB connection open for the duration. A business transaction that takes 20 minutes would hold that DB resource for 20 minutes, serializing all other access and destroying scalability. Application-level lock tables are the correct tool for cross-request locking.
5. Coarse-Grained Lock preserves aggregate integrity atomically. When an aggregate spans multiple rows/objects (Order + LineItems), locking each object independently creates a window where two sessions lock different members concurrently — neither knows about the other's intent on the group. A shared version object makes this impossible: a single version increment blocks all concurrent attempts on the entire aggregate.
6. Implicit Lock prevents the #1 failure mode: the forgotten lock call. One developer writing one new method without a lock call defeats the entire scheme — and because concurrency bugs are hard to reproduce, it may not be caught in testing. Implicit Lock at the framework level means the developer cannot forget: the base mapper always acquires the lock before loading.
7. Pessimistic lock managers must never block — always throw immediately. When a lock is unavailable, throw an exception instantly. Never wait for the lock to become available. Business transactions that span multiple system transactions cannot reasonably wait: the current holder might be gone for coffee. Immediate failure + early abort is the only practical design; it also eliminates the possibility of deadlock.
Scenario: A content management system where editors write articles. Typical edit session: 10–30 minutes. Team of 5 editors; each article usually has one assigned editor. Collisions rare but possible.
Trigger: "We sometimes lose edits when two people accidentally open the same draft. Should we add locking?"
Process:
Output: Add version INTEGER NOT NULL DEFAULT 0 + modified_by + modified_at to articles table. Abstract article mapper includes version in UPDATE WHERE clause. On row count 0 → show "This article was modified by [user] at [time]. Please copy your changes, reload, and re-apply." No lock table needed.
Scenario: Underwriters edit complex insurance policies. Editing a policy takes 45–90 minutes (data gathering, actuarial calculations, document review). Two underwriters might be assigned the same policy. If an underwriter finishes after 90 minutes and their save is rejected, the work is genuinely lost — not a minor inconvenience.
Trigger: "Underwriters are furious about rejected saves. Is there a better approach?"
Process:
SELECT FOR UPDATE on policy table.Output: Database lock table + shared version on Policy aggregate + LockingMapper + HTTP session expiration listener. UX: "Policy 12345 is currently being edited by Bob Smith. It will be available after his session ends or at [timeout time]."
Scenario: Customer service agents edit orders. An order has LineItems, ShippingAddress, and PromoCodes. One agent might add a LineItem while another changes the ShippingAddress at the same time. Each object in isolation is low-risk, but the order must be consistent as a whole.
Trigger: "We have a bug where the order total is wrong — looks like two people edited it at the same time."
Process:
Output: Single version table row per order, referenced by all members. Abstract mapper update() calls order.getVersion().increment() before any member update. Conflict error: "Order 8834 was modified by [user] at [time]. Please reload to see current state."
This skill is licensed under CC-BY-SA-4.0. Source: BookForge — Patterns of Enterprise Application Architecture by Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford.
Install related skills from ClawhHub:
clawhub install bookforge-optimistic-offline-lock-implementerclawhub install bookforge-pessimistic-offline-lock-implementerclawhub install bookforge-transaction-isolation-level-auditorOr install the full book set from GitHub: bookforge-skills