quickwit-oss/tantivy · error
Empty DocOpstamp is forbidden
Error message
Empty DocOpstamp is forbidden
What it means
During merge or apply_deletes, the writer collects per-doc operation stamps and takes their max via iterator .max().expect(...). An empty doc_opstamps list has no max, so this panic fires; the code assumes every segment being processed has at least one document with stamps.
Source
Thrown at src/indexer/index_writer.rs:244
}
/// `doc_opstamps` is required to be non-empty.
fn apply_deletes(
segment: &Segment,
delete_cursor: &mut DeleteCursor,
doc_opstamps: &[Opstamp],
) -> crate::Result<Option<BitSet>> {
if delete_cursor.get().is_none() {
// if there are no delete operation in the queue, no need
// to even open the segment.
return Ok(None);
}
let max_doc_opstamp: Opstamp = doc_opstamps
.iter()
.cloned()
.max()
.expect("Empty DocOpstamp is forbidden");
let segment_reader = SegmentReader::open(segment)?;
let doc_to_opstamps = DocToOpstampMapping::WithMap(doc_opstamps);
let max_doc = segment.meta().max_doc();
let mut deleted_bitset = BitSet::with_max_value_and_full(max_doc);
let may_have_deletes = compute_deleted_bitset(
&mut deleted_bitset,
&segment_reader,
delete_cursor,
&doc_to_opstamps,
max_doc_opstamp,
)?;
Ok(if may_have_deletes {
Some(deleted_bitset)
} else {
None
})View on GitHub (pinned to b5d8deb80c)
Solutions
- Ensure apply_deletes/index_documents are not invoked for empty segments — filter segments with max_doc()==0 before merging.
- Guard your merge/delete pipeline: skip segments with no documents.
- If hit through normal usage, report as a tantivy bug with a reproduction and upgrade to a patched version.
- After deleting all docs, avoid triggering merges/delete application on the now-empty segment until new documents exist.
Example fix
// before
let mut segments = reader.searchable_segments()?;
writer.merge(&segments.iter().map(|s| s.id()).collect::<Vec<_>>())?; // panics if a segment is empty
// after
let segments: Vec<_> = reader.searchable_segments()?.into_iter().filter(|s| s.meta().max_doc() > 0).collect();
if !segments.is_empty() { writer.merge(&segments.iter().map(|s| s.id()).collect::<Vec<_>>())?; } Defensive patterns
Strategy: validation
Validate before calling
// skip empty segments before merge/delete application
let segments: Vec<SegmentId> = reader.searchable_segments()?
.into_iter()
.filter(|s| s.meta().max_doc() > 0)
.map(|s| s.id())
.collect();
if segments.is_empty() { return Ok(()); } Type guard
fn mergeable(seg: &Segment) -> bool { seg.meta().max_doc() > 0 } Try / catch
null
Prevention
- Filter out empty segments (max_doc()==0) before merging
- Avoid applying deletes to segments with zero documents
- Avoid purging all documents and then immediately forcing merges
- Report and reproduce if triggered by default merge policies
When it happens
Trigger: Calling index_documents followed by delete application on a segment that yields an empty doc_opstamps vector — e.g. merging/deleting against an empty segment or a segment with zero matching docs in custom merge/segment-management code.
Common situations: Merging segments where one is empty; custom code manipulating doc_opstamps; indexing zero documents and then triggering delete application; edge cases after purging all documents.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed to acquire write lock on delete queue writer
- All columns re required to be numerical
- This lock should never be poisoned
- Failed to acquire read lock on SegmentManager.
- Invalid type code in JSON term
AI-assisted analysis of quickwit-oss/tantivy@b5d8deb80c (2026-09-05).
Data as JSON: /api/errors/5b4725956d4b11aa.
Report an issue: GitHub.