🎯 Our Strategy: Entity-Based Pessimistic Locking
The codebase employs a **pessimistic, entity-based locking approach** tailored for **Google Datastore's eventually-consistent nature** to protect critical operations like payment and allocation.
Key Observations
- No
@Versionfields: Does not use JPA optimistic locking. - Deliberate Single Retry: Uses
transactNew(1, work)for **controlled retry behavior** (not for application conflict resolution). - Entity-Based Locks: Locks are persisted entities with a **10-minute expiration** to prevent deadlocks.
- Graceful Degradation: Different strategies for handling contention based on context (e.g., ignoring for Gate Access, throwing for Payment Processing).
⚙️ Transaction Management
src/main/java/nz/co/parkable/dal/TransactionProvider.java)transactWithReturn(): Reuses existing transaction or creates a new one.transact(): Non-returning version.tryOnceWithReturn(): Creates new transaction with **single retry** (transactNew(1, work)).transactionless(): For read-heavy operations outside transactions.
transact() automatically retries on concurrency failure. Therefore, the work executed within the transaction **must be idempotent**.
Critical Transaction Areas
- 1. Sequence Generation: Atomic invoice number generation.
- 2. Payment Processing: Subscription billing with full transaction isolation.
- 3. Bay Allocation: Prevents double-booking.
- 4. Parking Request Creation: Combines ObjectLock + ParkingRequestLock + single-retry.
🛡️ Three-Layer Pessimistic Locking Implementation
Used for user-level operations (e.g., preventing duplicate parking requests).
// LockService.java - Creates an ObjectLock entity
try (LockCloseable ignored = objectLockService.createLock(lockKey)) {
// Protected operation
}
// Resource Locking Pattern (Fail Fast)
try (LockCloseable ignored = lockService.createLock(id)) {
// update logic
} catch (LockException e) {
throw new BadRequestException("This is being updated. Please wait...");
}
Used for resources like parking bays to prevent double-booking (Lock Before Allocate).
// ParkingRequestLockService.java: Bay Locking
transactionProvider.transact(() -> {
if (existingRequestLock != null) {
throw new BayInUseException(); // Fail immediately
}
parkingRequestLockRepo.save(lock);
});
Low-level check for Datastore aborts (Code.ABORTED or "transaction closed").
// ExceptionUtils.java
public static boolean isConcurrentModificationException(DatastoreException ex) {
return Code.ABORTED.getNumber() == ex.getCode()
|| (Code.INVALID_ARGUMENT.getNumber() == ex.getCode()
&& ex.getMessage().contains("transaction closed"));
}
**Usage:** Silently ignored for Gate Access; Throws exception for Payment Processing.
⚖️ Concurrency Control Approaches
Our strategy is **Application-Level Locking**. Below is a comparison with key alternatives.
| Approach | Contention | Mechanism/Example | Best Use Case ✅ | Disadvantages ❌ |
|---|---|---|---|---|
| Optimistic | Low | Uses @Version field; checks at commit time. |
User Profiles, CMS, Read-heavy workloads. | Fails late, requires application retry logic, wasted work. |
| Pessimistic (RDBMS) | High | SELECT... FOR UPDATE (Acquire exclusive lock immediately). |
Financial transactions, Inventory, Short transactions. | Reduces concurrency, can cause deadlocks, performance bottleneck. |
| Application Lock (Our Codebase) | Medium | Creating lock entities: if (lockRepo.exists(id)) throw; lockRepo.save(lock); |
**Google Datastore / NoSQL**, Cross-service coordination, Custom timeout logic. | Medium complexity. |
| MVCC | Any | Database maintains multiple versions (PostgreSQL, Oracle). | General RDBMS. | None (automatic with proper setup). |
💡 Future Considerations: Hybrid Approach
The current pessimistic strategy is excellent for high-value operations. For future optimization, a **Hybrid Approach** could be considered:
- Optimistic: Apply **
@Version** locking to **low-contention entities** (e.g., User settings, Organization configs). - Pessimistic: Keep the current entity-based locks for **write-heavy critical paths** (Payment, Bay Allocation, Gate Access).