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


##########
be/src/storage/index/snii/snii_blob_staging_directory.cpp:
##########
@@ -49,15 +46,25 @@ class SniiBlobStagingDirectory::StagingIndexOutput final
         try {
             StagingIndexOutput::close();
         } catch (const CLuceneError&) {
-            // A destructor may not throw. Nothing here can fail anyway --
-            // flushBuffer only appends to a vector -- but the base close() is
-            // declared throwing, so the guard has to exist.
+            // A destructor may not throw. The normal success path closes
+            // explicitly so it can report this staging I/O failure.
         }
     }
 
-    void close() override { BufferedIndexOutput::close(); }
+    void close() override {
+        if (_closed) {
+            return;
+        }
+        BufferedIndexOutput::close();
+        Status status = _file->finalize();

Review Comment:
   [P2] Avoid fsyncing disposable ANN staging
   
   This close now calls `StagedBlobFile::finalize()`, which unconditionally 
`fsync`s the scratch file. `SniiCompoundWriter` immediately rereads every byte 
into the real container, and `IndexFileWriter::begin_close()` later makes that 
container durable with `_idx_v2_writer->close(true)`; the staging inode is then 
unlinked and cannot recover an interrupted rowset. For GiB-scale HNSW/IVF 
output this forces a complete durable scratch write and latency barrier before 
the second full write, once per sub-file, and can reject a build on scratch 
writeback even when the cached bytes could still be copied to the final writer. 
Please give disposable ANN staging a non-durable seal-for-reading operation and 
leave durability/error reporting to the final container close.



##########
be/src/storage/index/snii/snii_blob_staging_directory.cpp:
##########
@@ -151,12 +163,18 @@ void SniiBlobStagingDirectory::touchFile(const char* 
/*name*/) {
 lucene::store::IndexOutput* SniiBlobStagingDirectory::createOutput(const char* 
name) {
     DORIS_CHECK(name != nullptr);
     // Same semantics as a filesystem directory: creating an existing name
-    // truncates it. The buffer is replaced rather than cleared, so a blob 
source
-    // already taken over the old content keeps reading the old content 
instead of
-    // seeing it mutate underneath.
-    auto buffer = std::make_shared<Buffer>();
-    _files[name] = buffer;
-    return _CLNEW StagingIndexOutput(std::move(buffer));
+    // truncates it. The file is replaced rather than reused, so a blob source
+    // already taken over the old content keeps reading the old content instead
+    // of seeing it mutate underneath.
+    std::unique_ptr<snii::bkd::StagedBlobFile> created;
+    Status status = snii::bkd::StagedBlobFile::create(name, &created);
+    if (!status.ok()) {
+        const std::string message = status.to_string();
+        _CLTHROWA(CL_ERR_IO, message.c_str());
+    }
+    auto file = std::shared_ptr<snii::bkd::StagedBlobFile>(std::move(created));

Review Comment:
   [P2] Bound ANN staging before vertical rowset close
   
   `StagedBlobFile::finalize()` deliberately keeps its fd open, and this map 
owns it until `IndexFileWriter::begin_close()`. In vertical compaction, 
`VerticalBetaRowsetWriter::_flush_columns()` finalizes ANN indexes for every 
destination segment, but the rowset-wide `_idx_files.begin_close()` runs only 
from `_close_file_writers()` after all column groups, segments, and post-merge 
index work complete. A valid multi-segment/multi-ANN rowset therefore 
accumulates one open fd per HNSW/IVF sub-file (two for IVF-on-disk) and a full 
temporary copy of every destination ANN before the first source drains; enough 
segments/indexes can hit `EMFILE` or fill the temp volume. This is distinct 
from the existing ADD INDEX thread: here the producers are gone and the 
directory owners in `_idx_files` retain the files before sealing. Please make 
finalized sources safely close/reopen to bound descriptors and introduce a safe 
phase/incremental-container lifecycle that can drain staged bytes earlier, with 
a
  vertical multi-destination lifecycle test.



##########
be/src/storage/index/ann/ann_index_writer.cpp:
##########
@@ -171,6 +171,17 @@ Status AnnIndexColumnWriter::_build_and_save(Int64 
min_train_rows, Int64 effecti
     // full-segment build buffer is released before saving the index.
     PODArray<float> empty_buffered_vectors;
     _buffered_vectors.swap(empty_buffered_vectors);
-    return _vector_index->save(_dir.get());
+    Status status = _vector_index->save(_dir.get());

Review Comment:
   [P2] Unwind ANN staging on later segment failures
   
   This only discards the staging directory when `save()` itself fails. After a 
successful ANN save, `SegmentWriter::finalize_columns_index()` can still fail 
in bloom/key-index work, and `SegmentWriter::finalize()` can fail writing the 
footer. `SegmentFlusher::_flush_segment_writer()` then returns before 
`close_inverted_index()`, while `SegmentCreator::flush()` retains its failed 
writer; the live rowset collection consequently keeps this finalized ANN 
file/fd through `IndexFileWriter::_indices_dirs` (and pre-clear failures also 
keep this producer's `_dir`). This is distinct from the existing save-failure 
thread because serialization has succeeded. Please add a segment/index-writer 
abort path that drops all SNII staging on every terminal finalize failure, with 
a held-owner test injecting a post-ANN failure and checking the exact staged 
path.



##########
be/test/storage/index/snii/snii_ann_container_test.cpp:
##########
@@ -412,6 +477,222 @@ class ScopedDebugPoints {
     const bool _was_enabled;
 };
 
+TEST_F(SniiAnnContainerTest, AnnStagingFinalizeFailureIsReturnedFromFinish) {
+    ScopedDebugPoints debug_points;
+    debug_points.enable("StagedBlobFile::finalize_error");
+
+    const std::string prefix = std::string(kTestDir) + "/finalize_failure_seg";
+    io::FileWriterPtr file_writer;
+    assert_ok(io::global_local_filesystem()->create_file(
+            InvertedIndexDescriptor::get_index_file_path_v2(prefix), 
&file_writer));
+    IndexFileWriter writer(io::global_local_filesystem(), prefix, 
"snii_ann_finalize_failure",
+                           /*seg_id=*/0, InvertedIndexStorageFormatPB::SNII, 
std::move(file_writer),
+                           /*can_use_ram_dir=*/false,
+                           /*tablet_id=*/9904);
+
+    Status finish_status = Status::OK();
+    ASSERT_NO_THROW({ finish_status = finish_ann_index(&writer, &_meta); });
+    EXPECT_FALSE(finish_status.ok());
+    EXPECT_NE(finish_status.to_string().find("injected blob staging finalize 
failure"),
+              std::string::npos)
+            << finish_status.to_string();
+}
+
+TEST_F(SniiAnnContainerTest, FinalBufferedAppendFailureIsReturnedFromFinish) {
+    ScopedDebugPoints debug_points;
+    debug_points.enable("StagedBlobFile::append_error");
+
+    const std::string prefix = std::string(kTestDir) + "/append_failure_seg";
+    io::FileWriterPtr file_writer;
+    assert_ok(io::global_local_filesystem()->create_file(
+            InvertedIndexDescriptor::get_index_file_path_v2(prefix), 
&file_writer));
+    IndexFileWriter writer(io::global_local_filesystem(), prefix, 
"snii_ann_append_failure",
+                           /*seg_id=*/0, InvertedIndexStorageFormatPB::SNII, 
std::move(file_writer),
+                           /*can_use_ram_dir=*/false,
+                           /*tablet_id=*/9905);
+
+    Status finish_status = Status::OK();
+    ASSERT_NO_THROW({ finish_status = finish_ann_index(&writer, &_meta); });
+    EXPECT_FALSE(finish_status.ok());
+    EXPECT_NE(finish_status.to_string().find("injected blob staging append 
failure"),
+              std::string::npos)
+            << finish_status.to_string();
+}
+
+TEST_F(SniiAnnContainerTest, IvfDataWriteFailuresAreReturnedFromFinish) {

Review Comment:
   [P2] Exercise IVF-on-disk through the SNII reader
   
   These new IVF cases only prove that staging write failures are returned. The 
successful production-reader test uses `_meta` (HNSW, one `ann.faiss`), while 
`IvfOnDiskSubFilesAreSealedInAscendingNameOrder` checks only the two 
names/order; the separate IVF save/load test uses a `RAMDirectory`. A wrong 
length/absolute extent or a bad `ann.ivfdata` handoff through 
`StagedBlobFile::read_at` and `DorisCompoundReader` would therefore leave all 
changed tests green and fail only when the new index is queried. Please seal 
`_ivf_on_disk_meta` through `IndexFileWriter`, load it with `AnnIndexReader`, 
and execute a query with checked neighbors.



##########
be/src/storage/index/snii/writer/snii_compound_writer.cpp:
##########
@@ -700,6 +708,7 @@ Status SniiCompoundWriter::finish() {
     if (out_ == nullptr)
         return Status::Error<ErrorCode::INVALID_ARGUMENT, false>("compound: 
null file writer");
     if (!failed_.ok()) {
+        release_all_blob_sources();

Review Comment:
   [P2] Release blob callbacks at the poison boundary
   
   This releases staged callbacks only if somebody calls `finish()` after the 
writer has been poisoned. A native-BKD column can first register a callback 
that is now the file's sole owner; if a later text index fails while writing 
its physical sections, `poison()` makes the compound permanently unsealable and 
`SegmentWriter::_write_inverted_index()` returns before 
`close_inverted_index()`, so production never reaches this branch. 
`SegmentCreator::flush()` also retains the failed writer, leaving the staged 
file/fd pinned in its compound callback. This is distinct from the existing 
in-`finish()` failure thread. Please release all blob sources on the first 
transition in `poison()` (or from an explicit abort path) and test a registered 
native-BKD blob followed by a later compound append failure while the outer 
writer remains alive.



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