bitflicker64 commented on code in PR #3382:
URL: https://github.com/apache/hugegraph/pull/3382#discussion_r4328882782


##########
hugegraph-pd/hg-pd-core/src/main/java/org/apache/hugegraph/pd/KvService.java:
##########
@@ -297,18 +298,65 @@ public void clearTTLData() {
             byte[] ttlStartKey = getTTLStoreKey("", 0);
             byte[] ttlEndKey = getTTLStoreKey("", System.currentTimeMillis());
             List<KV> kvList = meta.scanRange(ttlStartKey, ttlEndKey);
+            // A marker is K@T@<time>@<key>: the key starts after the 
delimiter that ends the time
+            int timeStart = (KV_PREFIX_DELIMITER + TTL_PREFIX + 
KV_DELIMITER).length();
             for (KV kv : kvList) {
                 String key = new String(kv.getKey());
-                int index = key.indexOf(KV_DELIMITER, 2);
+                int index = key.indexOf(KV_DELIMITER, timeStart);
                 String delKey = key.substring(index + 1);
-                delete(delKey);
-                meta.remove(kv.getKey());
+                byte[] stored = meta.getOne(getStoreKey(delKey));
+                removeExpired(delKey, 
key.substring(KV_PREFIX_DELIMITER.length()), stored);
             }
         } catch (Exception e) {
             log.error("clear ttl data with error :", e);
         }
     }
 
+    /**
+     * Deletes a key whose TTL marker expired, and the marker, while the key 
still holds the
+     * value the marker names. The value the cleanup read must carry the 
marker's expiry, so a
+     * value written after the marker (with another TTL, or none) is kept and 
only the stale
+     * marker goes. Right before the delete, the key must still hold the bytes 
the cleanup read:
+     * a renewal writes another expiry, so its bytes differ even for the same 
string. The check
+     * and the delete are separate store operations, so a renewal that lands 
between them is
+     * still deleted; binding them in one raft entry needs the txn RPC of 
#3376.
+     *
+     * @param key    the key, without the K@ prefix
+     * @param marker the marker key, without the K@ prefix: T@<time>@<key>
+     * @param stored what the cleanup read under the key, null if it is absent
+     */
+    public void removeExpired(String key, String marker, byte[] stored) throws 
PDException {
+        byte[] storeKey = getStoreKey(key);
+        byte[] markerKey = getStoreKey(marker);
+        if (stored == null) {
+            meta.remove(markerKey);
+            return;
+        }
+        V read;
+        try {
+            read = V.parseFrom(stored);
+        } catch (InvalidProtocolBufferException e) {
+            // Not a value KvService wrote: the plain delete of before
+            meta.remove(storeKey);
+            meta.remove(markerKey);
+            return;
+        }
+        int timeEnd = marker.indexOf(KV_DELIMITER, TTL_PREFIX.length() + 1);
+        long expireAt = Long.parseLong(marker.substring(TTL_PREFIX.length() + 
1, timeEnd));
+        if (read.getTtl() != expireAt) {
+            // Written after the marker, with another TTL or none: not the 
value it names
+            meta.remove(markerKey);
+            return;
+        }
+        if (!Arrays.equals(meta.getOne(storeKey), stored)) {
+            // The key changed since the read: keep it, drop the marker that 
named the old value
+            meta.remove(markerKey);
+            return;
+        }
+        meta.remove(storeKey);

Review Comment:
   Important: this deletes a value that has not expired when its marker sorts 
inside the scan range by bytes but not by number.
   
   `clearTTLData` selects markers with `scanRange(K@T@0@, K@T@<now>@)`, and the 
RocksDB iterator compares bytes, so the time is compared as a decimal string. 
Any `expireAt` with more digits than `now` whose leading digits compare lower 
is selected. With `now` = 1792521611424, `putTTL(k, v, 10^13)` gives expireAt 
11792521611424, and the marker `K@T@11792521611424@k` sorts below 
`K@T@1792521611424@` (the second byte, `1` against `7`). The same holds for 
TTLs of 9999999999999, 10^14, 99999999999999 and 10^15. For such a marker, 
`read.getTtl() == expireAt` and the bytes still match, so this line removes a 
value that `get` would still return as live. On master the misparse meant this 
path only dropped the marker and left the value in place; with the parse fixed, 
the value is deleted within a second of being written. `putTTL` does not 
validate the TTL, so a client that passes a large number to mean "keep it" 
loses the key.
   
   Please delete the value only once it has actually expired, using the same 
test `getValue` uses: keep the value when `read.getTtl() >= 
System.currentTimeMillis()`, for example by checking `expireAt` against the 
current time before the delete. Please also add a case to `KvServiceTest` with 
`put(key, value, 10_000_000_000_000L)` followed by `clearTTLData()` that 
asserts the value is still there.



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