apache/druid · error · SegmentLoadingException
Unable to copy key [%s] to file [%s]
Error message
Unable to copy key [%s] to file [%s]
What it means
After confirming equal lengths, mergeBloomFilterByteBuffers compares the header bytes (numHashFunctions and numBits fields before START_OF_SERIALIZED_LONGS) of both buffers byte-by-byte. Any difference in those header bytes means the filters use a different number of hash functions or bit layout, and merging would corrupt the result, so it throws this IllegalArgumentException. Note: this check compares every header byte, so even padding differences trip it.
Source
Thrown at extensions-contrib/cassandra-storage/src/main/java/org/apache/druid/storage/cassandra/CassandraDataSegmentPuller.java:82
try {
RetryUtils.retry(
() -> {
try (OutputStream os = new FileOutputStream(tmpFile)) {
ChunkedStorage
.newReader(indexStorage, key, os)
.withBatchSize(BATCH_SIZE)
.withConcurrencyLevel(CONCURRENCY)
.call();
}
return new FileUtils.FileCopyResult(tmpFile);
},
Predicates.alwaysTrue(),
10
);
}
catch (Exception e) {
throw new SegmentLoadingException(e, "Unable to copy key [%s] to file [%s]", key, tmpFile.getAbsolutePath());
}
try {
final FileUtils.FileCopyResult result = CompressionUtils.unzip(tmpFile, outDir);
log.info(
"Pull of file[%s] completed in %,d millis (%s bytes)", key, System.currentTimeMillis() - startTime,
result.size()
);
return result;
}
catch (Exception e) {
try {
FileUtils.deleteDirectory(outDir);
}
catch (IOException e1) {
log.error(e1, "Error clearing segment directory [%s]", outDir.getAbsolutePath());
e.addSuppressed(e1);
}
throw new SegmentLoadingException(e, e.getMessage());View on GitHub (pinned to 9b90983fd2)
Solutions
- Standardize bloomFilterNumHashFunctions (and numBits) across all producers of filters that will be merged.
- Deserialize both filters and verify getNumHashFunctions() matches before merging.
- Double-check bf1Start/bf2Start offsets so header comparisons are aligned to actual filter starts.
- Catch IllegalArgumentException and fall back to treating the merge as unsupported for those inputs.
Example fix
// before
// query A: bloomFilterNumHashFunctions=3, query B: bloomFilterNumHashFunctions=5
BloomKFilter.mergeBloomFilterByteBuffers(bufA, bufB, 0, 0); // throws
// after
// use one shared config for all stages
Query a = base.clone().withOverriddenContext(ImmutableMap.of("bloomFilterNumHashFunctions", 4, "bloomFilterNumBits", 1024));
Query b = base.clone().withOverriddenContext(ImmutableMap.of("bloomFilterNumHashFunctions", 4, "bloomFilterNumBits", 1024));
// now merge is safe Defensive patterns
Strategy: validation
Validate before calling
boolean sameHeader(ByteBuffer b1, int s1, ByteBuffer b2, int s2) {
for (int i = 0; i < BloomKFilter.START_OF_SERIALIZED_LONGS; i++) {
if (b1.get(s1 + i) != b2.get(s2 + i)) return false;
}
return true;
} Type guard
boolean isCompatibleMergeInput(ByteBuffer b1, int s1, ByteBuffer b2, int s2) {
return sameHeader(b1, s1, b2, s2) && serializedLen(b1, s1) == serializedLen(b2, s2);
} Try / catch
try {
BloomKFilter.mergeBloomFilterByteBuffers(bf1Buffer, bf2Buffer, start1, start2);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("NumHashFunctions/NumBits")) {
throw new IllegalStateException("Filters use different hash-function counts; standardize bloomFilterNumHashFunctions", e);
}
throw e;
} Prevention
- Fix one bloomFilterNumHashFunctions value (with numBits) in shared config for every query producing mergeable filters.
- Check header bytes equality before merging filters from external tools or other Druid versions.
- Validate start offsets so header comparison is not misaligned.
When it happens
Trigger: Calling mergeBloomFilterByteBuffers with two buffers of the same serialized length but different numHashFunctions/numBits header values — e.g. same numBits size in bytes but different hash function counts, or header bytes stored incorrectly.
Common situations: Filters built with identical bit sizes but different bloomFilterNumHashFunctions; endianness or version differences in how headers were written; merging buffers where one start offset points at the wrong byte, comparing misaligned headers.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Emit called unexpectedly before service start
- Got an exception while parsing file [%s]
- Cannot create bloom filter %s for invalid column type [%s]
- unknown event type [%s]
- interrupted flushing elements from queue
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/933bbffc977a9dc5.
Report an issue: GitHub.