Jackie-Jiang commented on code in PR #18980:
URL: https://github.com/apache/pinot/pull/18980#discussion_r4067819244


##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/upsert/UpsertUtils.java:
##########
@@ -116,6 +119,44 @@ public static void doRemoveDocId(IndexSegment segment, int 
docId) {
     }
   }
 
+  /// Estimates the total size in bytes of a primary-key-to-record-location 
map. Samples up to
+  /// [#PRIMARY_KEY_SAMPLE_SIZE] keys to compute the average key content size, 
then extrapolates over all keys,
+  /// so the cost stays bounded regardless of map size. Handles both unhashed 
([PrimaryKey]) and hashed
+  /// ([ByteArray]) keys.
+  ///
+  /// @param perEntryOverheadBytes estimated fixed per-entry overhead (e.g. 
map node + record location object)
+  public static long estimatePrimaryKeyMapSizeInBytes(Map<Object, ?> 
primaryKeyToRecordLocationMap,
+      long perEntryOverheadBytes) {
+    int numKeys = primaryKeyToRecordLocationMap.size();
+    if (numKeys == 0) {
+      return 0;
+    }
+    long sampledKeyBytes = 0;
+    int numSampled = 0;
+    for (Object key : primaryKeyToRecordLocationMap.keySet()) {
+      sampledKeyBytes += getPrimaryKeyContentSizeInBytes(key);
+      if (++numSampled >= PRIMARY_KEY_SAMPLE_SIZE) {
+        break;
+      }

Review Comment:
   [P2] The sample limit does not bound map traversal
   
   `ConcurrentHashMap` iteration scans empty buckets, and the backing table 
does not shrink after deletions. With fewer than 1,000 remaining keys, this 
scans the entire retained table. In an isolated JDK 25 benchmark, 64 keys took 
approximately 4 ms with 2 million buckets and 65 ms with 32 million buckets. 
This runs synchronously during segment transitions and adds another traversal 
after TTL cleanup.
   
   Please avoid arbitrary map traversal if bounded work is required, and 
coalesce or rate-limit refreshes per partition. The current O(1) claim is not 
valid.



##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/upsert/UpsertUtils.java:
##########
@@ -116,6 +119,44 @@ public static void doRemoveDocId(IndexSegment segment, int 
docId) {
     }
   }
 
+  /// Estimates the total size in bytes of a primary-key-to-record-location 
map. Samples up to
+  /// [#PRIMARY_KEY_SAMPLE_SIZE] keys to compute the average key content size, 
then extrapolates over all keys,
+  /// so the cost stays bounded regardless of map size. Handles both unhashed 
([PrimaryKey]) and hashed
+  /// ([ByteArray]) keys.
+  ///
+  /// @param perEntryOverheadBytes estimated fixed per-entry overhead (e.g. 
map node + record location object)
+  public static long estimatePrimaryKeyMapSizeInBytes(Map<Object, ?> 
primaryKeyToRecordLocationMap,
+      long perEntryOverheadBytes) {
+    int numKeys = primaryKeyToRecordLocationMap.size();
+    if (numKeys == 0) {
+      return 0;
+    }
+    long sampledKeyBytes = 0;
+    int numSampled = 0;
+    for (Object key : primaryKeyToRecordLocationMap.keySet()) {
+      sampledKeyBytes += getPrimaryKeyContentSizeInBytes(key);
+      if (++numSampled >= PRIMARY_KEY_SAMPLE_SIZE) {
+        break;
+      }
+    }
+    if (numSampled == 0) {
+      // ponytail: map drained concurrently between size() and keySet() 
iteration; nothing sampled.
+      return 0;
+    }
+    double avgKeyBytes = (double) sampledKeyBytes / numSampled;
+    return (long) (numKeys * (avgKeyBytes + perEntryOverheadBytes));
+  }
+
+  private static int getPrimaryKeyContentSizeInBytes(Object key) {
+    if (key instanceof PrimaryKey) {
+      return ((PrimaryKey) key).asBytes().length;

Review Comment:
   [P2] Estimate retained memory without serializing every sampled key
   
   `PrimaryKey.asBytes()` allocates and encodes data just to read its length. 
Sampling 1,000 four-kilobyte string keys allocated approximately 4.11 MB per 
refresh; four 256-character string columns allocated approximately 2.18 MB. 
Preloading repeats this work for each completed segment.
   
   Serialized length also misses the `PrimaryKey` wrapper, its values array, 
boxed values, and other retained objects. Please estimate directly from the 
existing key objects, including object/array overhead. The overall map estimate 
should also account for comparison values, which the shallow `RecordLocation` 
size excludes.



-- 
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