Coverage Report

Created: 2026-08-14 08:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/db/blob/blob_garbage_meter.cc
Line
Count
Source
1
//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2
//  This source code is licensed under both the GPLv2 (found in the
3
//  COPYING file in the root directory) and Apache 2.0 License
4
//  (found in the LICENSE.Apache file in the root directory).
5
6
#include "db/blob/blob_garbage_meter.h"
7
8
#include "db/blob/blob_index.h"
9
#include "db/blob/blob_log_format.h"
10
#include "db/dbformat.h"
11
#include "db/wide/wide_column_serialization.h"
12
13
namespace ROCKSDB_NAMESPACE {
14
15
0
Status BlobGarbageMeter::ProcessInFlow(const Slice& key, const Slice& value) {
16
0
  return ProcessFlow(key, value, /*is_inflow=*/true);
17
0
}
18
19
0
Status BlobGarbageMeter::ProcessOutFlow(const Slice& key, const Slice& value) {
20
0
  return ProcessFlow(key, value, /*is_inflow=*/false);
21
0
}
22
23
Status BlobGarbageMeter::GetBlobReferenceDetails(const ParsedInternalKey& ikey,
24
                                                 const BlobIndex& blob_index,
25
                                                 uint64_t* blob_file_number,
26
0
                                                 uint64_t* bytes) {
27
0
  assert(blob_file_number);
28
0
  assert(*blob_file_number == kInvalidBlobFileNumber);
29
0
  assert(bytes);
30
0
  assert(*bytes == 0);
31
32
0
  if (blob_index.IsInlined() || blob_index.HasTTL()) {
33
0
    return Status::Corruption("Unexpected TTL/inlined blob index");
34
0
  }
35
0
  if (blob_index.IsSameFile()) {
36
0
    return Status::OK();
37
0
  }
38
39
0
  *blob_file_number = blob_index.file_number();
40
0
  *bytes =
41
0
      blob_index.size() +
42
0
      BlobLogRecord::CalculateAdjustmentForRecordHeader(ikey.user_key.size());
43
44
0
  return Status::OK();
45
0
}
46
47
Status BlobGarbageMeter::ParseBlobIndexReference(const ParsedInternalKey& ikey,
48
                                                 const Slice& value,
49
                                                 uint64_t* blob_file_number,
50
0
                                                 uint64_t* bytes) {
51
0
  assert(blob_file_number);
52
0
  assert(*blob_file_number == kInvalidBlobFileNumber);
53
0
  assert(bytes);
54
0
  assert(*bytes == 0);
55
56
0
  if (ikey.type != kTypeBlobIndex) {
57
0
    return Status::OK();
58
0
  }
59
60
0
  BlobIndex blob_index;
61
62
0
  {
63
0
    const Status s = blob_index.DecodeFrom(value);
64
0
    if (!s.ok()) {
65
0
      return s;
66
0
    }
67
0
  }
68
69
0
  return GetBlobReferenceDetails(ikey, blob_index, blob_file_number, bytes);
70
0
}
71
72
void BlobGarbageMeter::AddFlow(uint64_t blob_file_number, uint64_t bytes,
73
0
                               bool is_inflow) {
74
0
  if (is_inflow) {
75
0
    flows_[blob_file_number].AddInFlow(bytes);
76
0
    return;
77
0
  }
78
79
  // Note: in order to measure the amount of additional garbage, we only need to
80
  // track the outflow for preexisting files, i.e. those that also had inflow.
81
  // (Newly written files would only have outflow.)
82
0
  auto it = flows_.find(blob_file_number);
83
0
  if (it != flows_.end()) {
84
0
    it->second.AddOutFlow(bytes);
85
0
  }
86
0
}
87
88
Status BlobGarbageMeter::ProcessFlow(const Slice& key, const Slice& value,
89
0
                                     bool is_inflow) {
90
0
  ParsedInternalKey ikey;
91
92
0
  {
93
0
    constexpr bool log_err_key = false;
94
0
    const Status s = ParseInternalKey(key, &ikey, log_err_key);
95
0
    if (!s.ok()) {
96
0
      return s;
97
0
    }
98
0
  }
99
100
0
  uint64_t blob_file_number = kInvalidBlobFileNumber;
101
0
  uint64_t bytes = 0;
102
0
  if (Status s =
103
0
          ParseBlobIndexReference(ikey, value, &blob_file_number, &bytes);
104
0
      !s.ok()) {
105
0
    return s;
106
0
  }
107
108
0
  if (blob_file_number != kInvalidBlobFileNumber) {
109
0
    AddFlow(blob_file_number, bytes, is_inflow);
110
0
    return Status::OK();
111
0
  }
112
113
0
  return ProcessEntityBlobReferences(ikey, value, is_inflow);
114
0
}
115
116
Status BlobGarbageMeter::ProcessEntityBlobReferences(
117
0
    const ParsedInternalKey& ikey, const Slice& value, bool is_inflow) {
118
0
  if (ikey.type != kTypeWideColumnEntity) {
119
0
    return Status::OK();
120
0
  }
121
122
0
  return WideColumnSerialization::ForEachBlobFileNumber(
123
0
      value, [&](const BlobIndex& blob_index) -> Status {
124
0
        uint64_t file_number = kInvalidBlobFileNumber;
125
0
        uint64_t blob_bytes = 0;
126
0
        if (const Status s = GetBlobReferenceDetails(ikey, blob_index,
127
0
                                                     &file_number, &blob_bytes);
128
0
            !s.ok()) {
129
0
          return s;
130
0
        }
131
0
        if (file_number != kInvalidBlobFileNumber) {
132
0
          AddFlow(file_number, blob_bytes, is_inflow);
133
0
        }
134
0
        return Status::OK();
135
0
      });
136
0
}
137
138
}  // namespace ROCKSDB_NAMESPACE