lxy-9602 commented on code in PR #286:
URL: https://github.com/apache/paimon-cpp/pull/286#discussion_r3966774747


##########
include/paimon/reader/batch_reader.h:
##########
@@ -81,6 +81,23 @@ class PAIMON_EXPORT BatchReader {
     /// bitmap. Noted that the returned bitmap has at least one valid row id.
     virtual Result<ReadBatchWithBitmap> NextBatchWithBitmap();
 
+    /// Starts whatever background work this reader would otherwise start on 
its first read, so a
+    /// caller that knows this reader is next can pay that startup while still 
consuming the
+    /// previous one.
+    ///
+    /// This is an optional hint and never changes what the reader returns: 
ordering, filtering and
+    /// metrics are the same with or without it. A reader with nothing to 
start, or one that is not
+    /// yet ready to start it, does nothing. Calling it before the reader is 
configured (for example
+    /// before `SetReadSchema()` on a file reader), or after `Close()`, is 
such a no-op.
+    ///
+    /// It reports no error on purpose: the caller may warm up a reader it 
never ends up reading,
+    /// and a hint about a file nobody reads must not fail the read in 
progress. An implementation
+    /// that cannot start its work leaves it to be started by the first read, 
which reports the
+    /// failure itself.
+    /// @warning The call starts background work owned by this reader, so it 
must be made from the
+    /// same thread that reads this reader, and not concurrently with any 
other call on it.
+    virtual void Warmup() {}

Review Comment:
   Consider move to file batch reader?



##########
include/paimon/read_context.h:
##########
@@ -351,6 +357,16 @@ class PAIMON_EXPORT ReadContextBuilder {
     /// @return Reference to this builder for method chaining.
     ReadContextBuilder& WithCacheConfig(const CacheConfig& config);
 
+    /// Set how far the reader prepares the next file before it is read.
+    ///
+    /// Warmup overlaps remote-storage latency with the read of the current 
file. Higher levels hide
+    /// more latency but use more memory, and may warm files that a query 
never reads (for example
+    /// when a LIMIT stops the scan early).
+    /// @param level The warmup level to use (default: WarmupLevel::DECODED).
+    /// @return Reference to this builder for method chaining.
+    /// @see WarmupLevel
+    ReadContextBuilder& SetWarmupLevel(WarmupLevel level);
+

Review Comment:
   `Warmup` and cache seem to be tightly coupled in the current implementation. 
`SetWarmupLevel(WarmupLevel::RAW)` with `SetReadAheadCacheEnabled(false)` 
appears to be effectively equivalent to `SetWarmupLevel(WarmupLevel::None)`. 
Please document this limitation in the comments.



##########
include/paimon/reader/file_batch_reader.h:
##########
@@ -63,6 +63,23 @@ class PAIMON_EXPORT FileBatchReader : public BatchReader {
 
     /// Get whether or not support read precisely while bitmap pushed down.
     virtual bool SupportPreciseBitmapSelection() const = 0;
+
+    /// Starts whatever background work this reader would otherwise start on 
its first read, so a
+    /// caller that knows this reader is next can pay that startup while still 
consuming the
+    /// previous one.
+    ///
+    /// This is an optional hint and never changes what the reader returns: 
ordering, filtering and
+    /// metrics are the same with or without it. A reader with nothing to 
start, or one that is not
+    /// yet ready to start it, does nothing. Calling it before the reader is 
configured (for example
+    /// before `SetReadSchema()`), or after `Close()`, is such a no-op.
+    ///
+    /// It reports no error on purpose: the caller may warm up a reader it 
never ends up reading,
+    /// and a hint about a file nobody reads must not fail the read in 
progress. An implementation
+    /// that cannot start its work leaves it to be started by the first read, 
which reports the
+    /// failure itself.
+    /// @warning The call starts background work owned by this reader, so it 
must be made from the
+    /// same thread that reads this reader, and not concurrently with any 
other call on it.

Review Comment:
   Is this something that can be customized by plugins? If not, should we 
clarify that in the comment? The behavior described by “The call starts 
background work owned by this reader” seems to be fixed by the implementation.



##########
src/paimon/common/reader/prefetch_file_batch_reader_impl.h:
##########
@@ -123,10 +125,19 @@ class PrefetchFileBatchReaderImpl : public 
PrefetchFileBatchReader {
         uint32_t prefetch_queue_capacity, bool 
enable_adaptive_prefetch_strategy,
         const std::shared_ptr<Executor>& executor, const 
std::shared_ptr<ReadAheadCache>& cache,
         const std::shared_ptr<PrefetchIoMetricsState>& io_metrics,
-        const std::shared_ptr<arrow::MemoryPool>& arrow_pool);
+        const std::shared_ptr<arrow::MemoryPool>& arrow_pool, WarmupLevel 
warmup_level);
 
     Status CleanUp();
     void Workloop();
+    /// Starts the background prefetch thread if it is not running yet. The 
first read does this
+    /// itself; Warmup() is the same call made earlier, so the two must not 
diverge.
+    void EnsureBackgroundThread();
+    /// Initializes and warms the read-ahead cache at most once per read-range 
generation, using the
+    /// first reader's PreBufferRange(). Shared by Workloop() (DECODED) and 
Warmup() (RAW), so the
+    /// two never Init the cache twice. A no-op when there is no cache. Errors 
are recorded via
+    /// SetReadStatus() rather than returned: a warmup hint for a file that 
may never be read must
+    /// not fail an in-flight read.
+    void WarmCacheOnce();

Review Comment:
   `exchange(true)` only elects one initializer; it does not make other threads 
wait for `cache_->Init()` to finish. If RAW warmup and `Workloop()` race, the 
losing thread may continue with an uninitialized cache. Please either rely on 
the documented non-concurrent contract and remove this race claim, or 
synchronize initialization completion.



##########
src/paimon/common/reader/data_file_reader_factory.cpp:
##########
@@ -77,15 +77,16 @@ Result<std::unique_ptr<FileBatchReader>> 
DataFileReaderFactory::Open(
     const std::shared_ptr<FileSystem>& file_system, const 
std::shared_ptr<Executor>& executor,
     const std::shared_ptr<MemoryPool>& pool, const 
std::shared_ptr<arrow::MemoryPool>& arrow_pool) {
     if (read_options.prefetch_enabled && 
FormatSupportsPrefetch(format_identifier)) {
-        PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<PrefetchFileBatchReaderImpl> 
prefetch_reader,
-                               PrefetchFileBatchReaderImpl::Create(
-                                   file_path, file_size, reader_builder, 
file_system,
-                                   read_options.prefetch_max_parallel_num,
-                                   read_options.read_batch_size, 
read_options.prefetch_batch_count,
-                                   read_options.adaptive_prefetch_strategy, 
executor,
-                                   /*initialize_read_ranges=*/false,
-                                   read_options.read_ahead_cache_enabled, 
read_options.cache_config,
-                                   read_options.prefetch_io_metrics_enabled, 
pool, arrow_pool));
+        PAIMON_ASSIGN_OR_RAISE(
+            std::unique_ptr<PrefetchFileBatchReaderImpl> prefetch_reader,
+            PrefetchFileBatchReaderImpl::Create(
+                file_path, file_size, reader_builder, file_system,
+                read_options.prefetch_max_parallel_num, 
read_options.read_batch_size,
+                read_options.prefetch_batch_count, 
read_options.adaptive_prefetch_strategy,
+                executor,
+                /*initialize_read_ranges=*/false, 
read_options.read_ahead_cache_enabled,
+                read_options.cache_config, 
read_options.prefetch_io_metrics_enabled, pool,
+                arrow_pool, read_options.warmup_level));

Review Comment:
   Please don’t put the new parameter at the end. Keeping `pool` as the last 
parameter is more consistent.



##########
src/paimon/core/io/key_value_record_reader.h:
##########
@@ -39,6 +39,13 @@ class KeyValueRecordReader {
 
     virtual std::shared_ptr<Metrics> GetReaderMetrics() const = 0;
 
+    /// Starts whatever background work this reader would otherwise start on 
its first read, so a
+    /// caller that knows this reader is next can pay that startup while still 
consuming the
+    /// previous one. Optional and reporting no error, like 
`FileBatchReader::Warmup()`: a reader
+    /// with nothing to start does nothing, and a hint about a file nobody 
reads must not fail the
+    /// read in progress.
+    virtual void Warmup() {}

Review Comment:
   Is it a good idea to keep an empty `{}` here? Could this cause a newly added 
`KeyValueRecordReader` in the future to miss forwarding `Warmup()`?



##########
src/paimon/core/operation/merge_file_split_read.cpp:
##########
@@ -662,14 +662,46 @@ MergeFileSplitRead::CreateRecordReadersForSection(
     const std::vector<SortedRun>& section, const BinaryRow& partition,
     DeletionVector::Factory dv_factory, const std::shared_ptr<Predicate>& 
predicate,
     const std::shared_ptr<DataFilePathFactory>& data_file_path_factory) const {
+    // Every run of a section is read by the same merge, so the section's 
files are built as ONE
+    // batch instead of run by run, and both section read paths go through 
this one build. The
+    // readers are then handed back to the run they belong to by position.
+    std::vector<std::shared_ptr<DataFileMeta>> section_files;
+    for (const SortedRun& run : section) {
+        const std::vector<std::shared_ptr<DataFileMeta>>& run_files = 
run.Files();
+        section_files.insert(section_files.end(), run_files.begin(), 
run_files.end());
+    }
+    PAIMON_ASSIGN_OR_RAISE(
+        std::vector<std::unique_ptr<FileBatchReader>> raw_file_readers,
+        CreateRawFileReaders(partition, section_files, read_schema_, 
predicate, dv_factory,
+                             /*row_ranges=*/{}, data_file_path_factory,
+                             /*extra_format_options=*/{}));
+    // A build that dropped a reader would silently shift every following file 
into the wrong run.

Review Comment:
   I’m not following how the refactoring of `CreateReaderForRun` is related to 
the current warmup support. As far as I see, `CreateRawFileReaders` doesn’t 
introduce any additional concurrency. The current changes also add more 
validation, so it doesn’t seem like they would have much impact on the final 
performance?



##########
src/paimon/core/io/complete_row_tracking_fields_reader_test.cpp:
##########
@@ -486,4 +486,21 @@ TEST_F(CompleteRowTrackingFieldsBatchReaderTest, 
TestOnlyReadRowTrackingFields)
     CheckResult(src_array, /*first_row_id=*/100, /*snapshot_id=*/4, 
read_schema, target_array);
 }
 
+// Warmup() is only a hint, but it must reach the wrapped reader: this wrapper 
sits between the
+// split read and the file, and swallowing the hint here would leave the whole 
stack below it cold
+// no matter which layer asked for the warmup.
+TEST_F(CompleteRowTrackingFieldsBatchReaderTest, 
TestWarmupForwardsToInnerReader) {

Review Comment:
   It doesn’t seem necessary to add a comment like this to every case.



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