Coverage Report

Created: 2026-09-17 15:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/io/fs/packed_file_manager.h
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
#pragma once
19
20
#include <butil/macros.h>
21
#include <gen_cpp/cloud.pb.h>
22
#include <glog/logging.h>
23
24
#include <atomic>
25
#include <chrono>
26
#include <condition_variable>
27
#include <map>
28
#include <memory>
29
#include <mutex>
30
#include <optional>
31
#include <string>
32
#include <thread>
33
#include <unordered_map>
34
#include <vector>
35
36
#include "common/status.h"
37
#include "io/fs/file_system.h"
38
#include "io/fs/file_writer.h"
39
#include "io/fs/packed_slice_location.h"
40
#include "io/fs/path.h"
41
#include "util/slice.h"
42
43
namespace doris::io {
44
45
// Upload state of the packed file a slice belongs to
46
enum class PackedSliceUploadState : uint8_t {
47
    PENDING = 0,
48
    UPLOADED,
49
    FAILED,
50
};
51
52
// A slice of a packed file, shared by PackedFileManager and the PackedFileWriter that
53
// produced it. The writer holds its handle for as long as it lives, so it can wait for the
54
// upload and read the location back at the end of the load, whatever the manager recycled
55
// from its by-path index in the meantime.
56
class PackedSliceHandle {
57
public:
58
1.05k
    explicit PackedSliceHandle(PackedSliceLocation location) : _location(std::move(location)) {}
59
60
1.00k
    const std::string& packed_file_path() const { return _location.packed_file_path; }
61
62
1
    int64_t create_time() const { return _location.create_time; }
63
64
5.13k
    PackedSliceUploadState upload_state() const {
65
5.13k
        return _upload_state.load(std::memory_order_acquire);
66
5.13k
    }
67
68
    // Called once the packed file this slice belongs to reaches a terminal state
69
1.01k
    void set_upload_result(PackedSliceUploadState state, int64_t packed_file_size) {
70
1.01k
        if (state == PackedSliceUploadState::UPLOADED) {
71
1.00k
            _packed_file_size.store(packed_file_size, std::memory_order_relaxed);
72
1.00k
        }
73
1.01k
        _upload_state.store(state, std::memory_order_release);
74
1.01k
    }
75
76
4.12k
    PackedSliceLocation location() const {
77
4.12k
        PackedSliceLocation location = _location;
78
4.12k
        if (upload_state() == PackedSliceUploadState::UPLOADED) {
79
1.00k
            location.packed_file_size = _packed_file_size.load(std::memory_order_relaxed);
80
1.00k
        }
81
4.12k
        return location;
82
4.12k
    }
83
84
private:
85
    const PackedSliceLocation _location; // Immutable once the slice has been appended
86
    std::atomic<int64_t> _packed_file_size {-1};
87
    std::atomic<PackedSliceUploadState> _upload_state {PackedSliceUploadState::PENDING};
88
};
89
90
using PackedSliceHandlePtr = std::shared_ptr<PackedSliceHandle>;
91
92
struct PackedAppendContext {
93
    std::string resource_id;
94
    int64_t tablet_id = 0;
95
    std::string rowset_id;
96
    int64_t first_segment_id = 0;
97
    int64_t txn_id = 0;
98
    uint64_t expiration_time = 0; // TTL expiration time in seconds since epoch, 0 means no TTL
99
    bool write_file_cache = true; // Whether to write data to file cache
100
};
101
102
// Global object that manages packing small files into larger files for S3 optimization
103
class PackedFileManager {
104
    struct PackedFileContext;
105
106
public:
107
    static PackedFileManager* instance();
108
109
    // Initialize manager state; file system will be resolved lazily
110
    Status init();
111
112
    // Write a small file to the current packed file. On success `handle` receives a handle
113
    // to the new slice, or nullptr if `data` was too large to be packed.
114
    Status append_small_file(const std::string& path, const Slice& data,
115
                             const PackedAppendContext& info, PackedSliceHandlePtr* handle);
116
117
    // Block until the packed file holding `handle` is uploaded to S3
118
    Status wait_upload_done(const PackedSliceHandlePtr& handle);
119
120
    // Look a slice location up by small file path, for readers that have no handle to the
121
    // slice. The entry lives as long as anything else holds the slice, so this only fails
122
    // for a file whose writer and packed file context are both long gone.
123
    Status get_packed_slice_location(const std::string& path, PackedSliceLocation* location);
124
125
    // Start the background management thread
126
    void start_background_manager();
127
128
    // Stop the background management thread
129
    void stop_background_manager();
130
131
    // Mark current packed file for upload and create new one
132
    Status mark_current_packed_file_for_upload(const std::string& resource_id);
133
134
    // Internal helper; expects caller holds _current_packed_file_mutex
135
    Status mark_current_packed_file_for_upload_locked(const std::string& resource_id);
136
137
    void record_packed_file_metrics(const PackedFileContext& packed_file);
138
139
private:
140
40
    PackedFileManager() = default;
141
    ~PackedFileManager();
142
143
    DISALLOW_COPY_AND_ASSIGN(PackedFileManager);
144
145
    // Background thread function for managing packed file lifecycle
146
    void background_manager();
147
148
    // Upload packed file to S3 and update meta service
149
    Status finalize_packed_file_upload(const std::string& packed_file_path, FileWriter* writer);
150
151
    // Update meta service with packed file information
152
    // table_id is used for rate limiting; -1 means no specific table (cross-table operation)
153
    Status update_meta_service(const std::string& packed_file_path,
154
                               const cloud::PackedFileInfoPB& packed_file_info,
155
                               int64_t table_id = -1);
156
157
    // Process uploading files
158
    void process_uploading_packed_files();
159
160
    // Clean up expired data
161
    void cleanup_expired_data();
162
163
    // Record the terminal upload state of `packed_file` on the slices it contains
164
    void mark_slices_upload_result(const PackedFileContext& packed_file,
165
                                   PackedSliceUploadState state);
166
167
    // Internal structure to track packed file state
168
    enum class PackedFileState {
169
        INIT,            // Initial state, no files written yet
170
        ACTIVE,          // Has files but doesn't meet upload conditions
171
        READY_TO_UPLOAD, // Ready for upload, metadata still being prepared
172
        UPLOADING,       // Upload triggered, waiting for writer close to finish
173
        UPLOADED,        // Upload completed
174
        FAILED           // Upload failed
175
    };
176
177
    struct PackedFileContext {
178
        std::string packed_file_path;
179
        std::unique_ptr<FileWriter> writer;
180
        std::unordered_map<std::string, PackedSliceHandlePtr> slice_locations;
181
        // Every slice appended to this packed file. `slice_locations` is keyed by path, so
182
        // writing one path twice into the same packed file only leaves the last handle
183
        // there, while the upload result still has to reach both.
184
        std::vector<PackedSliceHandlePtr> appended_slices;
185
        int64_t current_offset = 0;
186
        int64_t total_size = 0;
187
        int64_t create_time;
188
        int64_t upload_time = 0;
189
        std::chrono::steady_clock::time_point create_timestamp;
190
        std::optional<std::chrono::steady_clock::time_point> first_append_timestamp;
191
        std::optional<std::chrono::steady_clock::time_point> ready_to_upload_timestamp;
192
        std::optional<std::chrono::steady_clock::time_point> uploading_timestamp;
193
        std::atomic<PackedFileState> state {PackedFileState::INIT};
194
        std::condition_variable upload_cv;
195
        std::mutex upload_mutex;
196
        std::string last_error;
197
        std::string resource_id;
198
        FileSystemSPtr file_system;
199
    };
200
201
    // Create a new packed file state with file writer
202
    Status create_new_packed_file_context(const std::string& resource_id,
203
                                          std::unique_ptr<PackedFileContext>& packed_file_ctx);
204
205
    Status ensure_file_system(const std::string& resource_id, FileSystemSPtr* file_system);
206
207
    // Helper function to wait for packed file upload completion
208
    Status wait_for_packed_file_upload(PackedFileContext* packed_file_ptr);
209
210
    // Thread management
211
    std::atomic<bool> _stop_background_thread {false};
212
    std::unique_ptr<std::thread> _background_thread;
213
214
    // File system
215
    FileSystemSPtr _default_file_system;
216
    std::unordered_map<std::string, FileSystemSPtr> _file_systems;
217
    std::mutex _file_system_mutex;
218
219
    // Current active packed file
220
    std::unordered_map<std::string, std::unique_ptr<PackedFileContext>> _current_packed_files;
221
    std::timed_mutex _current_packed_file_mutex;
222
223
    // Merge files ready for upload or being processed
224
    std::unordered_map<std::string, std::shared_ptr<PackedFileContext>> _uploading_packed_files;
225
226
    // Uploaded packed files (kept for some time for wait_write_done)
227
    std::unordered_map<std::string, std::shared_ptr<PackedFileContext>> _uploaded_packed_files;
228
    std::mutex _packed_files_mutex;
229
230
    // Global index mapping small file path to packed file index, for readers that have no
231
    // handle to the slice, such as PackedFileSystem::open_file_impl() reading a segment back
232
    // before its rowset meta exists. An entry is only recycled once nothing else holds the
233
    // slice, so it outlives every writer and packed file context that could still read it.
234
    std::unordered_map<std::string, PackedSliceHandlePtr> _global_slice_locations;
235
    std::mutex _global_index_mutex;
236
237
#ifdef BE_TEST
238
public:
239
    void reset_packed_file_bvars_for_test() const;
240
    int64_t packed_file_total_count_for_test() const;
241
    int64_t packed_file_total_small_file_num_for_test() const;
242
    int64_t packed_file_total_size_bytes_for_test() const;
243
    double packed_file_avg_small_file_num_for_test() const;
244
    double packed_file_avg_file_size_for_test() const;
245
    void record_packed_file_metrics_for_test(const PackedFileContext* packed_file);
246
247
    // Test-only helpers to introspect/clear internal state
248
    void clear_state_for_test();
249
54
    auto& current_packed_files_for_test() { return _current_packed_files; }
250
41
    auto& uploading_packed_files_for_test() { return _uploading_packed_files; }
251
25
    auto& uploaded_packed_files_for_test() { return _uploaded_packed_files; }
252
122
    auto& global_slice_locations_for_test() { return _global_slice_locations; }
253
12
    auto& file_systems_for_test() { return _file_systems; }
254
2
    FileSystemSPtr& default_file_system_for_test() { return _default_file_system; }
255
    Status create_new_packed_file_state_for_test(const std::string& resource_id,
256
39
                                                 std::unique_ptr<PackedFileContext>& ctx) {
257
39
        return create_new_packed_file_context(resource_id, ctx);
258
39
    }
259
#endif
260
};
261
262
} // namespace doris::io