{"record":{"id":"af0fe4e168fcf911","repo":"apache/pulsar","slug":"segment-topic-not-loaded-segmenttopic","errorCode":null,"errorMessage":"Segment topic not loaded: ${segmentTopic}","messagePattern":"Segment topic not loaded: (.+?)","errorType":"http","errorClass":"RestException","httpStatus":404,"severity":"warning","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/Segments.java","lineNumber":321,"sourceCode":"            @Parameter(description = \"Segment descriptor (e.g. 0000-7fff-1)\", required = true)\n            @PathParam(\"descriptor\") String descriptor,\n            @Parameter(description = \"Subscription name\", required = true)\n            @PathParam(\"subscription\") String subscription,\n            @Parameter(description = \"Whether leader broker redirected this call to this broker.\")\n            @QueryParam(\"authoritative\") @DefaultValue(\"false\") boolean authoritative) {\n        validateNamespaceName(tenant, namespace);\n        TopicName segmentTopic = segmentTopicName(tenant, namespace, encodedTopic, descriptor);\n\n        validateSuperUserAccessAsync()\n                .thenCompose(__ -> validateTopicOwnershipAsync(segmentTopic, authoritative))\n                .thenCompose(__ -> pulsar().getBrokerService().getTopicIfExists(segmentTopic.toString()))\n                .thenAccept(optTopic -> {\n                    if (optTopic.isEmpty()) {\n                        // No topic loaded → no subscription cursor → no backlog. Returning\n                        // 0 here would be wrong (caller might mark the segment drained on\n                        // a topic that simply hasn't loaded yet); a 404 forces the caller\n                        // to retry, which matches our drain-poll contract.\n                        throw new RestException(Response.Status.NOT_FOUND,\n                                \"Segment topic not loaded: \" + segmentTopic);\n                    }\n                    var sub = optTopic.get().getSubscription(subscription);\n                    if (sub == null) {\n                        throw new RestException(Response.Status.NOT_FOUND,\n                                \"Subscription not found on segment: \" + subscription);\n                    }\n                    asyncResponse.resume(sub.getNumberOfEntriesInBacklog(false));\n                })\n                .exceptionally(ex -> {\n                    log.error().attr(\"clientAppId\", clientAppId()).attr(\"segment\", segmentTopic)\n                            .exception(ex).log(\"Failed to get segment subscription backlog\");\n                    resumeAsyncResponseExceptionally(asyncResponse, ex);\n                    return null;\n                });\n    }\n\n    @POST","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/admin/v2/Segments.java#L303-L339","documentation":"HTTP 404 thrown by the segment subscription-backlog endpoint when the segment topic is not currently loaded on a broker. The endpoint deliberately refuses to return 0 backlog for an unloaded topic, because that could be misread as 'segment drained'; a 404 tells the caller to retry once the topic loads.","triggerScenarios":"Polling GET backlog for a segment whose topic has no active broker ownership — right after broker restart, before any client touched the segment (segments are not auto-created/loaded), or after the segment was deleted.","commonSituations":"Drain-monitoring loops polling segments that have never received traffic; querying backlog immediately after a broker failover before bundles reassign; polling a segment of an idle scalable topic where the broker unloaded it.","solutions":["Make the drain-poll loop treat 404 as 'retry later' and re-poll after a delay.","Force the segment topic to load by connecting a reader/producer or an admin lookup before polling backlog.","Verify the segment still exists in the scalable topic metadata; remove it from the drain list if deleted."],"exampleFix":"// before\nconst backlog = await getBacklog(seg); // throws 404 when unloaded\n// after\nlet backlog;\ntry { backlog = await getBacklog(seg); }\ncatch (e) { if (e.status === 404) { await sleep(retryMs); continue; } throw e; }","handlingStrategy":"retry","validationCode":"const md = await admin.scalableTopics().getScalableTopicMetadataAsync(tenant, ns, parentTopic);\nif (!md.getSegments().containsKey(segmentId)) throw new Error('unknown segment');","typeGuard":null,"tryCatchPattern":"try {\n  const backlog = await admin.scalableTopics().getSubscriptionBacklog(segmentTopic, sub);\n} catch (e) {\n  if (e.status === 404 && /not loaded/.test(e.message)) { await sleep(pollMs); return pollBacklog(segmentTopic, sub); }\n  throw e;\n}","preventionTips":["In drain-poll loops, treat 404 as transient and retry with backoff — never as 'drained'.","Ensure segments are loaded (reader attached or lookup performed) before measuring backlog.","Avoid polling segments immediately after broker restarts; wait for bundle ownership to settle."],"tags":["rest","not-found","segment","backlog","polling","http-404"],"backgroundTag":"topic-not-loaded","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}