github-actions[bot] commented on code in PR #61518:
URL: https://github.com/apache/doris/pull/61518#discussion_r3615223399


##########
be/src/storage/id_manager.h:
##########
@@ -82,15 +82,17 @@ struct ExternalFileMappingInfo {
      * 1. If the file belongs to a partition, columns_from_path_keys and 
columns_from_path in TFileRangeDesc are needed when materializing the partition 
column
      * 2. path, file_type, modification_time,compress_type .... used to read 
the file
      * 3. TFileFormatType can distinguish whether it is 
iceberg/hive/hudi/paimon
-     */
+    */
     TFileRangeDesc scan_range_desc;
     bool enable_file_meta_cache;
+    std::string table_name;
 
     ExternalFileMappingInfo(int plan_node_id, const TFileRangeDesc& scan_range,
-                            bool file_meta_cache)
+                            bool file_meta_cache, std::string table_name_)

Review Comment:
   [P1] Update the remaining three-argument callers
   
   This makes `table_name` mandatory, but `parquet_read_lines.cpp:223` and 
`orc_read_lines.cpp:201` still construct `ExternalFileMappingInfo` with three 
arguments, and `id_manager_test.cpp:49` still constructs the external 
`FileMapping` overload with three. All three sources are included by the 
recursive `doris_be_test` glob, so that target cannot compile. Please update 
those callers (and assert the restored context) or retain a deliberate 
compatible overload/default.



##########
be/src/exec/scan/file_scanner_v2.cpp:
##########
@@ -817,9 +819,41 @@ Status 
FileScannerV2::_to_file_format(TFileFormatType::type format_type,
 
 Status FileScannerV2::_init_io_ctx() {
     _io_ctx = create_file_scan_io_context(_state);
+    if (_local_state) {
+        _io_ctx->table_name = 
_local_state->cast<FileScanLocalState>().table_name();

Review Comment:
   [P2] Pass this context to V2 Iceberg delete readers
   
   This names the V2 data-file IOContext, but both position- and 
equality-delete initialization create a fresh `IcebergDeleteFileIOContext` from 
`RuntimeState` only. That helper never copies these names before its 
Parquet/ORC reader opens the delete file, so cold delete-file blocks remain 
anonymous while the associated data file is attributed. Seed the delete-file 
context from the current table-reader IOContext (at least preserving the 
unambiguous table name) and cover both delete types.



##########
be/src/storage/id_manager.h:
##########
@@ -82,15 +82,17 @@ struct ExternalFileMappingInfo {
      * 1. If the file belongs to a partition, columns_from_path_keys and 
columns_from_path in TFileRangeDesc are needed when materializing the partition 
column
      * 2. path, file_type, modification_time,compress_type .... used to read 
the file
      * 3. TFileFormatType can distinguish whether it is 
iceberg/hive/hudi/paimon
-     */
+    */
     TFileRangeDesc scan_range_desc;
     bool enable_file_meta_cache;
+    std::string table_name;

Review Comment:
   [P2] Preserve context for internal row-ID rereads too
   
   The latest fix carries the table only in `ExternalFileMappingInfo`; 
`InternalFileMappingInfo` still records just tablet/rowset/segment. 
`SegmentIterator` emits that mapping for global row IDs, and 
`RowIdStorageReader::read_doris_format_row()` later loads the segment and 
creates fresh row-store/column-store IOContexts without names. A TopN 
lazy-materialization miss on a second-phase-only page is therefore persisted 
with `context_id = 0`. Please carry the scan context through the internal 
mapping and restore it in each second-phase IOContext.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java:
##########
@@ -776,6 +776,40 @@ public static Map<String, String> 
getPartitionInfoMap(PartitionData partitionDat
         return partitionInfoMap;
     }
 
+    public static Map<String, String> 
getIdentityPartitionInfoMapForCache(PartitionData partitionData,
+            PartitionSpec partitionSpec, String timeZone) {
+        Map<String, String> partitionInfoMap = Maps.newLinkedHashMap();
+        List<NestedField> fields = 
partitionData.getPartitionType().asNestedType().fields();
+        List<PartitionField> partitionFields = partitionSpec.fields();
+        Preconditions.checkArgument(fields.size() == partitionFields.size(),
+                "PartitionData fields size does not match PartitionSpec fields 
size");
+
+        for (int i = 0; i < fields.size(); i++) {
+            NestedField field = fields.get(i);
+            PartitionField partitionField = partitionFields.get(i);
+            if (!partitionField.transform().isIdentity()) {

Review Comment:
   [P1] Do not drop identity values in mixed specs
   
   For a spec such as `identity(dt), day(ts)`, `getIdentityPartitionColumns()` 
still marks `dt` as a path-only/non-file slot, but this return discards the 
`dt` value when the loop reaches `day(ts)`. `createIcebergSplit()` then keeps 
only an empty accounting sentinel, and `setIcebergParams()` unsets the generic 
path values without restoring `dt`, leaving the scan with no source for that 
required slot. Keep identity values used for materialization separate from the 
all-or-nothing cache-context decision, and add a mixed-spec 
range/materialization test.



##########
gensrc/proto/file_cache.proto:
##########
@@ -59,5 +59,6 @@ message BlockMetaPb {
     optional FileCacheType type = 1;
     optional uint64 size = 2;
     optional uint64 ttl = 3;
+    optional uint64 context_id = 4;

Review Comment:
   [P2] Preserve attribution across an old-BE rewrite
   
   The default-family migration now lets the previous BE reopen this DB, but 
that binary parses a new block into its old `(type,size,ttl)` `BlockMeta` and 
`change_key_meta_type()` serializes a fresh protobuf containing only fields 
1-3. A new-BE fill followed by rollback, TTL reconciliation, and re-upgrade 
therefore strips field 4 from a surviving block and makes `FILE_CACHE_INFO` 
anonymous. The current compatibility test covers opening only; please use a 
side/versioned association that old rewrites cannot erase, and add a new-write 
-> old-format type rewrite -> new-read test.



##########
be/src/io/cache/fs_file_cache_storage.cpp:
##########
@@ -321,8 +322,16 @@ Status FSFileCacheStorage::finalize(const FileCacheKey& 
key, const size_t size)
     }
 
     BlockMetaKey mkey(key.meta.tablet_id, UInt128Wrapper(key.hash), 
key.offset);
-    BlockMeta meta(key.meta.type, size, key.meta.expiration_time);
-    _meta_store->put(mkey, meta);
+    uint64_t context_id = 0;
+    if (_meta_store) {
+        // Table/partition context is only used for observability. Any 
dictionary lookup/write

Review Comment:
   [P2] Keep dictionary failures repairable
   
   `get_or_create_context_id()` also returns 0 for a RocksDB Get/Write error or 
malformed value, so this policy persists an anonymous block and reports 
successful finalization. Existing-cell hits, restart loading, and cache-type 
rewrites never replace that zero ID, making one transient dictionary failure 
permanent until eviction even after RocksDB recovers. Return an error-bearing 
result or retain pending context and retry/late-bind it; if cache writes must 
stay available, add a fault-injection test proving attribution is repaired 
after recovery.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/FileScanNode.java:
##########
@@ -95,12 +96,138 @@ protected void toThrift(TPlanNode planNode) {
         TFileScanNode fileScanNode = new TFileScanNode();
         fileScanNode.setTupleId(desc.getId().asInt());
         if (desc.getTable() != null) {
-            fileScanNode.setTableName(desc.getTable().getName());
+            
fileScanNode.setTableName(desc.getTable().getNameWithFullQualifiers());
         }
         planNode.setFileScanNode(fileScanNode);
         super.toThrift(planNode);
     }
 
+    public static void fillTablePartitionContext(TFileRangeDesc range, TableIf 
table, String partitionName) {
+        if (range == null) {
+            return;
+        }
+        if (partitionName != null && !partitionName.isEmpty()) {
+            range.setPartitionName(partitionName);
+        }
+    }
+
+    public static String buildPartitionName(List<String> partitionKeys, 
List<String> partitionValues) {
+        List<TPartitionKeyValue> partitionKeyValues = 
buildPartitionKeyValues(partitionKeys, partitionValues);
+        if (partitionKeyValues.isEmpty()) {
+            return "";
+        }
+        return buildPartitionName(partitionKeyValues);
+    }
+
+    public static List<TPartitionKeyValue> buildPartitionKeyValues(
+            List<String> partitionKeys, List<String> partitionValues) {
+        return buildPartitionKeyValues(partitionKeys, partitionValues, null);
+    }
+
+    public static List<TPartitionKeyValue> buildPartitionKeyValues(
+            List<String> partitionKeys, List<String> partitionValues, 
List<Boolean> partitionValueIsNull) {
+        if (partitionKeys == null || partitionValues == null) {
+            return Collections.emptyList();
+        }
+        if (partitionKeys.isEmpty() || partitionKeys.size() != 
partitionValues.size()
+                || (partitionValueIsNull != null && partitionKeys.size() != 
partitionValueIsNull.size())) {
+            return Collections.emptyList();
+        }
+        List<TPartitionKeyValue> partitionKeyValues = 
Lists.newArrayListWithCapacity(partitionKeys.size());
+        for (int i = 0; i < partitionKeys.size(); i++) {
+            boolean isNull = partitionValueIsNull != null && 
Boolean.TRUE.equals(partitionValueIsNull.get(i));
+            partitionKeyValues.add(buildPartitionKeyValue(
+                    partitionKeys.get(i), isNull ? null : 
partitionValues.get(i)));
+        }
+        return partitionKeyValues;
+    }
+
+    public static List<TPartitionKeyValue> buildPartitionKeyValues(Map<String, 
String> partitionValues) {
+        if (partitionValues == null || partitionValues.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return partitionValues.entrySet().stream()
+                .sorted(Map.Entry.comparingByKey())
+                .map(entry -> buildPartitionKeyValue(entry.getKey(), 
entry.getValue()))
+                .collect(Collectors.toList());
+    }
+
+    private static TPartitionKeyValue buildPartitionKeyValue(String key, 
String value) {
+        TPartitionKeyValue keyValue = new TPartitionKeyValue();
+        keyValue.setKey(key);
+        if (value == null) {
+            keyValue.setValue("");
+            keyValue.setIsNull(true);
+        } else {
+            keyValue.setValue(value);
+        }
+        return keyValue;
+    }
+
+    public static String buildPartitionName(List<TPartitionKeyValue> 
partitionKeyValues) {
+        if (partitionKeyValues == null || partitionKeyValues.isEmpty()) {
+            return "";
+        }
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < partitionKeyValues.size(); i++) {
+            TPartitionKeyValue keyValue = partitionKeyValues.get(i);
+            if (i > 0) {
+                builder.append("/");
+            }
+            builder.append(keyValue.getKey());
+            builder.append("=");
+            builder.append(keyValue.isSetValue() ? keyValue.getValue() : "");

Review Comment:
   [P2] Use a collision-free persistent partition identity
   
   This flattening ignores `is_null` and does not escape component boundaries. 
A SQL NULL and a real empty string both become `dt=`, and for fixed keys 
`[a,b]`, values `["x/b=y","z"]` and `["x","y/b=z"]` both become `a=x/b=y/b=z`. 
Because this string is interned as the durable dictionary key, distinct 
partitions can share one context ID and be permanently misreported. Encode the 
ordered structured list (including null flags) unambiguously; keep a separate 
human-readable label if needed.



##########
be/src/io/cache/file_cache_common.h:
##########
@@ -117,6 +118,9 @@ struct KeyMeta {
     uint64_t expiration_time; // absolute time
     FileCacheType type;
     int64_t tablet_id {0};
+    std::string table_name;

Review Comment:
   [P2] Keep deduplicated context out of every FileBlock
   
   `CacheContext(io_ctx)` copies both names on each indirect-cache read, 
including hits, and `add_cell()` copies them again into every resident 
`FileBlock`. After `finalize()` resolves an ID, later metadata rewrites use 
only `context_id`, yet the strings remain resident and restart loading expands 
every ID back into per-block strings. At the default 1 MiB block size, a 1 TiB 
cache has about 1,048,576 blocks, so two string objects plus payload per block 
add substantial untracked memory and allocation traffic. Please share/intern 
the context on the read path and retain only the ID after finalization/reload.



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