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


##########
be/src/exec/scan/parallel_scanner_builder.cpp:
##########
@@ -296,6 +296,12 @@ Status ParallelScannerBuilder::_load() {
 std::shared_ptr<OlapScanner> ParallelScannerBuilder::_build_scanner(
         BaseTabletSPtr tablet, int64_t version, const 
std::vector<OlapScanRange*>& key_ranges,
         TabletReadSource&& read_source, io::FileCacheStatistics&& 
initial_file_cache_stats) {
+    std::string table_name;
+    std::string partition_name;
+    if (auto it = _tablet_contexts.find(tablet->tablet_id()); it != 
_tablet_contexts.end()) {

Review Comment:
   This lookup happens only in `_build_scanner()`, after `build_scanners()` has 
already run `_load()`. `_load()` calls `get_segment_num_rows()` with 
`create_preload_io_context()`, which leaves both names empty; when row counts 
must be read from segment footers, that path can create and persist file-cache 
blocks with `context_id = 0`. Later scanner reads only reuse/touch the existing 
cells and do not merge this context into them, so those footer blocks remain 
anonymous. Please apply the tablet context to the preload `IOContext` before 
`get_segment_num_rows()` and cover that cache-populating path.



##########
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
+        // failure must degrade to an empty context id instead of affecting 
cache writes.
+        context_id =
+                _meta_store->get_or_create_context_id(key.meta.table_name, 
key.meta.partition_name);

Review Comment:
   `finalize()` runs once per file-cache block, and 
`get_or_create_context_id()` always issues a synchronous RocksDB `Get` even for 
an existing pair. Because the default block size is 1 MiB and no context ID is 
resolved before block splitting, a 1 TiB cold fill repeats roughly one million 
reads for what is commonly one table/partition, while holding each block's 
finalize path. Please intern/cache the forward mapping in memory or resolve the 
context once upstream and propagate its ID to sibling blocks.



##########
be/src/exec/scan/file_scanner.h:
##########
@@ -310,6 +313,9 @@ class FileScanner : public Scanner {
 
     Status _init_io_ctx() {
         _io_ctx = create_file_scan_io_context(_state);
+        if (_local_state) {

Review Comment:
   This adds the table context only to FileScanner V1, but supported non-load 
scans use FileScannerV2 by default (`enable_file_scanner_v2 = true`). V2's 
`_init_io_ctx()` only calls `create_file_scan_io_context()`, and 
`_prepare_next_split()` never copies `TFileRangeDesc.partition_name`, so 
ordinary Parquet/ORC queries still reach the cache with both names empty and 
persist `context_id = 0`. This is distinct from the existing V1 range-timing 
thread. Please mirror both the table initialization and per-split partition 
refresh in V2, with a two-partition V2 test.



##########
be/src/io/cache/cache_block_meta_store.cpp:
##########
@@ -97,6 +178,9 @@ Status CacheBlockMetaStore::init() {
     column_families.emplace_back(rocksdb::kDefaultColumnFamilyName, 
rocksdb::ColumnFamilyOptions());
     // File cache meta column family
     column_families.emplace_back(FILE_CACHE_META_COLUMN_FAMILY, 
rocksdb::ColumnFamilyOptions());
+    // File cache context dictionary column family
+    column_families.emplace_back(FILE_CACHE_CONTEXT_DICT_COLUMN_FAMILY,

Review Comment:
   Adding a persistent column family makes this local DB incompatible with 
rolling back to the previous BE: RocksDB's descriptor-based `Open` requires 
every existing family to be listed, while the old binary lists only `default` 
and `file_cache_meta`. After the new BE creates `file_cache_context_dict`, the 
old BE cannot reopen the metadata DB; initialization merely logs the failure 
and continues with no working metadata store. Please keep the dictionary in an 
existing family with namespaced keys (or otherwise preserve downgrade-open 
compatibility) and add an upgrade-then-downgrade reopen test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java:
##########
@@ -776,6 +777,41 @@ 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()) {
+                return null;
+            }
+            TypeID partitionTypeId = field.type().typeId();
+            if (partitionTypeId == TypeID.BINARY || partitionTypeId == 
TypeID.FIXED) {
+                return null;
+            }
+            String columnName = 
partitionSpec.schema().findColumnName(partitionField.sourceId());
+            if (columnName == null) {
+                return null;
+            }
+            Object value = partitionData.get(i);
+            try {
+                partitionInfoMap.put(columnName.toLowerCase(Locale.ROOT),

Review Comment:
   `getIdentityPartitionColumns()` preserves Iceberg's source-schema case, and 
`fillPartitionContextFromMap()` later performs an exact 
`containsKey(partitionKey)`. For an identity partition on source column `Dt`, 
this helper stores `dt` while the consumer asks for `Dt`, so it builds no path 
columns or partition context and the BE caches the file without a partition 
name. This is distinct from the earlier partition-field/source-column issue: 
the source column is now selected correctly, but its case is changed. Please 
keep the exact `columnName` and add a mixed-case identity-partition range test.



##########
be/src/information_schema/schema_file_cache_info_scanner.cpp:
##########
@@ -114,10 +118,20 @@ Status 
SchemaFileCacheInfoScanner::_fill_block_impl(Block* block) {
 
             // Convert hash to string
             std::string hash_str = key.hash.to_string();
+            std::string table_name;
+            std::string partition_name;
+            if (value.context_id != 0) {
+                if (auto context = meta_store->get_context(value.context_id); 
context) {

Review Comment:
   This resolves the dictionary inside the per-block metadata loop, so 
`FILE_CACHE_INFO` does a RocksDB `Get` for every row even when most rows share 
the same context ID. With 1 MiB blocks, a 1 TiB cache can add about one million 
reverse lookups for a single table/partition. The restart loader already uses 
the appropriate local `context_id -> pair` cache; please do the same here so 
the query scales with distinct contexts rather than blocks.



##########
be/src/cloud/cloud_internal_service.cpp:
##########
@@ -548,7 +548,9 @@ Status trigger_peer_server_fill(io::FileBlockSPtr& fb, 
int64_t fill_tablet_id,
                     // re-enters peer race, the original block can remain 
DOWNLOADING for the
                     // duration of nested peer retries and timeouts.
                     .is_warmup = false,
-                    .bypass_peer_read = true},
+                    .bypass_peer_read = true,
+                    .table_name = "",

Review Comment:
   A pull-through peer fill originates from an Olap scan whose `IOContext` now 
has table/partition names, but the race passes `ctx=nullptr`, 
`PFetchPeerDataRequest` carries no names, and this server-side download 
hardcodes both fields empty. A miss on the designated fill compute group 
therefore persists an anonymous block, and later named hits only touch/reuse 
it. Please copy the two strings into the race's owned arguments, add optional 
RPC fields for rolling compatibility, and initialize both the server 
CacheContext and download context from them.



##########
be/src/exec/scan/file_scanner.cpp:
##########
@@ -1642,6 +1643,7 @@ Status FileScanner::read_lines_from_range(const 
TFileRangeDesc& range,
                                           const ExternalFileMappingInfo& 
external_info,
                                           int64_t* init_reader_ms, int64_t* 
get_block_ms) {
     _current_range = range;
+    _update_io_context_from_range();

Review Comment:
   In the external row-ID/TopN second phase, `RowIdStorageReader` constructs 
this FileScanner with no `FileScanLocalState`. The new `_init_io_ctx()` 
therefore cannot set the table name, while this range update restores only the 
partition; cache misses for previously untouched column pages are persisted 
under an empty table plus a nonempty partition, and later hits do not repair 
that mapping. Please carry the scan-node table name in 
`ExternalFileMappingInfo` (or equivalent owned context) and restore it before 
the second-phase read, with a lazy-materialization cache-context test.



##########
be/src/io/cache/cache_block_meta_store.cpp:
##########
@@ -367,6 +455,77 @@ void CacheBlockMetaStore::delete_key(const BlockMetaKey& 
key) {
     _write_queue.enqueue(op);
 }
 
+uint64_t CacheBlockMetaStore::get_or_create_context_id(std::string_view 
table_name,
+                                                       std::string_view 
partition_name) {
+    if ((table_name.empty() && partition_name.empty()) || !_db || 
!_context_dict_cf_handle) {
+        return 0;
+    }
+
+    const std::string lookup_key = _build_context_key(table_name, 
partition_name);
+    std::string value;
+    rocksdb::Status status =
+            _db->Get(rocksdb::ReadOptions(), _context_dict_cf_handle.get(), 
lookup_key, &value);
+    if (status.ok()) {
+        auto context_id = parse_context_lookup_value(value);
+        if (context_id.has_value()) {
+            return *context_id;
+        }
+        LOG(WARNING) << "Invalid context lookup value for key";
+    } else if (!status.IsNotFound()) {
+        LOG(WARNING) << "Failed to get context id from rocksdb: " << 
status.ToString();
+    }
+
+    std::lock_guard<std::mutex> lock(_context_mutex);
+
+    value.clear();
+    status = _db->Get(rocksdb::ReadOptions(), _context_dict_cf_handle.get(), 
lookup_key, &value);
+    if (status.ok()) {
+        auto context_id = parse_context_lookup_value(value);
+        if (context_id.has_value()) {
+            return *context_id;
+        }
+        LOG(WARNING) << "Invalid context lookup value after retry for key";
+        return 0;
+    }
+    if (!status.IsNotFound()) {
+        LOG(WARNING) << "Failed to get context id from rocksdb after retry: " 
<< status.ToString();
+        return 0;
+    }
+
+    const uint64_t context_id = _next_context_id.fetch_add(1, 
std::memory_order_acq_rel);
+
+    rocksdb::WriteBatch batch;
+    batch.Put(_context_dict_cf_handle.get(), lookup_key, 
build_context_lookup_value(context_id));

Review Comment:
   These two dictionary records have no reclamation path. Normal LRU/block 
removal deletes only the `BlockMetaKey`, and 
`BlockFileCache::clear_file_cache_impl()` also removes blocks individually 
without reference accounting, so successively scanned/evicted or dropped 
external partitions leave their forward and reverse entries forever. 
`_load_next_context_id()` then scans the full historical set on every restart. 
Please add context lifetime management (for example a mark-and-sweep against 
live block metadata or durable reference counts) and verify that the last block 
eviction eventually reclaims both keys.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hudi/source/HudiScanNode.java:
##########
@@ -325,21 +326,39 @@ private void setHudiParams(TFileRangeDesc rangeDesc, 
HudiSplit hudiSplit) {
         tableFormatFileDesc.setHudiParams(fileDesc);
         Map<String, String> partitionValues = 
hudiSplit.getHudiPartitionValues();
         if (partitionValues != null) {
-            List<String> formPathKeys = new ArrayList<>();
-            List<String> formPathValues = new ArrayList<>();
-            for (Map.Entry<String, String> entry : partitionValues.entrySet()) 
{
-                formPathKeys.add(entry.getKey());
-                formPathValues.add(entry.getValue());
-            }
-            FilePartitionUtils.ParsedColumnsFromPath parsedColumnsFromPath =
-                    
FilePartitionUtils.normalizeColumnsFromPath(formPathValues);
-            rangeDesc.setColumnsFromPathKeys(formPathKeys);
-            rangeDesc.setColumnsFromPath(parsedColumnsFromPath.getValues());
-            
rangeDesc.setColumnsFromPathIsNull(parsedColumnsFromPath.getIsNull());
+            fillPartitionContextFromMap(rangeDesc, partitionValues, 
getPathPartitionKeys());
         }
         rangeDesc.setTableFormatParams(tableFormatFileDesc);
     }
 
+    private void fillPartitionContextFromMap(
+            TFileRangeDesc rangeDesc, Map<String, String> partitionValues, 
List<String> orderedPartitionKeys) {
+        List<String> fromPathKeys = new ArrayList<>();
+        List<String> fromPathValues = new ArrayList<>();
+        for (String partitionKey : orderedPartitionKeys) {
+            if (!partitionValues.containsKey(partitionKey)) {
+                continue;
+            }
+            fromPathKeys.add(partitionKey);
+            fromPathValues.add(partitionValues.get(partitionKey));
+        }
+        List<TPartitionKeyValue> partitionKeyValues =
+                FileScanNode.buildPartitionKeyValues(fromPathKeys, 
fromPathValues);

Review Comment:
   On the default runtime-partition-pruning path, 
`HudiUtils.getPartitionInfoMap()` copies Hive's `__HIVE_DEFAULT_PARTITION__` 
sentinel verbatim. The generic range construction has already normalized that 
value to `""` with `isNull=true`, but this call rebuilds and overwrites the 
fields without null flags, so BE materializes the sentinel as a non-null 
partition value (and numeric/date partitions can fail to cast). This is 
distinct from the prior actual-Java-null issue. Please normalize the Hudi map 
values with `FilePartitionUtils` and preserve the null flags, with a 
default-partition scan-range test.



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