{"record":{"id":"8a2bcc7884b3f2d2","repo":"prestodb/presto","slug":"pinot-unexpected-response-8a2bcc","errorCode":"PINOT_UNEXPECTED_RESPONSE","errorMessage":"Empty routingTableEntries for %s. RoutingTable: %s","messagePattern":"Empty routingTableEntries for (.+?)\\. RoutingTable: (.+?)","errorType":"error_code","errorClass":"PinotException","httpStatus":null,"severity":"error","filePath":"presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/PinotClusterInfoFetcher.java","lineNumber":438,"sourceCode":"        catch (Exception e) {\n            return getRoutingTableV1(tableName, responseBody);\n        }\n    }\n\n    private Map<String, Map<String, List<String>>> getRoutingTableV1(String tableName, String responseBody)\n    {\n        ImmutableMap.Builder<String, Map<String, List<String>>> routingTableMap = ImmutableMap.builder();\n        routingTablesJsonCodec.fromJson(responseBody).getRoutingTableSnapshot().forEach(snapshot -> {\n            String tableNameWithType = snapshot.getTableName();\n            // Response could contain info for tableName that matches the original table by prefix.\n            // e.g. when table name is \"table1\", response could contain routingTable for \"table1_staging\"\n            if (!tableName.equals(extractRawTableName(tableNameWithType))) {\n                log.debug(\"Ignoring routingTable for %s\", tableNameWithType);\n            }\n            else {\n                List<Map<String, List<String>>> routingTableEntriesList = snapshot.getRoutingTableEntries();\n                if (routingTableEntriesList.isEmpty()) {\n                    throw new PinotException(\n                            PINOT_UNEXPECTED_RESPONSE,\n                            Optional.empty(),\n                            String.format(\"Empty routingTableEntries for %s. RoutingTable: %s\", tableName, responseBody));\n                }\n\n                // We are given multiple routing tables for a table, each with different segment to host assignments\n                // We pick one randomly, so that a retry may hit a different server\n                Map<String, List<String>> routingTableEntries = routingTableEntriesList.get(new Random().nextInt(routingTableEntriesList.size()));\n                ImmutableMap.Builder<String, List<String>> routingTableBuilder = ImmutableMap.builder();\n                routingTableEntries.forEach((host, segments) -> {\n                    List<String> segmentsCopied = new ArrayList<>(segments);\n                    Collections.shuffle(segmentsCopied);\n                    routingTableBuilder.put(host, ImmutableList.copyOf(segmentsCopied));\n                });\n                routingTableMap.put(tableNameWithType, routingTableBuilder.build());\n            }\n        });\n        return routingTableMap.build();","sourceCodeStart":420,"sourceCodeEnd":456,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/PinotClusterInfoFetcher.java#L420-L456","documentation":"Thrown by getRoutingTableV1 when the controller's routing table response parses successfully but contains no routingTableEntries, i.e. Pinot cannot say which servers host the table's segments. The connector treats this as an unexpected controller response (PINOT_UNEXPECTED_RESPONSE).","triggerScenarios":"GET to the controller's routing table API returns a body whose snapshot has an empty routingTableEntries list for the requested table.","commonSituations":"Table exists but has no segments yet (just created, batch load not finished); table deleted while routing cache was being populated; Pinot controller returning degraded routing state during a rebalance; response body captured in the message shows the raw JSON.","solutions":["Check in Pinot whether the table has segments loaded (curl /tables/<table>/segments)","If the table was just created, wait for the batch/stream ingestion to load segments","Compare the Pinot controller version against the connector's expectations and inspect the logged responseBody","Drop and recreate/reload the table's segments if routing state is permanently empty"],"exampleFix":"// before\ncurl -X POST .../tables/myTable  # created table, no segments\n// after\ncurl -X POST .../segments/myTable/upload  # load segments before querying","handlingStrategy":"validation","validationCode":"// Ensure the table has segments before routing\ncurl -s http://<controller>:9000/tables/<table>/segments | jq '.segments | length'","typeGuard":null,"tryCatchPattern":"try {\n    connector.listTables(...);\n} catch (PinotException e) {\n    if (e.getMessage().contains(\"Empty routingTableEntries\")) {\n        // treat table as not queryable; skip or alert\n    }\n    throw e;\n}","preventionTips":["Never expose tables with zero loaded segments to Presto queries","Wait for ingestion completeness checks before querying new tables","Monitor Pinot routing state during segment rebalances","Log and inspect the responseBody included in the error to debug controller responses"],"tags":["pinot","controller","routing"],"backgroundTag":"pinot-empty-routing-table","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}