|
19 | 19 | package org.apache.pinot.controller.util;
|
20 | 20 |
|
21 | 21 | import com.fasterxml.jackson.databind.JsonNode;
|
| 22 | +import com.fasterxml.jackson.databind.ObjectMapper; |
| 23 | +import com.fasterxml.jackson.databind.node.ObjectNode; |
22 | 24 | import com.google.common.collect.BiMap;
|
23 | 25 | import java.io.IOException;
|
| 26 | +import java.util.ArrayList; |
24 | 27 | import java.util.Collections;
|
25 | 28 | import java.util.HashMap;
|
26 | 29 | import java.util.HashSet;
|
|
29 | 32 | import java.util.Set;
|
30 | 33 | import java.util.concurrent.Executor;
|
31 | 34 | import java.util.stream.Collectors;
|
| 35 | +import javax.annotation.Nullable; |
32 | 36 | import org.apache.hc.client5.http.io.HttpClientConnectionManager;
|
33 | 37 | import org.apache.helix.model.ExternalView;
|
34 | 38 | import org.apache.pinot.common.exception.InvalidConfigException;
|
|
40 | 44 | import org.apache.pinot.spi.utils.CommonConstants;
|
41 | 45 | import org.apache.pinot.spi.utils.JsonUtils;
|
42 | 46 | import org.apache.pinot.spi.utils.builder.TableNameBuilder;
|
| 47 | +import org.slf4j.Logger; |
| 48 | +import org.slf4j.LoggerFactory; |
43 | 49 |
|
44 | 50 |
|
45 | 51 | /**
|
|
50 | 56 | * the column indexes available.
|
51 | 57 | */
|
52 | 58 | public class TableMetadataReader {
|
| 59 | + private static final Logger log = LoggerFactory.getLogger(TableMetadataReader.class); |
53 | 60 | private final Executor _executor;
|
54 | 61 | private final HttpClientConnectionManager _connectionManager;
|
55 | 62 | private final PinotHelixResourceManager _pinotHelixResourceManager;
|
@@ -127,50 +134,95 @@ private TableReloadJsonResponse processSegmentMetadataReloadResponse(
|
127 | 134 |
|
128 | 135 | /**
|
129 | 136 | * This api takes in list of segments for which we need the metadata.
|
| 137 | + * This calls the server to get the metadata for all segments instead of making a call per segment. |
130 | 138 | */
|
131 |
| - public JsonNode getSegmentsMetadata(String tableNameWithType, List<String> columns, Set<String> segmentsToInclude, |
132 |
| - int timeoutMs) |
| 139 | + public JsonNode getSegmentsMetadata(String tableNameWithType, @Nullable List<String> columns, |
| 140 | + @Nullable List<String> segments, int timeoutMs) |
133 | 141 | throws InvalidConfigException, IOException {
|
134 |
| - return getSegmentsMetadataInternal(tableNameWithType, columns, segmentsToInclude, timeoutMs); |
| 142 | + return getSegmentsMetadataInternal(tableNameWithType, columns, segments, timeoutMs); |
135 | 143 | }
|
136 | 144 |
|
137 |
| - private JsonNode getSegmentsMetadataInternal(String tableNameWithType, List<String> columns, |
138 |
| - Set<String> segmentsToInclude, int timeoutMs) |
| 145 | + /** |
| 146 | + * Common helper used by both the new (server-level) and legacy (segment-level) endpoints. |
| 147 | + */ |
| 148 | + private JsonNode fetchAndAggregateMetadata(List<String> urls, BiMap<String, String> endpoints, boolean perSegmentJson, |
| 149 | + String tableNameWithType, int timeoutMs) |
139 | 150 | throws InvalidConfigException, IOException {
|
140 |
| - final Map<String, List<String>> serverToSegmentsMap = |
141 |
| - _pinotHelixResourceManager.getServerToSegmentsMap(tableNameWithType); |
142 |
| - BiMap<String, String> endpoints = |
143 |
| - _pinotHelixResourceManager.getDataInstanceAdminEndpoints(serverToSegmentsMap.keySet()); |
144 |
| - ServerSegmentMetadataReader serverSegmentMetadataReader = |
145 |
| - new ServerSegmentMetadataReader(_executor, _connectionManager); |
| 151 | + CompletionServiceHelper cs = new CompletionServiceHelper(_executor, _connectionManager, endpoints); |
| 152 | + CompletionServiceHelper.CompletionServiceResponse resp = |
| 153 | + cs.doMultiGetRequest(urls, tableNameWithType, perSegmentJson, timeoutMs); |
| 154 | + // all requests will fail if new server endpoint is not available |
| 155 | + if (resp._failedResponseCount > 0) { |
| 156 | + throw new RuntimeException(String.format("Got %d failed responses from total %d server instances. " |
| 157 | + + "Falling back to legacy segment metadata api", resp._failedResponseCount, urls.size())); |
| 158 | + } |
146 | 159 |
|
147 |
| - // Filter segments that we need |
148 |
| - for (Map.Entry<String, List<String>> serverToSegment : serverToSegmentsMap.entrySet()) { |
149 |
| - List<String> segments = serverToSegment.getValue(); |
150 |
| - if (segmentsToInclude != null && !segmentsToInclude.isEmpty()) { |
151 |
| - segments.retainAll(segmentsToInclude); |
| 160 | + ObjectMapper mapper = new ObjectMapper(); |
| 161 | + ObjectNode aggregatedNode = mapper.createObjectNode(); |
| 162 | + for (String body : resp._httpResponses.values()) { |
| 163 | + JsonNode node = JsonUtils.stringToJsonNode(body); |
| 164 | + // legacy returns one JSON per segment; new returns one JSON with many fields |
| 165 | + if (perSegmentJson) { |
| 166 | + String segmentName = node.get("segmentName").asText(); |
| 167 | + aggregatedNode.set(segmentName, node); |
| 168 | + } else { |
| 169 | + node.fields().forEachRemaining(entry -> aggregatedNode.set(entry.getKey(), entry.getValue())); |
152 | 170 | }
|
153 | 171 | }
|
| 172 | + return aggregatedNode; |
| 173 | + } |
154 | 174 |
|
155 |
| - List<String> segmentsMetadata = |
156 |
| - serverSegmentMetadataReader.getSegmentMetadataFromServer(tableNameWithType, serverToSegmentsMap, endpoints, |
157 |
| - columns, timeoutMs); |
158 |
| - Map<String, JsonNode> response = new HashMap<>(); |
159 |
| - for (String segmentMetadata : segmentsMetadata) { |
160 |
| - JsonNode responseJson = JsonUtils.stringToJsonNode(segmentMetadata); |
161 |
| - response.put(responseJson.get("segmentName").asText(), responseJson); |
| 175 | + private List<String> buildTableLevelUrls(Map<String, List<String>> serverToSegs, BiMap<String, String> endpoints, |
| 176 | + String tableNameWithType, List<String> columns, List<String> segmentsFilter, ServerSegmentMetadataReader reader) { |
| 177 | + List<String> urls = new ArrayList<>(serverToSegs.size()); |
| 178 | + for (String server : serverToSegs.keySet()) { |
| 179 | + urls.add(reader.generateTableMetadataServerURL( |
| 180 | + tableNameWithType, columns, segmentsFilter, endpoints.get(server))); |
162 | 181 | }
|
163 |
| - return JsonUtils.objectToJsonNode(response); |
| 182 | + return urls; |
164 | 183 | }
|
165 | 184 |
|
166 |
| - /** |
167 |
| - * This method retrieves the full segment metadata for a given table. |
168 |
| - * Currently supports only OFFLINE tables. |
169 |
| - * @return a map of segmentName to its metadata |
170 |
| - */ |
171 |
| - public JsonNode getSegmentsMetadata(String tableNameWithType, List<String> columns, int timeoutMs) |
| 185 | + private List<String> buildSegmentLevelUrls(Map<String, List<String>> serverToSegs, BiMap<String, String> endpoints, |
| 186 | + String tableNameWithType, List<String> columns, List<String> segmentsFilter, ServerSegmentMetadataReader reader) { |
| 187 | + List<String> urls = new ArrayList<>(); |
| 188 | + for (Map.Entry<String, List<String>> e : serverToSegs.entrySet()) { |
| 189 | + for (String segment : e.getValue()) { |
| 190 | + if (segmentsFilter == null || segmentsFilter.isEmpty() |
| 191 | + || segmentsFilter.contains(segment)) { |
| 192 | + urls.add(reader.generateSegmentMetadataServerURL( |
| 193 | + tableNameWithType, segment, columns, endpoints.get(e.getKey()))); |
| 194 | + } |
| 195 | + } |
| 196 | + } |
| 197 | + return urls; |
| 198 | + } |
| 199 | + |
| 200 | + private JsonNode getSegmentsMetadataInternal(String tableNameWithType, @Nullable List<String> columns, |
| 201 | + @Nullable List<String> segments, int timeoutMs) |
172 | 202 | throws InvalidConfigException, IOException {
|
173 |
| - return getSegmentsMetadataInternal(tableNameWithType, columns, null, timeoutMs); |
| 203 | + Map<String, List<String>> serverToSegs = |
| 204 | + _pinotHelixResourceManager.getServerToSegmentsMap(tableNameWithType); |
| 205 | + BiMap<String, String> endpoints = |
| 206 | + _pinotHelixResourceManager.getDataInstanceAdminEndpoints(serverToSegs.keySet()); |
| 207 | + ServerSegmentMetadataReader reader = |
| 208 | + new ServerSegmentMetadataReader(_executor, _connectionManager); |
| 209 | + |
| 210 | + // try table level endpoint first |
| 211 | + try { |
| 212 | + List<String> tableUrls = buildTableLevelUrls(serverToSegs, endpoints, |
| 213 | + tableNameWithType, columns, segments, reader); |
| 214 | + return fetchAndAggregateMetadata(tableUrls, endpoints, /*perSegmentJson=*/false, |
| 215 | + tableNameWithType, timeoutMs); |
| 216 | + } catch (RuntimeException e) { |
| 217 | + log.warn("Failed to fetch table metadata for table {} using new server endpoint, falling back to legacy " |
| 218 | + + "per-segment endpoint", tableNameWithType, e); |
| 219 | + } |
| 220 | + |
| 221 | + // legacy per segment endpoint |
| 222 | + List<String> segmentUrls = buildSegmentLevelUrls(serverToSegs, endpoints, |
| 223 | + tableNameWithType, columns, segments, reader); |
| 224 | + return fetchAndAggregateMetadata(segmentUrls, endpoints.inverse(), /*perSegmentJson=*/true, |
| 225 | + tableNameWithType, timeoutMs); |
174 | 226 | }
|
175 | 227 |
|
176 | 228 | /**
|
|
0 commit comments