Skip to content

v1.1.51

Choose a tag to compare

@github-actions github-actions released this 23 Jun 14:14

Distributed Lock Support

  • LockProvider — New LockProvider interface defining tryLock(key, expire, unit) and overloads with explicit retry counts. AutoReleaseLock (@FunctionalInterface extends AutoCloseable) for try-with-resources usage.
  • RedisLockProvider — Redis-backed implementation using SET NX PX with UUID-based safe release. Exponential backoff retry, configurable lock/inquiry/unlock counts. Inner class RedisLockHandle implements AutoReleaseLock.
  • HotKeyLockAutoConfiguration — Auto-wires LockProvider bean when StringRedisTemplate is available. Registered in AutoConfiguration.imports. Graceful degradation via @ConditionalOnClass (no-op when Redis absent).
  • HotKey Facade — New 4th constructor parameter LockProvider (nullable). New public methods: tryLock(key, expire, unit), tryLock(key, expire, unit, lockCount, inquiryCount, unlockCount), tryLockAndRun(key, expire, unit, action), tryLockAndRun(key, expire, unit, action, lockCount, inquiryCount, unlockCount).
  • HotKeyProperties — 3 new config properties: tryLockLockCount (default 2), tryLockInquiryCount (default 1), tryLockUnlockCount (default 2).
  • HotKeyConstants — New REDIS_LOCK_KEY_PREFIX = "hotkey:lock:".

Deployment Mode Enforcement

  • HotKeyModeException — New RuntimeException with operation, currentMode, requiredMode fields. Thrown when a HotKey API is called in an unsupported deployment mode.
  • requireAppCache / requireAppDetector — Former single requireCache() throwing UnsupportedOperationException replaced by two methods, both throwing HotKeyModeException: requireAppCache(operation) guards L1 cache operations; requireAppDetector(operation) guards local TopK detector operations. Operation name strings provide precise error messages.
  • Mode query methods — New public methods: isApp() (cache available), isWorker() (Worker TopK available), isAppOnly() (pure App mode), isWorkerOnly() (pure Worker mode).
  • currentModeLabel() — New private helper generating human-readable deployment mode labels ("App-only mode", "Worker-only mode", "Coexistence mode", "Uninitialized mode").
  • Stricter read method guardsget(), getWithSoftExpire(), etc. now call both requireAppCache() and requireAppDetector() (reference only checked cache).
  • notifyLocalDetector guards — All 5 notifyLocalDetector*() methods now call requireAppDetector() instead of NPE-ing in Worker-only mode as in the reference.
  • computeIfAbsent simplificationcomputeIfAbsent() and computeIfAbsentWithSoftExpire() convenience overloads no longer call requireAppCache() directly — they delegate to inner get()/getWithSoftExpire() which perform their own guard checks.

invalidateAll Rename

  • invalidateAll → invalidateAllLocal — The no-arg invalidateAll() method renamed to invalidateAllLocal(), emphasizing local-only clear semantics (no broadcast). invalidateAll(String...) and invalidateAll(Collection) remain unchanged (varargs overloads still exist — they broadcast batch invalidations).
  • putLocal silent no-op — All three putLocal() overloads now silently return in Worker-only mode (if (hotKeyCache == null) return;) instead of throwing. Design choice — local writes should always be safe.

HotKeyCache Resilience and Correctness

  • Read path try-catchget(), getWithSoftExpire(), and peek() now wrap their logic in try-catch: HotKeyBlockedException re-thrown as-is; all other RuntimeException logged and return Optional.empty(). Prevents internal failures from propagating to callers.
  • Writer exception handlingputThrough() now wraps writer.run() in try-catch; logs error and skips cache update when writer throws. Fixes partial-write issue where cache was updated despite data source failure.
  • buildPutThroughEntry version guardbuildPutThroughEntry() now calls VersionGuard.shouldSkipForSync(). If existing entry's dataVersion is already >= the new version, the existing entry is preserved unchanged. Prevents stale writes from overwriting newer data.
  • Conditional TOCTOU re-read — Re-reads from cache only when side effects actually occurred (real promotion or report not skipped), instead of always re-reading. Minor performance optimization.

HotKeyReadQuery Single-Execution Guard

  • AtomicBoolean executed — New executed field initialized AtomicBoolean(false). execute() checks compareAndSet(false, true); throws IllegalStateException("HotKeyReadQuery can only be executed once") if already invoked on the same builder. Prevents accidental double execution.

CacheSyncListener Async Error Handling

  • try-catch wrapping async task — Runnable tasks scheduled on syncScheduler are now wrapped in try-catch with logging of type/key/version. Catches uncaught exceptions that would otherwise be swallowed.

VersionGuard Worker Epoch + NodeId

  • shouldSkipForWorker 5-arg signature — New shouldSkipForWorker(CacheEntry, long, String, long) with incomingNodeId and incomingEpoch parameters. Logic:
    • Incoming epoch > existing epoch → accept (Worker restart — ADR-0010)
    • Incoming epoch < existing epoch → skip (stale)
    • Same epoch, same nodeId → normal ordering (existing >= incoming → skip)
    • Different nodeId, same epoch → accept (cross-Worker ownership transfer)
  • shouldSkipForWorker Cache-level overload — New 5-arg shouldSkipForWorker(Cache, String, long, String, long) overload. 3-arg version now delegates to 5-arg with null nodeId and 0 epoch.

CacheEntry Epoch Tracking

  • New field decisionNodeIdprivate final String decisionNodeId. Tracks which Worker node produced the decisionVersion. Used by VersionGuard.shouldSkipForWorker for per-Worker version space partitioning.
  • New field decisionEpochprivate final long decisionEpoch. Tracks Worker restart counter epoch. Used by VersionGuard.shouldSkipForWorker for restart detection (ADR-0010).

VersionController Lua Script Optimization

  • Pre-compiled script — Lua script INCR_SCRIPT now defined as a static final field instead of creating a new DefaultRedisScript object on every nextVersion() call. Avoids re-creating script object on every version allocation.
  • Simplified script content — Always calls EXPIRE after INCR now (conditional if tonumber(ARGV[1]) > 0 check removed).

HotKeySpringCache nullCachedKeys Removal

  • nullCachedKeys set removed — Entire nullCachedKeys (ConcurrentHashMap.newKeySet()) removed. lookup() no longer checks it, put()/evict()/clear() no longer manage it, get(Object, Callable) no longer populates it. Null caching behavior now handled entirely through NullValue sentinel inside CacheEntry without a separate keyset. Removes import java.util.Set and import java.util.concurrent.ConcurrentHashMap.

ClusterHealthView Dynamic Worker Count

  • knownWorkerCount made mutableknownWorkerCount changed from final to volatile with setter. @RequiredArgsConstructor removed; explicit constructor added.
  • isClusterHealthy() fallback — When knownWorkerCount <= 0, isClusterHealthy() now returns aliveCount > 0 (falls back to observed alive Workers) instead of hardcoded false as in reference. Enables backward compatibility.
  • Getter added@Getter added on knownWorkerCount.

Package Reorganization

  • HeavyKeeper typo fix — Package renamed from hotkeydetector/heavykepper/ (double p) to hotkeydetector/heavykeeper/. All imports updated.
  • Sync package split — Flat sync/ package split into sync/local/ (SyncMessage, CacheSyncPublisher, CacheSyncListener, CacheSyncProperties), sync/worker/ (WorkerMessage, WorkerListener, WorkerListenerProperties, WorkerHeartbeatVerifier, WorkerHeartbeatMessage), and sync/distributedlock/ (LockProvider, AutoReleaseLock, impl/RedisLockProvider). All imports updated.
  • Version classes movedVersionController.java and VersionGuard.java moved from sync/ to util/version/.
  • ClusterHealthView movedClusterHealthView.java moved from sync/ to sharding/.

Transaction Support Documentation

  • Rollback behaviorTransactionSupport Javadoc adds <p><b>Rollback behavior:</b> section documenting that runNowOrAfterCommit actions are NOT executed on transaction rollback.