apache/pulsar · error · IllegalStateException

Synthetic layout missing segment_id= + partition + (N= + n…

Error message

Synthetic layout missing segment_id= + partition +  (N= + n + )

What it means

SegmentRouter.routeModN maps a message key hash onto an active segment via a sign-safe modulo of the hash by the number of segments (N). This IllegalStateException is thrown when the computed partition index does not match any currently active segment, meaning the in-memory synthetic layout has drifted from the expected 0..N-1 segment id range. The library throws it rather than silently dropping or misrouting the message.

Solutions

  1. Rebuild or refresh the router's activeSegments list so it is contiguous and consistent with the value of n used for the modulo.
  2. Route under the same lock/consistency point that mutates activeSegments so segment removal cannot race with routing.
  3. Verify segment ids cover 0..N-1 at construction; fail fast or recompute n from activeSegments.size() instead of a stale count.
  4. Catch IllegalStateException in route() callers and retry against a refreshed router.

Example fix

// before
int n = configuredSegmentCount;
int partition = signSafeMod(hash32, n);
for (var segment : activeSegments) {
    if (segment.segmentId() == partition) {
        return segment.segmentId();
    }
}
throw new IllegalStateException("Synthetic layout missing segment_id=" + partition + " (N=" + n + ")");
// after
var snapshot = new ArrayList<>(activeSegments); // consistent view, taken under lock
int n = snapshot.size();
int partition = signSafeMod(hash32, n);
return snapshot.stream()
    .filter(s -> s.segmentId() == partition)
    .findFirst()
    .map(Segment::segmentId)
    .orElseGet(() -> snapshot.get(Math.floorMod(partition, snapshot.size())).segmentId());
Defensive patterns

Strategy: validation

Validate before calling

if (activeSegments.size() != n || activeSegments.stream().mapToInt(Segment::segmentId).distinct().count() != n) {
    throw new IllegalStateException("segment layout not contiguous for N=" + n);
}

Try / catch

try { return router.route(key); } catch (IllegalStateException e) { router.refreshLayout(); return router.route(key); }

Prevention

When it happens

Trigger: Calling route() (which delegates to routeModN) when activeSegments does not contain a segment whose segmentId() equals signSafeMod(hash32, n) — e.g. segments were closed/removed, reordered, or the active segment list is sparse while n still counts all segments.

Common situations: A segment was closed or failed while routing continued with a stale count of active segments; concurrent layout changes (segment add/remove) racing with route; constructing the router with non-contiguous segment ids; rebalancing/reshuffling the synthetic layout mid-flight.

Related errors


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

Appendix: source

Thrown at pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/SegmentRouter.java:103

            }
        }
        return true;
    }

    /**
     * Mod-N routing over {@code segment_id}, matching v4 partitioned-topic routing
     * ({@code signSafeMod(murmurHash3_32(key), N)}).
     */
    private static long routeModN(String key, List<ActiveSegment> activeSegments) {
        int hash32 = Murmur3_32Hash.getInstance().makeHash(key.getBytes(StandardCharsets.UTF_8));
        int n = activeSegments.size();
        int partition = signSafeMod(hash32, n);
        for (var segment : activeSegments) {
            if (segment.segmentId() == partition) {
                return segment.segmentId();
            }
        }
        throw new IllegalStateException(
                "Synthetic layout missing segment_id=" + partition + " (N=" + n + ")");
    }

    private static int signSafeMod(int dividend, int divisor) {
        int mod = dividend % divisor;
        return mod < 0 ? mod + divisor : mod;
    }

    /**
     * The raw 32-bit {@code Murmur3_32} hash of a key. Its two 16-bit halves drive the two independent
     * rings — high half → segment routing ({@link #segmentHash}), low half → entry-bucketing
     * ({@link #entryBucketHash}, PIP-486). Compute this <b>once</b> per key and split it, rather than
     * hashing the key twice.
     *
     * <p>The <i>raw</i> (unmasked) hash is required so the high half is full-range: {@code makeHash}
     * clears bit 31, which would confine the high half to {@code [0, 0x7FFF]}.
     */
    static int murmur(byte[] keyBytes) {

View on GitHub (pinned to 820761864e)