Coverage Report

Created: 2026-09-18 08:00

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_file.h"
30
#include "storage/options.h"
31
#include "util/threadpool.h"
32
33
namespace doris {
34
class RuntimeProfile;
35
template <typename T>
36
class AtomicCounter;
37
using IntAtomicCounter = AtomicCounter<int64_t>;
38
template <typename T>
39
class AtomicGauge;
40
using UIntGauge = AtomicGauge<uint64_t>;
41
class MetricEntity;
42
struct MetricPrototype;
43
class QueryContext;
44
class ResourceContext;
45
46
class SpillFileManager;
47
class SpillDataDir {
48
public:
49
    SpillDataDir(std::string path, int64_t capacity_bytes,
50
                 TStorageMedium::type storage_medium = TStorageMedium::HDD);
51
52
    Status init();
53
54
218
    const std::string& path() const { return _path; }
55
56
    std::string get_spill_data_path(const std::string& query_id = "") const;
57
58
    std::string get_spill_data_gc_path(const std::string& sub_dir_name = "") const;
59
60
662
    TStorageMedium::type storage_medium() const { return _storage_medium; }
61
62
    // check if the capacity reach the limit after adding the incoming data
63
    // return true if limit reached, otherwise, return false.
64
    bool reach_capacity_limit(int64_t incoming_data_size);
65
66
    Status update_capacity();
67
68
1.21k
    void update_spill_data_usage(int64_t incoming_data_size) {
69
1.21k
        std::lock_guard<std::mutex> l(_mutex);
70
1.21k
        _spill_data_bytes += incoming_data_size;
71
1.21k
        spill_disk_data_size->set_value(_spill_data_bytes);
72
1.21k
    }
73
74
11
    int64_t get_spill_data_bytes() {
75
11
        std::lock_guard<std::mutex> l(_mutex);
76
11
        return _spill_data_bytes;
77
11
    }
78
79
5
    int64_t get_spill_data_limit() {
80
5
        std::lock_guard<std::mutex> l(_mutex);
81
5
        return _spill_data_limit_bytes;
82
5
    }
83
84
    std::string debug_string();
85
86
private:
87
    bool _reach_disk_capacity_limit(int64_t incoming_data_size);
88
1.17k
    double _get_disk_usage(int64_t incoming_data_size) const {
89
1.17k
        return _disk_capacity_bytes == 0
90
1.17k
                       ? 0
91
1.17k
                       : (double)(_disk_capacity_bytes - _available_bytes + incoming_data_size) /
92
1.17k
                                 (double)_disk_capacity_bytes;
93
1.17k
    }
94
95
    friend class SpillFileManager;
96
    std::string _path;
97
98
    // protect _disk_capacity_bytes, _available_bytes, _spill_data_limit_bytes, _spill_data_bytes
99
    std::mutex _mutex;
100
    // the actual capacity of the disk of this data dir
101
    size_t _disk_capacity_bytes;
102
    int64_t _spill_data_limit_bytes = 0;
103
    // the actual available capacity of the disk of this data dir
104
    size_t _available_bytes = 0;
105
    int64_t _spill_data_bytes = 0;
106
    TStorageMedium::type _storage_medium;
107
108
    std::shared_ptr<MetricEntity> spill_data_dir_metric_entity;
109
    IntGauge* spill_disk_capacity = nullptr;
110
    IntGauge* spill_disk_limit = nullptr;
111
    IntGauge* spill_disk_avail_capacity = nullptr;
112
    IntGauge* spill_disk_data_size = nullptr;
113
    // for test
114
    IntGauge* spill_disk_has_spill_data = nullptr;
115
    IntGauge* spill_disk_has_spill_gc_data = nullptr;
116
};
117
118
// Adapts one external writer to the same root selection, capacity accounting and query cleanup
119
// used by Doris spill files.
120
class ExternalSpillSession {
121
public:
122
    ~ExternalSpillSession();
123
124
    Status get_paths(std::vector<std::string>* paths);
125
126
    Status reserve(const std::string& path, int64_t bytes);
127
128
    void update_accounting(const std::string& path, int64_t current_bytes_delta,
129
                           int64_t write_bytes, int64_t read_bytes);
130
131
private:
132
    friend class SpillFileManager;
133
134
    ExternalSpillSession(SpillFileManager* manager, QueryContext* query_context,
135
                         std::string relative_path);
136
    bool _contains(const std::string& path) const;
137
138
    SpillFileManager* _manager;
139
    std::weak_ptr<QueryContext> _query_context;
140
    std::shared_ptr<ResourceContext> _resource_context;
141
    std::string _query_id;
142
    std::string _relative_path;
143
    SpillDataDir* _data_dir = nullptr;
144
    std::string _path;
145
    int64_t _accounted_bytes = 0;
146
    std::mutex _mutex;
147
};
148
149
class SpillFileManager {
150
public:
151
    ~SpillFileManager();
152
    SpillFileManager(
153
            std::unordered_map<std::string, std::unique_ptr<SpillDataDir>>&& spill_store_map);
154
155
    Status init();
156
157
    void stop();
158
159
    // Create SpillFile and register it
160
    // @param relative_path  Operator-formatted path under the spill root,
161
    //                       e.g. "query_id/sort-node_id-task_id-unique_id"
162
    Status create_spill_file(const std::string& relative_path, SpillFileSPtr& spill_file);
163
164
    // Create a lazy managed session for an external spill implementation. A spill root is selected
165
    // and registered only when the external implementation first requests its path.
166
    Status create_external_spill_session(const std::string& relative_path,
167
                                         QueryContext* query_context,
168
                                         std::unique_ptr<ExternalSpillSession>* spill_session);
169
170
    /// Get a unique ID for constructing spill file paths.
171
293
    uint64_t next_id() { return id_++; }
172
173
    // Delete SpillFile data synchronously.
174
    void delete_spill_file(SpillFileSPtr spill_file);
175
176
    // Recursively delete a per-query spill directory during query teardown. Failed deletions are
177
    // retained by the manager and retried by its GC and shutdown paths.
178
    void delete_query_spill_directory(const std::string& query_id, SpillDataDir* data_dir);
179
180
    void gc(int32_t max_work_time_ms);
181
182
723
    void update_spill_write_bytes(int64_t bytes) { _spill_write_bytes_counter->increment(bytes); }
183
184
330
    void update_spill_read_bytes(int64_t bytes) { _spill_read_bytes_counter->increment(bytes); }
185
186
private:
187
    friend class ExternalSpillSession;
188
189
    struct PendingQuerySpillDirectory {
190
        int failed_count {0};
191
        std::string query_dir;
192
    };
193
194
    void _init_metrics();
195
    Status _init_spill_store_map();
196
    void _spill_gc_thread_callback();
197
    Status _try_delete_query_spill_directory(const PendingQuerySpillDirectory& pending_directory);
198
    void _retry_pending_query_spill_directories();
199
    Status _initialize_external_spill_session(ExternalSpillSession* spill_session);
200
    void _release_external_spill_session(ExternalSpillSession* spill_session);
201
    std::vector<SpillDataDir*> _get_stores_for_spill(TStorageMedium::type storage_medium);
202
    SpillDataDir* _get_store_for_spill();
203
204
    std::unordered_map<std::string, std::unique_ptr<SpillDataDir>> _spill_store_map;
205
206
    CountDownLatch _stop_background_threads_latch;
207
    std::shared_ptr<Thread> _spill_gc_thread;
208
209
    // Query cleanup uses the regular pending-deletion path. External leases only defer deletion
210
    // while an SDK task can still access the same query directory; filesystem I/O never holds this
211
    // mutex.
212
    std::mutex _pending_query_spill_directories_mutex;
213
    std::vector<PendingQuerySpillDirectory> _pending_query_spill_directories;
214
    std::unordered_map<std::string, size_t> _external_spill_directory_leases;
215
216
    std::atomic_uint64_t id_ = 0;
217
218
    std::shared_ptr<MetricEntity> _entity {nullptr};
219
220
    std::unique_ptr<doris::MetricPrototype> _spill_write_bytes_metric {nullptr};
221
    std::unique_ptr<doris::MetricPrototype> _spill_read_bytes_metric {nullptr};
222
223
    IntAtomicCounter* _spill_write_bytes_counter {nullptr};
224
    IntAtomicCounter* _spill_read_bytes_counter {nullptr};
225
};
226
} // namespace doris