be/src/storage/index/index_compound_reader.cpp
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 "storage/index/index_compound_reader.h" |
19 | | |
20 | | #include <CLucene/clucene-config.h> |
21 | | #include <CLucene/debug/error.h> |
22 | | #include <CLucene/debug/mem.h> |
23 | | #include <CLucene/store/RAMDirectory.h> |
24 | | #include <CLucene/util/Misc.h> |
25 | | |
26 | | #include <cstdio> |
27 | | #include <cstring> |
28 | | #include <cwchar> |
29 | | #include <memory> |
30 | | #include <utility> |
31 | | |
32 | | #include "storage/index/index_fs_directory.h" |
33 | | #include "storage/tablet/tablet_schema.h" |
34 | | #include "util/debug_points.h" |
35 | | #include "util/defer_op.h" |
36 | | |
37 | | namespace doris::io { |
38 | | class FileWriter; |
39 | | } // namespace doris::io |
40 | | |
41 | 406 | #define BUFFER_LENGTH 16384 |
42 | 3 | #define CL_MAX_PATH 4096 |
43 | | |
44 | | using FileWriterPtr = std::unique_ptr<doris::io::FileWriter>; |
45 | | |
46 | | namespace doris::segment_v2 { |
47 | | namespace { |
48 | | |
49 | | // The context of DorisCompoundReader::ScopedIOContext. |
50 | | thread_local const io::IOContext* scoped_io_ctx = nullptr; |
51 | | |
52 | | } // namespace |
53 | | |
54 | | DorisCompoundReader::ScopedIOContext::ScopedIOContext(const io::IOContext* io_ctx) |
55 | 19 | : _previous(scoped_io_ctx) { |
56 | 19 | scoped_io_ctx = io_ctx; |
57 | 19 | } |
58 | | |
59 | 19 | DorisCompoundReader::ScopedIOContext::~ScopedIOContext() { |
60 | 19 | scoped_io_ctx = _previous; |
61 | 19 | } |
62 | | |
63 | | /** Implementation of an IndexInput that reads from a portion of the |
64 | | * compound file. |
65 | | */ |
66 | | class CSIndexInput : public lucene::store::BufferedIndexInput { |
67 | | private: |
68 | | CL_NS(store)::IndexInput* base; |
69 | | std::string file_name; |
70 | | int64_t fileOffset; |
71 | | int64_t _length; |
72 | | const io::IOContext* _io_ctx = nullptr; |
73 | | |
74 | | protected: |
75 | | void readInternal(uint8_t* /*b*/, const int32_t /*len*/) override; |
76 | 6.43M | void seekInternal(const int64_t /*pos*/) override {} |
77 | | |
78 | | public: |
79 | | CSIndexInput(CL_NS(store)::IndexInput* base, const std::string& file_name, |
80 | | const int64_t fileOffset, const int64_t length, |
81 | | const int32_t read_buffer_size = CL_NS(store)::BufferedIndexInput::BUFFER_SIZE); |
82 | | CSIndexInput(const CSIndexInput& clone); |
83 | | ~CSIndexInput() override; |
84 | | void close() override; |
85 | | lucene::store::IndexInput* clone() const override; |
86 | 6.29M | int64_t length() const override { return _length; } |
87 | 1 | const char* getDirectoryType() const override { return DorisCompoundReader::getClassName(); } |
88 | 17 | const char* getObjectName() const override { return getClassName(); } |
89 | 17 | static const char* getClassName() { return "CSIndexInput"; } |
90 | | void setIoContext(const void* io_ctx) override; |
91 | | }; |
92 | | |
93 | | CSIndexInput::CSIndexInput(CL_NS(store)::IndexInput* base, const std::string& file_name, |
94 | | const int64_t fileOffset, const int64_t length, |
95 | | const int32_t read_buffer_size) |
96 | 14.8k | : BufferedIndexInput(read_buffer_size) { |
97 | 14.8k | this->base = base; |
98 | 14.8k | this->file_name = file_name; |
99 | 14.8k | this->fileOffset = fileOffset; |
100 | 14.8k | this->_length = length; |
101 | 14.8k | } |
102 | | |
103 | 6.14M | void CSIndexInput::readInternal(uint8_t* b, const int32_t len) { |
104 | 6.14M | std::lock_guard wlock(((DorisFSDirectory::FSIndexInput*)base)->_this_lock); |
105 | | |
106 | 6.14M | auto start = getFilePointer(); |
107 | 6.14M | if (start + len > _length) { |
108 | 0 | _CLTHROWA(CL_ERR_IO, "read past EOF"); |
109 | 0 | } |
110 | | |
111 | | // A stream without a context of its own reads with the thread's scoped one, if any. |
112 | 6.14M | const io::IOContext* io_ctx = _io_ctx != nullptr ? _io_ctx : scoped_io_ctx; |
113 | 6.14M | if (io_ctx) { |
114 | 579 | base->setIoContext(io_ctx); |
115 | 579 | } |
116 | | // The base stream copies the context, statistics pointer included, and outlives the query |
117 | | // that gave it, so it drops the context even when the read throws. |
118 | 6.14M | Defer reset_io_ctx {[&]() { |
119 | 6.14M | if (io_ctx) { |
120 | 579 | base->setIoContext(nullptr); |
121 | 579 | } |
122 | 6.14M | }}; |
123 | | |
124 | 6.14M | DBUG_EXECUTE_IF("CSIndexInput.readInternal", { |
125 | 6.14M | for (const auto& entry : InvertedIndexDescriptor::index_file_info_map) { |
126 | 6.14M | if (file_name.find(entry.first) != std::string::npos) { |
127 | 6.14M | if (!static_cast<const io::IOContext*>(base->getIoContext())->is_index_data) { |
128 | 6.14M | _CLTHROWA(CL_ERR_IO, |
129 | 6.14M | "The 'is_index_data' flag should be true for inverted index meta " |
130 | 6.14M | "files."); |
131 | 6.14M | } |
132 | 6.14M | } |
133 | 6.14M | } |
134 | 6.14M | for (const auto& entry : InvertedIndexDescriptor::normal_file_info_map) { |
135 | 6.14M | if (file_name.find(entry.first) != std::string::npos) { |
136 | 6.14M | if (static_cast<const io::IOContext*>(base->getIoContext())->is_index_data) { |
137 | 6.14M | _CLTHROWA(CL_ERR_IO, |
138 | 6.14M | "The 'is_index_data' flag should be false for non-meta inverted " |
139 | 6.14M | "index files."); |
140 | 6.14M | } |
141 | 6.14M | } |
142 | 6.14M | } |
143 | 6.14M | }); |
144 | | |
145 | 6.14M | base->seek(fileOffset + start); |
146 | 6.14M | bool read_from_buffer = true; |
147 | 6.14M | base->readBytes(b, len, read_from_buffer); |
148 | 6.14M | } |
149 | | |
150 | 6.38M | CSIndexInput::~CSIndexInput() = default; |
151 | | |
152 | 6.36M | lucene::store::IndexInput* CSIndexInput::clone() const { |
153 | 6.36M | return _CLNEW CSIndexInput(*this); |
154 | 6.36M | } |
155 | | |
156 | 6.36M | CSIndexInput::CSIndexInput(const CSIndexInput& clone) : BufferedIndexInput(clone) { |
157 | 6.36M | this->base = clone.base; |
158 | 6.36M | this->file_name = clone.file_name; |
159 | 6.36M | this->fileOffset = clone.fileOffset; |
160 | 6.36M | this->_length = clone._length; |
161 | 6.36M | } |
162 | | |
163 | 19.1k | void CSIndexInput::close() {} |
164 | | |
165 | 13.5M | void CSIndexInput::setIoContext(const void* io_ctx) { |
166 | 13.5M | _io_ctx = static_cast<const io::IOContext*>(io_ctx); |
167 | 13.5M | } |
168 | | |
169 | | DorisCompoundReader::DorisCompoundReader(CL_NS(store)::IndexInput* stream, |
170 | | const EntriesType& entries_clone, int32_t read_buffer_size, |
171 | | const io::IOContext* io_ctx) |
172 | 4.94k | : _stream(stream), |
173 | 4.94k | _entries(std::make_unique<EntriesType>()), |
174 | 4.94k | _read_buffer_size(read_buffer_size) { |
175 | | // After stream clone, the io_ctx needs to be reconfigured. |
176 | 4.94k | initialize(io_ctx); |
177 | | |
178 | 43.2k | for (const auto& e : entries_clone) { |
179 | 43.2k | const auto& origin_entry = e.second; |
180 | 43.2k | auto entry = std::make_unique<ReaderFileEntry>(); |
181 | 43.2k | entry->file_name = origin_entry->file_name; |
182 | 43.2k | entry->offset = origin_entry->offset; |
183 | 43.2k | entry->length = origin_entry->length; |
184 | 43.2k | (*_entries)[e.first] = std::move(entry); |
185 | 43.2k | } |
186 | 4.94k | }; |
187 | | |
188 | | DorisCompoundReader::DorisCompoundReader(CL_NS(store)::IndexInput* stream, int32_t read_buffer_size, |
189 | | const io::IOContext* io_ctx) |
190 | 67 | : _ram_dir(std::make_unique<lucene::store::RAMDirectory>()), |
191 | 67 | _stream(stream), |
192 | 67 | _entries(std::make_unique<EntriesType>()), |
193 | 67 | _read_buffer_size(read_buffer_size) { |
194 | | // After stream clone, the io_ctx needs to be reconfigured. |
195 | 67 | initialize(io_ctx); |
196 | | |
197 | 67 | try { |
198 | 67 | int32_t count = _stream->readVInt(); |
199 | 67 | uint8_t buffer[BUFFER_LENGTH]; |
200 | 486 | for (int32_t i = 0; i < count; i++) { |
201 | 419 | auto entry = std::make_unique<ReaderFileEntry>(); |
202 | | // Read the string length first |
203 | 419 | int32_t string_length = stream->readVInt(); |
204 | | // Allocate appropriate buffer for the string |
205 | 419 | std::wstring tid; |
206 | 419 | tid.resize(string_length); |
207 | | // Read the string characters directly |
208 | 419 | stream->readChars(tid.data(), 0, string_length); |
209 | 419 | std::string file_name_str(tid.begin(), tid.end()); |
210 | 419 | entry->file_name = file_name_str; |
211 | 419 | entry->offset = stream->readLong(); |
212 | 419 | entry->length = stream->readLong(); |
213 | 419 | VLOG_DEBUG << "string_length:" << string_length << " file_name:" << entry->file_name |
214 | 0 | << " offset:" << entry->offset << " length:" << entry->length; |
215 | 419 | DBUG_EXECUTE_IF("construct_DorisCompoundReader_failed", { |
216 | 419 | CLuceneError err; |
217 | 419 | err.set(CL_ERR_IO, "construct_DorisCompoundReader_failed"); |
218 | 419 | throw err; |
219 | 419 | }) |
220 | | // read header file data |
221 | 419 | if (entry->offset < 0) { |
222 | | //if offset is -1, it means it's size is lower than DorisFSDirectory::MAX_HEADER_DATA_SIZE, which is 128k. |
223 | 406 | _copyFile(entry->file_name.c_str(), static_cast<int32_t>(entry->length), buffer, |
224 | 406 | BUFFER_LENGTH); |
225 | 406 | } |
226 | 419 | _entries->emplace(std::move(file_name_str), std::move(entry)); |
227 | 419 | } |
228 | 67 | } catch (...) { |
229 | 0 | try { |
230 | 0 | if (_stream != nullptr) { |
231 | 0 | _stream->close(); |
232 | 0 | _CLDELETE(_stream) |
233 | 0 | } |
234 | 0 | if (_entries != nullptr) { |
235 | 0 | _entries->clear(); |
236 | 0 | } |
237 | 0 | if (_ram_dir) { |
238 | 0 | _ram_dir->close(); |
239 | 0 | } |
240 | 0 | } catch (CLuceneError& err) { |
241 | 0 | if (err.number() != CL_ERR_IO) { |
242 | 0 | throw err; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | throw; |
246 | 0 | } |
247 | 67 | } |
248 | | |
249 | | void DorisCompoundReader::_copyFile(const char* file, int32_t file_length, uint8_t* buffer, |
250 | 408 | int32_t buffer_length) { |
251 | 408 | std::unique_ptr<lucene::store::IndexOutput> output(_ram_dir->createOutput(file)); |
252 | 408 | int64_t start_ptr = output->getFilePointer(); |
253 | 408 | auto remainder = file_length; |
254 | 408 | auto chunk = buffer_length; |
255 | 408 | auto batch_len = file_length < chunk ? file_length : chunk; |
256 | | |
257 | 817 | while (remainder > 0) { |
258 | 409 | auto len = remainder < batch_len ? remainder : batch_len; |
259 | 409 | _stream->readBytes(buffer, len); |
260 | 409 | output->writeBytes(buffer, len); |
261 | 409 | remainder -= len; |
262 | 409 | } |
263 | 408 | if (remainder != 0) { |
264 | 0 | TCHAR buf[CL_MAX_PATH + 100]; |
265 | 0 | swprintf(buf, CL_MAX_PATH + 100, |
266 | 0 | _T("Non-zero remainder length after copying") |
267 | 0 | _T(": %d (id: %s, length: %d, buffer size: %d)"), |
268 | 0 | (int)remainder, file, (int)file_length, (int)chunk); |
269 | 0 | _CLTHROWT(CL_ERR_IO, buf); |
270 | 0 | } |
271 | | |
272 | 408 | int64_t end_ptr = output->getFilePointer(); |
273 | 408 | int64_t diff = end_ptr - start_ptr; |
274 | 408 | if (diff != file_length) { |
275 | 0 | TCHAR buf[100]; |
276 | 0 | swprintf(buf, 100, |
277 | 0 | _T("Difference in the output file offsets %d ") |
278 | 0 | _T("does not match the original file length %d"), |
279 | 0 | (int)diff, (int)file_length); |
280 | 0 | _CLTHROWA(CL_ERR_IO, buf); |
281 | 0 | } |
282 | 408 | output->close(); |
283 | 408 | } |
284 | | |
285 | 5.00k | DorisCompoundReader::~DorisCompoundReader() { |
286 | 5.00k | if (!_closed) { |
287 | 3.41k | try { |
288 | 3.41k | close(); |
289 | 3.41k | } catch (CLuceneError& err) { |
290 | 0 | LOG(ERROR) << "DorisCompoundReader finalize error:" << err.what(); |
291 | 0 | } |
292 | 3.41k | } |
293 | 5.00k | } |
294 | | |
295 | 5 | const char* DorisCompoundReader::getClassName() { |
296 | 5 | return "DorisCompoundReader"; |
297 | 5 | } |
298 | 4 | const char* DorisCompoundReader::getObjectName() const { |
299 | 4 | return getClassName(); |
300 | 4 | } |
301 | | |
302 | 3.21k | bool DorisCompoundReader::list(std::vector<std::string>* names) const { |
303 | 3.21k | if (_closed || _entries == nullptr) { |
304 | 1 | _CLTHROWA(CL_ERR_IO, "DorisCompoundReader is already closed"); |
305 | 1 | } |
306 | 29.4k | for (const auto& entry : *_entries) { |
307 | 29.4k | names->push_back(entry.first); |
308 | 29.4k | } |
309 | 3.20k | return true; |
310 | 3.21k | } |
311 | | |
312 | 1.22k | bool DorisCompoundReader::fileExists(const char* name) const { |
313 | 1.22k | if (_closed || _entries == nullptr) { |
314 | 1 | _CLTHROWA(CL_ERR_IO, "DorisCompoundReader is already closed"); |
315 | 1 | } |
316 | 1.22k | return _entries->find(std::string(name)) != _entries->end(); |
317 | 1.22k | } |
318 | | |
319 | 1 | int64_t DorisCompoundReader::fileModified(const char* name) const { |
320 | 1 | return 0; |
321 | 1 | } |
322 | | |
323 | 315 | int64_t DorisCompoundReader::fileLength(const char* name) const { |
324 | 315 | if (_closed || _entries == nullptr) { |
325 | 1 | _CLTHROWA(CL_ERR_IO, "DorisCompoundReader is already closed"); |
326 | 1 | } |
327 | 314 | auto it = _entries->find(std::string(name)); |
328 | 314 | if (it == _entries->end()) { |
329 | 1 | char buf[CL_MAX_PATH + 30]; |
330 | 1 | strcpy(buf, "File "); |
331 | 1 | strncat(buf, name, CL_MAX_PATH); |
332 | 1 | strcat(buf, " does not exist"); |
333 | 1 | _CLTHROWA(CL_ERR_IO, buf); |
334 | 1 | } |
335 | 313 | return it->second->length; |
336 | 314 | } |
337 | | |
338 | | bool DorisCompoundReader::openInput(const char* name, |
339 | | std::unique_ptr<lucene::store::IndexInput>& ret, |
340 | 43 | CLuceneError& error, int32_t bufferSize) { |
341 | 43 | if (_closed || _entries == nullptr) { |
342 | 0 | error.set(CL_ERR_IO, "DorisCompoundReader is already closed"); |
343 | 0 | return false; |
344 | 0 | } |
345 | 43 | lucene::store::IndexInput* tmp; |
346 | 43 | bool success = openInput(name, tmp, error, bufferSize); |
347 | 43 | if (success) { |
348 | 42 | ret.reset(tmp); |
349 | 42 | } |
350 | 43 | return success; |
351 | 43 | } |
352 | | |
353 | | bool DorisCompoundReader::openInput(const char* name, lucene::store::IndexInput*& ret, |
354 | 14.9k | CLuceneError& error, int32_t bufferSize) { |
355 | 14.9k | if (_stream == nullptr) { |
356 | 1 | error.set(CL_ERR_IO, "Stream closed"); |
357 | 1 | return false; |
358 | 1 | } |
359 | | |
360 | 14.9k | auto it = _entries->find(std::string(name)); |
361 | 14.9k | if (it == _entries->end()) { |
362 | 2 | char buf[CL_MAX_PATH + 26]; |
363 | 2 | snprintf(buf, CL_MAX_PATH + 26, "No sub-file with id %s found", name); |
364 | 2 | error.set(CL_ERR_IO, buf); |
365 | 2 | return false; |
366 | 2 | } |
367 | | |
368 | 14.9k | const auto& entry = it->second; |
369 | | |
370 | | // If file is in RAM, just return. |
371 | 14.9k | if (_ram_dir && _ram_dir->fileExists(name)) { |
372 | 161 | return _ram_dir->openInput(name, ret, error, bufferSize); |
373 | 161 | } |
374 | | |
375 | 14.8k | if (bufferSize < 1) { |
376 | 7.04k | bufferSize = _read_buffer_size; |
377 | 7.04k | } |
378 | | |
379 | 14.8k | ret = _CLNEW CSIndexInput(_stream, entry->file_name, entry->offset, entry->length, bufferSize); |
380 | 14.8k | return true; |
381 | 14.9k | } |
382 | | |
383 | 5.00k | void DorisCompoundReader::close() { |
384 | 5.00k | std::lock_guard<std::mutex> wlock(_this_lock); |
385 | 5.00k | if (_stream != nullptr) { |
386 | 5.00k | _stream->close(); |
387 | 5.00k | _CLDELETE(_stream) |
388 | 5.00k | } |
389 | 5.00k | if (_entries != nullptr) { |
390 | | // The life cycle of _entries should be consistent with that of the DorisCompoundReader. |
391 | | // DO NOT DELETE _entries here, it will be deleted in the destructor |
392 | | // When directory is closed, all _entries are cleared. But the directory may be called in other places. |
393 | | // If we delete the _entries object here, it will cause core dump. |
394 | 5.00k | _entries->clear(); |
395 | 5.00k | } |
396 | 5.00k | if (_ram_dir) { |
397 | 68 | _ram_dir->close(); |
398 | 68 | } |
399 | 5.00k | _closed = true; |
400 | 5.00k | } |
401 | | |
402 | 1 | bool DorisCompoundReader::doDeleteFile(const char* /*name*/) { |
403 | 1 | _CLTHROWA(CL_ERR_UnsupportedOperation, |
404 | 1 | "UnsupportedOperationException: DorisCompoundReader::doDeleteFile"); |
405 | 1 | } |
406 | | |
407 | 1 | void DorisCompoundReader::renameFile(const char* /*from*/, const char* /*to*/) { |
408 | 1 | _CLTHROWA(CL_ERR_UnsupportedOperation, |
409 | 1 | "UnsupportedOperationException: DorisCompoundReader::renameFile"); |
410 | 1 | } |
411 | | |
412 | 1 | void DorisCompoundReader::touchFile(const char* /*name*/) { |
413 | 1 | _CLTHROWA(CL_ERR_UnsupportedOperation, |
414 | 1 | "UnsupportedOperationException: DorisCompoundReader::touchFile"); |
415 | 1 | } |
416 | | |
417 | 1 | lucene::store::IndexOutput* DorisCompoundReader::createOutput(const char* /*name*/) { |
418 | 1 | _CLTHROWA(CL_ERR_UnsupportedOperation, |
419 | 1 | "UnsupportedOperationException: DorisCompoundReader::createOutput"); |
420 | 1 | } |
421 | | |
422 | 1 | std::string DorisCompoundReader::toString() const { |
423 | 1 | return "DorisCompoundReader@"; |
424 | 1 | } |
425 | | |
426 | 241 | CL_NS(store)::IndexInput* DorisCompoundReader::getDorisIndexInput() { |
427 | 241 | return _stream; |
428 | 241 | } |
429 | | |
430 | 5.00k | void DorisCompoundReader::initialize(const io::IOContext* io_ctx) { |
431 | 5.00k | _stream->setIoContext(io_ctx); |
432 | 5.00k | _stream->setIdxFileCache(true); |
433 | 5.00k | } |
434 | | |
435 | | } // namespace doris::segment_v2 |