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


##########
hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java:
##########
@@ -415,6 +442,29 @@ public void shutdown() {
         }
     }
 
+    private void reportOffline() {
+        if (fatalExitRequested.get() || storeInfo == null) {
+            return;
+        }
+        storeInfo.setState(Metapb.StoreState.Offline);
+        if (storeInfo.getId() == 0 || pdProvider == null ||
+            pdProvider.getPDClient() == null) {
+            return;
+        }
+        try {
+            // Preserve PD's current metadata, and never use Exiting for a 
temporary restart.
+            var client = pdProvider.getPDClient();
+            client.getCache().removeStore(storeInfo.getId());
+            Metapb.Store current = client.getStore(storeInfo.getId());
+            if (current.getState() == Metapb.StoreState.Up) {
+                
client.updateStore(current.toBuilder().setState(Metapb.StoreState.Offline).build());

Review Comment:
   Important: A graceful restart of one Store now makes the other Stores reject 
writes for the whole restart when `initial-store-count` equals the Store count. 
The Helm chart always configures this (`HG_PD_INITIAL_STORE_COUNT` is 
`store.replicas` in `pd-statefulset.yaml`), and so does 
`docker-compose-3pd-3store-3server.yml`.
   
   The chain at this head:
   1. This `updateStore(Offline)` goes through `PDService.setStore` to 
`StoreNodeService.updateStore`, whose Offline branch calls 
`storeInfoMeta.removeActiveStore`.
   2. The next `heartBeat` from any other Store calls `checkStoreStatus()`. 
That method sees `activeStores.size()` (2) < `getMinStoreCount()` (3) and sets 
`Cluster_Not_Ready`.
   3. The other Stores cache that state in `HeartbeatService.storeHeartbeat`. 
`isClusterReady()` then returns false, and `HgStoreNodeService.addRaftTask` 
fails every write with `CLUSTER_NOT_READY`.
   4. When the Store comes back, `register(Offline)` (`StoreNodeService` lines 
215-219) removes it from the active set again and calls `checkStoreStatus()` 
while it restores. The cluster becomes ready again only after the second, Up 
registration.
   
   Before this PR, nothing contacted PD on shutdown, and the keepalive entry 
(`store.keepAlive-timeout`, default 300 s) kept a Store that restarted within 
that time in the active set, so the cluster stayed `Cluster_OK`. Every shard 
group still has a 2/3 majority in this case, so the outage comes from the store 
count alone.
   
   Requested change: do not let a temporary restart (shutdown report or restore 
registration) drop the Store from the set that `checkStoreStatus` counts 
against `initial-store-count`. One way is to record the Offline state for 
visibility without calling `removeActiveStore`. Another is to have 
`checkStoreStatus` not count a Store that reported a graceful Offline as 
missing. Please add a PD test with 3 Stores and min count 3: one Store reports 
Offline, and a `heartBeat` from another Store still returns `Cluster_OK`.



##########
hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HeartbeatService.java:
##########
@@ -200,10 +200,36 @@ protected void registerStore() {
             
this.storeInfo.setStoreAddress(IpUtil.getNearestAddress(options.getGrpcAddress()));
             
this.storeInfo.setRaftAddress(IpUtil.getNearestAddress(options.getRaftAddress()));
 
+            // Obtain the identity needed by partition recovery without 
advertising this Store Up.
+            this.storeInfo.setState(Metapb.StoreState.Offline);

Review Comment:
   Important: This Offline registration also runs when a Store that is already 
serving reconnects, not only at startup. When the heartbeat stream fails (the 
callback at line 243), or `heartbeatFailCount` passes 
`MAX_HEARTBEAT_RETRY_COUNT`, `onStateChanged(Offline)` sends the loop back into 
`registerStore()`. This line then sends Offline.
   
   In PD, `lastStore` is Up, so `register` takes the new `restoring` branch. It 
calls `removeActiveStore`, broadcasts `NODE_EVENT_TYPE_NODE_OFFLINE` through 
`PDService`'s status listener, and calls `checkStoreStatus()`. Meanwhile, this 
Store re-runs `loadPartition`/`syncPartitionsFromPD` before it registers Up 
again. The partition engines kept running the whole time, so nothing needs 
restoring.
   
   Before this PR the same reconnect registered Up directly and PD saw no state 
change. A PD leader change closes every Store's stream, so after it all Stores 
flap Offline together. With `initial-store-count` equal to the Store count, any 
Store that heartbeats during that window caches `Cluster_Not_Ready` and rejects 
writes.
   
   Requested change: send the Offline-then-restore handshake only until this 
process has completed a restore once, for example with a `restored` flag set 
after the first successful `stateChanged`. Later re-registrations can then 
request Up directly as before. Please add a `StoreRegistrationTest` case: after 
a successful registration and `onStateChanged(Offline)`, the next 
`registerStore()` sends only Up.



##########
hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/HgStoreEngine.java:
##########
@@ -296,13 +291,13 @@ public void restoreLocalPartitionEngine() {
                     try {
                         createPartitionEngine(p);
                     } catch (Exception e) {
-                        log.error("Partition {}-{} restore  exception {}", 
p.getGraphName(),
-                                  p.getId(), e);
+                        throw new IllegalStateException("Failed to restore 
partition " +

Review Comment:
   Important: Throwing inside this `forEach` stops the loop at the first 
partition that fails, so partitions later in the iteration are never restored. 
Each retry (every second, from `registerStore`) iterates in the same order, 
hits the same failure first, and stops at the same place.
   
   Before this PR, the failure was logged and every other partition still got 
its `PartitionEngine`. Now, with one unrecoverable group (for example 
`raftGroupService.start` returning null, which makes 
`raftNode.addReplicatorStateListener` throw), the Store stays Offline. The raft 
groups whose replicas come after that group in the iteration then run one 
replica short for as long as the failure lasts. Each retry also re-runs 
`syncPartitionsFromPD` and re-registers with PD once a second.
   
   Requested change: try every partition, collect the failures, and throw one 
exception after the loop. That keeps the Store Offline as intended while the 
healthy local replicas still start. Please also add a test where the first of 
two partitions fails and the second still gets an engine.



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