raghavyadav01 commented on code in PR #19041:
URL: https://github.com/apache/pinot/pull/19041#discussion_r3752369533


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java:
##########
@@ -246,17 +254,57 @@ public void seal()
       writeSparseJsonColumn(sparseKeys);
     }
 
-    if (_coercionFailures > 0) {
-      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures", _columnName, _coercionFailures);
-      ServerMetrics serverMetrics = ServerMetrics.get();
-      if (serverMetrics != null) {
-        
serverMetrics.addMeteredGlobalValue(ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES,
 _coercionFailures);
-      }
+    long totalCoercionFailures = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercionFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures (keys: {})",
+          _columnName, totalCoercionFailures, _coercionFailuresPerKey);
+    }
+    long totalInferenceFailures = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInferenceFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': {} type inference failures fell back to 
STRING (keys: {})",
+          _columnName, totalInferenceFailures, _inferenceFailuresPerKey);
     }
+    emitMetrics(sparseKeys.size());
 
     emitParentColumnMetadata(sparseKeys);
   }
 
+  private void emitMetrics(int sparseKeyCount) {
+    ServerMetrics serverMetrics = ServerMetrics.get();
+    if (serverMetrics == null || _numDocs == 0) {
+      return;
+    }
+    String col = _columnName;
+
+    long totalCoercion = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercion > 0) {
+      serverMetrics.addMeteredTableValue(_tableNameWithType, col,
+          ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES, totalCoercion);
+    }
+    long totalInference = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInference > 0) {
+      serverMetrics.addMeteredTableValue(_tableNameWithType, col,
+          ServerMeter.OPEN_STRUCT_TYPE_INFERENCE_FAILURES, totalInference);
+    }
+
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_DENSE_KEY_COUNT, _resolvedDenseKeys.size());
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_SPARSE_KEY_COUNT, sparseKeyCount);
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_TOTAL_KEYS_DISCOVERED, 
_presenceBitmaps.size());
+
+    for (String key : _resolvedDenseKeys) {
+      RoaringBitmap presence = _presenceBitmaps.get(key);
+      if (presence != null) {
+        long fillPct = (long) presence.getCardinality() * 100 / _numDocs;
+        serverMetrics.setOrUpdateTableGauge(_tableNameWithType,
+            OpenStructNaming.materializedColumnName(col, key),

Review Comment:
   Have you checked how this one scrapes? `OpenStructNaming.SEPARATOR` is `$`, 
and every key/partition rule in `server.yml` uses `\w+` for that segment, so I 
don't think `openStructKeyFillRate.<table>.metrics$clicks` matches any rule — 
it would fall through to the javaagent default name with the key baked into the 
metric name and no `table`/`tableType` labels. Same for the per-key meters in 
`MutableOpenStructIndex`.
   
   Would it make sense to either drop the per-key dimension here, or add a 
`server.yml` rule that exports the key as a `column` label (with `$` sanitized)?
   
   On verification — `ServerPrometheusMetricsTest` is data-provider-driven over 
all `ServerGauge` values, so these do get exercised, but only with plain table 
names and int partitions. Could we add a case with a `$`-bearing key so CI 
actually covers the exported shape?



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/index/openstruct/MutableOpenStructIndex.java:
##########
@@ -162,7 +171,9 @@ private Object tryCoerce(String key, Object rawValue, 
DataType storedType) {
     } catch (Exception e) {
       ServerMetrics serverMetrics = ServerMetrics.get();
       if (serverMetrics != null) {
-        
serverMetrics.addMeteredGlobalValue(ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES,
 1);
+        serverMetrics.addMeteredTableValue(_tableNameWithType,
+            OpenStructNaming.materializedColumnName(_openStructColumn, key),
+            ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES, 1);

Review Comment:
   Does the unbounded-registry concern from the earlier thread still apply on 
this path? The splitter side was moved to `_resolvedDenseKeys`, but both meters 
here are still keyed on the raw ingested key, and meters are never removed from 
the registry — so a stream whose map keys carry ids would mint one meter per 
key for the lifetime of the process.
   
   Also, on the "config-bounded" reasoning for the splitter: 
`OpenStructIndexConfig.DEFAULT_MAX_DENSE_KEYS` is `-1` and `denseKeys` defaults 
to empty, so with default config `_resolvedDenseKeys` is every key at >= 50% 
fill — data-driven, not config-driven. Across segments the union of those keys 
is unbounded too, and each one leaves a gauge behind holding whatever the 
last-sealed segment wrote.
   
   Would you be open to keeping both meters at column granularity (the per-key 
detail is already in the log line), and gating the fill-rate gauge on an 
explicit `denseKeys` list or `maxDenseKeys > 0`?



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java:
##########
@@ -246,17 +254,57 @@ public void seal()
       writeSparseJsonColumn(sparseKeys);
     }
 
-    if (_coercionFailures > 0) {
-      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures", _columnName, _coercionFailures);
-      ServerMetrics serverMetrics = ServerMetrics.get();
-      if (serverMetrics != null) {
-        
serverMetrics.addMeteredGlobalValue(ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES,
 _coercionFailures);
-      }
+    long totalCoercionFailures = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercionFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures (keys: {})",
+          _columnName, totalCoercionFailures, _coercionFailuresPerKey);
+    }
+    long totalInferenceFailures = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInferenceFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': {} type inference failures fell back to 
STRING (keys: {})",
+          _columnName, totalInferenceFailures, _inferenceFailuresPerKey);
     }
+    emitMetrics(sparseKeys.size());
 
     emitParentColumnMetadata(sparseKeys);
   }
 
+  private void emitMetrics(int sparseKeyCount) {
+    ServerMetrics serverMetrics = ServerMetrics.get();
+    if (serverMetrics == null || _numDocs == 0) {
+      return;
+    }
+    String col = _columnName;
+
+    long totalCoercion = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercion > 0) {
+      serverMetrics.addMeteredTableValue(_tableNameWithType, col,
+          ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES, totalCoercion);
+    }
+    long totalInference = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInference > 0) {
+      serverMetrics.addMeteredTableValue(_tableNameWithType, col,
+          ServerMeter.OPEN_STRUCT_TYPE_INFERENCE_FAILURES, totalInference);
+    }
+
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_DENSE_KEY_COUNT, _resolvedDenseKeys.size());
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_SPARSE_KEY_COUNT, sparseKeyCount);
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_TOTAL_KEYS_DISCOVERED, 
_presenceBitmaps.size());

Review Comment:
   What's the intended reading of these three when a server hosts many segments 
of the same table? They're keyed on `(table, column)` with no segment 
dimension, so each seal overwrites the previous one and the value ends up 
meaning "whatever the last segment to seal here saw". 
`OPEN_STRUCT_TOTAL_KEYS_DISCOVERED` in particular reads like a table-wide count.
   
   Also, nothing calls `removeTableGauge` — is it worth clearing these when the 
table is deleted, or is the stale-value-until-restart behaviour acceptable here?



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java:
##########
@@ -201,7 +205,11 @@ private void addMap(@Nullable Map<String, Object> map) {
             ? keySpec.getDataType()
             : _inferredTypes.computeIfAbsent(key, k -> {
               DataType inferred = 
OpenStructTypeInference.inferDataType(rawValue);
-              return inferred != null ? inferred : DataType.STRING;
+              if (inferred == null) {
+                _inferenceFailuresPerKey.merge(key, 1L, Long::sum);
+                return DataType.STRING;
+              }
+              return inferred;
             });

Review Comment:
   Should this increment live outside `computeIfAbsent`? The mapping function 
only runs on the first sighting of a key, so a million unmappable values on one 
key records `1` — but the meter's unit is `"values"` and the description says 
"Number of OPEN_STRUCT entries dropped".
   
   Related: nothing is dropped here, the key falls back to STRING. 
`MutableOpenStructIndex.resolveStoredType` hits the same condition and returns 
`null`, dropping the entry. Is the same meter meant to cover both? A Map- or 
List-valued key would then be absent in the consuming segment but materialized 
as `toString()` output after seal, which `OpenStructConsumingSealedParityTest` 
doesn't currently cover — worth a case there if that divergence is intentional.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java:
##########
@@ -246,17 +254,57 @@ public void seal()
       writeSparseJsonColumn(sparseKeys);
     }
 
-    if (_coercionFailures > 0) {
-      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures", _columnName, _coercionFailures);
-      ServerMetrics serverMetrics = ServerMetrics.get();
-      if (serverMetrics != null) {
-        
serverMetrics.addMeteredGlobalValue(ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES,
 _coercionFailures);
-      }
+    long totalCoercionFailures = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercionFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures (keys: {})",
+          _columnName, totalCoercionFailures, _coercionFailuresPerKey);
+    }
+    long totalInferenceFailures = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInferenceFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': {} type inference failures fell back to 
STRING (keys: {})",
+          _columnName, totalInferenceFailures, _inferenceFailuresPerKey);
     }
+    emitMetrics(sparseKeys.size());
 
     emitParentColumnMetadata(sparseKeys);
   }
 
+  private void emitMetrics(int sparseKeyCount) {
+    ServerMetrics serverMetrics = ServerMetrics.get();
+    if (serverMetrics == null || _numDocs == 0) {
+      return;
+    }
+    String col = _columnName;
+
+    long totalCoercion = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercion > 0) {
+      serverMetrics.addMeteredTableValue(_tableNameWithType, col,
+          ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES, totalCoercion);
+    }
+    long totalInference = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInference > 0) {
+      serverMetrics.addMeteredTableValue(_tableNameWithType, col,
+          ServerMeter.OPEN_STRUCT_TYPE_INFERENCE_FAILURES, totalInference);
+    }
+
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_DENSE_KEY_COUNT, _resolvedDenseKeys.size());
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_SPARSE_KEY_COUNT, sparseKeyCount);
+    serverMetrics.setOrUpdateTableGauge(_tableNameWithType, col,
+        ServerGauge.OPEN_STRUCT_TOTAL_KEYS_DISCOVERED, 
_presenceBitmaps.size());
+
+    for (String key : _resolvedDenseKeys) {
+      RoaringBitmap presence = _presenceBitmaps.get(key);
+      if (presence != null) {
+        long fillPct = (long) presence.getCardinality() * 100 / _numDocs;

Review Comment:
   Is integer truncation OK here? `classify()` adds configured `denseKeys` 
before the fill-rate check, so a key present in 3 of 100k docs is dense and 
reports `0` — indistinguishable from no data, though that's exactly the case 
worth flagging (a configured dense key wasting a materialized column). Would 
scaling by 10000, or emitting cardinality and `_numDocs` separately, work 
better?



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/segment/creator/impl/openstruct/OpenStructColumnSplitter.java:
##########
@@ -246,17 +254,57 @@ public void seal()
       writeSparseJsonColumn(sparseKeys);
     }
 
-    if (_coercionFailures > 0) {
-      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures", _columnName, _coercionFailures);
-      ServerMetrics serverMetrics = ServerMetrics.get();
-      if (serverMetrics != null) {
-        
serverMetrics.addMeteredGlobalValue(ServerMeter.OPEN_STRUCT_TYPE_COERCION_FAILURES,
 _coercionFailures);
-      }
+    long totalCoercionFailures = 
_coercionFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalCoercionFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': dropped {} values due to type coercion 
failures (keys: {})",
+          _columnName, totalCoercionFailures, _coercionFailuresPerKey);
+    }
+    long totalInferenceFailures = 
_inferenceFailuresPerKey.values().stream().mapToLong(Long::longValue).sum();
+    if (totalInferenceFailures > 0) {
+      LOGGER.info("OPEN_STRUCT '{}': {} type inference failures fell back to 
STRING (keys: {})",
+          _columnName, totalInferenceFailures, _inferenceFailuresPerKey);
     }

Review Comment:
   Should these be DEBUG? The key space is user-controlled, so printing the 
whole map at INFO on every seal could flood the logs for a wide struct.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to