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


##########
hugegraph-store/hg-store-core/src/main/java/org/apache/hugegraph/store/upgrade/LegacyGraphIdChecker.java:
##########
@@ -0,0 +1,325 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hugegraph.store.upgrade;
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.hugegraph.config.HugeConfig;
+import org.apache.hugegraph.config.OptionSpace;
+import org.apache.hugegraph.pd.grpc.Metapb;
+import org.apache.hugegraph.rocksdb.access.RocksDBOptions;
+import org.apache.hugegraph.rocksdb.access.RocksDBSession;
+import org.apache.hugegraph.store.meta.GraphIdManager;
+import org.apache.hugegraph.store.meta.MetadataKeyHelper;
+import org.apache.hugegraph.store.util.HgStoreException;
+import org.rocksdb.ColumnFamilyDescriptor;
+import org.rocksdb.ColumnFamilyHandle;
+import org.rocksdb.ColumnFamilyOptions;
+import org.rocksdb.DBOptions;
+import org.rocksdb.InfoLogLevel;
+import org.rocksdb.Logger;
+import org.rocksdb.Options;
+import org.rocksdb.RocksDB;
+import org.rocksdb.RocksDBException;
+import org.rocksdb.RocksIterator;
+
+import com.google.protobuf.Int64Value;
+
+/** Read-only inspection before Store registration or an incoming snapshot is 
opened for writing. */
+public final class LegacyGraphIdChecker {
+
+    private static final byte[] LEGACY = {(byte) 0xFF, (byte) 0xFE};
+    private static final Pattern DB_NAME = 
Pattern.compile("([0-9]{5,})(?:_[0-9]+){0,2}");
+    private static final Pattern SNAPSHOT_NAME = 
Pattern.compile("snapshot_([0-9]+)");
+
+    private LegacyGraphIdChecker() {
+    }
+
+    public static HugeConfig configuration(Map<String, Object> options) {
+        OptionSpace.register("rocksdb", RocksDBOptions.class.getName());
+        return new HugeConfig(options);
+    }
+
+    public static void checkStore(String dataPaths, HugeConfig config) {
+        try {
+            inspect(dataPaths, config, false);
+        } catch (IOException | RocksDBException | RuntimeException e) {
+            throw new StartupPreflightException("GraphID upgrade preflight 
refused Store startup: " +
+                    e.getMessage() + ". Preserve the complete 1.7 backup. Run 
" +
+                    "bin/hugegraph-store-check.sh --check while the Store is 
stopped; " +
+                    "see docs/graphid-upgrade.md", e);
+        }
+    }
+
+    /** Identifies the startup refusal across Spring's initialization 
wrappers. */
+    public static final class StartupPreflightException extends 
HgStoreException {
+
+        private static final long serialVersionUID = 1L;
+
+        private StartupPreflightException(String message, Throwable cause) {
+            super(message, cause);
+        }
+    }
+
+    /** A running Store may have unflushed writes with WAL disabled: never 
certify its disk view. */
+    public static void checkOffline(String dataPaths, HugeConfig config) 
throws IOException, RocksDBException {
+        inspect(dataPaths, config, true);
+    }
+
+    public static void checkDatabase(Path path, HugeConfig config) throws 
IOException, RocksDBException {
+        try (Database database = new Database(path, config)) {
+            for (int i = 0; i < database.names.size(); i++) {
+                try (RocksIterator iterator = 
database.db.newIterator(database.handles.get(i))) {
+                    iterator.seek(LEGACY);
+                    if (iterator.isValid() && startsWith(iterator.key(), 
LEGACY)) {
+                        throw new IllegalStateException("Legacy GraphID 0xFFFE 
in " + path +
+                                ", column family " + new 
String(database.names.get(i), StandardCharsets.UTF_8));
+                    }
+                    iterator.status();
+                }
+            }
+            // Also reject a historical manual FFFE mapping whose business 
rows have been removed.
+            Map<String, Long> ids = new TreeMap<>();
+            byte[] prefix = MetadataKeyHelper.getGraphIDKey("");
+            try (RocksIterator iterator = database.db.newIterator()) {
+                for (iterator.seek(prefix); iterator.isValid() && 
startsWith(iterator.key(), prefix);
+                     iterator.next()) {
+                    byte[] key = iterator.key();
+                    byte[] name = Arrays.copyOfRange(key, prefix.length, 
key.length);
+                    String graph = new String(name, StandardCharsets.UTF_8);
+                    // Replacement characters can collapse distinct damaged 
keys and hide an invalid ID.
+                    if (!Arrays.equals(name, 
graph.getBytes(StandardCharsets.UTF_8))) {
+                        throw new IllegalStateException("Invalid UTF-8 GraphID 
metadata in " + path);
+                    }
+                    ids.put(graph, 
Int64Value.parseFrom(iterator.value()).getValue());
+                }
+                iterator.status();
+            }
+            try {
+                GraphIdManager.checkGraphIds(ids, -1);
+            } catch (HgStoreException e) {
+                throw new IllegalStateException("Invalid GraphID metadata in " 
+ path + ": " + e.getMessage(), e);
+            }
+        }
+    }
+
+    private static void inspect(String dataPaths, HugeConfig config, boolean 
offline)
+            throws IOException, RocksDBException {
+        List<Path> roots = new ArrayList<>();
+        for (String value : dataPaths.split(",", -1)) {
+            if (value.isBlank()) {
+                throw new IOException("Empty Store data path");
+            }
+            Path root = Path.of(value).toAbsolutePath().normalize();
+            if (offline && !Files.isDirectory(root)) {
+                throw new IOException("Expected an existing stopped Store data 
directory: " + root);
+            }
+            roots.add(root);
+        }
+        try (DatabaseLocks locks = new DatabaseLocks()) {
+            Path metadata = roots.get(0).resolve("hgstore-metadata");
+            if (Files.exists(metadata.resolve("CURRENT"))) {
+                locks.add(metadata);
+            }
+            Set<Path> databases = new LinkedHashSet<>();
+            Set<Path> snapshots = new LinkedHashSet<>();
+            Set<Integer> discovered = new HashSet<>();
+            for (Path root : roots) {
+                discover(root, null, databases, snapshots, discovered);
+            }
+            if (Files.exists(metadata.resolve("CURRENT"))) {
+                try (Database database = new Database(metadata, config);
+                     RocksIterator iterator = database.db.newIterator()) {
+                    byte[] prefix = 
MetadataKeyHelper.getPartitionStorePrefix();
+                    for (iterator.seek(prefix); iterator.isValid() && 
startsWith(iterator.key(), prefix);
+                         iterator.next()) {
+                        Metapb.PartitionStore location = 
Metapb.PartitionStore.parseFrom(iterator.value());
+                        // Normal moved roots are remapped by 
PartitionManager.resetPartitionPath().
+                        if (!discovered.contains(location.getPartitionId())) {
+                            discover(Path.of(location.getStoreLocation()), 
location.getPartitionId(),
+                                     databases, snapshots, new HashSet<>());
+                        }
+                    }
+                    iterator.status();
+                }
+            }
+            for (Path database : databases) {
+                requireCurrent(database);
+                locks.add(database);

Review Comment:
   Important: `loadSnapshot` moves a checkpoint (no `LOCK` file) into 
`db/<partition>_<v1>`, and only a later read-write open creates `LOCK`, so a 
Store stopped or crashed in between now fails startup here with a bare 
`NoSuchFileException` from `toRealPath()` on healthy data. In 
`DatabaseLocks.add`, skip locking when `LOCK` is absent, since no RocksDB 
writer can hold it, and add a test that starts against a checkpoint-made DB 
directory.



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