{"record":{"id":"8da339df8fda1622","repo":"apereo/cas","slug":"dynamodb-batch-write-returned-unprocessed-ite","errorCode":null,"errorMessage":"DynamoDb batch write returned [{}] unprocessed item collection(s). Retrying attempt [{}] in [{}]ms","messagePattern":"DynamoDb batch write returned \\[(.+?)\\] unprocessed item collection\\(s\\)\\. Retrying attempt \\[(.+?)\\] in \\[(.+?)\\]ms","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-dynamodb-ticket-registry/src/main/java/org/apereo/cas/ticket/registry/DynamoDbTicketRegistryFacilitator.java","lineNumber":706,"sourceCode":"        }\n    }\n\n    private void submitBatchWriteRequest(final Map<String, List<WriteRequest>> requestItems) {\n        var unprocessedItems = new HashMap<>(requestItems);\n        var attempt = 0;\n        while (!unprocessedItems.isEmpty()) {\n            val batchRequest = BatchWriteItemRequest.builder().requestItems(unprocessedItems).build();\n            val response = amazonDynamoDBClient.batchWriteItem(batchRequest);\n            unprocessedItems = new HashMap<>(response.unprocessedItems());\n            if (!unprocessedItems.isEmpty()) {\n                attempt++;\n                if (attempt >= BATCH_WRITE_MAX_ATTEMPTS) {\n                    throw new IllegalStateException(\"DynamoDb batch write failed to process [%s] item collection(s) after [%s] attempts\"\n                        .formatted(unprocessedItems.size(), attempt));\n                }\n                val delay = Math.min(BATCH_WRITE_MAX_RETRY_DELAY_MILLIS,\n                    BATCH_WRITE_RETRY_DELAY_MILLIS * (1L << Math.min(attempt, 8)));\n                LOGGER.warn(\"DynamoDb batch write returned [{}] unprocessed item collection(s). Retrying attempt [{}] in [{}]ms\",\n                    unprocessedItems.size(), attempt, delay);\n                FunctionUtils.doUnchecked(__ -> Thread.sleep(delay));\n            }\n        }\n    }\n\n    /**\n     * Column names for tables holding tickets.\n     */\n    @Getter\n    @RequiredArgsConstructor\n    public enum ColumnNames {\n\n        /**\n         * id column.\n         */\n        ID(\"id\"),\n        /**","sourceCodeStart":688,"sourceCodeEnd":724,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-dynamodb-ticket-registry/src/main/java/org/apereo/cas/ticket/registry/DynamoDbTicketRegistryFacilitator.java#L688-L724","documentation":"DynamoDb's batchWriteItem API can return 'unprocessed items' when throughput is exceeded; this facilitator retries the unprocessed collections with exponential backoff up to BATCH_WRITE_MAX_ATTEMPTS. This warning is logged on each retry, reporting how many unprocessed item collections remain, the attempt number, and the computed backoff delay. It is not fatal until the max-attempts IllegalStateException is thrown.","triggerScenarios":"Calling any bulk write path (e.g. putAll / batch persistence via DynamoDbTicketRegistryFacilitator) while DynamoDB responds with UnprocessedItems for part of the batch — triggered by exceeding table or account write capacity, hot partition keys, or DynamoDB throttling during heavy ticket traffic.","commonSituations":"Large SSO bursts with many service-ticket writes in one batch; DynamoDB table provisioned below required WCU; on-demand table experiencing adaptive capacity issues; batches spanning many partitions under load.","solutions":["Wait/rely on the built-in exponential backoff retry; the operation usually succeeds within max attempts.","Increase the table's write capacity (WCU or on-demand mode) or enable auto scaling.","Reduce batch size so fewer items share a partition, and stagger bulk operations.","Check DynamoDB CloudWatch ThrottledRequests/WriteThrottleEvents metrics to confirm throttling as the root cause."],"exampleFix":"// before: fixed low capacity, big burst batches\nregistry.putAll(tickets); // hundreds at once\n// after: chunk the batch and let retries absorb throttling\nval chunkSize = 25;\nCollectors.partition(tickets, chunkSize).forEach(chunk -> registry.putAll(chunk));\n// and increase table WCU / switch billing mode to PAY_PER_REQUEST","handlingStrategy":"retry","validationCode":"// Check table capacity before large batch writes\ndescribeTable(tableName).table().billingModeSummary().billingMode() == BillingMode.PAY_PER_REQUEST\n    || tableDesc.provisionedThroughput().writeCapacityUnits() >= expectedWcus;","typeGuard":null,"tryCatchPattern":"try {\n    registry.putAll(tickets);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"batch write failed to process\")) {\n        // re-queue tickets for later write after backoff\n        retryQueue.addAll(tickets);\n    } else {\n        throw e;\n    }\n}","preventionTips":["Use PAY_PER_REQUEST (on-demand) billing for spiky ticket traffic.","Keep DynamoDB batch sizes small and uniformly distributed across partition keys.","Monitor WriteThrottleEvents in CloudWatch and alert before saturation.","Schedule bulk maintenance writes during low-traffic windows."],"tags":["dynamodb","batch-write","throttling","retry","backoff"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}