{"record":{"id":"ba85d03db4c42b1a","repo":"spring-projects/spring-ai","slug":"error-reserving-atomic-timestamps-for-conversation","errorCode":null,"errorMessage":"Error reserving atomic timestamps for conversation + conversationId + , using fallback: + e.getMessage()","messagePattern":"Error reserving atomic timestamps for conversation \\+ conversationId \\+ , using fallback: \\+ e\\.getMessage\\(\\)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"memory-repositories/spring-ai-model-chat-memory-repository-redis/src/main/java/org/springframework/ai/chat/memory/repository/redis/RedisChatMemoryRepository.java","lineNumber":230,"sourceCode":"\t\t\tlong nextTimestamp = Long.parseLong(result.toString());\n\n\t\t\t// Set expiration on the counter key (same as the messages)\n\t\t\tif (this.config.getTimeToLiveSeconds() != -1) {\n\t\t\t\tthis.jedisClient.expire(sequenceKey, this.config.getTimeToLiveSeconds());\n\t\t\t}\n\n\t\t\tif (logger.isDebugEnabled()) {\n\t\t\t\tlogger.debug(\"Reserved \" + count + \" timestamp(s) starting at \" + nextTimestamp + \" for conversation \"\n\t\t\t\t\t\t+ conversationId);\n\t\t\t}\n\n\t\t\treturn nextTimestamp;\n\t\t}\n\n\t\tcatch (Exception e) {\n\t\t\t// Log error and fall back to current timestamp with nanoTime for uniqueness\n\t\t\tif (logger.isWarnEnabled()) {\n\t\t\t\tlogger.warn(\"Error reserving atomic timestamps for conversation \" + conversationId\n\t\t\t\t\t\t+ \", using fallback: \" + e.getMessage());\n\t\t\t}\n\t\t\t// Add nanoseconds to ensure uniqueness even in fallback scenario\n\t\t\treturn Instant.now().toEpochMilli() * 1000 + (System.nanoTime() % 1000);\n\t\t}\n\t}\n\n\tpublic List<Message> get(String conversationId) {\n\t\treturn get(conversationId, this.config.getMaxMessagesPerConversation());\n\t}\n\n\tpublic List<Message> get(String conversationId, int lastN) {\n\t\tAssert.notNull(conversationId, \"Conversation ID must not be null\");\n\t\tAssert.isTrue(lastN > 0, \"LastN must be greater than 0\");\n\n\t\t// Use QueryBuilders to create a tag field query for conversation_id\n\t\tQueryNode queryNode = QueryBuilders.intersect(\"conversation_id\",\n\t\t\t\tValues.tags(RediSearchUtil.escape(conversationId)));","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/memory-repositories/spring-ai-model-chat-memory-repository-redis/src/main/java/org/springframework/ai/chat/memory/repository/redis/RedisChatMemoryRepository.java#L212-L248","documentation":"RedisChatMemoryRepository.reserveTimestampsForConversation() tries to atomically reserve timestamps in Redis for a conversation; if the Redis operation throws any Exception it logs this warning and falls back to a locally computed timestamp (Instant.now().toEpochMilli()*1000 + nanoTime%1000). Functionality continues but timestamp ordering/uniqueness guarantees across nodes are weakened.","triggerScenarios":"Redis connection failure, timeouts, or errors from the Lua/atomic reserve command inside reserveTimestampsForConversation, invoked via nextTimestamp() and timestamp() while writing messages.","commonSituations":"Redis down or restarted mid-run; network blips between app and Redis; Redis cluster failover; connection pool exhaustion; wrong host/port causing connection errors on first write.","solutions":["Check Redis availability and the underlying connection exception in logs (only e.getMessage() is logged)","Verify spring.data.redis host/port/password and that Redis accepts commands (redis-cli ping)","Fix pool/timeout settings if errors are caused by pool exhaustion or timeouts","After connectivity is restored, re-write affected messages if strict ordering matters — fallback timestamps may not be globally unique","Consider enabling Redis persistence/sentinel so the atomic reserve path stays healthy"],"exampleFix":"// before\nLong ts = repository.timestamp(conversationId); // silently falls back on Redis failure\n// after\nif (!redisHealthy()) {\n    throw new IllegalStateException(\"Redis unavailable; timestamp reservation would fall back\");\n}\nLong ts = repository.timestamp(conversationId);","handlingStrategy":"fallback","validationCode":"try (Jedis j = pool.getResource()) { assertEquals(\"PONG\", j.ping()); } // check Redis health before writes","typeGuard":"boolean redisHealthy = connection != null && \"PONG\".equals(connection.ping());","tryCatchPattern":"try { long ts = repository.timestamp(conversationId); }\ncatch (Exception e) { log.warn(\"timestamp reservation failed; ordering not guaranteed across instances\", e); throw e; } // or apply your own monotonic per-instance sequence","preventionTips":["Monitor Redis availability and connection-pool saturation","Configure redis timeouts/retries so transient blips don't hit the atomic reserve path","Remember the fallback is per-JVM; avoid multi-node ordering assumptions when warnings appear","Keep Redis persisted and use Sentinel/Cluster for failover"],"tags":["redis","timestamp","fallback","chat-memory"],"backgroundTag":"network-request-failed","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}