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


##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SemanticModelMetaService.java:
##########
@@ -385,11 +407,65 @@ void deleteSemanticModelWithVersion(
             SessionUtils.doWithoutCommit(
                 SemanticModelVersionInfoMapper.class,
                 mapper -> 
mapper.softDeleteSemanticModelVersionsBySemanticModelId(semanticModelId)),
+        () ->
+            SessionUtils.doWithoutCommit(
+                SemanticModelMetricMapper.class,
+                mapper -> 
mapper.softDeleteSemanticModelMetricsBySemanticModelId(semanticModelId)),
         () ->
             EntityChangeLogWriter.append(
                 semanticModelIdent, Entity.EntityType.SEMANTIC_MODEL, 
OperateType.DROP));
   }
 
+  private static void reconcileMetricIdentities(
+      SemanticModelPO model, SemanticModelDefinition definition) {
+    Set<String> names = new LinkedHashSet<>();
+    Metric[] metrics = definition.metrics();
+    if (metrics != null) {
+      for (Metric metric : metrics) {
+        Preconditions.checkArgument(
+            names.add(metric.name()), "Duplicate metric name: %s", 
metric.name());
+      }
+    }
+
+    // The caller has already inserted or version-checked the model in this 
transaction. A locking
+    // read also observes the latest identities on backends using 
repeatable-read isolation.
+    SessionUtils.doWithoutCommit(
+        SemanticModelMetricMapper.class,
+        mapper -> {
+          Map<String, SemanticModelMetricPO> live =
+              mapper
+                  
.selectSemanticModelMetricsByModelIdForUpdate(model.getSemanticModelId())
+                  .stream()
+                  .collect(
+                      Collectors.toMap(SemanticModelMetricPO::metricName, 
Function.identity()));

Review Comment:
   [Nit] `Collectors.toMap` throws `IllegalStateException: Duplicate key ...` 
if two live rows ever share a name for one model. Per the design that cannot 
happen - uniqueness is enforced by validation plus model concurrency control, 
deliberately `without a database name-uniqueness index` - which also means this 
map is the only place the invariant is ever observed, and one anomalous row 
would make every subsequent create, alter and overwrite of that model fail with 
a message naming neither the model nor the metric. A `toMap(..., ..., (a, b) -> 
{ throw new IllegalStateException(...) })` carrying `semanticModelId` and the 
metric name (or keeping the lower ID and retiring the other) would turn an 
opaque failure into a diagnosable one at no runtime cost.
   
   Verified by: read lines 433-440; confirmed 
`scripts/mysql/schema-2.0.0-mysql.sql` defines only `KEY idx_smmm_model_live 
(semantic_model_id, deleted_at)` for this table, unlike `uk_sid_smn_del` on 
`semantic_model_meta` (line 577), and that 
`design-docs/gravitino-schema-metrics-discovery-design.md:319-321` makes that 
omission intentional.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SemanticModelMetaService.java:
##########
@@ -250,6 +260,13 @@ public <E extends Entity & HasIdentifier> 
SemanticModelEntity updateSemanticMode
               throw semanticModelWriteFailure(identifier, oldSemanticModelPO);
             }
           },
+          () -> {
+            if (!Objects.equals(
+                oldSemanticModelPO.getSemanticModelVersionInfoPO().metrics(),
+                newSemanticModelPO.getSemanticModelVersionInfoPO().metrics())) 
{
+              reconcileMetricIdentities(newSemanticModelPO, 
newEntity.definition());

Review Comment:
   [Question] Because this is the only reconciliation trigger on the alter 
path, a model that has no identity rows never acquires them: as long as its 
`metrics` value stays byte-identical, reconciliation is skipped forever, and 
the upgrade scripts only `CREATE TABLE` without a backfill. For released 
upgrades that is a non-issue - `design-docs/gravitino-semantic-model-design.md` 
states that Semantic Model tables are introduced in 2.0.0 and that released 
versions have no Semantic Model data to migrate - but a database written by an 
earlier 2.0.0-SNAPSHOT build will hold Semantic Models with metrics and no rows 
in the new table, and they would stay invisible to the schema-level discovery 
built on top of this.
   
   Is a self-heal worth it (for example, also reconcile when the live set is 
empty while the new definition has names), or is pre-release in-place schema 
churn explicitly out of scope here?
   
   Verified by: grepped every call site of `reconcileMetricIdentities` (only 
lines 185 and 267) and read all three `upgrade-1.3.0-to-2.0.0-*.sql` diffs, 
which add the table and nothing else.



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SemanticModelMetaService.java:
##########
@@ -250,6 +260,13 @@ public <E extends Entity & HasIdentifier> 
SemanticModelEntity updateSemanticMode
               throw semanticModelWriteFailure(identifier, oldSemanticModelPO);
             }
           },
+          () -> {
+            if (!Objects.equals(
+                oldSemanticModelPO.getSemanticModelVersionInfoPO().metrics(),
+                newSemanticModelPO.getSemanticModelVersionInfoPO().metrics())) 
{

Review Comment:
   [Nit] Comparing the serialized `metrics` JSON means any content-only edit 
(description, expression, metric order) also triggers reconciliation, and 
`reconcileMetricIdentities` then issues `SELECT ... FOR UPDATE` over every live 
identity row of the model only to write nothing. Only the *name set* can change 
identities, and both definitions are in scope here, so 
`oldSemanticModelEntity.definition().metrics()` vs 
`newEntity.definition().metrics()` compared as name sets would skip the locking 
read for the common content-only alter and keep today's behaviour for every 
name change.
   
   Verified by: read `updateSemanticModel` (lines 215-281) and 
`reconcileMetricIdentities` (lines 419-468) in full on 6ec0cde - the reconcile 
body always runs the locking select before deciding there is nothing to insert 
or retire; `testExactNamesContentChangesAndReintroduction` asserts exactly this 
no-op case (same four names, new expression and description, IDs unchanged).



##########
core/src/main/java/org/apache/gravitino/storage/relational/service/SemanticModelMetaService.java:
##########
@@ -385,11 +407,65 @@ void deleteSemanticModelWithVersion(
             SessionUtils.doWithoutCommit(
                 SemanticModelVersionInfoMapper.class,
                 mapper -> 
mapper.softDeleteSemanticModelVersionsBySemanticModelId(semanticModelId)),
+        () ->
+            SessionUtils.doWithoutCommit(
+                SemanticModelMetricMapper.class,
+                mapper -> 
mapper.softDeleteSemanticModelMetricsBySemanticModelId(semanticModelId)),
         () ->
             EntityChangeLogWriter.append(
                 semanticModelIdent, Entity.EntityType.SEMANTIC_MODEL, 
OperateType.DROP));
   }
 
+  private static void reconcileMetricIdentities(
+      SemanticModelPO model, SemanticModelDefinition definition) {
+    Set<String> names = new LinkedHashSet<>();
+    Metric[] metrics = definition.metrics();
+    if (metrics != null) {
+      for (Metric metric : metrics) {
+        Preconditions.checkArgument(
+            names.add(metric.name()), "Duplicate metric name: %s", 
metric.name());
+      }
+    }
+
+    // The caller has already inserted or version-checked the model in this 
transaction. A locking
+    // read also observes the latest identities on backends using 
repeatable-read isolation.
+    SessionUtils.doWithoutCommit(
+        SemanticModelMetricMapper.class,
+        mapper -> {
+          Map<String, SemanticModelMetricPO> live =
+              mapper
+                  
.selectSemanticModelMetricsByModelIdForUpdate(model.getSemanticModelId())
+                  .stream()
+                  .collect(
+                      Collectors.toMap(SemanticModelMetricPO::metricName, 
Function.identity()));
+          for (String name : names) {
+            if (live.remove(name) != null) {
+              continue;
+            }
+            IdGenerator generator = GravitinoEnv.getInstance().idGenerator();

Review Comment:
   [Nit] Two small things in this loop: the 
`GravitinoEnv.getInstance().idGenerator()` lookup and its `checkState` repeat 
once per new metric and can be hoisted above the loop, and each identity is a 
separate round trip. Gravitino's precedent for inserting a model's child rows 
is one multi-row statement - 
`TableColumnMapper.insertColumnPOs(List<ColumnPO>)` backed by a `<foreach>` 
VALUES list 
(`core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableColumnBaseSQLProvider.java:61`).
 An `insertSemanticModelMetrics(List<SemanticModelMetricPO>)` provider would 
make a create with N metrics one INSERT instead of N, inside the same 
transaction. Not blocking: metric counts per model are presumably small.
   
   Verified by: read the loop at lines 441-458 and the column batch-insert 
mapper/provider it would mirror (`TableColumnMapper.java:39-40`).



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