apache/druid · error · IllegalStateException
Record sequenceNumber[%s] is smaller than current sequenceNu
Error message
Record sequenceNumber[%s] is smaller than current sequenceNumber[%s] for partition[%s]
What it means
SeekableStreamIndexTaskRunner.verifyProjectedOffsets compares the offset of an incoming record against the task's current (persisted) offset for the partition. Records from the stream must be at or after the current offset; a record whose sequence number is smaller than the current one means the stream is replaying already-processed data or offsets are corrupted, so the task throws an IllegalStateException instead of silently duplicating data.
Source
Thrown at indexing-service/src/main/java/org/apache/druid/indexing/seekablestream/SeekableStreamIndexTaskRunner.java:2236
*/
private boolean verifyRecordInRange(
final PartitionIdType partition,
final SequenceOffsetType recordOffset
)
{
// Verify that the record is at least as high as its currOffset.
final SequenceOffsetType currOffset = Preconditions.checkNotNull(
currOffsets.get(partition),
"Current offset is null for partition[%s]",
partition
);
final OrderedSequenceNumber<SequenceOffsetType> recordSequenceNumber = createSequenceNumber(recordOffset);
final OrderedSequenceNumber<SequenceOffsetType> currentSequenceNumber = createSequenceNumber(currOffset);
final int comparisonToCurrent = recordSequenceNumber.compareTo(currentSequenceNumber);
if (comparisonToCurrent < 0) {
throw new ISE(
"Record sequenceNumber[%s] is smaller than current sequenceNumber[%s] for partition[%s]",
recordOffset,
currOffset,
partition
);
}
// Check if the record has already been read.
if (isRecordAlreadyRead(partition, recordOffset)) {
return false;
}
// Finally, check if this record comes before the endOffsets for this partition.
return isMoreToReadBeforeReadingRecord(recordSequenceNumber.get(), endOffsets.get(partition));
}
/**
* checks if the input seqNum marks end of shard. Used by Kinesis onlyView on GitHub (pinned to 9b90983fd2)
Solutions
- Reset the supervisor (POST /druid/indexer/v1/supervisor/<id>/reset) so offsets are rebuilt consistently with the current stream position.
- Restore/repair the task's persisted checkpoints so currOffset matches where the record supplier actually starts.
- Ensure you never move source topic/stream offsets backward while tasks are running.
- Verify partition ordering guarantees (single writer per partition, correct partition key) so records don't arrive with decreasing sequence numbers.
Example fix
// before: source offsets reset backward under a running task kafka-consumer-groups.sh --reset-offsets --to-earliest ... // after: stop/reset supervisor first curl -X POST 'http://overlord:8087/druid/indexer/v1/supervisor/my-supervisor/reset'
Defensive patterns
Strategy: validation
Validate before calling
// ensure source offsets are never moved backward while a task runs // e.g. before resetting Kafka offsets: stop the supervisor first curl -X POST http://overlord:8087/druid/indexer/v1/supervisor/<id>/suspend
Try / catch
try { ingest(); } catch (ISE e) { if (e.getMessage().contains("is smaller than current sequenceNumber")) { resetSupervisor(); } else { throw e; } } Prevention
- Never reset stream offsets backward under running tasks
- Restore checkpoints only from consistent backups
- Preserve per-partition ordering guarantees in producers
When it happens
Trigger: During addRecord/ingestion, recordOffset for partition is less than currOffset — e.g. after checkpoint restore the current offset is ahead of what the record supplier delivers, the topic/stream was reset to an earlier point, or records arrive out of order on a partition that isn't actually strictly ordered.
Common situations: Manually resetting Kafka offsets backward while a task is mid-run; using a non-earliest sequence number policy on Kinesis after resharding; corrupted checkpoints restored from backup; delivering records from a producer writing older offsets after a failover.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- New sequence startOffset[%s] does not equal expected prior o
- Exclusive start partitions[%s] for new sequence don't match
- Resume command was not accepted within 5 seconds
- Unable to create RecordSupplier: %s
- Expected instance of %s, got %s
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/1d3ff983f3d12a58.
Report an issue: GitHub.