jbonofre commented on code in PR #5122:
URL: https://github.com/apache/polaris/pull/5122#discussion_r3653008831


##########
runtime/service/src/main/java/org/apache/polaris/service/idempotency/EntityIdempotency.java:
##########
@@ -130,24 +175,31 @@ public static Map<String, String> recordKey(
   }
 
   private static List<KeyEntry> decode(String raw) {
-    if (raw == null || raw.isEmpty()) {
+    if (raw == null || !raw.startsWith(FORMAT_SMILE_V1)) {
       return List.of();
     }
-    if (!raw.startsWith(WINDOW_FORMAT_SMILE_V1)) {
-      throw new IllegalArgumentException("Unrecognized idempotency key window 
format");
-    }
-    byte[] smile = 
Base64.getUrlDecoder().decode(raw.substring(WINDOW_FORMAT_SMILE_V1.length()));
+    // An unreadable window (bad base64 or SMILE decode failure) is treated as 
no live keys rather
+    // than failing the operation: a corrupt property degrades to normal 
behavior and the next
+    // recordKey overwrites it.
     try {
+      byte[] smile = 
Base64.getUrlDecoder().decode(raw.substring(FORMAT_SMILE_V1.length()));
       return SMILE_MAPPER.readValue(smile, new TypeReference<List<KeyEntry>>() 
{});
-    } catch (IOException e) {
-      throw new RuntimeException("Failed to decode idempotency key window", e);
+    } catch (IllegalArgumentException | IOException e) {
+      // Server-written data, so a decode failure means corruption or a 
tampered property; surface

Review Comment:
   nit: the code does not surface it to the caller (it swallows and logs WARN). 
I read "Surface it" as "throw". I just suggest to reword from "surface it" to 
"log it".



##########
runtime/service/src/main/java/org/apache/polaris/service/idempotency/EntityIdempotency.java:
##########
@@ -52,20 +56,32 @@
  * evolve the encoding later without ambiguity.
  *
  * <p>For {@code createTable} this window holds a single entry, but the shape 
generalizes to a
- * bounded per-entity window for repeated mutations (e.g. {@code 
updateTable}). Expired keys are
- * dropped inline whenever the entity is rewritten ({@link #recordKey}); 
expiry is also honored at
- * read time ({@link #hasLiveKey}) so a stale-but-not-yet-purged key is 
treated as absent.
+ * per-entity window for repeated mutations (e.g. {@code updateTable}). The 
window is bounded only
+ * by expiry (TTL): expired keys are dropped inline whenever the entity is 
rewritten ({@link
+ * #recordKey}), and expiry is also honored at read time ({@link #hasLiveKey}) 
so a
+ * stale-but-not-yet-purged key is treated as absent. Live keys are never 
evicted by count; a large
+ * safety ceiling ({@link #MAX_LIVE_KEYS}) bounds the window by failing the 
write rather than
+ * dropping a still-live key.
  */
 public final class EntityIdempotency {
 
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(EntityIdempotency.class);
+
   /** Internal-properties key under which the per-entity idempotency key 
window is stored. */
   public static final String IDEMPOTENCY_KEYS_PROPERTY = 
"polaris-idempotency-keys";
 
-  /** Upper bound on live idempotency keys stored on a single entity. */
-  public static final int MAX_WINDOW_SIZE = 64;
-
   /** Magic prefix for the version 1 SMILE window ({@code IS1} + base64url 
bytes). */
-  private static final String WINDOW_FORMAT_SMILE_V1 = "IS1";
+  private static final String FORMAT_SMILE_V1 = "IS1";
+
+  /**
+   * Safety ceiling on the number of live keys retained on a single entity. 
Unlike a count-based
+   * eviction cap, this never drops a live key: when the window is full the 
write fails instead, so
+   * a key that could still be retried is never silently discarded. The limit 
is deliberately high —
+   * reaching it requires this many successful commits to one table within the 
key TTL — so
+   * legitimate workloads never hit it, while still bounding stored 
idempotency data against
+   * unbounded growth (a DoS guardrail).
+   */
+  private static final int MAX_LIVE_KEYS = 10_000;

Review Comment:
   I have a question about this value (10,000) vs the storage limits.
   
   If we have 10,000 UUID entries (encoded with base64), it means about several 
hundred KB stored in a single internal-property value. Depending on the 
persistence backend's column/document size limit, a write could fail at the 
storage layer before the 10,000 guardrail (as a non-retryable error, not the 
graceful 503 error code intends).
   I think it would be good to double check the guardrail sits below any real 
storage ceiling, or a least noting the assumption. 



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

Reply via email to