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


##########
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:
   Thanks, good catch. Fixed in ec682a31dd. The comment on `createScheduler` 
now says that the policy only drops polls whose delay has not elapsed. 
Already-due tasks, including zero-delay drain hops, can still run after 
`shutdown()`, and it is the `closed` guard in `runPollCycle` that keeps them 
away from the closing store. I also added 
`testAlreadyDueDrainHopDoesNotPollAfterClose`. It uses the production 
scheduler, queues an already-due drain hop behind a busy worker during 
`close()`, and asserts that the hop executes without polling. Removing the 
`closed` guard makes that test fail.



##########
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:
   Yes, continuous draining is deliberate: every cycle is bounded by 
`pollBatchSize` and always advances the cursor, so a sustained backlog is 
drained at the rate the database and listeners support. That is now stated 
explicitly in `docs/gravitino-server-config.md` (Change Log Propagation) and in 
the PR description. I took your suggestion on the tail query (ec682a31dd): 
during a full-batch drain, `selectMaxChangeId` is sampled at most once per 
`pollIntervalSecs`, and failed attempts count toward that limit too. Partial 
and empty polls still refresh the tail every cycle, so a sustained drain issues 
roughly one query per cycle instead of two. This is covered by 
`testDrainSamplesTailOncePerIntervalAndRefreshesAfterCatchingUp` and 
`testFailedTailSamplesAreAlsoRateLimitedDuringDrain`. `docs/metrics.md` 
(32184fed76) now explains that `record-lag` is sampled, and that 
`batch-size-records` at `pollBatchSize` is the backlog signal. I did not add a 
delay floor, to keep drain throughput indepen
 dent of an arbitrary constant.



##########
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:
   Done in ec682a31dd. The "Expected propagation and failure behavior" bullet 
in `design-docs/gravitino-entity-cache-multinode-design.md` now describes 
`pollBatchSize`, the immediate next poll after a full batch, and the interval 
after an empty, partial, or failed poll. It also links to the Change Log 
Propagation section of the server config doc for the capacity and database-load 
trade-offs.



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