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


##########
hugegraph-server/hugegraph-hstore/src/main/java/org/apache/hugegraph/backend/store/hstore/HstoreStore.java:
##########
@@ -765,16 +771,68 @@ public void checkAndRegisterOlapTable(Id pkId) {
 
         @Override
         public void clearOlapTable(Id pkId) {
+            this.deleteOlapData(pkId);
         }
 
         @Override
         public void removeOlapTable(Id pkId) {
+            // Every OLAP property shares this table and its registered 
manager.
+            this.deleteOlapData(pkId);
+        }
+
+        private void deleteOlapData(Id pkId) {
+            E.checkArgumentNotNull(pkId, "OLAP property ID");
+            Session session = this.session(HugeType.OLAP);
+            String tableName = this.table(HugeType.OLAP).table();
+            E.checkState(!session.hasChanges(), "Can't clear OLAP data with 
pending graph mutations");
+            E.checkState(super.sessions.existsTable(tableName), "Not exist 
table '%s'", tableName);
+            byte[] propertyPrefix = BytesBuffer.allocate(pkId.length() + 
9).writeId(pkId).bytes();
+            int pending = 0;
+            try {
+                // A property prefix alone also matches legacy vertex-only 
keys from other properties.
+                try (BackendColumnIterator columns = session.scan(tableName)) {
+                    while (columns.hasNext()) {
+                        BackendColumn column = columns.next();
+                        if (!OlapKey.matchesProperty(column.value, pkId)) {
+                            boolean targetCompound = column.name != null &&
+                                                     column.name.length > 
propertyPrefix.length &&
+                                                     
Bytes.prefixWith(column.name, propertyPrefix);
+                            E.checkState(!targetCompound, "OLAP value must 
match property '%s'", pkId);

Review Comment:
   Minor: one inconsistent row makes both clear and remove fail on every retry, 
and the message does not say which row.
   
   Evidence: this check, and the two `E.checkState` calls on the matching 
branch below, abort the whole cleanup. `OlapPropertyKeyRemoveJob.execute()` 
calls `removeOlapPk()` first, so the schema removal after it never runs and the 
property key can no longer be removed. The messages only carry `pkId`, and 
there is no API to delete a single raw OLAP row.
   
   Requested change: include the offending key (for example 
`Bytes.toHex(column.name)`) in these messages, and say in 
`docs/shared-foundation-migration.md` that such a row blocks cleanup until it 
is repaired.



##########
hugegraph-server/hugegraph-hstore/src/main/java/org/apache/hugegraph/backend/store/hstore/HstoreStore.java:
##########
@@ -765,16 +771,68 @@ public void checkAndRegisterOlapTable(Id pkId) {
 
         @Override
         public void clearOlapTable(Id pkId) {
+            this.deleteOlapData(pkId);
         }
 
         @Override
         public void removeOlapTable(Id pkId) {
+            // Every OLAP property shares this table and its registered 
manager.
+            this.deleteOlapData(pkId);
+        }
+
+        private void deleteOlapData(Id pkId) {
+            E.checkArgumentNotNull(pkId, "OLAP property ID");
+            Session session = this.session(HugeType.OLAP);
+            String tableName = this.table(HugeType.OLAP).table();
+            E.checkState(!session.hasChanges(), "Can't clear OLAP data with 
pending graph mutations");
+            E.checkState(super.sessions.existsTable(tableName), "Not exist 
table '%s'", tableName);
+            byte[] propertyPrefix = BytesBuffer.allocate(pkId.length() + 
9).writeId(pkId).bytes();
+            int pending = 0;
+            try {
+                // A property prefix alone also matches legacy vertex-only 
keys from other properties.
+                try (BackendColumnIterator columns = session.scan(tableName)) {
+                    while (columns.hasNext()) {
+                        BackendColumn column = columns.next();
+                        if (!OlapKey.matchesProperty(column.value, pkId)) {
+                            boolean targetCompound = column.name != null &&
+                                                     column.name.length > 
propertyPrefix.length &&
+                                                     
Bytes.prefixWith(column.name, propertyPrefix);
+                            E.checkState(!targetCompound, "OLAP value must 
match property '%s'", pkId);
+                            continue;
+                        }
+                        BytesBuffer key = BytesBuffer.wrap(column.name);
+                        Id first = key.readId();
+                        Id vertexId = first;
+                        if (key.remaining() != 0) {
+                            E.checkState(pkId.equals(first), "OLAP key must 
match property '%s'", pkId);
+                            vertexId = key.readId();
+                            E.checkState(key.remaining() == 0, "Unexpected 
trailing bytes in OLAP key");
+                        }
+                        session.delete(tableName, vertexId.asBytes(), 
column.name);
+                        if (++pending == OLAP_DELETE_BATCH_SIZE) {
+                            session.commit();
+                            pending = 0;
+                        }
+                    }
+                }
+                if (pending != 0) {
+                    session.commit();
+                }
+            } catch (RuntimeException | Error failure) {
+                try {
+                    session.rollback();

Review Comment:
   Important: this rollback does not drop the deletes queued for the failed 
batch, so they can be applied later.
   
   Evidence, all at this head:
   - `HstoreSession.delete()` calls `prepare()` (`graph.beginTx()`) and then 
`graph.delete()`. In tx mode `NodeTxExecutor.prepareTx()` only appends the 
operation to `entries`; nothing is sent to Store yet.
   - `HstoreSession.rollback()` calls `graph.rollback()` -> 
`NodeTxExecutor.rollbackTx()`, which rolls back the node sessions, sets `isTx = 
false` and clears `sessions`. It never resets `entries`. Only `doCommit()` 
resets it, in its `finally`.
   - The session is thread-local (`BackendSessionPool.getOrNewSession()`), so 
the queue stays on the task worker thread. The next write on that thread calls 
`beginTx()` (just `setTx(true)`), appends, and `doCommit()` replays every 
entry, including the stale deletes.
   
   So when the scan fails or a row is rejected after 1 to 1023 matching rows 
were queued (a Store scan error, or the `E.checkState` rejections above), the 
task reports failure and `rollback()` returns normally, yet those deletes are 
still pending. If the property is written again before that worker thread 
commits something else, the replay removes the new values. 
`testOlapCleanupRejects*` and `testOlapCleanupRollbackAndCloseOnCommitFailure` 
cannot see this because `Session` is a mock whose `rollback()` clears the 
fixture's own list.
   
   The gap is in `NodeTxExecutor.rollbackTx()` and predates this PR, but this 
path is new and the doc text added here promises that the pending session is 
rolled back. Requested change: reset `entries` in `NodeTxExecutor.rollbackTx()` 
(same as `doCommit()` does) and add a test that runs queue -> rollback -> next 
commit against a real `NodeTxExecutor`, or validate the batch before queuing 
any delete so a rejected row never leaves queued work behind.



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