jerryshao commented on code in PR #13619:
URL: https://github.com/apache/gravitino/pull/13619#discussion_r4215479315


##########
core/src/main/java/org/apache/gravitino/storage/relational/EntityChangeLogPoller.java:
##########
@@ -190,28 +207,90 @@ public void close() {
   }
 
   @VisibleForTesting
-  void pollChanges() {
+  ScheduledExecutorService createScheduler() {
+    ScheduledThreadPoolExecutor executor =
+        new ScheduledThreadPoolExecutor(
+            1,
+            r -> {
+              Thread t = new Thread(r);
+              t.setName("Gravitino-EntityChangeLogPoller");
+              t.setDaemon(true);
+              return t;
+            });
+    // Each poll is a one-shot delayed task. By default such tasks still run 
after shutdown(), so
+    // close() would wait for the pending poll and run it against a store that 
is shutting down.
+    executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);

Review Comment:
   [Nit] This comment overstates what the policy does, and the overstatement is 
the kind a future reader could act on.
   
   `ScheduledThreadPoolExecutor.onShutdown()` only removes a non-periodic task 
when `!keepDelayed && t.getDelay(NANOSECONDS) > 0`. A task whose delay has 
already elapsed is kept and still runs after `shutdown()`, regardless of this 
policy. Every drain hop schedules with delay `0` (`runPollCycle`, line 238), so 
for a poller that is actively draining a backlog -- the exact case this PR adds 
-- `close()` does leave an already-due task that runs.
   
   It is harmless today, but only because `runPollCycle` returns at line 232 on 
the `closed` flag, and `close()` sets `closed` before `shutdown()`. So the real 
guarantee is the `closed` guard; the policy only helps the idle case. Worth 
saying that here, so nobody later reads the `closed` check as redundant with 
the policy and deletes it. `testCloseDropsThePendingPollInsteadOfWaitingForIt` 
only covers the idle case too (interval 2 s, nothing due), so it would not 
catch that regression.
   
   Verified by: compiled a standalone probe against the installed JDK (21.0.12) 
-- with `setExecuteExistingDelayedTasksAfterShutdownPolicy(false)`, a task 
queued at delay 0 behind a busy worker still ran after `shutdown()` 
(`taskRanAfterShutdown=true`), while a task 2 s out was dropped and 
`shutdown()` returned in 0 ms. The `getDelay() > 0` condition in `onShutdown()` 
is unchanged between JDK 17 and 21.



##########
docs/gravitino-server-config.md:
##########
@@ -326,9 +326,18 @@ Caches are local to each server, so a metalake modified on 
one server would othe
 its neighbors. Every server writes its changes to an entity change log table 
and polls that table
 to invalidate what other servers have touched. A separate cleaner trims old 
rows.
 
+Each poll reads at most `pollBatchSize` records. When a poll returns a full 
batch, the next poll
+starts immediately instead of waiting `pollIntervalSecs`, so a backlog is 
drained as fast as the
+server can read and apply batches rather than at a fixed `pollBatchSize / 
pollIntervalSecs`
+records per second. Once a poll returns fewer records than the batch size, or 
fails, the server
+waits `pollIntervalSecs` again. A change written on one server therefore 
normally becomes visible
+on the others within about `pollIntervalSecs`, plus the time needed to drain 
any backlog ahead of
+it. Each poll holds its whole batch in memory, so raise `pollBatchSize` in 
moderate steps.

Review Comment:
   [Nit] This paragraph is good, but it leaves the in-repo design doc 
contradicting it.
   
   `design-docs/gravitino-entity-cache-multinode-design.md:341` still describes 
the old fixed-rate model: "every other node sees it after the next successful 
poll. The interval is set by `gravitino.entityChangeLog.pollIntervalSecs`" -- 
no mention of `pollBatchSize` or of backlog draining, which is precisely what 
this PR changes. One sentence there, pointing at the behaviour you just 
documented here, would keep the two in sync.
   
   Verified by: read 
`design-docs/gravitino-entity-cache-multinode-design.md:336-343`; the "Expected 
propagation and failure behavior" section is untouched by this PR (`git diff 
origin/main...HEAD` lists no `design-docs/` file).



##########
core/src/main/java/org/apache/gravitino/storage/relational/EntityChangeLogPoller.java:
##########
@@ -190,28 +207,90 @@ public void close() {
   }
 
   @VisibleForTesting
-  void pollChanges() {
+  ScheduledExecutorService createScheduler() {
+    ScheduledThreadPoolExecutor executor =
+        new ScheduledThreadPoolExecutor(
+            1,
+            r -> {
+              Thread t = new Thread(r);
+              t.setName("Gravitino-EntityChangeLogPoller");
+              t.setDaemon(true);
+              return t;
+            });
+    // Each poll is a one-shot delayed task. By default such tasks still run 
after shutdown(), so
+    // close() would wait for the pending poll and run it against a store that 
is shutting down.
+    executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+    return executor;
+  }
+
+  /**
+   * Runs one poll cycle and schedules the next one: immediately when the 
cycle read a full batch,
+   * otherwise after the poll interval. A failed cycle also waits the full 
interval, so a database
+   * outage is not retried in a tight loop.
+   */
+  private void runPollCycle() {
+    if (closed) {
+      return;
+    }
+    long nextDelayMillis = TimeUnit.SECONDS.toMillis(pollIntervalSecs);
+    try {
+      if (pollChanges()) {
+        nextDelayMillis = 0;
+      }

Review Comment:
   [Question] Was DB pressure during a sustained drain considered? There is no 
floor on the delay and no cap on consecutive immediate hops, so a server whose 
write rate stays at or above `pollBatchSize` per cycle polls as fast as the 
database answers, indefinitely.
   
   Each cycle issues two statements, not one: `selectEntityChanges` plus the 
best-effort tail sample `selectMaxChangeId` at lines 304-307, each through its 
own `SessionUtils.getWithoutCommit` session. The tail sample is 
observability-only (your own comment at 300-301 says so) and during a drain it 
tells you nothing new, since a full batch is already the backlog signal that 
line 293 acts on. Skipping it while the previous cycle reported a backlog, or 
giving the zero-delay hop a small floor (a few ms), would cut the steady-state 
query rate roughly in half for no loss of drain speed.
   
   Not blocking -- continuous draining is the point of the PR, and the cursor 
always advances so this cannot become a spin. I am asking whether the unbounded 
rate is deliberate.
   
   Verified by: read `runPollCycle` (231-245), `doPollChanges` (284-294) and 
`fetchNextDelivery` (297-341) in full; confirmed both statements run on every 
cycle regardless of the delay chosen, and confirmed via 
`EntityChangeLogBaseSQLProvider.java:58,62` that they are two separate queries.



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