airborne12 opened a new pull request, #67859:
URL: https://github.com/apache/doris/pull/67859

   ### What problem does this PR solve?
   
   Issue Number: N/A
   
   Related PR: #67702 (closed)
   
   Problem Summary:
   
   A segment whose schema owns an inverted index always owns an index file, even
   when no logical index had anything to write: an all-NULL VARIANT column 
extracts
   no subcolumn, so no CLucene directory is ever opened and the container is 
closed
   with nothing in it. That empty container is committed by `close()`, not by
   `create_file()`, and `IndexFileWriter`'s empty-file path only closed three 
named
   implementations -- `StreamSinkFileWriter`, `S3FileWriter`, 
`PackedFileWriter`.
   `LocalFileWriter` and `HdfsFileWriter` were left open, and `~LocalFileWriter`
   aborts -- and DELETES -- a writer it was never asked to close. So on local
   storage the file silently disappeared and `show_nested_index_file` reported
   `E-6003 ... not found` where `E-6004 ... is empty` was the truth.
   
   Closing through `FileWriter::state()` instead of a type whitelist fixes 
that, but
   on its own it is not enough: it makes one latent `IndexBuilder` invariant
   violation real, and it leaves a second `IndexBuilder` bug -- already 
reachable
   today on the sink-node and remote paths, where the empty file does persist --
   untouched.
   
   **`IndexBuilder` writes a container for every segment it rewrites**, 
including
   when the ALTER leaves the schema with no index at all: dropping the last 
index,
   or adding an index whose column does not exist. Until now the local writer's
   destructor happened to delete those files. Once they are closed they persist 
as
   orphans that nothing ever links, copies, uploads, removes or checksums --
   `BetaRowset` decides whether to touch the container by asking
   `has_inverted_or_ann_index()` of the rowset's own schema, and both 
segment-writer
   paths (`SegmentFlusher::_create_segment_writer`,
   `VerticalBetaRowsetWriter`) already gate creation the same way. 
`IndexBuilder`
   was the one caller that did not.
   
   **`IndexBuilder` refuses to read an empty container.**
   `update_inverted_index_info()` tolerates `E-6003` ("this rowset predates the
   index") but not `E-6004` ("this rowset's index is empty"), even though both 
mean
   the same thing there -- there is nothing to carry over, build everything 
from the
   raw columns. `BUILD INDEX` / `ADD INDEX` / `DROP INDEX` on a table with an
   all-NULL VARIANT index therefore fails the whole ALTER with
   `[E-6004]inverted index file ... is empty`.
   
   So: three changes, one invariant.
   
   > a rowset's schema owns an inverted/ANN index **iff** every one of its 
segments
   > owns an index file, possibly zero length
   
   1. `IndexFileWriter::begin_close()` / `finish_close()` close the empty 
container
      through `FileWriter` for any non-null, non-CLOSED writer, preserving the
      two-phase asynchronous protocol and propagating close errors.
   2. `IndexBuilder::handle_single_rowset()` writes no container when the output
      rowset schema owns no index -- on the drop path and the add path alike.
   3. `IndexBuilder::update_inverted_index_info()` reads an empty container 
exactly
      the way it reads a missing one.
   
   Consumers already accept `E-6004`: queries downgrade unconditionally
   (`SegmentIterator::_downgrade_without_index`), index compaction marks the 
segment
   ineligible and rebuilds, `CollectionStatistics` skips it. SNII is untouched 
--
   its container always carries a header, so it is never zero length, and its
   `has_container` probe stays `E-6003`-only.
   
   #67702 made change 1 and then updated
   `regression-test/plugins/plugin_curl_requester.groovy` and
   `test_drop_index_with_format_v2.groovy` to expect `E-6004` after dropping 
every
   index. That expectation describes the orphan file, so this PR leaves both at
   `E-6003` and removes the orphan instead.
   
   ### Release note
   
   Persist empty V2/V3 inverted index files through every FileWriter 
implementation
   instead of only the remote ones; stop failing ALTER on a segment whose index 
file
   is legitimately empty; stop leaving an unreferenced index file behind when 
the
   last index of a table is dropped.
   
   ### Check List (For Author)
   
   - Test: Unit Test / Regression test
       - `be/test/storage/index/inverted/empty_index_file_test.cpp`: 
implementation-
         independent lifecycle coverage. A `RecordingFileWriter` that implements
         `FileWriter` and nothing else pins the dispatch; plus already-closed,
         synchronous-close, both error paths, null writer, and local zero-length
         persistence after destruction followed by the reader-side `E-6004`
         contract (and its `E-6003` counterpart).
       - `be/test/io/fs/s3_file_writer_test.cpp`: exactly one `put_object`, zero
         `upload_part`, one empty object at the expected key, and no second PUT 
on a
         retried finish.
       - `be/test/storage/index/index_builder_test.cpp`:
         `BuildIndexOverEmptyIndexFileTest` builds an index over a rowset whose
         container has been emptied -- it fails with `[E-6004] ... is empty` 
without
         change 3. `DropInvertedIndexTest` / `DropAnnIndexTest` /
         `NonExistentColumnIndexTest` already assert zero `.idx` files after the
         rewrite; change 2 is what keeps them true.
       - 
`regression-test/suites/inverted_index_p0/test_variant_empty_index_file.groovy`
         now covers the local write path as well as memtable-on-sink-node, and
         enforces the expected query error instead of passing when none is 
raised.
       - 
`regression-test/suites/inverted_index_p0/test_empty_index_file_lifecycle.groovy`
         walks one empty-index table through load, full compaction, light schema
         change, direct schema change, `ADD INDEX` + `BUILD INDEX`, dropping 
one of
         two indexes and dropping the last one, asserting the invariant at each
         step, for v2 and v3 and for both write paths.
   - Behavior changed: Yes. Empty index files are now persisted on local storage
     too; a rowset whose schema has no index never carries an index file.
   - Does this need documentation: No
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_01Tp6z4cVB4buRW8JwU1KRdM
   


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