apache/pulsar · error · java.lang.IllegalArgumentException

Entry-bucket 0 must span at least [0,1] to hold the canonica

Error message

Entry-bucket 0 must span at least [0,1] to hold the canonical hash

What it means

Entry hashes are normalized to per-bucket canonical values, and bucket 0's canonical hash is 1. If the first declared range ends below 1 (i.e. bucket 0 is just [0,0]), the canonical hash 1 falls outside every bucket, breaking routing and the draining/handoff machinery. validateBucketBoundaries therefore requires ranges.get(0).getEnd() >= 1.

Source

Thrown at pulsar-broker/src/main/java/org/apache/pulsar/broker/service/persistent/PersistentEntryBucketDispatcherMultipleConsumers.java:101

        }
        List<Range> ranges = new ArrayList<>(count);
        int expectedStart = 0;
        for (int i = 0; i < count; i++) {
            IntRange r = ksm.getHashRangeAt(i);
            if (r.getStart() != expectedStart || r.getEnd() < r.getStart()) {
                throw new IllegalArgumentException("Entry-bucket boundaries must be ascending, "
                        + "contiguous and start at 0: found [" + r.getStart() + "," + r.getEnd()
                        + "] where start " + expectedStart + " was expected");
            }
            ranges.add(Range.of(r.getStart(), r.getEnd()));
            expectedStart = r.getEnd() + 1;
        }
        if (expectedStart != EntryBucketConsumerSelector.DEFAULT_RANGE_SIZE) {
            throw new IllegalArgumentException("Entry-bucket boundaries must tile the 16-bit ring: "
                    + "last range ends at " + (expectedStart - 1));
        }
        if (ranges.get(0).getEnd() < 1) {
            throw new IllegalArgumentException(
                    "Entry-bucket 0 must span at least [0,1] to hold the canonical hash");
        }
        return ranges;
    }

    @Override
    public synchronized CompletableFuture<Void> addConsumer(Consumer consumer) {
        // A segment's bucketing is immutable, so every consumer must declare the boundaries the
        // dispatcher was created with — a mismatch is a client bug or a stale layout, not a race.
        try {
            List<Range> declared = validateBucketBoundaries(consumer.getKeySharedMeta());
            if (!declared.equals(bucketSelector.getBucketRanges())) {
                return CompletableFuture.failedFuture(new BrokerServiceException.ConsumerAssignException(
                        "Consumer declares different entry-bucket boundaries than the subscription: "
                                + declared + " != " + bucketSelector.getBucketRanges()));
            }
        } catch (IllegalArgumentException e) {
            return CompletableFuture.failedFuture(

View on GitHub (pinned to 820761864e)

Solutions

  1. Widen the first range to at least [0,1] (end >= 1) and merge the following segment accordingly, keeping overall contiguity and full ring coverage.
  2. Adjust the boundary generator so the first segment always spans at least two hash values.
  3. If you need 65536 single-hash buckets, use the regular Key_Shared hashing path instead of entry-bucket dispatch — entry-bucket segmentation assumes coarser buckets.

Example fix

// before
ksm.addHashRange().setStart(0).setEnd(0);    // bucket 0 can't hold canonical hash 1
ksm.addHashRange().setStart(1).setEnd(21844);
...
// after
ksm.addHashRange().setStart(0).setEnd(1);    // spans at least [0,1]
ksm.addHashRange().setStart(2).setEnd(21844);
...
Defensive patterns

Strategy: validation

Validate before calling

static void assertFirstBucketHoldsCanonicalHash(KeySharedMeta ksm) {
    if (ksm.getHashRangesCount() == 0 || ksm.getHashRangeAt(0).getEnd() < 1) {
        throw new IllegalArgumentException(
            "first bucket range must end at >= 1 (canonical hash 1)");
    }
}

Type guard

static boolean firstBucketSpansCanonicalHash(KeySharedMeta ksm) {
    return ksm.getHashRangesCount() > 0 && ksm.getHashRangeAt(0).getEnd() >= 1;
}

Prevention

When it happens

Trigger: A boundary list whose first range is exactly [0,0] — typically when a very large number of fine-grained segments each get width 1, or when a generator emits [0,0] as the first single-value bucket.

Common situations: Generating one range per hash value (65536 ranges of width 1), which puts the canonical hash 1 in bucket 1's range and leaves bucket 0 unable to hold its canonical value; scripted boundary generation that doesn't special-case the first segment.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/0593f70a5253cea6. Report an issue: GitHub.