Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "cloud/cloud_rowset_writer.h" |
19 | | |
20 | | #include "common/logging.h" |
21 | | #include "common/status.h" |
22 | | #include "io/cache/block_file_cache_factory.h" |
23 | | #include "io/fs/packed_file_manager.h" |
24 | | #include "io/fs/packed_file_writer.h" |
25 | | #include "storage/rowset/rowset_factory.h" |
26 | | |
27 | | namespace doris { |
28 | | |
29 | 196k | CloudRowsetWriter::CloudRowsetWriter(CloudStorageEngine& engine) : _engine(engine) {} |
30 | | |
31 | 197k | CloudRowsetWriter::~CloudRowsetWriter() { |
32 | | // Must cancel any pending delete bitmap tasks before destruction. |
33 | | // Otherwise, the lambda in _generate_delete_bitmap may execute after the |
34 | | // CloudRowsetWriter destructor runs but before BaseBetaRowsetWriter destructor, |
35 | | // causing virtual function calls to resolve to BaseBetaRowsetWriter::_build_rowset_meta |
36 | | // instead of CloudRowsetWriter::_build_rowset_meta (use-after-free on vtable). |
37 | 197k | if (_calc_delete_bitmap_token != nullptr) { |
38 | 52.1k | _calc_delete_bitmap_token->cancel(); |
39 | 52.1k | } |
40 | 197k | } |
41 | | |
42 | 193k | Status CloudRowsetWriter::init(const RowsetWriterContext& rowset_writer_context) { |
43 | 193k | _context = rowset_writer_context; |
44 | 193k | _rowset_meta = std::make_shared<RowsetMeta>(); |
45 | | |
46 | 193k | if (_context.is_local_rowset()) { |
47 | | // In cloud mode, this branch implies it is an intermediate rowset for external merge sort, |
48 | | // we use `global_local_filesystem` to write data to `tmp_file_dir`(see `local_segment_path`). |
49 | 2.44k | _context.tablet_path = io::FileCacheFactory::instance()->pick_one_cache_path(); |
50 | 191k | } else { |
51 | 191k | _rowset_meta->set_remote_storage_resource(*_context.storage_resource); |
52 | 191k | } |
53 | | |
54 | 193k | _rowset_meta->set_rowset_id(_context.rowset_id); |
55 | 193k | _rowset_meta->set_partition_id(_context.partition_id); |
56 | 193k | _rowset_meta->set_tablet_id(_context.tablet_id); |
57 | 193k | _rowset_meta->set_db_id(_context.db_id); |
58 | 193k | _rowset_meta->set_table_id(_context.table_id); |
59 | 193k | _rowset_meta->set_index_id(_context.index_id); |
60 | 193k | _rowset_meta->set_tablet_schema_hash(_context.tablet_schema_hash); |
61 | 193k | _rowset_meta->set_rowset_type(_context.rowset_type); |
62 | 193k | _rowset_meta->set_rowset_state(_context.rowset_state); |
63 | 193k | _rowset_meta->set_segments_overlap(_context.segments_overlap); |
64 | 193k | _rowset_meta->set_txn_id(_context.txn_id); |
65 | 193k | _rowset_meta->set_txn_expiration(_context.txn_expiration); |
66 | 193k | _rowset_meta->set_compaction_level(_context.compaction_level); |
67 | 193k | if (_context.rowset_state == PREPARED || _context.rowset_state == COMMITTED) { |
68 | 176k | _is_pending = true; |
69 | 176k | _rowset_meta->set_load_id(_context.load_id); |
70 | 176k | } else { |
71 | | // Rowset generated by compaction or schema change |
72 | 17.4k | _rowset_meta->set_version(_context.version); |
73 | 17.4k | DCHECK_NE(_context.newest_write_timestamp, -1); |
74 | 17.4k | _rowset_meta->set_newest_write_timestamp(_context.newest_write_timestamp); |
75 | 17.4k | } |
76 | 193k | _rowset_meta->set_tablet_schema(_context.tablet_schema); |
77 | 193k | _rowset_meta->set_job_id(_context.job_id); |
78 | 193k | if (_context.write_binlog_opt().enable) { |
79 | 2 | _rowset_meta->mark_row_binlog(); |
80 | 2 | } |
81 | 193k | _context.segment_collector = std::make_shared<SegmentCollectorT<BaseBetaRowsetWriter>>(this); |
82 | 193k | _context.file_writer_creator = std::make_shared<FileWriterCreatorT<BaseBetaRowsetWriter>>(this); |
83 | 193k | if (_context.mow_context != nullptr) { |
84 | 52.0k | _calc_delete_bitmap_token = _engine.calc_delete_bitmap_executor_for_load()->create_token(); |
85 | 52.0k | } |
86 | 193k | return Status::OK(); |
87 | 193k | } |
88 | | |
89 | 215k | Status CloudRowsetWriter::_build_rowset_meta(RowsetMeta* rowset_meta, bool check_segment_num) { |
90 | 215k | VLOG_NOTICE << "start to build rowset meta. tablet_id=" << rowset_meta->tablet_id() |
91 | 100 | << ", rowset_id=" << rowset_meta->rowset_id() |
92 | 100 | << ", check_segment_num=" << check_segment_num; |
93 | | // Call base class implementation |
94 | 215k | RETURN_IF_ERROR(BaseBetaRowsetWriter::_build_rowset_meta(rowset_meta, check_segment_num)); |
95 | | |
96 | | // Collect packed file segment index information for interim rowsets as well. |
97 | 215k | return _collect_all_packed_slice_locations(rowset_meta); |
98 | 215k | } |
99 | | |
100 | 197k | Status CloudRowsetWriter::build(RowsetSharedPtr& rowset) { |
101 | 197k | if (_calc_delete_bitmap_token != nullptr) { |
102 | 52.1k | RETURN_IF_ERROR(_calc_delete_bitmap_token->wait()); |
103 | 52.1k | } |
104 | 197k | RETURN_IF_ERROR(_close_file_writers()); |
105 | | |
106 | | // TODO(plat1ko): check_segment_footer |
107 | | |
108 | 197k | RETURN_IF_ERROR(_build_rowset_meta(_rowset_meta.get())); |
109 | | // At this point all writers have been closed, so collecting packed file indices is safe. |
110 | 197k | RETURN_IF_ERROR(_collect_all_packed_slice_locations(_rowset_meta.get())); |
111 | | // If the current load is a partial update, new segments may be appended to the tmp rowset after the tmp rowset |
112 | | // has been committed if conflicts occur due to concurrent partial updates. However, when the recycler do recycling, |
113 | | // it will generate the paths for the segments to be recycled on the object storage based on the number of segments |
114 | | // in the rowset meta. If these newly added segments are written to the object storage and the transaction is aborted |
115 | | // due to a failure before successfully updating the rowset meta of the corresponding tmp rowset, these newly added |
116 | | // segments cannot be recycled by the recycler on the object storage. Therefore, we need a new state `BEGIN_PARTIAL_UPDATE` |
117 | | // to indicate that the recycler should use list+delete to recycle segments. After the tmp rowset's rowset meta being |
118 | | // updated successfully, the `rowset_state` will be set to `COMMITTED` and the recycler can do recycling based on the |
119 | | // number of segments in the rowset meta safely. |
120 | | // |
121 | | // rowset_state's FSM: |
122 | | // |
123 | | // transfer 0 |
124 | | // PREPARED ---------------------------> COMMITTED |
125 | | // | ^ |
126 | | // | transfer 1 | |
127 | | // | | transfer 2 |
128 | | // |--> BEGIN_PARTIAL_UPDATE ------| |
129 | | // |
130 | | // transfer 0 (PREPARED -> COMMITTED): finish writing a rowset and the rowset' meta will not be changed |
131 | | // transfer 1 (PREPARED -> BEGIN_PARTIAL_UPDATE): finish writing a rowset, but may append new segments later and the rowset's meta may be changed |
132 | | // transfer 2 (BEGIN_PARTIAL_UPDATE -> VISIBLE): finish adding new segments and the rowset' meta will not be changed, the rowset is visible to users |
133 | 197k | if (_context.partial_update_info && _context.partial_update_info->is_partial_update()) { |
134 | 4.80k | _rowset_meta->set_rowset_state(BEGIN_PARTIAL_UPDATE); |
135 | 192k | } else { |
136 | 192k | _rowset_meta->set_rowset_state(COMMITTED); |
137 | 192k | } |
138 | | |
139 | 197k | _rowset_meta->set_tablet_schema(_context.tablet_schema); |
140 | | |
141 | 197k | if (_rowset_meta->newest_write_timestamp() == -1) { |
142 | 178k | _rowset_meta->set_newest_write_timestamp(UnixSeconds()); |
143 | 178k | } |
144 | | |
145 | 197k | if (auto seg_file_size = _seg_files.segments_file_size(_segment_start_id); |
146 | 197k | !seg_file_size.has_value()) [[unlikely]] { |
147 | 0 | LOG(ERROR) << "expected segment file sizes, but none presents: " << seg_file_size.error(); |
148 | 197k | } else { |
149 | 197k | _rowset_meta->add_segments_file_size(seg_file_size.value()); |
150 | 197k | } |
151 | 197k | if (_context.tablet_schema->has_inverted_index() || _context.tablet_schema->has_ann_index()) { |
152 | 22.2k | if (auto idx_files_info = _idx_files.inverted_index_file_info(_segment_start_id); |
153 | 22.2k | !idx_files_info.has_value()) [[unlikely]] { |
154 | 0 | LOG(ERROR) << "expected inverted index files info, but none presents: " |
155 | 0 | << idx_files_info.error(); |
156 | 22.2k | } else { |
157 | 22.2k | _rowset_meta->add_inverted_index_files_info(idx_files_info.value()); |
158 | 22.2k | } |
159 | 22.2k | } |
160 | | |
161 | 197k | RETURN_NOT_OK_STATUS_WITH_WARN( |
162 | 197k | RowsetFactory::create_rowset(_context.tablet_schema, _context.tablet_path, _rowset_meta, |
163 | 197k | &rowset), |
164 | 197k | "rowset init failed when build new rowset"); |
165 | 197k | _already_built = true; |
166 | 197k | return Status::OK(); |
167 | 197k | } |
168 | | |
169 | 411k | Status CloudRowsetWriter::_collect_all_packed_slice_locations(RowsetMeta* rowset_meta) { |
170 | 18.4E | VLOG_NOTICE << "start to collect packed slice locations for rowset meta. tablet_id=" |
171 | 18.4E | << rowset_meta->tablet_id() << ", rowset_id=" << rowset_meta->rowset_id(); |
172 | 411k | if (!_context.packed_file_active) { |
173 | 278k | return Status::OK(); |
174 | 278k | } |
175 | | |
176 | | // Collect segment file packed indices |
177 | 132k | const auto& file_writers = _seg_files.get_file_writers(); |
178 | 132k | for (const auto& [seg_id, writer_ptr] : file_writers) { |
179 | 132k | auto segment_path = _context.segment_path(seg_id); |
180 | 132k | RETURN_IF_ERROR( |
181 | 132k | _collect_packed_slice_location(writer_ptr.get(), segment_path, rowset_meta)); |
182 | 132k | } |
183 | | |
184 | | // Collect inverted index file packed indices |
185 | 132k | const auto& idx_file_writers = _idx_files.get_file_writers(); |
186 | 132k | for (const auto& [seg_id, idx_writer_ptr] : idx_file_writers) { |
187 | 13.1k | if (idx_writer_ptr != nullptr && idx_writer_ptr->get_file_writer() != nullptr) { |
188 | 13.1k | auto segment_path = _context.segment_path(seg_id); |
189 | 13.1k | auto index_prefix_view = |
190 | 13.1k | InvertedIndexDescriptor::get_index_file_path_prefix(segment_path); |
191 | 13.1k | std::string index_path = |
192 | 13.1k | InvertedIndexDescriptor::get_index_file_path_v2(std::string(index_prefix_view)); |
193 | 13.1k | RETURN_IF_ERROR(_collect_packed_slice_location(idx_writer_ptr->get_file_writer(), |
194 | 13.1k | index_path, rowset_meta)); |
195 | 13.1k | } |
196 | 13.1k | } |
197 | | |
198 | 132k | return Status::OK(); |
199 | 132k | } |
200 | | |
201 | | Status CloudRowsetWriter::_collect_packed_slice_location(io::FileWriter* file_writer, |
202 | | const std::string& file_path, |
203 | 145k | RowsetMeta* rowset_meta) { |
204 | 145k | VLOG_NOTICE << "collect packed slice location for file: " << file_path; |
205 | | // Check if file writer is closed |
206 | 145k | if (file_writer->state() != io::FileWriter::State::CLOSED) { |
207 | | // Writer is still open; index will be collected after it is closed. |
208 | 2.20k | return Status::OK(); |
209 | 2.20k | } |
210 | | |
211 | | // Check if file is actually in packed file (not direct write for large files) |
212 | 143k | if (!file_writer->is_in_packed_file()) { |
213 | 590 | return Status::OK(); |
214 | 590 | } |
215 | | |
216 | | // Get packed slice location directly from PackedFileManager |
217 | 142k | io::PackedSliceLocation index; |
218 | 142k | RETURN_IF_ERROR( |
219 | 142k | io::PackedFileManager::instance()->get_packed_slice_location(file_path, &index)); |
220 | 142k | if (index.packed_file_path.empty()) { |
221 | 0 | return Status::OK(); // File not in packed file, skip |
222 | 0 | } |
223 | | |
224 | 142k | rowset_meta->add_packed_slice_location(file_path, index.packed_file_path, index.offset, |
225 | 142k | index.size, index.packed_file_size); |
226 | | LOG(INFO) << "collect packed file index: " << file_path << " -> " << index.packed_file_path |
227 | 142k | << ", offset: " << index.offset << ", size: " << index.size; |
228 | 142k | return Status::OK(); |
229 | 142k | } |
230 | | |
231 | | } // namespace doris |