Coverage Report

Created: 2026-10-17 14:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/spill/spill_file_manager.h
Line
Count
Source
1
2
// Licensed to the Apache Software Foundation (ASF) under one
3
// or more contributor license agreements.  See the NOTICE file
4
// distributed with this work for additional information
5
// regarding copyright ownership.  The ASF licenses this file
6
// to you under the Apache License, Version 2.0 (the
7
// "License"); you may not use this file except in compliance
8
// with the License.  You may obtain a copy of the License at
9
//
10
//   http://www.apache.org/licenses/LICENSE-2.0
11
//
12
// Unless required by applicable law or agreed to in writing,
13
// software distributed under the License is distributed on an
14
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
// KIND, either express or implied.  See the License for the
16
// specific language governing permissions and limitations
17
// under the License.
18
19
#pragma once
20
#include <atomic>
21
#include <memory>
22
#include <mutex>
23
#include <string>
24
#include <unordered_map>
25
#include <vector>
26
27
#include "common/metrics/metrics.h"
28
#include "common/status.h"
29
#include "exec/spill/spill_data_dir.h"
30
#include "exec/spill/spill_file.h"
31
#include "util/stopwatch.hpp"
32
#include "util/threadpool.h"
33
34
namespace doris {
35
class RuntimeProfile;
36
template <typename T>
37
class AtomicCounter;
38
using IntAtomicCounter = AtomicCounter<int64_t>;
39
template <typename T>
40
class AtomicGauge;
41
using UIntGauge = AtomicGauge<uint64_t>;
42
class MetricEntity;
43
struct MetricPrototype;
44
class QueryContext;
45
class ResourceContext;
46
47
class RemoteSpillDataDir;
48
class SpillFileManager;
49
struct StorePath;
50
51
// Reconcile the byte counter for one remote spill prefix with the objects still listed there.
52
// Used after uncertain publication or partial deletion, never on the normal write path.
53
Status reconcile_remote_spill_bytes(SpillDataDir* data_dir, const io::FileSystemSPtr& fs,
54
                                    const std::string& dir, int64_t* accounted_bytes);
55
56
// Adapts one external writer to the same root selection, capacity accounting and query cleanup
57
// used by Doris spill files.
58
class ExternalSpillSession {
59
public:
60
    ~ExternalSpillSession();
61
62
    Status get_paths(std::vector<std::string>* paths);
63
64
    Status reserve(const std::string& path, int64_t bytes);
65
66
    void update_accounting(const std::string& path, int64_t current_bytes_delta,
67
                           int64_t write_bytes, int64_t read_bytes);
68
69
private:
70
    friend class SpillFileManager;
71
72
    ExternalSpillSession(SpillFileManager* manager, QueryContext* query_context,
73
                         std::string relative_path);
74
    bool _contains(const std::string& path) const;
75
76
    SpillFileManager* _manager;
77
    std::weak_ptr<QueryContext> _query_context;
78
    std::shared_ptr<ResourceContext> _resource_context;
79
    std::string _query_id;
80
    std::string _relative_path;
81
    SpillDataDir* _data_dir = nullptr;
82
    std::string _path;
83
    int64_t _accounted_bytes = 0;
84
    std::mutex _mutex;
85
};
86
87
class SpillFileManager {
88
public:
89
    ~SpillFileManager();
90
    // Builds the spill stores from the configuration: the remote store when
91
    // spill_storage_type is "s3", and one local store per configured spill path.
92
    explicit SpillFileManager(const std::vector<StorePath>& spill_store_paths);
93
    SpillFileManager(
94
            std::unordered_map<std::string, std::unique_ptr<SpillDataDir>>&& spill_store_map);
95
96
    // Validates the spill configuration and initializes the stores.
97
    Status init();
98
99
    void stop();
100
101
    // Create SpillFile and register it
102
    // @param relative_path  Operator-formatted path under the spill root,
103
    //                       e.g. "query_id/sort-node_id-task_id-unique_id"
104
    Status create_spill_file(const std::string& relative_path, SpillFileSPtr& spill_file);
105
106
    // Create a lazy managed session for an external spill implementation. A spill root is selected
107
    // and registered only when the external implementation first requests its path.
108
    Status create_external_spill_session(const std::string& relative_path,
109
                                         QueryContext* query_context,
110
                                         std::unique_ptr<ExternalSpillSession>* spill_session);
111
112
    /// Get a unique ID for constructing spill file paths.
113
293
    uint64_t next_id() { return id_++; }
114
115
    // Delete SpillFile data synchronously.
116
    void delete_spill_file(SpillFileSPtr spill_file);
117
118
    // Recursively delete a per-query spill directory of a local store during query teardown.
119
    // Failed deletions are retained by the manager and retried by its GC and shutdown paths.
120
    // A no-op for the remote store: its spill is deleted per spill file only.
121
    void delete_query_spill_directory(const std::string& query_id, SpillDataDir* data_dir);
122
123
    // Take over a spill file directory whose deletion failed. Its objects are still stored, so
124
    // `charged_bytes` stay charged to `data_dir` until a retry deletes them; otherwise a
125
    // deletion outage would free capacity that is still in use.
126
    void retry_spill_directory_deletion(SpillDataDir* data_dir, io::FileSystemSPtr fs,
127
                                        std::string dir, int64_t charged_bytes,
128
                                        int64_t persisted_bytes);
129
130
    void gc(int32_t max_work_time_ms);
131
132
931
    void update_spill_write_bytes(int64_t bytes) { _spill_write_bytes_counter->increment(bytes); }
133
134
733
    void update_spill_read_bytes(int64_t bytes) { _spill_read_bytes_counter->increment(bytes); }
135
136
    /// Bytes and PutObject/UploadPart requests issued by spill against object storage.
137
    void update_spill_remote_write(int64_t bytes, int64_t put_requests);
138
139
    /// Bytes and GET requests issued by spill against object storage.
140
    void update_spill_remote_read(int64_t bytes, int64_t get_requests);
141
142
    /// Number of spill directories whose deletion failed and is being retried.
143
    size_t pending_delete_dir_count();
144
145
    /// Bytes of completed spill objects this process currently holds in object storage;
146
    /// uncommitted capacity reservations are excluded. Sent to the master FE with every disk
147
    /// report (cloud mode) for SHOW DATA.
148
    int64_t remote_spill_data_bytes();
149
150
private:
151
    friend class ExternalSpillSession;
152
153
    struct PendingSpillDirectory {
154
        int failed_count {0};
155
        // A query directory or the directory of one spill file.
156
        std::string dir;
157
        SpillDataDir* data_dir {nullptr};
158
        // Pinned for remote spill: a later change of the spill vault must not redirect GC.
159
        io::FileSystemSPtr fs;
160
        // Bytes of the objects under `dir` still charged to `data_dir`; released once deleted.
161
        int64_t charged_bytes {0};
162
        int64_t persisted_bytes {0};
163
    };
164
165
    struct SpillGcStats {
166
        bool has_work = false;
167
        size_t backlog_dirs = 0;
168
        size_t deleted_dirs = 0;
169
        size_t deleted_files = 0;
170
        size_t failed_deletes = 0;
171
    };
172
173
    void _init_metrics();
174
    Status _init_spill_store_map();
175
    void _spill_gc_thread_callback();
176
    Status _try_delete_spill_directory(const PendingSpillDirectory& pending_directory);
177
    /// Retry the queued failed deletions, at least one, until `max_work_time_ns` has passed on
178
    /// `watch`; a remote one can take longer, since it waits for the object store. Remote ones
179
    /// are only retried when `retry_remote` is set. The rest stay queued for the next round.
180
    void _retry_pending_spill_directories(const MonotonicStopWatch& watch, int64_t max_work_time_ns,
181
                                          bool retry_remote);
182
    /// Retry every queued failed deletion of a local store, for shutdown: it does not wait for
183
    /// the object store.
184
    void _retry_pending_local_spill_directories();
185
    void _gc_spill_store(LocalSpillDataDir* store_dir, const MonotonicStopWatch& watch,
186
                         int64_t max_work_time_ns, SpillGcStats* stats);
187
    /// Queue a failed deletion, merged with a pending ancestor or absorbing pending descendants
188
    /// of the same store so that an outage keeps about one entry per query.
189
    void _add_pending_directory(PendingSpillDirectory pending_directory);
190
    Status _initialize_external_spill_session(ExternalSpillSession* spill_session);
191
    void _release_external_spill_session(ExternalSpillSession* spill_session);
192
    std::vector<SpillDataDir*> _get_stores_for_spill(TStorageMedium::type storage_medium);
193
    std::vector<SpillDataDir*> _get_local_stores_for_spill(TStorageMedium::type storage_medium);
194
    SpillDataDir* _get_local_store_for_external_spill();
195
196
    std::unordered_map<std::string, std::unique_ptr<SpillDataDir>> _spill_store_map;
197
    // Views of _spill_store_map by kind. With spill_storage_type=s3 a BE has the remote store
198
    // for query spill and keeps its local stores for external writers.
199
    std::vector<LocalSpillDataDir*> _local_stores;
200
    RemoteSpillDataDir* _remote_store = nullptr;
201
202
    CountDownLatch _stop_background_threads_latch;
203
    std::shared_ptr<Thread> _spill_gc_thread;
204
205
    // External spill leases defer query-directory deletion until native callbacks finish.
206
    // Filesystem I/O never holds this mutex.
207
    std::mutex _pending_spill_directories_mutex;
208
    std::vector<PendingSpillDirectory> _pending_spill_directories;
209
    std::unordered_map<std::string, size_t> _external_spill_directory_leases;
210
211
    std::atomic_uint64_t id_ = 0;
212
213
    std::shared_ptr<MetricEntity> _entity {nullptr};
214
215
    std::unique_ptr<doris::MetricPrototype> _spill_write_bytes_metric {nullptr};
216
    std::unique_ptr<doris::MetricPrototype> _spill_read_bytes_metric {nullptr};
217
218
    IntAtomicCounter* _spill_write_bytes_counter {nullptr};
219
    IntAtomicCounter* _spill_read_bytes_counter {nullptr};
220
221
    std::unique_ptr<doris::MetricPrototype> _spill_remote_write_bytes_metric {nullptr};
222
    std::unique_ptr<doris::MetricPrototype> _spill_remote_read_bytes_metric {nullptr};
223
    std::unique_ptr<doris::MetricPrototype> _spill_remote_put_requests_metric {nullptr};
224
    std::unique_ptr<doris::MetricPrototype> _spill_remote_get_requests_metric {nullptr};
225
    std::unique_ptr<doris::MetricPrototype> _spill_pending_delete_dir_count_metric {nullptr};
226
227
    IntAtomicCounter* _spill_remote_write_bytes_counter {nullptr};
228
    IntAtomicCounter* _spill_remote_read_bytes_counter {nullptr};
229
    IntAtomicCounter* _spill_remote_put_requests_counter {nullptr};
230
    IntAtomicCounter* _spill_remote_get_requests_counter {nullptr};
231
    IntGauge* _spill_pending_delete_dir_count_gauge {nullptr};
232
};
233
} // namespace doris