Coverage Report

Created: 2026-10-23 15:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/spill/spill_data_dir.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 <gen_cpp/Types_types.h>
21
22
#include <cstddef>
23
#include <cstdint>
24
#include <memory>
25
#include <mutex>
26
#include <string>
27
28
#include "common/status.h"
29
#include "io/fs/file_system.h"
30
31
namespace doris {
32
33
template <typename T>
34
class AtomicGauge;
35
using IntGauge = AtomicGauge<int64_t>;
36
class MetricEntity;
37
template <typename T>
38
class AtomicCounter;
39
using IntAtomicCounter = AtomicCounter<int64_t>;
40
41
/// A spill store: one local disk (LocalSpillDataDir) or the object storage of a cloud storage
42
/// vault (RemoteSpillDataDir). The IO of a spill file goes through the file system pinned in its
43
/// SpillFile when it was created, so that SpillFile / SpillFileWriter / SpillFileReader share one
44
/// code path for both kinds and a remote file stays on its vault. The base class owns
45
/// what both kinds share: the spill root, the byte accounting against a limit, and the metrics.
46
class SpillDataDir {
47
public:
48
    virtual ~SpillDataDir();
49
50
    virtual Status init() = 0;
51
52
4.70k
    virtual bool is_remote() const { return false; }
53
54
    /// Whether the store can be used. Local stores are always ready; a remote store becomes
55
    /// ready once its storage vault is bound.
56
5.03k
    virtual bool ready() const { return true; }
57
58
    /// Current file system of this store, used for new spill files and local query directories.
59
    /// Existing files keep the one they were created on. Remote stores return nullptr until ready.
60
    virtual io::FileSystemSPtr fs() const = 0;
61
62
    /// Refresh capacity and the byte limit from the disk / the configs.
63
    virtual Status update_capacity() = 0;
64
65
    /// Disk usage ratio after adding `incoming_data_size`, used to spread spill over local
66
    /// disks; object storage reports 0.
67
1
    virtual double get_disk_usage(int64_t incoming_data_size) const { return 0.0; }
68
69
232
    const std::string& path() const { return _path; }
70
71
    /// Root of spill data of this store, optionally for one query:
72
    ///   local:  {path}/spill[/query_id]
73
    ///   remote: spill/{ip}_{port}[/query_id]   (relative to the vault prefix)
74
    std::string get_spill_data_path(const std::string& query_id = "") const;
75
76
670
    TStorageMedium::type storage_medium() const { return _storage_medium; }
77
78
    // check if the capacity reach the limit after adding the incoming data
79
    // return true if limit reached, otherwise, return false.
80
    bool reach_capacity_limit(int64_t incoming_data_size);
81
82
    /// Atomically check the capacity limit and account `bytes` as used. Returns
83
    /// DISK_REACH_CAPACITY_LIMIT without accounting when the limit would be exceeded.
84
    /// With force=true the bytes are accounted without checking (used for part footers so
85
    /// that an open part can always be closed).
86
    Status try_reserve(int64_t bytes, bool force = false);
87
88
    /// Give back bytes accounted by try_reserve().
89
425
    void release(int64_t bytes) { update_spill_data_usage(-bytes); }
90
91
    void update_spill_data_usage(int64_t incoming_data_size);
92
93
    int64_t get_spill_data_bytes();
94
95
    /// Logical SpillFile lifecycle counters (not physical parts or external-writer files).
96
    /// Deletion is counted once cleanup succeeds, including an unwritten file's no-op cleanup.
97
    void record_spill_file_created();
98
    void record_spill_file_deleted(size_t count = 1);
99
100
    // Bytes of remote objects that exist or may exist: successfully closed parts, plus the upper
101
    // bound of a part whose close failed until it is reconciled. Capacity reservations remain
102
    // tracked separately in get_spill_data_bytes() and must not be published as persisted usage.
103
0
    virtual void record_persisted_bytes(int64_t bytes) {}
104
269
    virtual void release_persisted_bytes(int64_t bytes) {}
105
0
    virtual int64_t get_persisted_bytes() { return 0; }
106
107
    int64_t get_spill_data_limit();
108
109
    std::string debug_string();
110
111
protected:
112
    /// @param path         identifies the store (a directory for local disks, "s3:{vault}" for
113
    ///                     object storage); used in logs, metrics and error messages.
114
    /// @param spill_root   initial spill root; a remote store fills it in when it is bound.
115
    /// @param metric_path  value of the "path" label of the metric entity.
116
    SpillDataDir(std::string path, std::string spill_root, const std::string& metric_path,
117
                 int64_t capacity_bytes, TStorageMedium::type storage_medium);
118
119
    /// Called with _mutex held.
120
    virtual bool _reach_limit_unlocked(int64_t incoming_data_size) = 0;
121
122
    std::string _path;
123
    // Root of spill data: local "{path}/spill", remote "spill/{ip}_{port}".
124
    std::string _spill_root;
125
126
    // protect _disk_capacity_bytes, _available_bytes, _spill_data_limit_bytes, _spill_data_bytes
127
    std::mutex _mutex;
128
    // the actual capacity of the disk of this data dir
129
    size_t _disk_capacity_bytes;
130
    int64_t _spill_data_limit_bytes = 0;
131
    // the actual available capacity of the disk of this data dir
132
    size_t _available_bytes = 0;
133
    int64_t _spill_data_bytes = 0;
134
    TStorageMedium::type _storage_medium;
135
136
    std::shared_ptr<MetricEntity> spill_data_dir_metric_entity;
137
    IntAtomicCounter* spill_file_created_total = nullptr;
138
    IntAtomicCounter* spill_file_deleted_total = nullptr;
139
    IntGauge* spill_disk_capacity = nullptr;
140
    IntGauge* spill_disk_limit = nullptr;
141
    IntGauge* spill_disk_avail_capacity = nullptr;
142
    IntGauge* spill_disk_data_size = nullptr;
143
    // Inodes may run out before disk space; 0 total means the filesystem does not report them.
144
    IntGauge* spill_disk_inode_total = nullptr;
145
    IntGauge* spill_disk_inode_available = nullptr;
146
    // for test
147
    IntGauge* spill_disk_has_spill_data = nullptr;
148
    IntGauge* spill_disk_has_spill_gc_data = nullptr;
149
};
150
151
/// Spill store on one local disk (spill_storage_type=local).
152
class LocalSpillDataDir final : public SpillDataDir {
153
public:
154
    LocalSpillDataDir(std::string path, int64_t capacity_bytes,
155
                      TStorageMedium::type storage_medium = TStorageMedium::HDD);
156
157
    Status init() override;
158
159
    io::FileSystemSPtr fs() const override;
160
161
    Status update_capacity() override;
162
163
1.12k
    double get_disk_usage(int64_t incoming_data_size) const override {
164
1.12k
        return _disk_capacity_bytes == 0
165
1.12k
                       ? 0
166
1.12k
                       : (double)(_disk_capacity_bytes - _available_bytes + incoming_data_size) /
167
1.12k
                                 (double)_disk_capacity_bytes;
168
1.12k
    }
169
170
    /// {path}/spill_gc[/sub_dir]: spill directories moved here are deleted by the GC thread.
171
    std::string get_spill_data_gc_path(const std::string& sub_dir_name = "") const;
172
173
protected:
174
    bool _reach_limit_unlocked(int64_t incoming_data_size) override;
175
176
private:
177
    bool _reach_disk_capacity_limit(int64_t incoming_data_size);
178
    void _update_inode_usage();
179
};
180
181
} // namespace doris