mirror of
https://github.com/ceph/ceph
synced 2026-08-02 07:03:18 +00:00
21235 lines
642 KiB
C++
21235 lines
642 KiB
C++
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
|
|
// vim: ts=8 sw=2 sts=2 expandtab
|
|
|
|
/*
|
|
* Ceph - scalable distributed file system
|
|
*
|
|
* Copyright (C) 2014 Red Hat
|
|
*
|
|
* This is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License version 2.1, as published by the Free Software
|
|
* Foundation. See file COPYING.
|
|
*
|
|
*/
|
|
|
|
#include <bit>
|
|
#include <utility>
|
|
#include <memory>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <algorithm>
|
|
|
|
#include <boost/container/flat_set.hpp>
|
|
#include <boost/algorithm/string.hpp>
|
|
#include <boost/random/mersenne_twister.hpp>
|
|
#include <boost/random/uniform_real.hpp>
|
|
|
|
#include "common/dout.h"
|
|
#include "include/cpp-btree/btree_set.h"
|
|
|
|
#include "BlueStore.h"
|
|
#include "bluestore_common.h"
|
|
#include "os/bluestore/bluestore_types.h"
|
|
#include "simple_bitmap.h"
|
|
#include "os/kv.h"
|
|
#include "include/compat.h"
|
|
#include "include/intarith.h"
|
|
#include "include/stringify.h"
|
|
#include "include/str_map.h"
|
|
#include "include/util.h"
|
|
#include "common/debug.h"
|
|
#include "common/errno.h"
|
|
#include "common/JSONFormatter.h"
|
|
#include "common/safe_io.h"
|
|
#include "common/PriorityCache.h"
|
|
#include "common/url_escape.h"
|
|
#include "Allocator.h"
|
|
#include "FreelistManager.h"
|
|
#include "BlueFS.h"
|
|
#include "BlueRocksEnv.h"
|
|
#include "auth/Crypto.h"
|
|
#include "common/EventTrace.h"
|
|
#include "perfglue/heap_profiler.h"
|
|
#include "common/blkdev.h"
|
|
#include "common/numa.h"
|
|
#include "common/pretty_binary.h"
|
|
#include "common/WorkQueue.h"
|
|
#include "kv/KeyValueHistogram.h"
|
|
#include "Writer.h"
|
|
#include "Compression.h"
|
|
#include "BlueAdmin.h"
|
|
|
|
#if defined(WITH_LTTNG)
|
|
#define TRACEPOINT_DEFINE
|
|
#define TRACEPOINT_PROBE_DYNAMIC_LINKAGE
|
|
#include "tracing/bluestore.h"
|
|
#undef TRACEPOINT_PROBE_DYNAMIC_LINKAGE
|
|
#undef TRACEPOINT_DEFINE
|
|
#else
|
|
#define tracepoint(...)
|
|
#endif
|
|
|
|
#define dout_context cct
|
|
#define dout_subsys ceph_subsys_bluestore
|
|
|
|
using bid_t = decltype(BlueStore::Blob::id);
|
|
|
|
// bluestore_cache_onode
|
|
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::Onode, bluestore_onode,
|
|
bluestore_cache_onode);
|
|
|
|
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::Buffer, bluestore_buffer,
|
|
bluestore_cache_buffer);
|
|
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::Extent, bluestore_extent,
|
|
bluestore_extent);
|
|
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::Blob, bluestore_blob,
|
|
bluestore_blob);
|
|
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::SharedBlob, bluestore_shared_blob,
|
|
bluestore_shared_blob);
|
|
|
|
// bluestore_txc
|
|
MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::TransContext, bluestore_transcontext,
|
|
bluestore_txc);
|
|
using std::byte;
|
|
using std::deque;
|
|
using std::min;
|
|
using std::make_pair;
|
|
using std::numeric_limits;
|
|
using std::pair;
|
|
using std::less;
|
|
using std::list;
|
|
using std::make_unique;
|
|
using std::map;
|
|
using std::max;
|
|
using std::ostream;
|
|
using std::ostringstream;
|
|
using std::set;
|
|
using std::string;
|
|
using std::stringstream;
|
|
using std::unique_ptr;
|
|
using std::vector;
|
|
|
|
using ceph::bufferlist;
|
|
using ceph::bufferptr;
|
|
using ceph::coarse_mono_clock;
|
|
using ceph::decode;
|
|
using ceph::encode;
|
|
using ceph::Formatter;
|
|
using ceph::JSONFormatter;
|
|
using ceph::make_timespan;
|
|
using ceph::mono_clock;
|
|
using ceph::mono_time;
|
|
using ceph::timespan_str;
|
|
|
|
using namespace std::literals;
|
|
|
|
// kv store prefixes
|
|
const string PREFIX_SUPER = "S"; // field -> value
|
|
const string PREFIX_STAT = "T"; // field -> value(int64 array)
|
|
const string PREFIX_COLL = "C"; // collection name -> cnode_t
|
|
const string PREFIX_OBJ = "O"; // object name -> onode_t
|
|
const string PREFIX_OMAP = "M"; // u64 + keyname -> value
|
|
const string PREFIX_PGMETA_OMAP = "P"; // u64 + keyname -> value(for meta coll)
|
|
const string PREFIX_PERPOOL_OMAP = "m"; // s64 + u64 + keyname -> value
|
|
const string PREFIX_PERPG_OMAP = "p"; // u64(pool) + u32(hash) + u64(id) + keyname -> value
|
|
const string PREFIX_DEFERRED = "L"; // id -> deferred_transaction_t
|
|
const string PREFIX_ALLOC = "B"; // u64 offset -> u64 length (freelist)
|
|
const string PREFIX_ALLOC_BITMAP = "b";// (see BitmapFreelistManager)
|
|
const string PREFIX_SHARED_BLOB = "X"; // u64 SB id -> shared_blob_t
|
|
|
|
const string BLUESTORE_GLOBAL_STATFS_KEY = "bluestore_statfs";
|
|
|
|
// Label offsets where they might be replicated. It is possible on previous versions where these offsets
|
|
// were already used so labels won't exist there.
|
|
static constexpr uint64_t _1G = uint64_t(1024)*1024*1024;
|
|
const vector<uint64_t> bdev_label_positions = {
|
|
BDEV_FIRST_LABEL_POSITION,
|
|
_1G,
|
|
10*_1G,
|
|
100*_1G,
|
|
1000*_1G};
|
|
|
|
/*
|
|
* extent map blob encoding
|
|
*
|
|
* we use the low bits of the blobid field to indicate some common scenarios
|
|
* and spanning vs local ids. See ExtentMap::{encode,decode}_some().
|
|
*/
|
|
#define BLOBID_FLAG_CONTIGUOUS 0x1 // this extent starts at end of previous
|
|
#define BLOBID_FLAG_ZEROOFFSET 0x2 // blob_offset is 0
|
|
#define BLOBID_FLAG_SAMELENGTH 0x4 // length matches previous extent
|
|
#define BLOBID_FLAG_SPANNING 0x8 // has spanning blob id
|
|
#define BLOBID_SHIFT_BITS 4
|
|
|
|
/*
|
|
* object name key structure
|
|
*
|
|
* encoded u8: shard + 2^7 (so that it sorts properly)
|
|
* encoded u64: poolid + 2^63 (so that it sorts properly)
|
|
* encoded u32: hash (bit reversed)
|
|
*
|
|
* escaped string: namespace
|
|
*
|
|
* escaped string: key or object name
|
|
* 1 char: '<', '=', or '>'. if =, then object key == object name, and
|
|
* we are done. otherwise, we are followed by the object name.
|
|
* escaped string: object name (unless '=' above)
|
|
*
|
|
* encoded u64: snap
|
|
* encoded u64: generation
|
|
* 'o'
|
|
*/
|
|
#define ONODE_KEY_SUFFIX 'o'
|
|
|
|
/*
|
|
* extent shard key
|
|
*
|
|
* object prefix key
|
|
* u32
|
|
* 'x'
|
|
*/
|
|
#define EXTENT_SHARD_KEY_SUFFIX 'x'
|
|
|
|
/*
|
|
* string encoding in the key
|
|
*
|
|
* The key string needs to lexicographically sort the same way that
|
|
* ghobject_t does. We do this by escaping anything <= to '#' with #
|
|
* plus a 2 digit hex string, and anything >= '~' with ~ plus the two
|
|
* hex digits.
|
|
*
|
|
* We use ! as a terminator for strings; this works because it is < #
|
|
* and will get escaped if it is present in the string.
|
|
*
|
|
* NOTE: There is a bug in this implementation: due to implicit
|
|
* character type conversion in comparison it may produce unexpected
|
|
* ordering. Unfortunately fixing the bug would mean invalidating the
|
|
* keys in existing deployments. Instead we do additional sorting
|
|
* where it is needed.
|
|
*/
|
|
template<typename S>
|
|
static void append_escaped(const string &in, S *out)
|
|
{
|
|
char hexbyte[in.length() * 3 + 1];
|
|
char* ptr = &hexbyte[0];
|
|
for (string::const_iterator i = in.begin(); i != in.end(); ++i) {
|
|
if (*i <= '#') { // bug: unexpected result for *i > 0x7f
|
|
*ptr++ = '#';
|
|
*ptr++ = "0123456789abcdef"[(*i >> 4) & 0x0f];
|
|
*ptr++ = "0123456789abcdef"[*i & 0x0f];
|
|
} else if (*i >= '~') { // bug: unexpected result for *i > 0x7f
|
|
*ptr++ = '~';
|
|
*ptr++ = "0123456789abcdef"[(*i >> 4) & 0x0f];
|
|
*ptr++ = "0123456789abcdef"[*i & 0x0f];
|
|
} else {
|
|
*ptr++ = *i;
|
|
}
|
|
}
|
|
*ptr++ = '!';
|
|
out->append(hexbyte, ptr - &hexbyte[0]);
|
|
}
|
|
|
|
inline unsigned h2i(char c)
|
|
{
|
|
if ((c >= '0') && (c <= '9')) {
|
|
return c - 0x30;
|
|
} else if ((c >= 'a') && (c <= 'f')) {
|
|
return c - 'a' + 10;
|
|
} else if ((c >= 'A') && (c <= 'F')) {
|
|
return c - 'A' + 10;
|
|
} else {
|
|
return 256; // make it always larger than 255
|
|
}
|
|
}
|
|
|
|
static int decode_escaped(const char *p, string *out)
|
|
{
|
|
char buff[256];
|
|
char* ptr = &buff[0];
|
|
char* max = &buff[252];
|
|
const char *orig_p = p;
|
|
while (*p && *p != '!') {
|
|
if (*p == '#' || *p == '~') {
|
|
unsigned hex = 0;
|
|
p++;
|
|
hex = h2i(*p++) << 4;
|
|
if (hex > 255) {
|
|
return -EINVAL;
|
|
}
|
|
hex |= h2i(*p++);
|
|
if (hex > 255) {
|
|
return -EINVAL;
|
|
}
|
|
*ptr++ = hex;
|
|
} else {
|
|
*ptr++ = *p++;
|
|
}
|
|
if (ptr > max) {
|
|
out->append(buff, ptr-buff);
|
|
ptr = &buff[0];
|
|
}
|
|
}
|
|
if (ptr != buff) {
|
|
out->append(buff, ptr-buff);
|
|
}
|
|
return p - orig_p;
|
|
}
|
|
|
|
template<typename T>
|
|
static void _key_encode_shard(shard_id_t shard, T *key)
|
|
{
|
|
key->push_back((char)((uint8_t)shard.id + (uint8_t)0x80));
|
|
}
|
|
|
|
static const char *_key_decode_shard(const char *key, shard_id_t *pshard)
|
|
{
|
|
pshard->id = (uint8_t)*key - (uint8_t)0x80;
|
|
return key + 1;
|
|
}
|
|
|
|
static void get_coll_range(const coll_t& cid, int bits,
|
|
ghobject_t *temp_start, ghobject_t *temp_end,
|
|
ghobject_t *start, ghobject_t *end, bool legacy)
|
|
{
|
|
spg_t pgid;
|
|
constexpr uint32_t MAX_HASH = std::numeric_limits<uint32_t>::max();
|
|
// use different nspaces due to we use different schemes when encoding
|
|
// keys for listing objects
|
|
const std::string_view MAX_NSPACE = legacy ? "\x7f" : "\xff";
|
|
if (cid.is_pg(&pgid)) {
|
|
start->shard_id = pgid.shard;
|
|
*temp_start = *start;
|
|
|
|
start->hobj.pool = pgid.pool();
|
|
temp_start->hobj.pool = -2ll - pgid.pool();
|
|
|
|
*end = *start;
|
|
*temp_end = *temp_start;
|
|
|
|
uint32_t reverse_hash = hobject_t::_reverse_bits(pgid.ps());
|
|
start->hobj.set_bitwise_key_u32(reverse_hash);
|
|
temp_start->hobj.set_bitwise_key_u32(reverse_hash);
|
|
|
|
uint64_t end_hash = reverse_hash + (1ull << (32 - bits));
|
|
if (end_hash > MAX_HASH) {
|
|
// make sure end hobj is even greater than the maximum possible hobj
|
|
end->hobj.set_bitwise_key_u32(MAX_HASH);
|
|
temp_end->hobj.set_bitwise_key_u32(MAX_HASH);
|
|
end->hobj.nspace = MAX_NSPACE;
|
|
} else {
|
|
end->hobj.set_bitwise_key_u32(end_hash);
|
|
temp_end->hobj.set_bitwise_key_u32(end_hash);
|
|
}
|
|
} else {
|
|
start->shard_id = shard_id_t::NO_SHARD;
|
|
start->hobj.pool = -1ull;
|
|
|
|
*end = *start;
|
|
start->hobj.set_bitwise_key_u32(0);
|
|
end->hobj.set_bitwise_key_u32(MAX_HASH);
|
|
end->hobj.nspace = MAX_NSPACE;
|
|
// no separate temp section
|
|
*temp_start = *end;
|
|
*temp_end = *end;
|
|
}
|
|
|
|
start->generation = 0;
|
|
end->generation = 0;
|
|
temp_start->generation = 0;
|
|
temp_end->generation = 0;
|
|
}
|
|
|
|
static void get_shared_blob_key(uint64_t sbid, string *key)
|
|
{
|
|
key->clear();
|
|
_key_encode_u64(sbid, key);
|
|
}
|
|
|
|
static int get_key_shared_blob(const string& key, uint64_t *sbid)
|
|
{
|
|
const char *p = key.c_str();
|
|
if (key.length() < sizeof(uint64_t))
|
|
return -1;
|
|
_key_decode_u64(p, sbid);
|
|
return 0;
|
|
}
|
|
|
|
template<typename S>
|
|
static void _key_encode_prefix(const ghobject_t& oid, S *key)
|
|
{
|
|
_key_encode_shard(oid.shard_id, key);
|
|
_key_encode_u64(oid.hobj.pool + 0x8000000000000000ull, key);
|
|
_key_encode_u32(oid.hobj.get_bitwise_key_u32(), key);
|
|
}
|
|
|
|
static const char *_key_decode_prefix(const char *p, ghobject_t *oid)
|
|
{
|
|
p = _key_decode_shard(p, &oid->shard_id);
|
|
|
|
uint64_t pool;
|
|
p = _key_decode_u64(p, &pool);
|
|
oid->hobj.pool = pool - 0x8000000000000000ull;
|
|
|
|
unsigned hash;
|
|
p = _key_decode_u32(p, &hash);
|
|
|
|
oid->hobj.set_bitwise_key_u32(hash);
|
|
|
|
return p;
|
|
}
|
|
|
|
|
|
#define ENCODED_KEY_PREFIX_LEN (1 + 8 + 4)
|
|
|
|
static int _get_key_object(const char *p, ghobject_t *oid)
|
|
{
|
|
int r;
|
|
|
|
p = _key_decode_prefix(p, oid);
|
|
|
|
r = decode_escaped(p, &oid->hobj.nspace);
|
|
if (r < 0)
|
|
return -2;
|
|
p += r + 1;
|
|
|
|
string k;
|
|
r = decode_escaped(p, &k);
|
|
if (r < 0)
|
|
return -3;
|
|
p += r + 1;
|
|
if (*p == '=') {
|
|
// no key
|
|
++p;
|
|
oid->hobj.oid.name = k;
|
|
} else if (*p == '<' || *p == '>') {
|
|
// key + name
|
|
++p;
|
|
r = decode_escaped(p, &oid->hobj.oid.name);
|
|
if (r < 0)
|
|
return -5;
|
|
p += r + 1;
|
|
oid->hobj.set_key(k);
|
|
} else {
|
|
// malformed
|
|
return -6;
|
|
}
|
|
|
|
p = _key_decode_u64(p, &oid->hobj.snap.val);
|
|
p = _key_decode_u64(p, &oid->generation);
|
|
|
|
if (*p != ONODE_KEY_SUFFIX) {
|
|
return -7;
|
|
}
|
|
p++;
|
|
if (*p) {
|
|
// if we get something other than a null terminator here,
|
|
// something goes wrong.
|
|
return -8;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
template<typename S>
|
|
static int get_key_object(const S& key, ghobject_t *oid)
|
|
{
|
|
if (key.length() < ENCODED_KEY_PREFIX_LEN)
|
|
return -1;
|
|
if (key.length() == ENCODED_KEY_PREFIX_LEN)
|
|
return -2;
|
|
const char *p = key.c_str();
|
|
return _get_key_object(p, oid);
|
|
}
|
|
|
|
template<typename S>
|
|
static void _get_object_key(const ghobject_t& oid, S *key)
|
|
{
|
|
size_t max_len = ENCODED_KEY_PREFIX_LEN +
|
|
(oid.hobj.nspace.length() * 3 + 1) +
|
|
(oid.hobj.get_key().length() * 3 + 1) +
|
|
1 + // for '<', '=', or '>'
|
|
(oid.hobj.oid.name.length() * 3 + 1) +
|
|
8 + 8 + 1;
|
|
key->reserve(max_len);
|
|
|
|
_key_encode_prefix(oid, key);
|
|
|
|
append_escaped(oid.hobj.nspace, key);
|
|
|
|
if (oid.hobj.get_key().length()) {
|
|
// is a key... could be < = or >.
|
|
append_escaped(oid.hobj.get_key(), key);
|
|
// (ASCII chars < = and > sort in that order, yay)
|
|
int r = oid.hobj.get_key().compare(oid.hobj.oid.name);
|
|
if (r) {
|
|
key->append(r > 0 ? ">" : "<");
|
|
append_escaped(oid.hobj.oid.name, key);
|
|
} else {
|
|
// same as no key
|
|
key->append("=");
|
|
}
|
|
} else {
|
|
// no key
|
|
append_escaped(oid.hobj.oid.name, key);
|
|
key->append("=");
|
|
}
|
|
|
|
_key_encode_u64(oid.hobj.snap, key);
|
|
_key_encode_u64(oid.generation, key);
|
|
|
|
key->push_back(ONODE_KEY_SUFFIX);
|
|
}
|
|
|
|
template<typename S>
|
|
static void get_object_key(CephContext *cct, const ghobject_t& oid, S *key)
|
|
{
|
|
key->clear();
|
|
_get_object_key(oid, key);
|
|
|
|
// sanity check
|
|
if (true) {
|
|
ghobject_t t;
|
|
int r = get_key_object(*key, &t);
|
|
if (r || t != oid) {
|
|
derr << " r " << r << dendl;
|
|
derr << "key " << pretty_binary_string(*key) << dendl;
|
|
derr << "oid " << oid << dendl;
|
|
derr << " t " << t << dendl;
|
|
ceph_assert(r == 0 && t == oid);
|
|
}
|
|
}
|
|
}
|
|
|
|
// extent shard keys are the onode key, plus a u32, plus 'x'. the trailing
|
|
// char lets us quickly test whether it is a shard key without decoding any
|
|
// of the prefix bytes.
|
|
template<typename S>
|
|
static void get_extent_shard_key(const S& onode_key, uint32_t offset,
|
|
string *key)
|
|
{
|
|
key->clear();
|
|
key->reserve(onode_key.length() + 4 + 1);
|
|
key->append(onode_key.c_str(), onode_key.size());
|
|
_key_encode_u32(offset, key);
|
|
key->push_back(EXTENT_SHARD_KEY_SUFFIX);
|
|
}
|
|
|
|
static void rewrite_extent_shard_key(uint32_t offset, string *key)
|
|
{
|
|
ceph_assert(key->size() > sizeof(uint32_t) + 1);
|
|
ceph_assert(*key->rbegin() == EXTENT_SHARD_KEY_SUFFIX);
|
|
_key_encode_u32(offset, key->size() - sizeof(uint32_t) - 1, key);
|
|
}
|
|
|
|
template<typename S>
|
|
static void generate_extent_shard_key_and_apply(
|
|
const S& onode_key,
|
|
uint32_t offset,
|
|
string *key,
|
|
std::function<void(const string& final_key)> apply)
|
|
{
|
|
if (key->empty()) { // make full key
|
|
ceph_assert(!onode_key.empty());
|
|
get_extent_shard_key(onode_key, offset, key);
|
|
} else {
|
|
rewrite_extent_shard_key(offset, key);
|
|
}
|
|
apply(*key);
|
|
}
|
|
|
|
int get_key_extent_shard(const string& key, string *onode_key, uint32_t *offset)
|
|
{
|
|
ceph_assert(key.size() > sizeof(uint32_t) + 1);
|
|
ceph_assert(*key.rbegin() == EXTENT_SHARD_KEY_SUFFIX);
|
|
int okey_len = key.size() - sizeof(uint32_t) - 1;
|
|
*onode_key = key.substr(0, okey_len);
|
|
const char *p = key.data() + okey_len;
|
|
_key_decode_u32(p, offset);
|
|
return 0;
|
|
}
|
|
|
|
static bool is_extent_shard_key(const string& key)
|
|
{
|
|
return *key.rbegin() == EXTENT_SHARD_KEY_SUFFIX;
|
|
}
|
|
|
|
static void get_deferred_key(uint64_t seq, string *out)
|
|
{
|
|
_key_encode_u64(seq, out);
|
|
}
|
|
|
|
static void get_pool_stat_key(int64_t pool_id, string *key)
|
|
{
|
|
key->clear();
|
|
_key_encode_u64(pool_id, key);
|
|
}
|
|
|
|
static int get_key_pool_stat(const string& key, uint64_t* pool_id)
|
|
{
|
|
const char *p = key.c_str();
|
|
if (key.length() < sizeof(uint64_t))
|
|
return -1;
|
|
_key_decode_u64(p, pool_id);
|
|
return 0;
|
|
}
|
|
|
|
|
|
template <int LogLevelV>
|
|
void _dump_extent_map(CephContext *cct, const BlueStore::ExtentMap &em)
|
|
{
|
|
uint64_t pos = 0;
|
|
for (auto& s : em.shards) {
|
|
dout(LogLevelV) << __func__ << " shard " << *s.shard_info
|
|
<< (s.loaded ? " (loaded)" : "")
|
|
<< (s.dirty ? " (dirty)" : "")
|
|
<< dendl;
|
|
}
|
|
for (auto& e : em.extent_map) {
|
|
dout(LogLevelV) << __func__ << " " << e << dendl;
|
|
ceph_assert(e.logical_offset >= pos);
|
|
pos = e.logical_offset + e.length;
|
|
const bluestore_blob_t& blob = e.blob->get_blob();
|
|
if (blob.has_csum()) {
|
|
vector<uint64_t> v;
|
|
unsigned n = blob.get_csum_count();
|
|
for (unsigned i = 0; i < n; ++i)
|
|
v.push_back(blob.get_csum_item(i));
|
|
dout(LogLevelV) << __func__ << " csum: " << std::hex << v << std::dec
|
|
<< dendl;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <int LogLevelV>
|
|
void _dump_onode(CephContext *cct, const BlueStore::Onode& o)
|
|
{
|
|
if (!cct->_conf->subsys.should_gather<ceph_subsys_bluestore, LogLevelV>())
|
|
return;
|
|
dout(LogLevelV) << __func__ << " " << &o << " " << o.oid
|
|
<< " nid " << o.onode.nid
|
|
<< " size 0x" << std::hex << o.onode.size
|
|
<< " (" << std::dec << o.onode.size << ")"
|
|
<< " expected_object_size " << o.onode.expected_object_size
|
|
<< " expected_write_size " << o.onode.expected_write_size
|
|
<< " in " << o.onode.extent_map_shards.size() << " shards"
|
|
<< ", " << o.extent_map.spanning_blob_map.size()
|
|
<< " spanning blobs"
|
|
<< dendl;
|
|
for (auto& [zone, offset] : o.onode.zone_offset_refs) {
|
|
dout(LogLevelV) << __func__ << " zone ref 0x" << std::hex << zone
|
|
<< " offset 0x" << offset << std::dec << dendl;
|
|
}
|
|
for (auto p = o.onode.attrs.begin();
|
|
p != o.onode.attrs.end();
|
|
++p) {
|
|
dout(LogLevelV) << __func__ << " attr " << p->first
|
|
<< " len " << p->second.length() << dendl;
|
|
}
|
|
_dump_extent_map<LogLevelV>(cct, o.extent_map);
|
|
|
|
for (auto& b : o.bc.buffer_map) {
|
|
dout(LogLevelV) << __func__ << " 0x" << std::hex << b.offset << "~"
|
|
<< b.length << std::dec << " " << b
|
|
<< dendl;
|
|
}
|
|
}
|
|
|
|
template <int LogLevelV>
|
|
void _dump_transaction(CephContext *cct, ObjectStore::Transaction *t)
|
|
{
|
|
dout(LogLevelV) << __func__ << " transaction dump:\n";
|
|
JSONFormatter f(true);
|
|
f.open_object_section("transaction");
|
|
t->dump(&f);
|
|
f.close_section();
|
|
f.flush(*_dout);
|
|
*_dout << dendl;
|
|
}
|
|
|
|
// Buffer
|
|
|
|
ostream& operator<<(ostream& out, const BlueStore::Buffer& b)
|
|
{
|
|
out << "buffer(" << &b << " space " << b.space << " 0x" << std::hex
|
|
<< b.offset << "~" << b.length << std::dec
|
|
<< " " << BlueStore::Buffer::get_state_name(b.state);
|
|
if (b.flags)
|
|
out << " " << BlueStore::Buffer::get_flag_name(b.flags);
|
|
return out << ")";
|
|
}
|
|
|
|
//pool_fsck_stats_t
|
|
|
|
std::ostream& operator<<(std::ostream& out, const BlueStore::pool_fsck_stats_t& s)
|
|
{
|
|
out << "(" << s.num_objects << " objects, "
|
|
<< s.shared_blobs << " shared blobs, "
|
|
<< s.omaps << " omaps, "
|
|
<< s.omap_key_size << " bytes in omap keys, "
|
|
<< s.omap_val_size << " bytes in omap vals, "
|
|
<< s.stored << " bytes stored, "
|
|
<< s.allocated << " bytes allocated"
|
|
<< ")";
|
|
return out;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/*
|
|
* Due to a bug in key string encoding (see a comment for append_escaped)
|
|
* the KeyValueDB iterator does not lexicographically sort the same
|
|
* way that ghobject_t does: objects with the same hash may have wrong order.
|
|
*
|
|
* This is the iterator wrapper that fixes the keys order.
|
|
*/
|
|
|
|
class CollectionListIterator {
|
|
public:
|
|
CollectionListIterator(const KeyValueDB::Iterator &it)
|
|
: m_it(it) {
|
|
}
|
|
virtual ~CollectionListIterator() {
|
|
}
|
|
|
|
virtual bool valid() const = 0;
|
|
virtual const ghobject_t &oid() const = 0;
|
|
virtual void lower_bound(const ghobject_t &oid) = 0;
|
|
virtual void upper_bound(const ghobject_t &oid) = 0;
|
|
virtual void next() = 0;
|
|
|
|
virtual int cmp(const ghobject_t &oid) const = 0;
|
|
|
|
bool is_ge(const ghobject_t &oid) const {
|
|
return cmp(oid) >= 0;
|
|
}
|
|
|
|
bool is_lt(const ghobject_t &oid) const {
|
|
return cmp(oid) < 0;
|
|
}
|
|
|
|
protected:
|
|
KeyValueDB::Iterator m_it;
|
|
};
|
|
|
|
class SimpleCollectionListIterator : public CollectionListIterator {
|
|
public:
|
|
SimpleCollectionListIterator(CephContext *cct, const KeyValueDB::Iterator &it)
|
|
: CollectionListIterator(it), m_cct(cct) {
|
|
}
|
|
|
|
bool valid() const override {
|
|
return m_it->valid();
|
|
}
|
|
|
|
const ghobject_t &oid() const override {
|
|
ceph_assert(valid());
|
|
|
|
return m_oid;
|
|
}
|
|
|
|
void lower_bound(const ghobject_t &oid) override {
|
|
string key;
|
|
get_object_key(m_cct, oid, &key);
|
|
|
|
m_it->lower_bound(key);
|
|
get_oid();
|
|
}
|
|
|
|
void upper_bound(const ghobject_t &oid) override {
|
|
string key;
|
|
get_object_key(m_cct, oid, &key);
|
|
|
|
m_it->upper_bound(key);
|
|
get_oid();
|
|
}
|
|
|
|
void next() override {
|
|
ceph_assert(valid());
|
|
|
|
m_it->next();
|
|
get_oid();
|
|
}
|
|
|
|
int cmp(const ghobject_t &oid) const override {
|
|
ceph_assert(valid());
|
|
|
|
string key;
|
|
get_object_key(m_cct, oid, &key);
|
|
|
|
return m_it->key().compare(key);
|
|
}
|
|
|
|
private:
|
|
CephContext *m_cct;
|
|
ghobject_t m_oid;
|
|
|
|
void get_oid() {
|
|
m_oid = ghobject_t();
|
|
while (m_it->valid() && is_extent_shard_key(m_it->key())) {
|
|
m_it->next();
|
|
}
|
|
if (!valid()) {
|
|
return;
|
|
}
|
|
|
|
int r = get_key_object(m_it->key(), &m_oid);
|
|
ceph_assert(r == 0);
|
|
}
|
|
};
|
|
|
|
class SortedCollectionListIterator : public CollectionListIterator {
|
|
public:
|
|
SortedCollectionListIterator(const KeyValueDB::Iterator &it)
|
|
: CollectionListIterator(it), m_chunk_iter(m_chunk.end()) {
|
|
}
|
|
|
|
bool valid() const override {
|
|
return m_chunk_iter != m_chunk.end();
|
|
}
|
|
|
|
const ghobject_t &oid() const override {
|
|
ceph_assert(valid());
|
|
|
|
return m_chunk_iter->first;
|
|
}
|
|
|
|
void lower_bound(const ghobject_t &oid) override {
|
|
std::string key;
|
|
_key_encode_prefix(oid, &key);
|
|
|
|
m_it->lower_bound(key);
|
|
m_chunk_iter = m_chunk.end();
|
|
if (!get_next_chunk()) {
|
|
return;
|
|
}
|
|
|
|
if (this->oid().shard_id != oid.shard_id ||
|
|
this->oid().hobj.pool != oid.hobj.pool ||
|
|
this->oid().hobj.get_bitwise_key_u32() != oid.hobj.get_bitwise_key_u32()) {
|
|
return;
|
|
}
|
|
|
|
m_chunk_iter = m_chunk.lower_bound(oid);
|
|
if (m_chunk_iter == m_chunk.end()) {
|
|
get_next_chunk();
|
|
}
|
|
}
|
|
|
|
void upper_bound(const ghobject_t &oid) override {
|
|
lower_bound(oid);
|
|
|
|
if (valid() && this->oid() == oid) {
|
|
next();
|
|
}
|
|
}
|
|
|
|
void next() override {
|
|
ceph_assert(valid());
|
|
|
|
m_chunk_iter++;
|
|
if (m_chunk_iter == m_chunk.end()) {
|
|
get_next_chunk();
|
|
}
|
|
}
|
|
|
|
int cmp(const ghobject_t &oid) const override {
|
|
ceph_assert(valid());
|
|
|
|
if (this->oid() < oid) {
|
|
return -1;
|
|
}
|
|
if (this->oid() > oid) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private:
|
|
std::map<ghobject_t, std::string> m_chunk;
|
|
std::map<ghobject_t, std::string>::iterator m_chunk_iter;
|
|
|
|
bool get_next_chunk() {
|
|
while (m_it->valid() && is_extent_shard_key(m_it->key())) {
|
|
m_it->next();
|
|
}
|
|
|
|
if (!m_it->valid()) {
|
|
return false;
|
|
}
|
|
|
|
ghobject_t oid;
|
|
int r = get_key_object(m_it->key(), &oid);
|
|
ceph_assert(r == 0);
|
|
|
|
m_chunk.clear();
|
|
while (true) {
|
|
m_chunk.insert({oid, m_it->key()});
|
|
|
|
do {
|
|
m_it->next();
|
|
} while (m_it->valid() && is_extent_shard_key(m_it->key()));
|
|
|
|
if (!m_it->valid()) {
|
|
break;
|
|
}
|
|
|
|
ghobject_t next;
|
|
r = get_key_object(m_it->key(), &next);
|
|
ceph_assert(r == 0);
|
|
if (next.shard_id != oid.shard_id ||
|
|
next.hobj.pool != oid.hobj.pool ||
|
|
next.hobj.get_bitwise_key_u32() != oid.hobj.get_bitwise_key_u32()) {
|
|
break;
|
|
}
|
|
oid = next;
|
|
}
|
|
|
|
m_chunk_iter = m_chunk.begin();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
// Garbage Collector
|
|
|
|
void BlueStore::GarbageCollector::process_protrusive_extents(
|
|
const BlueStore::ExtentMap& extent_map,
|
|
uint64_t start_offset,
|
|
uint64_t end_offset,
|
|
uint64_t start_touch_offset,
|
|
uint64_t end_touch_offset,
|
|
uint64_t min_alloc_size)
|
|
{
|
|
ceph_assert(start_offset <= start_touch_offset && end_offset>= end_touch_offset);
|
|
|
|
uint64_t lookup_start_offset = p2align(start_offset, min_alloc_size);
|
|
uint64_t lookup_end_offset = round_up_to(end_offset, min_alloc_size);
|
|
|
|
dout(30) << __func__ << " (hex): [" << std::hex
|
|
<< lookup_start_offset << ", " << lookup_end_offset
|
|
<< ")" << std::dec << dendl;
|
|
|
|
for (auto it = extent_map.seek_lextent(lookup_start_offset);
|
|
it != extent_map.extent_map.end() &&
|
|
it->logical_offset < lookup_end_offset;
|
|
++it) {
|
|
uint64_t alloc_unit_start = it->logical_offset / min_alloc_size;
|
|
uint64_t alloc_unit_end = (it->logical_end() - 1) / min_alloc_size;
|
|
|
|
dout(30) << __func__ << " " << *it
|
|
<< "alloc_units: " << alloc_unit_start << ".." << alloc_unit_end
|
|
<< dendl;
|
|
|
|
Blob* b = it->blob.get();
|
|
|
|
if (it->logical_offset >=start_touch_offset &&
|
|
it->logical_end() <= end_touch_offset) {
|
|
// Process extents within the range affected by
|
|
// the current write request.
|
|
// Need to take into account if existing extents
|
|
// can be merged with them (uncompressed case)
|
|
if (!b->get_blob().is_compressed()) {
|
|
if (blob_info_counted && used_alloc_unit == alloc_unit_start) {
|
|
--blob_info_counted->expected_allocations; // don't need to allocate
|
|
// new AU for compressed
|
|
// data since another
|
|
// collocated uncompressed
|
|
// blob already exists
|
|
dout(30) << __func__ << " --expected:"
|
|
<< alloc_unit_start << dendl;
|
|
}
|
|
used_alloc_unit = alloc_unit_end;
|
|
blob_info_counted = nullptr;
|
|
}
|
|
} else if (b->get_blob().is_compressed()) {
|
|
|
|
// additionally we take compressed blobs that were not impacted
|
|
// by the write into account too
|
|
BlobInfo& bi =
|
|
affected_blobs.emplace(
|
|
b, BlobInfo(b->get_referenced_bytes())).first->second;
|
|
|
|
int adjust =
|
|
(used_alloc_unit && used_alloc_unit == alloc_unit_start) ? 0 : 1;
|
|
bi.expected_allocations += alloc_unit_end - alloc_unit_start + adjust;
|
|
dout(30) << __func__ << " expected_allocations="
|
|
<< bi.expected_allocations << " end_au:"
|
|
<< alloc_unit_end << dendl;
|
|
|
|
blob_info_counted = &bi;
|
|
used_alloc_unit = alloc_unit_end;
|
|
|
|
ceph_assert(it->length <= bi.referenced_bytes);
|
|
bi.referenced_bytes -= it->length;
|
|
dout(30) << __func__ << " affected_blob:" << *b
|
|
<< " unref 0x" << std::hex << it->length
|
|
<< " referenced = 0x" << bi.referenced_bytes
|
|
<< std::dec << dendl;
|
|
// NOTE: we can't move specific blob to resulting GC list here
|
|
// when reference counter == 0 since subsequent extents might
|
|
// decrement its expected_allocation.
|
|
// Hence need to enumerate all the extents first.
|
|
if (!bi.collect_candidate) {
|
|
bi.first_lextent = it;
|
|
bi.collect_candidate = true;
|
|
}
|
|
bi.last_lextent = it;
|
|
} else {
|
|
if (blob_info_counted && used_alloc_unit == alloc_unit_start) {
|
|
// don't need to allocate new AU for compressed data since another
|
|
// collocated uncompressed blob already exists
|
|
--blob_info_counted->expected_allocations;
|
|
dout(30) << __func__ << " --expected_allocations:"
|
|
<< alloc_unit_start << dendl;
|
|
}
|
|
used_alloc_unit = alloc_unit_end;
|
|
blob_info_counted = nullptr;
|
|
}
|
|
}
|
|
|
|
for (auto b_it = affected_blobs.begin();
|
|
b_it != affected_blobs.end();
|
|
++b_it) {
|
|
Blob* b = b_it->first;
|
|
BlobInfo& bi = b_it->second;
|
|
if (bi.referenced_bytes == 0) {
|
|
uint64_t len_on_disk = b_it->first->get_blob().get_ondisk_length();
|
|
int64_t blob_expected_for_release =
|
|
round_up_to(len_on_disk, min_alloc_size) / min_alloc_size;
|
|
|
|
dout(30) << __func__ << " " << *(b_it->first)
|
|
<< " expected4release=" << blob_expected_for_release
|
|
<< " expected_allocations=" << bi.expected_allocations
|
|
<< dendl;
|
|
int64_t benefit = blob_expected_for_release - bi.expected_allocations;
|
|
if (benefit >= g_conf()->bluestore_gc_enable_blob_threshold) {
|
|
if (bi.collect_candidate) {
|
|
auto it = bi.first_lextent;
|
|
bool bExit = false;
|
|
do {
|
|
if (it->blob.get() == b) {
|
|
extents_to_collect.insert(it->logical_offset, it->length);
|
|
}
|
|
bExit = it == bi.last_lextent;
|
|
++it;
|
|
} while (!bExit);
|
|
}
|
|
expected_for_release += blob_expected_for_release;
|
|
expected_allocations += bi.expected_allocations;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int64_t BlueStore::GarbageCollector::estimate(
|
|
uint64_t start_offset,
|
|
uint64_t length,
|
|
const BlueStore::ExtentMap& extent_map,
|
|
const BlueStore::old_extent_map_t& old_extents,
|
|
uint64_t min_alloc_size)
|
|
{
|
|
|
|
affected_blobs.clear();
|
|
extents_to_collect.clear();
|
|
used_alloc_unit = boost::optional<uint64_t >();
|
|
blob_info_counted = nullptr;
|
|
|
|
uint64_t gc_start_offset = start_offset;
|
|
uint64_t gc_end_offset = start_offset + length;
|
|
|
|
uint64_t end_offset = start_offset + length;
|
|
|
|
for (auto it = old_extents.begin(); it != old_extents.end(); ++it) {
|
|
Blob* b = it->e.blob.get();
|
|
if (b->get_blob().is_compressed()) {
|
|
|
|
// update gc_start_offset/gc_end_offset if needed
|
|
gc_start_offset = min(gc_start_offset, (uint64_t)it->e.blob_start());
|
|
gc_end_offset = std::max(gc_end_offset, (uint64_t)it->e.blob_end());
|
|
|
|
auto o = it->e.logical_offset;
|
|
auto l = it->e.length;
|
|
|
|
uint64_t ref_bytes = b->get_referenced_bytes();
|
|
// micro optimization to bypass blobs that have no more references
|
|
if (ref_bytes != 0) {
|
|
dout(30) << __func__ << " affected_blob:" << *b
|
|
<< " unref 0x" << std::hex << o << "~" << l
|
|
<< std::dec << dendl;
|
|
affected_blobs.emplace(b, BlobInfo(ref_bytes));
|
|
}
|
|
}
|
|
}
|
|
dout(30) << __func__ << " gc range(hex): [" << std::hex
|
|
<< gc_start_offset << ", " << gc_end_offset
|
|
<< ")" << std::dec << dendl;
|
|
|
|
// enumerate preceeding extents to check if they reference affected blobs
|
|
if (gc_start_offset < start_offset || gc_end_offset > end_offset) {
|
|
process_protrusive_extents(extent_map,
|
|
gc_start_offset,
|
|
gc_end_offset,
|
|
start_offset,
|
|
end_offset,
|
|
min_alloc_size);
|
|
}
|
|
return expected_for_release - expected_allocations;
|
|
}
|
|
|
|
// LruOnodeCacheShard
|
|
struct LruOnodeCacheShard : public BlueStore::OnodeCacheShard {
|
|
typedef boost::intrusive::list<
|
|
BlueStore::Onode,
|
|
boost::intrusive::member_hook<
|
|
BlueStore::Onode,
|
|
boost::intrusive::list_member_hook<>,
|
|
&BlueStore::Onode::lru_item> > list_t;
|
|
|
|
list_t lru;
|
|
|
|
explicit LruOnodeCacheShard(CephContext *cct) : BlueStore::OnodeCacheShard(cct) {}
|
|
|
|
void _add(BlueStore::Onode* o, int level) override
|
|
{
|
|
o->set_cached();
|
|
if (o->pin_nref == 1) {
|
|
(level > 0) ? lru.push_front(*o) : lru.push_back(*o);
|
|
o->cache_age_bin = age_bins.front();
|
|
*(o->cache_age_bin) += 1;
|
|
}
|
|
++num; // we count both pinned and unpinned entries
|
|
dout(20) << __func__ << " " << this << " " << o->oid << " added, num="
|
|
<< num << dendl;
|
|
}
|
|
void _rm(BlueStore::Onode* o) override
|
|
{
|
|
o->clear_cached();
|
|
if (o->lru_item.is_linked()) {
|
|
*(o->cache_age_bin) -= 1;
|
|
lru.erase(lru.iterator_to(*o));
|
|
}
|
|
ceph_assert(num);
|
|
--num;
|
|
dout(20) << __func__ << " " << this << " " << " " << o->oid << " removed, num=" << num << dendl;
|
|
}
|
|
|
|
void maybe_unpin(BlueStore::Onode* o) override
|
|
{
|
|
OnodeCacheShard* ocs = this;
|
|
ocs->lock.lock();
|
|
// It is possible that during waiting split_cache moved us to different OnodeCacheShard.
|
|
while (ocs != o->c->get_onode_cache()) {
|
|
ocs->lock.unlock();
|
|
ocs = o->c->get_onode_cache();
|
|
ocs->lock.lock();
|
|
}
|
|
if (o->is_cached() && o->pin_nref == 1) {
|
|
if(!o->lru_item.is_linked()) {
|
|
if (o->exists) {
|
|
lru.push_front(*o);
|
|
o->cache_age_bin = age_bins.front();
|
|
*(o->cache_age_bin) += 1;
|
|
dout(20) << __func__ << " " << this << " " << o->oid << " unpinned"
|
|
<< dendl;
|
|
} else {
|
|
ceph_assert(num);
|
|
--num;
|
|
o->clear_cached();
|
|
dout(20) << __func__ << " " << this << " " << o->oid << " removed"
|
|
<< dendl;
|
|
// remove will also decrement nref
|
|
o->c->onode_space._remove(o->oid);
|
|
}
|
|
} else if (o->exists) {
|
|
// move onode within LRU
|
|
lru.erase(lru.iterator_to(*o));
|
|
lru.push_front(*o);
|
|
if (o->cache_age_bin != age_bins.front()) {
|
|
*(o->cache_age_bin) -= 1;
|
|
o->cache_age_bin = age_bins.front();
|
|
*(o->cache_age_bin) += 1;
|
|
}
|
|
dout(20) << __func__ << " " << this << " " << o->oid << " touched"
|
|
<< dendl;
|
|
}
|
|
}
|
|
ocs->lock.unlock();
|
|
}
|
|
|
|
void _trim_to(uint64_t new_size) override
|
|
{
|
|
if (new_size >= lru.size()) {
|
|
return; // don't even try
|
|
}
|
|
uint64_t n = num - new_size; // note: we might get empty LRU
|
|
// before n == 0 due to pinned
|
|
// entries. And hence being unable
|
|
// to reach new_size target.
|
|
while (n-- > 0 && lru.size() > 0) {
|
|
BlueStore::Onode *o = &lru.back();
|
|
lru.pop_back();
|
|
|
|
dout(20) << __func__ << " rm " << o->oid << " "
|
|
<< o->nref << " " << o->cached << dendl;
|
|
|
|
*(o->cache_age_bin) -= 1;
|
|
if (o->pin_nref > 1) {
|
|
dout(20) << __func__ << " " << this << " " << " " << " " << o->oid << dendl;
|
|
} else {
|
|
ceph_assert(num);
|
|
--num;
|
|
o->clear_cached();
|
|
o->c->onode_space._remove(o->oid);
|
|
}
|
|
}
|
|
}
|
|
void _move_pinned(OnodeCacheShard *to, BlueStore::Onode *o) override
|
|
{
|
|
if (to == this) {
|
|
return;
|
|
}
|
|
_rm(o);
|
|
ceph_assert(o->nref > 1);
|
|
to->_add(o, 0);
|
|
}
|
|
void add_stats(uint64_t *onodes, uint64_t *pinned_onodes) override
|
|
{
|
|
std::lock_guard l(lock);
|
|
*onodes += num;
|
|
*pinned_onodes += num - lru.size();
|
|
}
|
|
#ifdef DEBUG_CACHE
|
|
void _audit(const char *when) override
|
|
{
|
|
}
|
|
#endif
|
|
};
|
|
|
|
// OnodeCacheShard
|
|
BlueStore::OnodeCacheShard *BlueStore::OnodeCacheShard::create(
|
|
CephContext* cct,
|
|
string type,
|
|
PerfCounters *logger)
|
|
{
|
|
BlueStore::OnodeCacheShard *c = nullptr;
|
|
// Currently we only implement an LRU cache for onodes
|
|
c = new LruOnodeCacheShard(cct);
|
|
c->logger = logger;
|
|
return c;
|
|
}
|
|
|
|
// LruBufferCacheShard
|
|
struct LruBufferCacheShard : public BlueStore::BufferCacheShard {
|
|
typedef boost::intrusive::list<
|
|
BlueStore::Buffer,
|
|
boost::intrusive::member_hook<
|
|
BlueStore::Buffer,
|
|
boost::intrusive::list_member_hook<>,
|
|
&BlueStore::Buffer::lru_item> > list_t;
|
|
list_t lru;
|
|
|
|
explicit LruBufferCacheShard(BlueStore* store) : BlueStore::BufferCacheShard(store) {}
|
|
|
|
void _add(BlueStore::Buffer *b, int level, BlueStore::Buffer *near) override {
|
|
if (near && !near->is_writing()) {
|
|
auto q = lru.iterator_to(*near);
|
|
lru.insert(q, *b);
|
|
} else if (level > 0) {
|
|
lru.push_front(*b);
|
|
} else {
|
|
lru.push_back(*b);
|
|
}
|
|
buffer_bytes += b->length;
|
|
b->cache_age_bin = age_bins.front();
|
|
*(b->cache_age_bin) += b->length;
|
|
num = lru.size();
|
|
}
|
|
void _rm(BlueStore::Buffer *b) override {
|
|
ceph_assert(buffer_bytes >= b->length);
|
|
buffer_bytes -= b->length;
|
|
ceph_assert(*(b->cache_age_bin) >= b->length);
|
|
*(b->cache_age_bin) -= b->length;
|
|
auto q = lru.iterator_to(*b);
|
|
lru.erase(q);
|
|
num = lru.size();
|
|
}
|
|
void _move(BlueStore::BufferCacheShard *src, BlueStore::Buffer *b) override {
|
|
src->_rm(b);
|
|
_add(b, 0, nullptr);
|
|
}
|
|
void _adjust_size(BlueStore::Buffer *b, int64_t delta) override {
|
|
ceph_assert((int64_t)buffer_bytes + delta >= 0);
|
|
buffer_bytes += delta;
|
|
ceph_assert(*(b->cache_age_bin) + delta >= 0);
|
|
*(b->cache_age_bin) += delta;
|
|
}
|
|
void _touch(BlueStore::Buffer *b) override {
|
|
auto p = lru.iterator_to(*b);
|
|
lru.erase(p);
|
|
lru.push_front(*b);
|
|
*(b->cache_age_bin) -= b->length;
|
|
b->cache_age_bin = age_bins.front();
|
|
*(b->cache_age_bin) += b->length;
|
|
num = lru.size();
|
|
_audit("_touch_buffer end");
|
|
}
|
|
|
|
void _trim_to(uint64_t max) override
|
|
{
|
|
while (buffer_bytes > max) {
|
|
auto i = lru.rbegin();
|
|
if (i == lru.rend()) {
|
|
// stop if lru is now empty
|
|
break;
|
|
}
|
|
|
|
BlueStore::Buffer *b = &*i;
|
|
ceph_assert(b->is_clean());
|
|
dout(20) << __func__ << " rm " << *b << dendl;
|
|
ceph_assert(*(b->cache_age_bin) >= b->length);
|
|
*(b->cache_age_bin) -= b->length;
|
|
b->space->_rm_buffer(this, b);
|
|
}
|
|
num = lru.size();
|
|
}
|
|
|
|
void add_stats(uint64_t *extents,
|
|
uint64_t *blobs,
|
|
uint64_t *buffers,
|
|
uint64_t *bytes) override {
|
|
std::lock_guard l(lock);
|
|
*extents += num_extents;
|
|
*blobs += num_blobs;
|
|
*buffers += num;
|
|
*bytes += buffer_bytes;
|
|
}
|
|
#ifdef DEBUG_CACHE
|
|
void _audit(const char *when) override
|
|
{
|
|
dout(10) << __func__ << " " << when << " start" << dendl;
|
|
uint64_t s = 0;
|
|
for (auto i = lru.begin(); i != lru.end(); ++i) {
|
|
s += i->length;
|
|
}
|
|
if (s != buffer_bytes) {
|
|
derr << __func__ << " buffer_size " << buffer_bytes << " actual " << s
|
|
<< dendl;
|
|
for (auto i = lru.begin(); i != lru.end(); ++i) {
|
|
derr << __func__ << " " << *i << dendl;
|
|
}
|
|
ceph_assert(s == buffer_bytes);
|
|
}
|
|
dout(20) << __func__ << " " << when << " buffer_bytes " << buffer_bytes
|
|
<< " ok" << dendl;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
// TwoQBufferCacheShard
|
|
|
|
struct TwoQBufferCacheShard : public BlueStore::BufferCacheShard {
|
|
typedef boost::intrusive::list<
|
|
BlueStore::Buffer,
|
|
boost::intrusive::member_hook<
|
|
BlueStore::Buffer,
|
|
boost::intrusive::list_member_hook<>,
|
|
&BlueStore::Buffer::lru_item> > list_t;
|
|
list_t hot; ///< "Am" hot buffers
|
|
list_t warm_in; ///< "A1in" newly warm buffers
|
|
list_t warm_out; ///< "A1out" empty buffers we've evicted
|
|
|
|
enum {
|
|
BUFFER_NEW = 0,
|
|
BUFFER_WARM_IN, ///< in warm_in
|
|
BUFFER_WARM_OUT, ///< in warm_out
|
|
BUFFER_HOT, ///< in hot
|
|
BUFFER_TYPE_MAX
|
|
};
|
|
|
|
uint64_t list_bytes[BUFFER_TYPE_MAX] = {0}; ///< bytes per type
|
|
|
|
public:
|
|
explicit TwoQBufferCacheShard(BlueStore* store) : BufferCacheShard(store) {}
|
|
|
|
void _add(BlueStore::Buffer *b, int level, BlueStore::Buffer *near) override
|
|
{
|
|
dout(20) << __func__ << " level " << level << " near " << near
|
|
<< " on " << *b
|
|
<< " which has cache_private " << b->cache_private << dendl;
|
|
ceph_assert(b->is_clean() || b->is_empty());
|
|
if (near) {
|
|
b->cache_private = near->cache_private;
|
|
}
|
|
if (near && !near->is_writing()) {
|
|
switch (b->cache_private) {
|
|
case BUFFER_WARM_IN:
|
|
warm_in.insert(warm_in.iterator_to(*near), *b);
|
|
break;
|
|
case BUFFER_WARM_OUT:
|
|
ceph_assert(b->is_empty());
|
|
warm_out.insert(warm_out.iterator_to(*near), *b);
|
|
break;
|
|
case BUFFER_HOT:
|
|
hot.insert(hot.iterator_to(*near), *b);
|
|
break;
|
|
default:
|
|
ceph_abort_msg("bad cache_private");
|
|
}
|
|
} else {
|
|
// we got a hint from discard
|
|
switch (b->cache_private) {
|
|
case BUFFER_NEW:
|
|
b->cache_private = BUFFER_WARM_IN;
|
|
if (level > 0) {
|
|
warm_in.push_front(*b);
|
|
} else {
|
|
// take caller hint to start at the back of the warm queue
|
|
warm_in.push_back(*b);
|
|
}
|
|
break;
|
|
case BUFFER_WARM_IN:
|
|
// stay in warm_in. move to front, even though 2Q doesn't actually
|
|
// do this.
|
|
dout(20) << __func__ << " move to front of warm " << *b << dendl;
|
|
warm_in.push_front(*b);
|
|
break;
|
|
case BUFFER_WARM_OUT:
|
|
b->cache_private = BUFFER_HOT;
|
|
// move to hot. fall-thru
|
|
case BUFFER_HOT:
|
|
dout(20) << __func__ << " move to front of hot " << *b << dendl;
|
|
hot.push_front(*b);
|
|
break;
|
|
default:
|
|
ceph_abort_msg("bad cache_private");
|
|
}
|
|
}
|
|
b->cache_age_bin = age_bins.front();
|
|
if (!b->is_empty()) {
|
|
buffer_bytes += b->length;
|
|
list_bytes[b->cache_private] += b->length;
|
|
*(b->cache_age_bin) += b->length;
|
|
}
|
|
num = hot.size() + warm_in.size();
|
|
}
|
|
|
|
void _rm(BlueStore::Buffer *b) override
|
|
{
|
|
dout(20) << __func__ << " " << *b << dendl;
|
|
if (!b->is_empty()) {
|
|
ceph_assert(buffer_bytes >= b->length);
|
|
buffer_bytes -= b->length;
|
|
ceph_assert(list_bytes[b->cache_private] >= b->length);
|
|
list_bytes[b->cache_private] -= b->length;
|
|
ceph_assert(*(b->cache_age_bin) >= b->length);
|
|
*(b->cache_age_bin) -= b->length;
|
|
}
|
|
switch (b->cache_private) {
|
|
case BUFFER_WARM_IN:
|
|
warm_in.erase(warm_in.iterator_to(*b));
|
|
break;
|
|
case BUFFER_WARM_OUT:
|
|
warm_out.erase(warm_out.iterator_to(*b));
|
|
break;
|
|
case BUFFER_HOT:
|
|
hot.erase(hot.iterator_to(*b));
|
|
break;
|
|
default:
|
|
ceph_abort_msg("bad cache_private");
|
|
}
|
|
num = hot.size() + warm_in.size();
|
|
}
|
|
|
|
void _move(BlueStore::BufferCacheShard *srcc, BlueStore::Buffer *b) override
|
|
{
|
|
TwoQBufferCacheShard *src = static_cast<TwoQBufferCacheShard*>(srcc);
|
|
src->_rm(b);
|
|
|
|
// preserve which list we're on (even if we can't preserve the order!)
|
|
switch (b->cache_private) {
|
|
case BUFFER_WARM_IN:
|
|
ceph_assert(!b->is_empty());
|
|
warm_in.push_back(*b);
|
|
break;
|
|
case BUFFER_WARM_OUT:
|
|
ceph_assert(b->is_empty());
|
|
warm_out.push_back(*b);
|
|
break;
|
|
case BUFFER_HOT:
|
|
ceph_assert(!b->is_empty());
|
|
hot.push_back(*b);
|
|
break;
|
|
default:
|
|
ceph_abort_msg("bad cache_private");
|
|
}
|
|
if (!b->is_empty()) {
|
|
buffer_bytes += b->length;
|
|
list_bytes[b->cache_private] += b->length;
|
|
*(b->cache_age_bin) += b->length;
|
|
}
|
|
num = hot.size() + warm_in.size();
|
|
}
|
|
|
|
void _adjust_size(BlueStore::Buffer *b, int64_t delta) override
|
|
{
|
|
dout(20) << __func__ << " delta " << delta << " on " << *b << dendl;
|
|
if (!b->is_empty()) {
|
|
ceph_assert((int64_t)buffer_bytes + delta >= 0);
|
|
buffer_bytes += delta;
|
|
ceph_assert((int64_t)list_bytes[b->cache_private] + delta >= 0);
|
|
list_bytes[b->cache_private] += delta;
|
|
ceph_assert(*(b->cache_age_bin) + delta >= 0);
|
|
*(b->cache_age_bin) += delta;
|
|
}
|
|
}
|
|
|
|
void _touch(BlueStore::Buffer *b) override {
|
|
switch (b->cache_private) {
|
|
case BUFFER_WARM_IN:
|
|
// do nothing (somewhat counter-intuitively!)
|
|
break;
|
|
case BUFFER_WARM_OUT:
|
|
// move from warm_out to hot LRU
|
|
ceph_abort_msg("this happens via discard hint");
|
|
break;
|
|
case BUFFER_HOT:
|
|
// move to front of hot LRU
|
|
hot.erase(hot.iterator_to(*b));
|
|
hot.push_front(*b);
|
|
break;
|
|
}
|
|
*(b->cache_age_bin) -= b->length;
|
|
b->cache_age_bin = age_bins.front();
|
|
*(b->cache_age_bin) += b->length;
|
|
num = hot.size() + warm_in.size();
|
|
_audit("_touch_buffer end");
|
|
}
|
|
|
|
void _trim_to(uint64_t max) override
|
|
{
|
|
if (buffer_bytes > max) {
|
|
uint64_t kin = max * cct->_conf->bluestore_2q_cache_kin_ratio;
|
|
uint64_t khot = max - kin;
|
|
|
|
// pre-calculate kout based on average buffer size too,
|
|
// which is typical(the warm_in and hot lists may change later)
|
|
uint64_t kout = 0;
|
|
uint64_t buffer_num = hot.size() + warm_in.size();
|
|
if (buffer_num) {
|
|
uint64_t avg_size = buffer_bytes / buffer_num;
|
|
ceph_assert(avg_size);
|
|
uint64_t calculated_num = max / avg_size;
|
|
kout = calculated_num * cct->_conf->bluestore_2q_cache_kout_ratio;
|
|
}
|
|
|
|
if (list_bytes[BUFFER_HOT] < khot) {
|
|
// hot is small, give slack to warm_in
|
|
kin += khot - list_bytes[BUFFER_HOT];
|
|
} else if (list_bytes[BUFFER_WARM_IN] < kin) {
|
|
// warm_in is small, give slack to hot
|
|
khot += kin - list_bytes[BUFFER_WARM_IN];
|
|
}
|
|
|
|
// adjust warm_in list
|
|
int64_t to_evict_bytes = list_bytes[BUFFER_WARM_IN] - kin;
|
|
uint64_t evicted = 0;
|
|
|
|
while (to_evict_bytes > 0) {
|
|
auto p = warm_in.rbegin();
|
|
if (p == warm_in.rend()) {
|
|
// stop if warm_in list is now empty
|
|
break;
|
|
}
|
|
|
|
BlueStore::Buffer *b = &*p;
|
|
ceph_assert(b->is_clean());
|
|
dout(20) << __func__ << " buffer_warm_in -> out " << *b << dendl;
|
|
ceph_assert(buffer_bytes >= b->length);
|
|
buffer_bytes -= b->length;
|
|
ceph_assert(list_bytes[BUFFER_WARM_IN] >= b->length);
|
|
list_bytes[BUFFER_WARM_IN] -= b->length;
|
|
ceph_assert(*(b->cache_age_bin) >= b->length);
|
|
*(b->cache_age_bin) -= b->length;
|
|
to_evict_bytes -= b->length;
|
|
evicted += b->length;
|
|
b->state = BlueStore::Buffer::STATE_EMPTY;
|
|
b->data.clear();
|
|
warm_in.erase(warm_in.iterator_to(*b));
|
|
warm_out.push_front(*b);
|
|
b->cache_private = BUFFER_WARM_OUT;
|
|
}
|
|
|
|
if (evicted > 0) {
|
|
dout(20) << __func__ << " evicted " << byte_u_t(evicted)
|
|
<< " from warm_in list, done evicting warm_in buffers"
|
|
<< dendl;
|
|
}
|
|
|
|
// adjust hot list
|
|
to_evict_bytes = list_bytes[BUFFER_HOT] - khot;
|
|
evicted = 0;
|
|
|
|
while (to_evict_bytes > 0) {
|
|
auto p = hot.rbegin();
|
|
if (p == hot.rend()) {
|
|
// stop if hot list is now empty
|
|
break;
|
|
}
|
|
|
|
BlueStore::Buffer *b = &*p;
|
|
dout(20) << __func__ << " buffer_hot rm " << *b << dendl;
|
|
ceph_assert(b->is_clean());
|
|
// adjust evict size before buffer goes invalid
|
|
to_evict_bytes -= b->length;
|
|
evicted += b->length;
|
|
b->space->_rm_buffer(this, b);
|
|
}
|
|
|
|
if (evicted > 0) {
|
|
dout(20) << __func__ << " evicted " << byte_u_t(evicted)
|
|
<< " from hot list, done evicting hot buffers"
|
|
<< dendl;
|
|
}
|
|
|
|
// adjust warm out list too, if necessary
|
|
int64_t n = warm_out.size() - kout;
|
|
while (n-- > 0) {
|
|
BlueStore::Buffer *b = &*warm_out.rbegin();
|
|
ceph_assert(b->is_empty());
|
|
dout(20) << __func__ << " buffer_warm_out rm " << *b << dendl;
|
|
b->space->_rm_buffer(this, b);
|
|
}
|
|
}
|
|
num = hot.size() + warm_in.size();
|
|
}
|
|
|
|
void add_stats(uint64_t *extents,
|
|
uint64_t *blobs,
|
|
uint64_t *buffers,
|
|
uint64_t *bytes) override {
|
|
std::lock_guard l(lock);
|
|
*extents += num_extents;
|
|
*blobs += num_blobs;
|
|
*buffers += num;
|
|
*bytes += buffer_bytes;
|
|
}
|
|
|
|
#ifdef DEBUG_CACHE
|
|
void _audit(const char *when) override
|
|
{
|
|
dout(10) << __func__ << " " << when << " start" << dendl;
|
|
uint64_t s = 0;
|
|
for (auto i = hot.begin(); i != hot.end(); ++i) {
|
|
ceph_assert(i->cache_private == BUFFER_HOT);
|
|
s += i->length;
|
|
}
|
|
|
|
uint64_t hot_bytes = s;
|
|
if (hot_bytes != list_bytes[BUFFER_HOT]) {
|
|
derr << __func__ << " hot_list_bytes "
|
|
<< list_bytes[BUFFER_HOT]
|
|
<< " != actual " << hot_bytes
|
|
<< dendl;
|
|
ceph_assert(hot_bytes == list_bytes[BUFFER_HOT]);
|
|
}
|
|
|
|
for (auto i = warm_in.begin(); i != warm_in.end(); ++i) {
|
|
ceph_assert(i->cache_private == BUFFER_WARM_IN);
|
|
s += i->length;
|
|
}
|
|
|
|
uint64_t warm_in_bytes = s - hot_bytes;
|
|
if (warm_in_bytes != list_bytes[BUFFER_WARM_IN]) {
|
|
derr << __func__ << " warm_in_list_bytes "
|
|
<< list_bytes[BUFFER_WARM_IN]
|
|
<< " != actual " << warm_in_bytes
|
|
<< dendl;
|
|
ceph_assert(warm_in_bytes == list_bytes[BUFFER_WARM_IN]);
|
|
}
|
|
|
|
if (s != buffer_bytes) {
|
|
derr << __func__ << " buffer_bytes " << buffer_bytes << " actual " << s
|
|
<< dendl;
|
|
ceph_assert(s == buffer_bytes);
|
|
}
|
|
|
|
for (auto i = warm_out.begin(); i != warm_out.end(); ++i) {
|
|
ceph_assert(i->cache_private == BUFFER_WARM_OUT);
|
|
ceph_assert(i->is_empty());
|
|
}
|
|
dout(20) << __func__ << " " << when << " buffer_bytes " << buffer_bytes
|
|
<< " ok" << dendl;
|
|
}
|
|
#endif
|
|
};
|
|
|
|
// BuferCacheShard
|
|
|
|
BlueStore::BufferCacheShard *BlueStore::BufferCacheShard::create(
|
|
BlueStore* store,
|
|
string type,
|
|
PerfCounters *logger)
|
|
{
|
|
BufferCacheShard *c = nullptr;
|
|
if (type == "lru")
|
|
c = new LruBufferCacheShard(store);
|
|
else if (type == "2q")
|
|
c = new TwoQBufferCacheShard(store);
|
|
else
|
|
ceph_abort_msg("unrecognized cache type");
|
|
c->logger = logger;
|
|
return c;
|
|
}
|
|
|
|
// Buffer
|
|
std::atomic<uint64_t> BlueStore::Buffer::total = 0;
|
|
|
|
// BufferSpace
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.BufferSpace(" << this << " in " << cache << ") "
|
|
|
|
void BlueStore::BufferSpace::_add_buffer(BufferCacheShard* cache,
|
|
Buffer* b,
|
|
uint16_t cache_private, int level,
|
|
Buffer *near)
|
|
{
|
|
ldout(cache->cct, 20) << __func__ << "? " << b << dendl;
|
|
cache->_audit("_add_buffer start");
|
|
ceph_assert(!b->set_item.is_linked());
|
|
// illegal to provide both near and cache_private
|
|
ceph_assert(!(near && cache_private != 0));
|
|
bool add_to_map = true;
|
|
if (b->is_writing()) {
|
|
ceph_assert(b->txc);
|
|
// we might get already cached data for which resetting mempool is inppropriate
|
|
// hence calling try_assign_to_mempool
|
|
if (b->txc->add_writing(&onode, b->offset, b->length)) {
|
|
b->data.try_assign_to_mempool(mempool::mempool_bluestore_writing);
|
|
} else if (b->flags & Buffer::FLAG_NOCACHE) {
|
|
//txc is being finished and hence it hasn't added us to writing list.
|
|
// And we don't need to cache this buffer.
|
|
// So we delete it.
|
|
ldout(cache->cct, 20) << __func__ <<
|
|
" not added to writing, releasing " << b
|
|
<< dendl;
|
|
delete b;
|
|
b = nullptr;
|
|
add_to_map = false;
|
|
} else {
|
|
//txc is being finished and hence it hasn't added us to writing list.
|
|
// So we can cache it
|
|
b->state = Buffer::STATE_CLEAN;
|
|
b->txc = nullptr;
|
|
b->maybe_rebuild();
|
|
}
|
|
}
|
|
if (add_to_map) {
|
|
ldout(cache->cct, 20) << __func__ << " added " << b << dendl;
|
|
b->data.reassign_to_mempool(mempool::mempool_bluestore_cache_data);
|
|
b->cache_private = cache_private;
|
|
buffer_map.insert(*b);
|
|
if (!b->is_writing()) {
|
|
cache->_add(b, level, near);
|
|
}
|
|
}
|
|
cache->_audit("_add_buffer end");
|
|
}
|
|
|
|
void BlueStore::BufferSpace::__rm_buffer(BufferCacheShard* cache,
|
|
Buffer* b)
|
|
{
|
|
ceph_assert(b);
|
|
cache->_audit("_rm_buffer start");
|
|
if (!b->is_writing()) {
|
|
cache->_rm(b);
|
|
}
|
|
ldout(cache->cct, 20) << __func__ << " erasing " << b << dendl;
|
|
__erase_from_map(b);
|
|
cache->_audit("_rm_buffer end");
|
|
}
|
|
|
|
void BlueStore::BufferSpace::__erase_from_map(Buffer* b)
|
|
{
|
|
ceph_assert(b);
|
|
buffer_map.erase(buffer_map.iterator_to(*b));
|
|
delete b;
|
|
}
|
|
|
|
void BlueStore::BufferSpace::_clear(BufferCacheShard* cache)
|
|
{
|
|
// note: we already hold cache->lock
|
|
ldout(cache->cct, 20) << __func__ << dendl;
|
|
while (!buffer_map.empty()) {
|
|
__rm_buffer(cache, &*buffer_map.begin());
|
|
}
|
|
}
|
|
|
|
int BlueStore::BufferSpace::_discard(BufferCacheShard* cache,
|
|
uint32_t offset, uint32_t length)
|
|
{
|
|
// note: we already hold cache->lock
|
|
ldout(cache->cct, 20) << __func__ << std::hex << " 0x" << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
int cache_private = 0;
|
|
cache->_audit("discard start");
|
|
auto i = _data_lower_bound(offset);
|
|
uint32_t end = offset + length;
|
|
while (i != buffer_map.end()) {
|
|
Buffer* b = &*i;
|
|
// First iteration either finds a buffer that contains the offset or the next buffer after it.
|
|
// Subsequent iterations are either buffers inside range or after the range.
|
|
// If we already found a buffer that doesn't overlaps with the range, we can break, as it must be next to the range.
|
|
bool overlaps = offset < b->end() && end > b->offset;
|
|
if (!overlaps) {
|
|
break;
|
|
}
|
|
if (b->cache_private > cache_private) {
|
|
cache_private = b->cache_private;
|
|
}
|
|
if (b->offset < offset) {
|
|
int64_t front = offset - b->offset;
|
|
if (b->end() > end) {
|
|
// drop middle (split)
|
|
uint32_t tail = b->end() - end;
|
|
if (b->data.length()) {
|
|
bufferlist bl;
|
|
bl.substr_of(b->data, b->length - tail, tail);
|
|
_add_buffer(cache,
|
|
new Buffer(this, b->state, b->txc, end, bl, b->flags),
|
|
0, 0, b);
|
|
} else {
|
|
_add_buffer(cache,
|
|
new Buffer(this, b->state, b->txc, end, tail, b->flags),
|
|
0, 0, b);
|
|
}
|
|
if (!b->is_writing()) {
|
|
cache->_adjust_size(b, front - (int64_t)b->length);
|
|
}
|
|
b->truncate(front);
|
|
b->maybe_rebuild();
|
|
cache->_audit("discard end 1");
|
|
break;
|
|
} else {
|
|
// drop tail
|
|
if (!b->is_writing()) {
|
|
cache->_adjust_size(b, front - (int64_t)b->length);
|
|
}
|
|
b->truncate(front);
|
|
b->maybe_rebuild();
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
if (b->end() <= end) {
|
|
// drop entire buffer
|
|
auto i0 = i++;
|
|
__rm_buffer(cache, &*i0);
|
|
continue;
|
|
}
|
|
// drop front
|
|
uint32_t keep = b->end() - end;
|
|
if (b->data.length()) {
|
|
bufferlist bl;
|
|
bl.substr_of(b->data, b->length - keep, keep);
|
|
_add_buffer(cache,
|
|
new Buffer(this, b->state, b->txc, end, bl, b->flags), 0, 0, b);
|
|
} else {
|
|
_add_buffer(cache,
|
|
new Buffer(this, b->state, b->txc, end, keep, b->flags), 0, 0, b);
|
|
}
|
|
__rm_buffer(cache, &*i);
|
|
cache->_audit("discard end 2");
|
|
break;
|
|
}
|
|
return cache_private;
|
|
}
|
|
|
|
void BlueStore::BufferSpace::read(
|
|
BufferCacheShard* cache,
|
|
uint32_t offset,
|
|
uint32_t length,
|
|
BlueStore::ready_regions_t& res,
|
|
interval_set<uint32_t>& res_intervals,
|
|
int flags)
|
|
{
|
|
res.clear();
|
|
res_intervals.clear();
|
|
uint32_t want_bytes = length;
|
|
uint32_t end = offset + length;
|
|
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
for (auto i = _data_lower_bound(offset);
|
|
i != buffer_map.end() && offset < end && i->offset < end; ++i) {
|
|
Buffer* b = &*i;
|
|
ceph_assert(b->end() > offset);
|
|
|
|
bool val = false;
|
|
if (flags & BYPASS_CLEAN_CACHE)
|
|
val = b->is_writing();
|
|
else
|
|
val = b->is_writing() || b->is_clean();
|
|
if (val) {
|
|
if (b->offset < offset) {
|
|
uint32_t skip = offset - b->offset;
|
|
uint32_t l = min(length, b->length - skip);
|
|
res[offset].substr_of(b->data, skip, l);
|
|
res_intervals.insert(offset, l);
|
|
offset += l;
|
|
length -= l;
|
|
if (!b->is_writing()) {
|
|
cache->_touch(b);
|
|
}
|
|
continue;
|
|
}
|
|
if (b->offset > offset) {
|
|
uint32_t gap = b->offset - offset;
|
|
if (length <= gap) {
|
|
break;
|
|
}
|
|
offset += gap;
|
|
length -= gap;
|
|
}
|
|
if (!b->is_writing()) {
|
|
cache->_touch(b);
|
|
}
|
|
if (b->length > length) {
|
|
res[offset].substr_of(b->data, 0, length);
|
|
res_intervals.insert(offset, length);
|
|
break;
|
|
} else {
|
|
res[offset].append(b->data);
|
|
res_intervals.insert(offset, b->length);
|
|
if (b->length == length)
|
|
break;
|
|
offset += b->length;
|
|
length -= b->length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t hit_bytes = res_intervals.size();
|
|
ceph_assert(hit_bytes <= want_bytes);
|
|
uint64_t miss_bytes = want_bytes - hit_bytes;
|
|
cache->logger->inc(l_bluestore_buffer_hit_bytes, hit_bytes);
|
|
cache->logger->inc(l_bluestore_buffer_miss_bytes, miss_bytes);
|
|
}
|
|
|
|
void BlueStore::BufferSpace::_finish_write(BufferCacheShard* cache,
|
|
TransContext* txc,
|
|
uint32_t offset, uint32_t len)
|
|
{
|
|
ldout(cache->cct, 10) << __func__ << " txc " << txc
|
|
<< std::hex << " 0x" << offset << "~" << len << std::dec
|
|
<< dendl;
|
|
|
|
uint32_t end = offset + len;
|
|
std::lock_guard l(cache->lock);
|
|
auto i = _data_lower_bound(offset);
|
|
while (i != buffer_map.end() && offset < end && i->offset < end) {
|
|
Buffer* b = &*i;
|
|
i++;
|
|
ceph_assert(b->end() > offset);
|
|
if (b->txc == txc && b->is_writing()) {
|
|
ldout(cache->cct, 20) << __func__ << " finish " << *b
|
|
<< dendl;
|
|
if (b->flags & Buffer::FLAG_NOCACHE) {
|
|
__erase_from_map(b);
|
|
} else {
|
|
b->state = Buffer::STATE_CLEAN;
|
|
b->txc = nullptr;
|
|
b->maybe_rebuild();
|
|
b->data.reassign_to_mempool(mempool::mempool_bluestore_cache_data);
|
|
cache->_add(b, 1, nullptr);
|
|
}
|
|
}
|
|
}
|
|
cache->_trim();
|
|
cache->_audit("finish_write end");
|
|
ldout(cache->cct, 20) << __func__ << " done." << dendl;
|
|
}
|
|
|
|
/*
|
|
copy Buffers that are in writing queue
|
|
*/
|
|
void BlueStore::BufferSpace::_dup_writing(TransContext* txc, Collection* collection, OnodeRef onode, uint32_t offset, uint32_t length)
|
|
{
|
|
uint64_t end = offset + length;
|
|
BufferSpace &to = onode->bc;
|
|
BufferCacheShard *cache = collection->cache;
|
|
ldout(cache->cct, 20) << __func__ << " offset=" << std::hex << offset << " length=" << std::hex << length << dendl;
|
|
for (auto i = _data_lower_bound(offset);
|
|
i != buffer_map.end() && offset < end && i->offset < end; ++i) {
|
|
Buffer *b = &*i;
|
|
if (!b->is_writing()) {
|
|
continue;
|
|
}
|
|
|
|
bufferlist buffer_to_copy;
|
|
uint32_t offset_to_copy = 0;
|
|
if (b->offset >= offset) {
|
|
if (b->end() > end) {
|
|
// take head
|
|
uint64_t tail = b->end() - end;
|
|
auto new_length = b->data.length() - tail;
|
|
buffer_to_copy.substr_of(b->data, 0, new_length);
|
|
offset_to_copy = b->offset;
|
|
} else {
|
|
// take whole buffer
|
|
buffer_to_copy = b->data;
|
|
offset_to_copy = b->offset;
|
|
}
|
|
} else {
|
|
if (b->end() > end) {
|
|
uint64_t front = offset - b->offset;
|
|
uint64_t tail = b->end() - end;
|
|
// take middle
|
|
uint64_t new_length = b->data.length() - front - tail;
|
|
buffer_to_copy.substr_of(b->data, front, new_length);
|
|
offset_to_copy = b->offset + front;
|
|
} else {
|
|
// take tail
|
|
uint64_t front = offset - b->offset;
|
|
uint64_t new_length = b->data.length() - front;
|
|
buffer_to_copy.substr_of(b->data, front, new_length);
|
|
offset_to_copy = b->offset + front;
|
|
}
|
|
}
|
|
Buffer* to_b = new Buffer(&onode->bc, b->state, b->txc, offset_to_copy,
|
|
std::move(buffer_to_copy), b->flags);
|
|
ldout(cache->cct, 20) << __func__ << " offset=" << std::hex << offset
|
|
<< " length=" << std::hex << length << " buffer=" << *to_b << dendl;
|
|
ceph_assert(to_b->is_writing());
|
|
to._discard(collection->cache, to_b->offset, to_b->length);
|
|
to._add_buffer(collection->cache, to_b, to_b->cache_private, 0, nullptr);
|
|
} // for
|
|
}
|
|
|
|
// lists content of BufferSpace
|
|
// BufferSpace must be under exclusive access
|
|
std::ostream& operator<<(std::ostream& out, const BlueStore::BufferSpace& bc)
|
|
{
|
|
for (auto& b : bc.buffer_map) {
|
|
out << " [0x" << std::hex << b.offset << "]=" << b << std::dec;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// TransContext
|
|
bool BlueStore::TransContext::add_writing(Onode* o, uint32_t off, uint32_t len)
|
|
{
|
|
std::lock_guard l(writings_lock);
|
|
|
|
// Need to indicate non-initial observers that we're done.
|
|
if (were_writings && writings.empty()) {
|
|
return false;
|
|
}
|
|
writings.emplace_back(o, off, len);
|
|
were_writings = true;
|
|
return true;
|
|
}
|
|
|
|
void BlueStore::TransContext::finish_writing()
|
|
{
|
|
write_list_t finished;
|
|
{
|
|
std::lock_guard l(writings_lock);
|
|
finished.swap(writings);
|
|
}
|
|
for (auto& e : finished) {
|
|
e.onode->finish_write(this, e.offset, e.length);
|
|
}
|
|
}
|
|
|
|
// OnodeSpace
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.OnodeSpace(" << this << " in " << cache << ") "
|
|
|
|
BlueStore::OnodeRef BlueStore::OnodeSpace::add_onode(const ghobject_t& oid,
|
|
OnodeRef& o)
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
// add entry or return existing one
|
|
auto p = onode_map.emplace(oid, o);
|
|
if (!p.second) {
|
|
ldout(cache->cct, 30) << __func__ << " " << oid << " " << o
|
|
<< " raced, returning existing " << p.first->second
|
|
<< dendl;
|
|
return p.first->second;
|
|
}
|
|
ldout(cache->cct, 20) << __func__ << " " << oid << " " << o << dendl;
|
|
cache->_add(o.get(), 1);
|
|
cache->_trim();
|
|
return o;
|
|
}
|
|
|
|
void BlueStore::OnodeSpace::_remove(const ghobject_t& oid)
|
|
{
|
|
ldout(cache->cct, 20) << __func__ << " " << oid << " " << dendl;
|
|
onode_map.erase(oid);
|
|
}
|
|
|
|
BlueStore::OnodeRef BlueStore::OnodeSpace::lookup(const ghobject_t& oid)
|
|
{
|
|
ldout(cache->cct, 30) << __func__ << dendl;
|
|
OnodeRef o;
|
|
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
auto p = onode_map.find(oid);
|
|
if (p == onode_map.end()) {
|
|
ldout(cache->cct, 30) << __func__ << " " << oid << " miss" << dendl;
|
|
cache->logger->inc(l_bluestore_onode_misses);
|
|
} else {
|
|
ldout(cache->cct, 30) << __func__ << " " << oid << " hit " << p->second
|
|
<< " " << p->second->nref
|
|
<< " " << p->second->cached
|
|
<< dendl;
|
|
// This will pin onode and implicitly touch the cache when Onode
|
|
// eventually will become unpinned
|
|
o = p->second;
|
|
|
|
cache->logger->inc(l_bluestore_onode_hits);
|
|
}
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
void BlueStore::OnodeSpace::clear()
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
ldout(cache->cct, 10) << __func__ << " " << onode_map.size()<< dendl;
|
|
for (auto &p : onode_map) {
|
|
cache->_rm(p.second.get());
|
|
}
|
|
onode_map.clear();
|
|
}
|
|
|
|
bool BlueStore::OnodeSpace::empty()
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
return onode_map.empty();
|
|
}
|
|
|
|
void BlueStore::OnodeSpace::rename(
|
|
OnodeRef& oldo,
|
|
const ghobject_t& old_oid,
|
|
const ghobject_t& new_oid,
|
|
const mempool::bluestore_cache_meta::string& new_okey)
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
ldout(cache->cct, 30) << __func__ << " " << old_oid << " -> " << new_oid
|
|
<< dendl;
|
|
auto po = onode_map.find(old_oid);
|
|
auto pn = onode_map.find(new_oid);
|
|
ceph_assert(po != pn);
|
|
|
|
ceph_assert(po != onode_map.end());
|
|
if (pn != onode_map.end()) {
|
|
ldout(cache->cct, 30) << __func__ << " removing target " << pn->second
|
|
<< dendl;
|
|
cache->_rm(pn->second.get());
|
|
onode_map.erase(pn);
|
|
}
|
|
OnodeRef o = po->second;
|
|
|
|
// install a non-existent onode at old location
|
|
oldo.reset(new Onode(o->c, old_oid, o->key));
|
|
po->second = oldo;
|
|
cache->_add(oldo.get(), 1);
|
|
// add at new position and fix oid, key.
|
|
// This will pin 'o' and implicitly touch cache
|
|
// when it will eventually become unpinned
|
|
onode_map.insert(make_pair(new_oid, o));
|
|
|
|
o->oid = new_oid;
|
|
o->key = new_okey;
|
|
cache->_trim();
|
|
}
|
|
|
|
bool BlueStore::OnodeSpace::map_any(std::function<bool(Onode*)> f)
|
|
{
|
|
std::lock_guard l(cache->lock);
|
|
ldout(cache->cct, 20) << __func__ << dendl;
|
|
for (auto& i : onode_map) {
|
|
if (f(i.second.get())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <int LogLevelV = 30>
|
|
void BlueStore::OnodeSpace::dump(CephContext *cct)
|
|
{
|
|
for (auto& i : onode_map) {
|
|
ldout(cct, LogLevelV) << i.first << " : " << i.second
|
|
<< " " << i.second->nref
|
|
<< " " << i.second->cached
|
|
<< dendl;
|
|
}
|
|
}
|
|
|
|
// SharedBlob
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.sharedblob(" << this << ") "
|
|
#undef dout_context
|
|
#define dout_context collection->store->cct
|
|
|
|
void BlueStore::SharedBlob::dump(Formatter* f) const
|
|
{
|
|
f->dump_bool("loaded", loaded);
|
|
if (loaded) {
|
|
persistent->dump(f);
|
|
} else {
|
|
f->dump_unsigned("sbid_unloaded", sbid_unloaded);
|
|
}
|
|
}
|
|
|
|
ostream& operator<<(ostream& out, const BlueStore::SharedBlob& sb)
|
|
{
|
|
out << "SharedBlob(" << &sb;
|
|
|
|
if (sb.loaded) {
|
|
out << " loaded " << *sb.persistent;
|
|
} else {
|
|
out << " sbid 0x" << std::hex << sb.sbid_unloaded << std::dec;
|
|
}
|
|
return out << ")";
|
|
}
|
|
|
|
BlueStore::SharedBlob::SharedBlob(uint64_t i, Collection *_coll)
|
|
: collection(_coll), sbid_unloaded(i)
|
|
{
|
|
ceph_assert(sbid_unloaded > 0);
|
|
}
|
|
|
|
BlueStore::SharedBlob::~SharedBlob()
|
|
{
|
|
if (loaded && persistent) {
|
|
delete persistent;
|
|
}
|
|
}
|
|
|
|
void BlueStore::SharedBlob::put()
|
|
{
|
|
if (--nref == 0) {
|
|
dout(20) << __func__ << " " << this
|
|
<< " removing self from set " << get_parent()
|
|
<< dendl;
|
|
again:
|
|
auto coll_snap = collection;
|
|
if (coll_snap) {
|
|
std::lock_guard l(coll_snap->cache->lock);
|
|
if (coll_snap != collection) {
|
|
goto again;
|
|
}
|
|
if (!coll_snap->shared_blob_set.remove(this, true)) {
|
|
// race with lookup
|
|
return;
|
|
}
|
|
}
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void BlueStore::SharedBlob::get_ref(uint64_t offset, uint32_t length)
|
|
{
|
|
ceph_assert(persistent);
|
|
persistent->ref_map.get(offset, length);
|
|
}
|
|
|
|
void BlueStore::SharedBlob::put_ref(uint64_t offset, uint32_t length,
|
|
PExtentVector *r,
|
|
bool *unshare)
|
|
{
|
|
ceph_assert(persistent);
|
|
persistent->ref_map.put(offset, length, r,
|
|
unshare && !*unshare ? unshare : nullptr);
|
|
}
|
|
|
|
// SharedBlobSet
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.sharedblobset(" << this << ") "
|
|
|
|
template <int LogLevelV = 30>
|
|
void BlueStore::SharedBlobSet::dump(CephContext *cct)
|
|
{
|
|
std::lock_guard l(lock);
|
|
for (auto& i : sb_map) {
|
|
ldout(cct, LogLevelV) << i.first << " : " << *i.second << dendl;
|
|
}
|
|
}
|
|
|
|
// Blob
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.blob(" << this << ") "
|
|
|
|
BlueStore::Blob::~Blob()
|
|
{
|
|
again:
|
|
auto coll_cache = get_cache();
|
|
if (coll_cache) {
|
|
std::lock_guard l(coll_cache->lock);
|
|
if (coll_cache != get_cache()) {
|
|
goto again;
|
|
}
|
|
coll_cache->rm_blob();
|
|
}
|
|
}
|
|
|
|
void BlueStore::Blob::dump(Formatter* f) const
|
|
{
|
|
if (is_spanning()) {
|
|
f->dump_unsigned("spanning_id ", id);
|
|
}
|
|
blob.dump(f);
|
|
if (shared_blob) {
|
|
f->dump_object("shared", *shared_blob);
|
|
}
|
|
}
|
|
|
|
ostream& operator<<(ostream& out, const BlueStore::Blob& b)
|
|
{
|
|
out << "Blob(" << &b;
|
|
if (b.is_spanning()) {
|
|
out << " spanning " << b.id;
|
|
}
|
|
out << " " << b.get_blob() << " " << b.get_blob_use_tracker();
|
|
if (b.shared_blob) {
|
|
out << " " << *b.shared_blob;
|
|
} else {
|
|
out << " (shared_blob=NULL)";
|
|
}
|
|
out << ")";
|
|
return out;
|
|
}
|
|
|
|
void BlueStore::Blob::get_ref(
|
|
Collection *coll,
|
|
uint32_t offset,
|
|
uint32_t length)
|
|
{
|
|
// Caller has to initialize Blob's logical length prior to increment
|
|
// references. Otherwise one is neither unable to determine required
|
|
// amount of counters in case of per-au tracking nor obtain min_release_size
|
|
// for single counter mode.
|
|
ceph_assert(get_blob().get_logical_length() != 0);
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << " " << *this << dendl;
|
|
|
|
if (used_in_blob.is_empty()) {
|
|
uint32_t min_release_size =
|
|
get_blob().get_release_size(coll->store->min_alloc_size);
|
|
uint64_t l = get_blob().get_logical_length();
|
|
dout(20) << __func__ << " init 0x" << std::hex << l << ", "
|
|
<< min_release_size << std::dec << dendl;
|
|
used_in_blob.init(l, min_release_size);
|
|
}
|
|
used_in_blob.get(
|
|
offset,
|
|
length);
|
|
}
|
|
|
|
bool BlueStore::Blob::put_ref(
|
|
Collection *coll,
|
|
uint32_t offset,
|
|
uint32_t length,
|
|
PExtentVector *r)
|
|
{
|
|
PExtentVector logical;
|
|
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << " " << *this << dendl;
|
|
|
|
bool empty = used_in_blob.put(
|
|
offset,
|
|
length,
|
|
&logical);
|
|
r->clear();
|
|
// nothing to release
|
|
if (!empty && logical.empty()) {
|
|
return false;
|
|
}
|
|
|
|
bluestore_blob_t& b = dirty_blob();
|
|
return b.release_extents(empty, logical, r);
|
|
}
|
|
|
|
bool BlueStore::Blob::can_reuse_blob(uint32_t min_alloc_size,
|
|
uint32_t target_blob_size,
|
|
uint32_t b_offset,
|
|
uint32_t *length0) {
|
|
ceph_assert(min_alloc_size);
|
|
ceph_assert(target_blob_size);
|
|
if (!get_blob().is_mutable()) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t length = *length0;
|
|
uint32_t end = b_offset + length;
|
|
|
|
// Currently for the sake of simplicity we omit blob reuse if data is
|
|
// unaligned with csum chunk. Later we can perform padding if needed.
|
|
if (get_blob().has_csum() &&
|
|
((b_offset % get_blob().get_csum_chunk_size()) != 0 ||
|
|
(end % get_blob().get_csum_chunk_size()) != 0)) {
|
|
return false;
|
|
}
|
|
|
|
auto blen = get_blob().get_logical_length();
|
|
uint32_t new_blen = blen;
|
|
|
|
// make sure target_blob_size isn't less than current blob len
|
|
target_blob_size = std::max(blen, target_blob_size);
|
|
|
|
if (b_offset >= blen) {
|
|
// new data totally stands out of the existing blob
|
|
new_blen = end;
|
|
} else {
|
|
// new data overlaps with the existing blob
|
|
new_blen = std::max(blen, end);
|
|
|
|
uint32_t overlap = 0;
|
|
if (new_blen > blen) {
|
|
overlap = blen - b_offset;
|
|
} else {
|
|
overlap = length;
|
|
}
|
|
|
|
if (!get_blob().is_unallocated(b_offset, overlap)) {
|
|
// abort if any piece of the overlap has already been allocated
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (new_blen > blen) {
|
|
int64_t overflow = int64_t(new_blen) - target_blob_size;
|
|
// Unable to decrease the provided length to fit into max_blob_size
|
|
if (overflow >= length) {
|
|
return false;
|
|
}
|
|
|
|
// FIXME: in some cases we could reduce unused resolution
|
|
if (get_blob().has_unused()) {
|
|
return false;
|
|
}
|
|
|
|
if (overflow > 0) {
|
|
new_blen -= overflow;
|
|
length -= overflow;
|
|
*length0 = length;
|
|
}
|
|
|
|
if (new_blen > blen) {
|
|
ceph_assert(dirty_blob().is_mutable());
|
|
dirty_blob().add_tail(new_blen);
|
|
used_in_blob.add_tail(new_blen,
|
|
get_blob().get_release_size(min_alloc_size));
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.blob(" << this << ") "
|
|
#undef dout_context
|
|
#define dout_context cct
|
|
|
|
void BlueStore::Blob::dup(const Blob& from, bool copy_used_in_blob)
|
|
{
|
|
set_shared_blob(from.shared_blob);
|
|
blob.dup(from.blob);
|
|
if (copy_used_in_blob) {
|
|
used_in_blob = from.used_in_blob;
|
|
} else {
|
|
ceph_assert(from.blob.is_compressed());
|
|
ceph_assert(from.used_in_blob.num_au <= 1);
|
|
used_in_blob.init(from.used_in_blob.au_size, from.used_in_blob.au_size);
|
|
}
|
|
for (auto p : blob.get_extents()) {
|
|
if (p.is_valid()) {
|
|
get_dirty_shared_blob()->get_ref(p.offset, p.length);
|
|
}
|
|
}
|
|
}
|
|
|
|
// copies part of a Blob
|
|
// it is used to create a consistent blob out of parts of other blobs
|
|
void BlueStore::Blob::copy_from(
|
|
CephContext* cct, const Blob& from, uint32_t min_release_size, uint32_t start, uint32_t len)
|
|
{
|
|
dout(20) << __func__ << " to=" << *this << " from=" << from
|
|
<< " [" << std::hex << start << "~" << len
|
|
<< "] min_release=" << min_release_size << std::dec << dendl;
|
|
|
|
auto& bto = blob;
|
|
auto& bfrom = from.blob;
|
|
ceph_assert(!bfrom.is_compressed()); // not suitable for compressed (immutable) blobs
|
|
ceph_assert(!bfrom.has_unused());
|
|
// below to asserts are not required to make function work
|
|
// they check if it is run in desired context
|
|
ceph_assert(bfrom.is_shared());
|
|
ceph_assert(shared_blob);
|
|
ceph_assert(shared_blob == from.shared_blob);
|
|
|
|
// split len to pre_len, main_len, post_len
|
|
uint32_t start_aligned = p2align(start, min_release_size);
|
|
uint32_t start_roundup = p2roundup(start, min_release_size);
|
|
uint32_t end_aligned = p2align(start + len, min_release_size);
|
|
uint32_t end_roundup = p2roundup(start + len, min_release_size);
|
|
dout(25) << __func__ << " extent split:"
|
|
<< std::hex << start_aligned << "~" << start_roundup << "~"
|
|
<< end_aligned << "~" << end_roundup << std::dec << dendl;
|
|
|
|
if (bto.get_logical_length() == 0) {
|
|
// this is initialization
|
|
bto.adjust_to(from.blob, end_roundup);
|
|
ceph_assert(min_release_size == from.used_in_blob.au_size);
|
|
used_in_blob.init(end_roundup, min_release_size);
|
|
} else if (bto.get_logical_length() < end_roundup) {
|
|
ceph_assert(!bto.is_compressed());
|
|
bto.add_tail(end_roundup);
|
|
used_in_blob.add_tail(end_roundup, used_in_blob.au_size);
|
|
}
|
|
|
|
if (end_aligned >= start_roundup) {
|
|
copy_extents(cct, from, start_aligned,
|
|
start_roundup - start_aligned,/*pre_len*/
|
|
end_aligned - start_roundup,/*main_len*/
|
|
end_roundup - end_aligned/*post_len*/);
|
|
} else {
|
|
// it is uncommon case that <start, start + len) in single allocation unit
|
|
copy_extents(cct, from, start_aligned,
|
|
start_roundup - start_aligned,/*pre_len*/
|
|
0 /*main_len*/, 0/*post_len*/);
|
|
}
|
|
// copy relevant csum items
|
|
if (bto.has_csum()) {
|
|
size_t csd_value_size = bto.get_csum_value_size();
|
|
size_t csd_item_start = p2align(start, uint32_t(1 << bto.csum_chunk_order)) >> bto.csum_chunk_order;
|
|
size_t csd_item_end = p2roundup(start + len, uint32_t(1 << bto.csum_chunk_order)) >> bto.csum_chunk_order;
|
|
ceph_assert(bto. csum_data.length() >= csd_item_end * csd_value_size);
|
|
ceph_assert(bfrom.csum_data.length() >= csd_item_end * csd_value_size);
|
|
memcpy(bto. csum_data.c_str() + csd_item_start * csd_value_size,
|
|
bfrom.csum_data.c_str() + csd_item_start * csd_value_size,
|
|
(csd_item_end - csd_item_start) * csd_value_size);
|
|
}
|
|
used_in_blob.get(start, len);
|
|
dout(20) << __func__ << " result=" << *this << dendl;
|
|
}
|
|
|
|
void BlueStore::Blob::copy_extents(
|
|
CephContext* cct, const Blob& from, uint32_t start,
|
|
uint32_t pre_len, uint32_t main_len, uint32_t post_len)
|
|
{
|
|
// There are 2 valid states:
|
|
// 1) `to` is not defined on [pos~len] range
|
|
// (need to copy this region - return true)
|
|
// 2) `from` and `to` are exact on [pos~len] range
|
|
// (no need to copy region - return false)
|
|
// Otherwise just assert.
|
|
auto check_sane_need_copy = [&](
|
|
const PExtentVector& from,
|
|
const PExtentVector& to,
|
|
uint32_t pos, uint32_t len) -> bool
|
|
{
|
|
uint32_t pto = pos;
|
|
auto ito = to.begin();
|
|
while (ito != to.end() && pto >= ito->length) {
|
|
pto -= ito->length;
|
|
++ito;
|
|
}
|
|
if (ito == to.end()) return true; // case 1 - obviously empty
|
|
if (!ito->is_valid()) {
|
|
// now sanity check that all the rest is invalid too
|
|
pto += len;
|
|
while (ito != to.end() && pto >= ito->length) {
|
|
ceph_assert(!ito->is_valid());
|
|
pto -= ito->length;
|
|
++ito;
|
|
}
|
|
return true;
|
|
}
|
|
uint32_t pfrom = pos;
|
|
auto ifrom = from.begin();
|
|
while (ifrom != from.end() && pfrom >= ifrom->length) {
|
|
pfrom -= ifrom->length;
|
|
++ifrom;
|
|
}
|
|
ceph_assert(ifrom != from.end());
|
|
ceph_assert(ifrom->is_valid());
|
|
// here we require from and to be the same
|
|
while (len > 0) {
|
|
ceph_assert(ifrom->offset + pfrom == ito->offset + pto);
|
|
uint32_t jump = std::min(len, ifrom->length - pfrom);
|
|
jump = std::min(jump, ito->length - pto);
|
|
pfrom += jump;
|
|
if (pfrom == ifrom->length) {
|
|
pfrom = 0;
|
|
++ifrom;
|
|
}
|
|
pto += jump;
|
|
if (pto == ito->length) {
|
|
pto = 0;
|
|
++ito;
|
|
}
|
|
len -= jump;
|
|
}
|
|
return false;
|
|
};
|
|
const PExtentVector& exfrom = from.blob.get_extents();
|
|
PExtentVector& exto = blob.dirty_extents();
|
|
dout(20) << __func__ << " 0x" << std::hex << start << " "
|
|
<< pre_len << "/" << main_len << "/" << post_len << std::dec << dendl;
|
|
|
|
// the extents that cover same area must be the same
|
|
if (pre_len > 0) {
|
|
if (check_sane_need_copy(exfrom, exto, start, pre_len)) {
|
|
main_len += pre_len; // also copy pre_len
|
|
} else {
|
|
start += pre_len; // skip, already there
|
|
}
|
|
}
|
|
if (post_len > 0) {
|
|
if (check_sane_need_copy(exfrom, exto, start + main_len, post_len)) {
|
|
main_len += post_len; // also copy post_len
|
|
} else {
|
|
// skip, already there
|
|
}
|
|
}
|
|
// it is possible that here is nothing to copy
|
|
if (main_len > 0) {
|
|
copy_extents_over_empty(cct, from, start, main_len);
|
|
}
|
|
}
|
|
|
|
// assumes that target (this->extents) has hole in relevant location
|
|
void BlueStore::Blob::copy_extents_over_empty(
|
|
CephContext* cct, const Blob& from, uint32_t start, uint32_t len)
|
|
{
|
|
dout(20) << __func__ << " to=" << *this << " from=" << from
|
|
<< "[0x" << std::hex << start << "~" << len << std::dec << "]" << dendl;
|
|
uint32_t padding;
|
|
auto& exto = blob.dirty_extents();
|
|
auto ito = exto.begin();
|
|
PExtentVector::iterator prev = exto.end();
|
|
uint32_t sto = start;
|
|
|
|
auto try_append = [&](PExtentVector::iterator& it, uint64_t disk_offset, uint32_t disk_len) {
|
|
if (prev != exto.end()) {
|
|
if (prev->is_valid()) {
|
|
if (prev->offset + prev->length == disk_offset) {
|
|
get_dirty_shared_blob()->get_ref(disk_offset, disk_len);
|
|
prev->length += disk_len;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
it = exto.insert(it, bluestore_pextent_t(disk_offset, disk_len));
|
|
prev = it;
|
|
++it;
|
|
get_dirty_shared_blob()->get_ref(disk_offset, disk_len);
|
|
};
|
|
|
|
while (ito != exto.end() && sto >= ito->length) {
|
|
sto -= ito->length;
|
|
prev = ito;
|
|
++ito;
|
|
}
|
|
if (ito == exto.end()) {
|
|
// putting data after end, just expand / push back
|
|
if (sto > 0) {
|
|
exto.emplace_back(bluestore_pextent_t::INVALID_OFFSET, sto);
|
|
ito = exto.end();
|
|
prev = ito;
|
|
}
|
|
padding = 0;
|
|
} else {
|
|
ceph_assert(!ito->is_valid()); // there can be no collision
|
|
ceph_assert(ito->length >= sto + len); // for at least len, starting with remainder sto
|
|
padding = ito->length - (sto + len); // add this much after copying
|
|
ito = exto.erase(ito); // cut a hole
|
|
if (sto > 0) {
|
|
ito = exto.insert(ito, bluestore_pextent_t(bluestore_pextent_t::INVALID_OFFSET, sto));
|
|
prev = ito;
|
|
++ito;
|
|
}
|
|
}
|
|
|
|
const auto& exfrom = from.blob.get_extents();
|
|
auto itf = exfrom.begin();
|
|
uint32_t sf = start;
|
|
while (itf != exfrom.end() && sf >= itf->length) {
|
|
sf -= itf->length;
|
|
++itf;
|
|
}
|
|
|
|
uint32_t skip_on_first = sf;
|
|
while (itf != exfrom.end() && len > 0) {
|
|
ceph_assert(itf->is_valid());
|
|
uint32_t to_copy = std::min<uint32_t>(itf->length - skip_on_first, len);
|
|
try_append(ito, itf->offset + skip_on_first, to_copy);
|
|
len -= to_copy;
|
|
skip_on_first = 0;
|
|
++itf;
|
|
}
|
|
ceph_assert(len == 0);
|
|
|
|
if (padding > 0) {
|
|
exto.insert(ito, bluestore_pextent_t(bluestore_pextent_t::INVALID_OFFSET, padding));
|
|
}
|
|
dout(20) << __func__ << " result=" << *this << dendl;
|
|
}
|
|
|
|
// Checks if two Blobs can be joined together.
|
|
// The important (unchecked) condition is that both Blobs belong to the same object.
|
|
// Verifies if 'other' Blob can be deleted but its content moved to 'this' Blob.
|
|
// Requirements:
|
|
// 1) checksums: same type and size
|
|
// 2) tracker: same au size
|
|
// 3) extents: must be disjointed
|
|
// 4) unused: ignored, will be cleared
|
|
//
|
|
// Returns:
|
|
// false - Blobs are incompatible
|
|
// true - Blobs can be merged
|
|
//
|
|
// Returned blob_width is a distance between 'other' Blob's blob_start() and last logical_offset
|
|
// that can refer to 'other' Blob extents. It is used to limit iteration on ExtentMap.
|
|
bool BlueStore::Blob::can_merge_blob(const Blob* other, uint32_t& blob_width) const
|
|
{
|
|
const Blob* x = other;
|
|
const Blob* y = this;
|
|
// checksums
|
|
const bluestore_blob_t& xb = x->get_blob();
|
|
const bluestore_blob_t& yb = y->get_blob();
|
|
if (xb.has_csum() != yb.has_csum()) return false;
|
|
if (xb.has_csum()) {
|
|
if (xb.csum_type != yb.csum_type) return false;
|
|
if (xb.csum_chunk_order != yb.csum_chunk_order) return false;
|
|
}
|
|
// trackers
|
|
const bluestore_blob_use_tracker_t& xtr = x->get_blob_use_tracker();
|
|
const bluestore_blob_use_tracker_t& ytr = y->get_blob_use_tracker();
|
|
if (xtr.au_size != ytr.au_size) return false;
|
|
// unused
|
|
// ignore unused, we will clear it up anyway
|
|
// extents
|
|
// the success is when there is no offset that is used by both blobs
|
|
auto skip_empty = [&](const PExtentVector& list, PExtentVector::const_iterator& it, uint32_t& pos) {
|
|
while (it != list.end() && !it->is_valid()) {
|
|
pos += it->length;
|
|
++it;
|
|
}
|
|
};
|
|
bool can_merge = true;
|
|
const PExtentVector& xe = x->get_blob().get_extents();
|
|
const PExtentVector& ye = y->get_blob().get_extents();
|
|
PExtentVector::const_iterator xi = xe.begin();
|
|
PExtentVector::const_iterator yi = ye.begin();
|
|
uint32_t xp = 0;
|
|
uint32_t yp = 0;
|
|
|
|
skip_empty(xe, xi, xp);
|
|
skip_empty(ye, yi, yp);
|
|
|
|
while (xi != xe.end() && yi != ye.end()) {
|
|
if (xp <= yp) {
|
|
if (yp < xp + xi->length) {
|
|
// collision
|
|
can_merge = false;
|
|
break;
|
|
}
|
|
xp += xi->length;
|
|
++xi;
|
|
skip_empty(xe, xi, xp);
|
|
} else {
|
|
if (xp < yp + yi->length) {
|
|
// collision
|
|
can_merge = false;
|
|
break;
|
|
}
|
|
yp += yi->length;
|
|
++yi;
|
|
skip_empty(ye, yi, yp);
|
|
}
|
|
}
|
|
if (can_merge) {
|
|
// scan remaining extents in x
|
|
while (xi != xe.end()) {
|
|
xp += xi->length;
|
|
++xi;
|
|
}
|
|
blob_width = xp;
|
|
}
|
|
return can_merge;
|
|
}
|
|
|
|
// Merges 2 blobs together. Move extents, csum, tracker from src to dst.
|
|
uint32_t BlueStore::Blob::merge_blob(CephContext* cct, Blob* blob_to_dissolve)
|
|
{
|
|
Blob* dst = this;
|
|
Blob* src = blob_to_dissolve;
|
|
const bluestore_blob_t& src_blob = src->get_blob();
|
|
bluestore_blob_t& dst_blob = dst->dirty_blob();
|
|
dout(20) << __func__ << " to=" << *dst << " from" << *src << dendl;
|
|
|
|
// drop unused, do not recalc it, unlikely those chunks could be used in future
|
|
dst_blob.clear_flag(bluestore_blob_t::FLAG_HAS_UNUSED);
|
|
if (dst_blob.get_logical_length() < src_blob.get_logical_length()) {
|
|
// expand to accomodate
|
|
ceph_assert(!dst_blob.is_compressed());
|
|
dst_blob.add_tail(src_blob.get_logical_length());
|
|
used_in_blob.add_tail(src_blob.get_logical_length(), used_in_blob.au_size);
|
|
}
|
|
const PExtentVector& src_extents = src_blob.get_extents();
|
|
const PExtentVector& dst_extents = dst_blob.get_extents();
|
|
PExtentVector tmp_extents;
|
|
tmp_extents.reserve(src_extents.size() + dst_extents.size());
|
|
|
|
uint32_t csum_chunk_order = src_blob.csum_chunk_order;
|
|
uint32_t csum_value_size = 0;
|
|
const char* src_csum_ptr = nullptr;
|
|
char* dst_csum_ptr = nullptr;
|
|
if (src_blob.has_csum()) {
|
|
ceph_assert(src_blob.csum_type == dst_blob.csum_type);
|
|
ceph_assert(src_blob.csum_chunk_order == dst_blob.csum_chunk_order);
|
|
csum_value_size = src_blob.get_csum_value_size();
|
|
src_csum_ptr = src_blob.csum_data.c_str();
|
|
dst_csum_ptr = dst_blob.csum_data.c_str();
|
|
}
|
|
const bluestore_blob_use_tracker_t& src_tracker = src->get_blob_use_tracker();
|
|
bluestore_blob_use_tracker_t& dst_tracker = dst->dirty_blob_use_tracker();
|
|
ceph_assert(src_tracker.au_size == dst_tracker.au_size);
|
|
uint32_t tracker_au_size = src_tracker.au_size;
|
|
const uint32_t* src_tracker_aus = src_tracker.get_au_array();
|
|
uint32_t* dst_tracker_aus = dst_tracker.dirty_au_array();
|
|
|
|
auto skip_empty = [&](const PExtentVector& list, PExtentVector::const_iterator& it, uint32_t& pos) {
|
|
while (it != list.end()) {
|
|
if (it->is_valid()) {
|
|
return;
|
|
}
|
|
pos += it->length;
|
|
++it;
|
|
}
|
|
pos = std::numeric_limits<uint32_t>::max();
|
|
return;
|
|
};
|
|
|
|
auto move_data = [&](uint32_t pos, uint32_t len) {
|
|
if (src_blob.has_csum()) {
|
|
// copy csum
|
|
ceph_assert((pos % (1 << csum_chunk_order)) == 0);
|
|
ceph_assert((len % (1 << csum_chunk_order)) == 0);
|
|
uint32_t start = p2align(pos, uint32_t(1 << csum_chunk_order));
|
|
uint32_t end = p2roundup(pos + len, uint32_t(1 << csum_chunk_order));
|
|
uint32_t item_no = start >> csum_chunk_order;
|
|
uint32_t item_cnt = (end - start) >> csum_chunk_order;
|
|
ceph_assert(dst_blob.csum_data.length() >= (item_no + item_cnt) * csum_value_size);
|
|
memcpy(dst_csum_ptr + item_no * csum_value_size,
|
|
src_csum_ptr + item_no * csum_value_size,
|
|
item_cnt * csum_value_size);
|
|
}
|
|
uint32_t start = p2align(pos, tracker_au_size) / tracker_au_size;
|
|
uint32_t end = p2roundup(pos + len, tracker_au_size) / tracker_au_size;
|
|
for (uint32_t i = start; i < end; i++) {
|
|
ceph_assert(i < dst_tracker.get_num_au());
|
|
dst_tracker_aus[i] += src_tracker_aus[i];
|
|
}
|
|
};
|
|
|
|
// Main loop creates new PExtentVector by merging src and dst PExtentVectors.
|
|
// It will replace dst's PExtentVector.
|
|
// When we process extent from dst, csum and tracer data is already in place.
|
|
// When we process extent from src, we need to copy csum and tracer to dst.
|
|
|
|
uint32_t src_pos = 0; //offset of next non-empty extent
|
|
uint32_t dst_pos = 0;
|
|
uint32_t pos = 0; //already processed amount
|
|
auto src_it = src_extents.begin(); // iterator to next non-empty extent
|
|
auto dst_it = dst_extents.begin();
|
|
|
|
skip_empty(src_extents, src_it, src_pos);
|
|
skip_empty(dst_extents, dst_it, dst_pos);
|
|
while (src_it != src_extents.end() || dst_it != dst_extents.end()) {
|
|
if (src_pos > pos) {
|
|
if (dst_pos > pos) {
|
|
// empty space
|
|
uint32_t m = std::min(src_pos - pos, dst_pos - pos);
|
|
// emit empty
|
|
tmp_extents.emplace_back(bluestore_pextent_t::INVALID_OFFSET, m);
|
|
pos += m;
|
|
} else {
|
|
// copy from dst, src must not have conflicting extent
|
|
ceph_assert(src_pos >= dst_pos + dst_it->length);
|
|
// use extent from destination
|
|
tmp_extents.push_back(*dst_it);
|
|
dst_pos += dst_it->length;
|
|
pos = dst_pos;
|
|
++dst_it;
|
|
skip_empty(dst_extents, dst_it, dst_pos);
|
|
}
|
|
} else {
|
|
// copy from src, dst must not have conflicting extent
|
|
ceph_assert(dst_pos >= src_pos + src_it->length);
|
|
// use extent from source
|
|
tmp_extents.push_back(*src_it);
|
|
// copy blob data
|
|
move_data(src_pos, src_it->length);
|
|
src_pos += src_it->length;
|
|
pos = src_pos;
|
|
++src_it;
|
|
skip_empty(src_extents, src_it, src_pos);
|
|
}
|
|
}
|
|
if (pos < dst_blob.get_logical_length()) {
|
|
// this is a candidate for improvement;
|
|
// instead of artifically add extents, trim blob
|
|
tmp_extents.emplace_back(bluestore_pextent_t::INVALID_OFFSET, dst_blob.get_logical_length() - pos);
|
|
}
|
|
// now apply freshly merged tmp_extents into dst blob
|
|
dst_blob.dirty_extents().swap(tmp_extents);
|
|
|
|
dout(20) << __func__ << " result=" << *dst << dendl;
|
|
return dst_blob.get_logical_length();
|
|
}
|
|
|
|
#undef dout_context
|
|
#define dout_context collection->store->cct
|
|
|
|
void BlueStore::Blob::split(Collection *coll, uint32_t blob_offset, Blob *r)
|
|
{
|
|
dout(10) << __func__ << " 0x" << std::hex << blob_offset << std::dec
|
|
<< " start " << *this << dendl;
|
|
ceph_assert(blob.can_split());
|
|
ceph_assert(used_in_blob.can_split());
|
|
bluestore_blob_t &lb = dirty_blob();
|
|
bluestore_blob_t &rb = r->dirty_blob();
|
|
|
|
used_in_blob.split(
|
|
blob_offset,
|
|
&(r->used_in_blob));
|
|
|
|
lb.split(blob_offset, rb);
|
|
|
|
dout(10) << __func__ << " 0x" << std::hex << blob_offset << std::dec
|
|
<< " finish " << *this << dendl;
|
|
dout(10) << __func__ << " 0x" << std::hex << blob_offset << std::dec
|
|
<< " and " << *r << dendl;
|
|
}
|
|
|
|
|
|
void BlueStore::Blob::maybe_prune_tail() {
|
|
if (get_blob().can_prune_tail()) {
|
|
dirty_blob().prune_tail();
|
|
used_in_blob.prune_tail(get_blob().get_ondisk_length());
|
|
dout(20) << __func__ << " pruned tail, now " << get_blob() << dendl;
|
|
}
|
|
}
|
|
|
|
void BlueStore::Blob::decode(
|
|
bufferptr::const_iterator& p,
|
|
uint64_t struct_v,
|
|
uint64_t* sbid,
|
|
bool include_ref_map,
|
|
Collection *coll)
|
|
{
|
|
denc(blob, p, struct_v);
|
|
if (blob.is_shared()) {
|
|
denc(*sbid, p);
|
|
}
|
|
if (include_ref_map) {
|
|
if (struct_v > 1) {
|
|
used_in_blob.decode(p);
|
|
} else {
|
|
used_in_blob.clear();
|
|
bluestore_extent_ref_map_t legacy_ref_map;
|
|
legacy_ref_map.decode(p);
|
|
if (coll) {
|
|
for (auto r : legacy_ref_map.ref_map) {
|
|
get_ref(
|
|
coll,
|
|
r.first,
|
|
r.second.refs * r.second.length);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extent
|
|
|
|
void BlueStore::Extent::dump(Formatter* f) const
|
|
{
|
|
f->dump_unsigned("logical_offset", logical_offset);
|
|
f->dump_unsigned("length", length);
|
|
f->dump_unsigned("blob_offset", blob_offset);
|
|
f->dump_object("blob", *blob);
|
|
}
|
|
|
|
ostream& operator<<(ostream& out, const BlueStore::Extent& e)
|
|
{
|
|
return out << std::hex << "0x" << e.logical_offset << "~" << e.length
|
|
<< ": 0x" << e.blob_offset << "~" << e.length << std::dec
|
|
<< " " << *e.blob;
|
|
}
|
|
|
|
// OldExtent
|
|
BlueStore::OldExtent* BlueStore::OldExtent::create(CollectionRef c,
|
|
uint32_t lo,
|
|
uint32_t o,
|
|
uint32_t l,
|
|
BlobRef& b) {
|
|
OldExtent* oe = new OldExtent(lo, o, l, b);
|
|
b->put_ref(c.get(), o, l, &(oe->r));
|
|
oe->blob_empty = !b->is_referenced();
|
|
return oe;
|
|
}
|
|
|
|
// ExtentMap
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.extentmap(" << this << ") "
|
|
#undef dout_context
|
|
#define dout_context onode->c->store->cct
|
|
|
|
BlueStore::ExtentMap::ExtentMap(Onode *o, size_t inline_shard_prealloc_size)
|
|
: onode(o),
|
|
inline_bl(inline_shard_prealloc_size) {
|
|
}
|
|
|
|
void BlueStore::ExtentMap::dump(Formatter* f) const
|
|
{
|
|
f->open_array_section("extents");
|
|
|
|
for (auto& e : extent_map) {
|
|
f->dump_object("extent", e);
|
|
}
|
|
f->close_section();
|
|
}
|
|
|
|
void BlueStore::ExtentMap::scan_shared_blobs(
|
|
uint64_t start, uint64_t length,
|
|
std::multimap<uint64_t /*blob.logical_offset*/, Blob*>& candidates)
|
|
{
|
|
Collection* c = onode->c;
|
|
uint64_t end = start + length;
|
|
// last_encoded_id will be used to process each blob only once
|
|
// so reset them first
|
|
auto ep_start = seek_lextent(start);
|
|
for (auto ep = ep_start; ep != extent_map.end(); ++ep) {
|
|
// ep->logical_offset and ep->blob_start() are different
|
|
// ep->blob_start() allows us to include blobs that do have some empty space in the beginning
|
|
if (ep->blob_start() >= end) {
|
|
break;
|
|
}
|
|
ep->blob->last_encoded_id = -1;
|
|
}
|
|
|
|
// reuse, extent_map could not change
|
|
for (auto ep = ep_start; ep != extent_map.end(); ++ep) {
|
|
if (ep->blob_start() >= end) {
|
|
break;
|
|
}
|
|
if (ep->blob->last_encoded_id == -1) {
|
|
const bluestore_blob_t& blob = ep->blob->get_blob();
|
|
if (blob.is_shared()) {
|
|
// excellent time to load the blob
|
|
c->load_shared_blob(ep->blob->get_shared_blob());
|
|
if (!blob.is_compressed()) {
|
|
// Restrict elastic shared blobs to non-compressed blobs.
|
|
// Fsck cannot handle case when one shared blob contains refs to
|
|
// both shared and non-shared blobs.
|
|
|
|
// todo consider change to emplace_hint
|
|
candidates.emplace(ep->blob_start(), ep->blob.get());
|
|
}
|
|
}
|
|
// mark as processed
|
|
ep->blob->last_encoded_id = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
BlueStore::Blob* BlueStore::ExtentMap::find_mergable_companion(
|
|
Blob* blob_to_dissolve, uint32_t blob_start, uint32_t& blob_width,
|
|
std::multimap<uint64_t /*blob_start*/, Blob*>& candidates)
|
|
{
|
|
dout(30) << __func__ << std::hex << " blob_start=0x" << blob_start << std::dec << dendl;
|
|
Blob* result = nullptr;
|
|
for (auto it = candidates.find(blob_start);
|
|
it != candidates.end() && it->first == blob_start;
|
|
++it) {
|
|
dout(30) << __func__ << " trying " << it->second << dendl;
|
|
if (it->second->can_merge_blob(blob_to_dissolve, blob_width)) {
|
|
dout(20) << __func__ << " merging " << blob_to_dissolve << " to " << it->second << dendl;
|
|
result = it->second;
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void BlueStore::ExtentMap::reblob_extents(uint32_t blob_start, uint32_t blob_end,
|
|
BlobRef from_blob, BlobRef to_blob)
|
|
{
|
|
if (from_blob->is_spanning()) {
|
|
// Mark spanning blobs no longer spanning.
|
|
// If needed will be re-spanned again in reshard().
|
|
dout(20) << __func__ << " removing spanning blob" << dendl;
|
|
spanning_blob_map.erase(from_blob->id);
|
|
from_blob->id = -1;
|
|
}
|
|
auto prev = extent_map.end();
|
|
for (auto ep = seek_lextent(blob_start); ep != extent_map.end();) {
|
|
Extent* e = &(*ep);
|
|
if (e->logical_offset > blob_end) break;
|
|
if (e->blob == from_blob) {
|
|
e->blob = to_blob;
|
|
}
|
|
if (prev != extent_map.end()) {
|
|
if (prev->blob == e->blob &&
|
|
prev->blob_offset + prev->length == e->blob_offset &&
|
|
prev->logical_offset + prev->length == e->logical_offset) {
|
|
prev->length += e->length;
|
|
ep = extent_map.erase(ep);
|
|
// we have to manually delete Extent, otherwise memory leak
|
|
delete e;
|
|
// prev still the same
|
|
continue;
|
|
}
|
|
}
|
|
prev = ep;
|
|
++ep;
|
|
}
|
|
}
|
|
|
|
// Convert blobs in selected range to shared blobs.
|
|
void BlueStore::ExtentMap::make_range_shared_maybe_merge(
|
|
TransContext* txc, OnodeRef& onoderef, uint64_t srcoff, uint64_t length)
|
|
{
|
|
ceph_assert(onoderef == onode);
|
|
uint64_t end = srcoff + length;
|
|
uint32_t dirty_range_begin = OBJECT_MAX_SIZE;
|
|
uint32_t dirty_range_end = 0;
|
|
Collection* c = onode->c;
|
|
BlueStore* store = c->store;
|
|
// load entire object; in most cases we clone entire object anyway
|
|
fault_range(store->db, 0, OBJECT_MAX_SIZE);
|
|
std::multimap<uint64_t /*blob_start*/, Blob*> candidates;
|
|
scan_shared_blobs(srcoff, length, candidates);
|
|
|
|
for (auto ep = seek_lextent(srcoff);
|
|
ep != extent_map.end(); ) {
|
|
auto& e = *ep;
|
|
if (e.logical_offset >= end) {
|
|
break;
|
|
}
|
|
dout(25) << __func__ << " src " << e << " bc=" << onoderef->bc << dendl;
|
|
const bluestore_blob_t &blob = e.blob->get_blob();
|
|
// make sure it is shared
|
|
if (!blob.is_shared()) {
|
|
dirty_range_begin = std::min<uint32_t>(dirty_range_begin, e.blob_start());
|
|
// first try to find a shared blob nearby
|
|
// that can accomodate extra extents
|
|
uint32_t blob_width; // to signal when extents end
|
|
dout(20) << __func__ << std::hex << " e.blob_start=" << e.blob_start()
|
|
<< " e.logical_offset=" << e.logical_offset << std::dec << dendl;
|
|
Blob *b = blob.is_compressed() ? nullptr :
|
|
find_mergable_companion(e.blob.get(), e.blob_start(), blob_width, candidates);
|
|
if (b) {
|
|
dout(20) << __func__ << " merging to: " << *b << " bc=" << onode->bc << dendl;
|
|
uint32_t b_logical_length = b->merge_blob(store->cct, e.blob.get());
|
|
for (auto p : blob.get_extents()) {
|
|
if (p.is_valid()) {
|
|
b->get_dirty_shared_blob()->get_ref(p.offset, p.length);
|
|
}
|
|
}
|
|
// reblob extents might erase e
|
|
dirty_range_end = std::max<uint32_t>(dirty_range_end, e.blob_start() + b_logical_length);
|
|
uint32_t goto_logical_offset = e.logical_offset + e.length;
|
|
reblob_extents(e.blob_start(), e.blob_start() + blob_width,
|
|
e.blob, b);
|
|
ep = seek_lextent(goto_logical_offset);
|
|
dout(20) << __func__ << " merged: " << *b << dendl;
|
|
} else {
|
|
// no candidate, has to convert to shared
|
|
c->make_blob_shared(store->_assign_blobid(txc), e.blob);
|
|
ceph_assert(e.logical_end() > 0);
|
|
dirty_range_end = std::max<uint32_t>(dirty_range_end, e.logical_end());
|
|
++ep;
|
|
}
|
|
} else {
|
|
c->load_shared_blob(e.blob->get_shared_blob());
|
|
++ep;
|
|
}
|
|
}
|
|
if (dirty_range_begin < dirty_range_end) {
|
|
// source onode got modified in the process
|
|
dirty_range(dirty_range_begin, dirty_range_end - dirty_range_begin);
|
|
maybe_reshard(dirty_range_begin, dirty_range_end);
|
|
txc->write_onode(onoderef);
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentMap::dup(BlueStore* b, TransContext* txc,
|
|
CollectionRef& c, OnodeRef& oldo, OnodeRef& newo, uint64_t& srcoff,
|
|
uint64_t& length, uint64_t& dstoff) {
|
|
//_dup_writing needs cache lock
|
|
BufferCacheShard* bcs = c->cache;
|
|
bcs->lock.lock();
|
|
while(bcs != c->cache) {
|
|
bcs->lock.unlock();
|
|
bcs = c->cache;
|
|
bcs->lock.lock();
|
|
}
|
|
|
|
vector<BlobRef> id_to_blob(oldo->extent_map.extent_map.size());
|
|
for (auto& e : oldo->extent_map.extent_map) {
|
|
e.blob->last_encoded_id = -1;
|
|
}
|
|
|
|
int n = 0;
|
|
uint64_t end = srcoff + length;
|
|
uint32_t dirty_range_begin = 0;
|
|
uint32_t dirty_range_end = 0;
|
|
bool src_dirty = false;
|
|
for (auto ep = oldo->extent_map.seek_lextent(srcoff);
|
|
ep != oldo->extent_map.extent_map.end();
|
|
++ep) {
|
|
auto& e = *ep;
|
|
if (e.logical_offset >= end) {
|
|
break;
|
|
}
|
|
dout(20) << __func__ << " src " << e << dendl;
|
|
BlobRef cb;
|
|
bool blob_duped = true;
|
|
if (e.blob->last_encoded_id >= 0) {
|
|
cb = id_to_blob[e.blob->last_encoded_id];
|
|
blob_duped = false;
|
|
} else {
|
|
// dup the blob
|
|
const bluestore_blob_t& blob = e.blob->get_blob();
|
|
// make sure it is shared
|
|
if (!blob.is_shared()) {
|
|
c->make_blob_shared(b->_assign_blobid(txc), e.blob);
|
|
if (!src_dirty) {
|
|
src_dirty = true;
|
|
dirty_range_begin = e.logical_offset;
|
|
}
|
|
ceph_assert(e.logical_end() > 0);
|
|
// -1 to exclude next potential shard
|
|
dirty_range_end = e.logical_end() - 1;
|
|
} else {
|
|
c->load_shared_blob(e.blob->get_shared_blob());
|
|
}
|
|
cb = c->new_blob();
|
|
e.blob->last_encoded_id = n;
|
|
id_to_blob[n] = cb;
|
|
e.blob->dup(*cb);
|
|
|
|
// bump the extent refs on the copied blob's extents
|
|
for (auto p : blob.get_extents()) {
|
|
if (p.is_valid()) {
|
|
e.blob->get_shared_blob()->get_ref(p.offset, p.length);
|
|
}
|
|
}
|
|
txc->write_shared_blob(e.blob->get_shared_blob());
|
|
dout(20) << __func__ << " new " << *cb << dendl;
|
|
}
|
|
|
|
int skip_front, skip_back;
|
|
if (e.logical_offset < srcoff) {
|
|
skip_front = srcoff - e.logical_offset;
|
|
} else {
|
|
skip_front = 0;
|
|
}
|
|
if (e.logical_end() > end) {
|
|
skip_back = e.logical_end() - end;
|
|
} else {
|
|
skip_back = 0;
|
|
}
|
|
|
|
Extent* ne = new Extent(e.logical_offset + skip_front + dstoff - srcoff,
|
|
e.blob_offset + skip_front, e.length - skip_front - skip_back, cb);
|
|
newo->extent_map.extent_map.insert(*ne);
|
|
ne->blob->get_ref(c.get(), ne->blob_offset, ne->length);
|
|
// fixme: we may leave parts of new blob unreferenced that could
|
|
// be freed (relative to the shared_blob).
|
|
txc->statfs_delta.stored() += ne->length;
|
|
if (e.blob->get_blob().is_compressed()) {
|
|
txc->statfs_delta.compressed_original() += ne->length;
|
|
if (blob_duped) {
|
|
txc->statfs_delta.compressed() +=
|
|
cb->get_blob().get_compressed_payload_length();
|
|
}
|
|
}
|
|
dout(20) << __func__ << " dst " << *ne << dendl;
|
|
++n;
|
|
}
|
|
// By default do not copy buffers to clones, and let them read data by
|
|
// themselves. The exception are 'writing' buffers, which are not yet
|
|
// stable on device.
|
|
oldo->bc._dup_writing(txc, newo->c, newo, dstoff, length);
|
|
|
|
if (src_dirty) {
|
|
oldo->extent_map.dirty_range(dirty_range_begin,
|
|
dirty_range_end - dirty_range_begin);
|
|
txc->write_onode(oldo);
|
|
}
|
|
txc->write_onode(newo);
|
|
|
|
if (dstoff + length > newo->onode.size) {
|
|
newo->onode.size = dstoff + length;
|
|
}
|
|
newo->extent_map.dirty_range(dstoff, length);
|
|
//_dup_writing needs cache lock
|
|
bcs->lock.unlock();
|
|
}
|
|
|
|
void BlueStore::ExtentMap::dup_esb(BlueStore* b, TransContext* txc,
|
|
CollectionRef& c, OnodeRef& oldo, OnodeRef& newo, uint64_t& srcoff,
|
|
uint64_t& length, uint64_t& dstoff) {
|
|
ceph_assert(onode == oldo);
|
|
ceph_assert(onode->c == c);
|
|
BufferCacheShard* bcs = c->cache;
|
|
bcs->lock.lock();
|
|
while(bcs != c->cache) {
|
|
bcs->lock.unlock();
|
|
bcs = c->cache;
|
|
bcs->lock.lock();
|
|
}
|
|
|
|
dout(25) << __func__ << " start oldo=" << dendl;
|
|
_dump_onode<25>(onode->c->store->cct, *oldo);
|
|
dout(25) << __func__ << " start newo=" << dendl;
|
|
_dump_onode<25>(onode->c->store->cct, *newo);
|
|
|
|
make_range_shared_maybe_merge(txc, oldo, srcoff, length);
|
|
vector<BlobRef> id_to_blob(extent_map.size());
|
|
for (auto& e : extent_map) {
|
|
e.blob->last_encoded_id = -1;
|
|
}
|
|
|
|
int n = 0;
|
|
uint64_t end = srcoff + length;
|
|
uint32_t dirty_range_begin = 0;
|
|
uint32_t dirty_range_end = 0;
|
|
bool src_dirty = false;
|
|
for (auto ep = seek_lextent(srcoff); ep != extent_map.end(); ++ep) {
|
|
auto& e = *ep;
|
|
if (e.logical_offset >= end) {
|
|
break;
|
|
}
|
|
dout(20) << __func__ << " src " << e << dendl;
|
|
BlobRef cb;
|
|
bool blob_duped = true;
|
|
if (e.blob->last_encoded_id >= 0) {
|
|
cb = id_to_blob[e.blob->last_encoded_id];
|
|
blob_duped = false;
|
|
} else {
|
|
// dup the blob
|
|
const bluestore_blob_t& blob = e.blob->get_blob();
|
|
ceph_assert(blob.is_shared());
|
|
ceph_assert(e.blob->is_shared_loaded());
|
|
ceph_assert(!blob.has_unused());
|
|
cb = c->new_blob();
|
|
e.blob->last_encoded_id = n;
|
|
id_to_blob[n] = cb;
|
|
ceph_assert(ep->blob_start() < end);
|
|
// dup entire blob or dup parts only
|
|
if (blob.is_compressed()) {
|
|
// copy whole blob, but without used_in_blob
|
|
cb->dup(*e.blob, false);
|
|
} else if (e.blob_start() >= srcoff && e.blob_end() <= end) {
|
|
// copy whole blob, including used_in_blob
|
|
cb->dup(*e.blob, true);
|
|
} else {
|
|
// we must copy source blob diligently region-by-region
|
|
// initialize shared_blob
|
|
cb->dirty_blob().set_flag(bluestore_blob_t::FLAG_SHARED);
|
|
cb->set_shared_blob(e.blob->get_shared_blob());
|
|
}
|
|
|
|
txc->write_shared_blob(e.blob->get_shared_blob());
|
|
dout(20) << __func__ << " new " << *cb << dendl;
|
|
}
|
|
|
|
int skip_front, skip_back;
|
|
if (e.logical_offset < srcoff) {
|
|
skip_front = srcoff - e.logical_offset;
|
|
} else {
|
|
skip_front = 0;
|
|
}
|
|
if (e.logical_end() > end) {
|
|
skip_back = e.logical_end() - end;
|
|
} else {
|
|
skip_back = 0;
|
|
}
|
|
|
|
Extent* ne = new Extent(e.logical_offset + skip_front + dstoff - srcoff,
|
|
e.blob_offset + skip_front, e.length - skip_front - skip_back, cb);
|
|
newo->extent_map.extent_map.insert(*ne);
|
|
if (e.blob->get_blob().is_compressed()) {
|
|
// blob itself was copied, but used_in_blob was not
|
|
cb->get_ref(c.get(), e.blob_offset + skip_front, e.length - skip_front - skip_back);
|
|
} else
|
|
if (e.blob_start() >= srcoff && e.blob_end() <= end) {
|
|
// blob already copied
|
|
} else {
|
|
// copy part
|
|
uint32_t min_release_size = e.blob->get_blob().get_release_size(c->store->min_alloc_size);
|
|
cb->copy_from(b->cct, *e.blob, min_release_size,
|
|
e.blob_offset + skip_front, e.length - skip_front - skip_back);
|
|
}
|
|
|
|
// fixme: we may leave parts of new blob unreferenced that could
|
|
// be freed (relative to the shared_blob).
|
|
txc->statfs_delta.stored() += ne->length;
|
|
if (e.blob->get_blob().is_compressed()) {
|
|
txc->statfs_delta.compressed_original() += ne->length;
|
|
if (blob_duped) {
|
|
txc->statfs_delta.compressed() +=
|
|
cb->get_blob().get_compressed_payload_length();
|
|
}
|
|
}
|
|
dout(20) << __func__ << " dst " << *ne << dendl;
|
|
++n;
|
|
}
|
|
// By default do not copy buffers to clones, and let them read data by
|
|
// themselves. The exception are 'writing' buffers, which are not yet
|
|
// stable on device.
|
|
oldo->bc._dup_writing(txc, newo->c, newo, dstoff, length);
|
|
|
|
if (src_dirty) {
|
|
dirty_range(dirty_range_begin, dirty_range_end - dirty_range_begin);
|
|
txc->write_onode(oldo);
|
|
}
|
|
|
|
if (dstoff + length > newo->onode.size) {
|
|
newo->onode.size = dstoff + length;
|
|
}
|
|
newo->extent_map.dirty_range(dstoff, length);
|
|
newo->extent_map.maybe_reshard(dstoff, dstoff + length);
|
|
txc->write_onode(newo);
|
|
dout(25) << __func__ << " end oldo=" << dendl;
|
|
_dump_onode<25>(onode->c->store->cct, *oldo);
|
|
dout(25) << __func__ << " end newo=" << dendl;
|
|
_dump_onode<25>(onode->c->store->cct, *newo);
|
|
bcs->lock.unlock();
|
|
}
|
|
|
|
void BlueStore::ExtentMap::update(KeyValueDB::Transaction t,
|
|
bool just_after_reshard)
|
|
{
|
|
auto cct = onode->c->store->cct; //used by dout
|
|
bool do_check = onode->c->store->debug_extent_map_encode_check;
|
|
dout(20) << __func__ << " " << onode->oid << (just_after_reshard ? " force" : "") << dendl;
|
|
if (onode->onode.extent_map_shards.empty()) {
|
|
if (inline_bl.length() == 0) {
|
|
unsigned n;
|
|
// we need to encode inline_bl to measure encoded length
|
|
bool never_happen = encode_some(0, OBJECT_MAX_SIZE, inline_bl, &n,
|
|
do_check, do_check && just_after_reshard);
|
|
inline_bl.reassign_to_mempool(mempool::mempool_bluestore_inline_bl);
|
|
ceph_assert(!never_happen);
|
|
size_t len = inline_bl.length();
|
|
dout(20) << __func__ << " inline shard " << len << " bytes from " << n
|
|
<< " extents" << dendl;
|
|
if (!just_after_reshard && len > cct->_conf->bluestore_extent_map_shard_max_size) {
|
|
request_reshard(0, OBJECT_MAX_SIZE);
|
|
return;
|
|
}
|
|
}
|
|
// will persist in the onode key.
|
|
} else {
|
|
// pending shard update
|
|
struct dirty_shard_t {
|
|
Shard *shard;
|
|
bufferlist bl;
|
|
dirty_shard_t(Shard *s) : shard(s) {}
|
|
};
|
|
vector<dirty_shard_t> encoded_shards;
|
|
// allocate slots for all shards in a single call instead of
|
|
// doing multiple allocations - one per each dirty shard
|
|
encoded_shards.reserve(shards.size());
|
|
|
|
auto shard = shards.begin();
|
|
auto previous_shard = shard;
|
|
while (shard != shards.end()) {
|
|
ceph_assert(shard->shard_info->offset >= previous_shard->shard_info->offset);
|
|
auto next_shard = shard + 1;
|
|
if (!shard->dirty) {
|
|
previous_shard = shard;
|
|
shard = next_shard;
|
|
continue;
|
|
}
|
|
|
|
uint32_t endoff;
|
|
if (next_shard == shards.end()) {
|
|
endoff = OBJECT_MAX_SIZE;
|
|
} else {
|
|
endoff = next_shard->shard_info->offset;
|
|
}
|
|
encoded_shards.emplace_back(dirty_shard_t(&(*shard)));
|
|
bufferlist& bl = encoded_shards.back().bl;
|
|
if (encode_some(shard->shard_info->offset, endoff - shard->shard_info->offset,
|
|
bl, &shard->extents, do_check, do_check && just_after_reshard)) {
|
|
if (just_after_reshard) {
|
|
_dump_extent_map<-1>(cct, *this);
|
|
derr << __func__ << " encode_some needs reshard" << dendl;
|
|
ceph_assert(!just_after_reshard);
|
|
}
|
|
}
|
|
size_t len = bl.length();
|
|
|
|
dout(20) << __func__ << " shard 0x" << std::hex
|
|
<< shard->shard_info->offset << std::dec << " is " << len
|
|
<< " bytes (was " << shard->shard_info->bytes << ") from "
|
|
<< shard->extents << " extents" << dendl;
|
|
|
|
if (!just_after_reshard) {
|
|
if (len > cct->_conf->bluestore_extent_map_shard_max_size) {
|
|
// we are big; reshard ourselves
|
|
request_reshard(shard->shard_info->offset, endoff);
|
|
}
|
|
// avoid resharding the trailing shard, even if it is small
|
|
else if (next_shard != shards.end() &&
|
|
len < g_conf()->bluestore_extent_map_shard_min_size) {
|
|
ceph_assert(endoff != OBJECT_MAX_SIZE);
|
|
if (shard == shards.begin()) {
|
|
// we are the first shard, combine with next shard
|
|
request_reshard(shard->shard_info->offset, endoff + 1);
|
|
} else {
|
|
// combine either with the previous shard or the next,
|
|
// whichever is smaller
|
|
if (previous_shard->shard_info->bytes > next_shard->shard_info->bytes) {
|
|
request_reshard(shard->shard_info->offset, endoff + 1);
|
|
} else {
|
|
request_reshard(previous_shard->shard_info->offset, endoff);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
previous_shard = shard;
|
|
shard = next_shard;
|
|
}
|
|
if (needs_reshard()) {
|
|
return;
|
|
}
|
|
|
|
// schedule DB update for dirty shards
|
|
string key;
|
|
for (auto& it : encoded_shards) {
|
|
dout(20) << __func__ << " encoding key for shard 0x" << std::hex
|
|
<< it.shard->shard_info->offset << std::dec << dendl;
|
|
it.shard->dirty = false;
|
|
it.shard->shard_info->bytes = it.bl.length();
|
|
generate_extent_shard_key_and_apply(
|
|
onode->key,
|
|
it.shard->shard_info->offset,
|
|
&key,
|
|
[&](const string& final_key) {
|
|
t->set(PREFIX_OBJ, final_key, it.bl);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
bid_t BlueStore::ExtentMap::allocate_spanning_blob_id()
|
|
{
|
|
if (spanning_blob_map.empty())
|
|
return 0;
|
|
bid_t bid = spanning_blob_map.rbegin()->first + 1;
|
|
// bid is valid and available.
|
|
if (bid >= 0)
|
|
return bid;
|
|
// Find next unused bid;
|
|
bid = rand() % (numeric_limits<bid_t>::max() + 1);
|
|
const auto begin_bid = bid;
|
|
do {
|
|
if (!spanning_blob_map.count(bid))
|
|
return bid;
|
|
else {
|
|
bid++;
|
|
if (bid < 0) bid = 0;
|
|
}
|
|
} while (bid != begin_bid);
|
|
auto cct = onode->c->store->cct; // used by dout
|
|
_dump_onode<0>(cct, *onode);
|
|
ceph_abort_msg("no available blob id");
|
|
}
|
|
|
|
BlueStore::ExtentMap::ReshardPlan
|
|
BlueStore::ExtentMap::reshard_decision(uint32_t segment_size) {
|
|
ReshardPlan plan;
|
|
auto cct = onode->c->store->cct; // used by dout
|
|
|
|
dout(10) << __func__ << " 0x[" << std::hex << needs_reshard_begin << ","
|
|
<< needs_reshard_end << ") segment 0x" << segment_size << std::dec
|
|
<< " of " << onode->onode.extent_map_shards.size()
|
|
<< " shards on " << onode->oid << dendl;
|
|
for (auto& p : spanning_blob_map) {
|
|
dout(20) << __func__ << " spanning blob " << p.first << " " << *p.second
|
|
<< dendl;
|
|
}
|
|
// determine shard index range
|
|
unsigned shard_index_begin = 0, shard_index_end = 0;
|
|
if (!shards.empty()) {
|
|
while (shard_index_begin + 1 < shards.size() &&
|
|
shards[shard_index_begin + 1].shard_info->offset <= needs_reshard_begin) {
|
|
++shard_index_begin;
|
|
}
|
|
needs_reshard_begin = shards[shard_index_begin].shard_info->offset;
|
|
for (shard_index_end = shard_index_begin; shard_index_end < shards.size(); ++shard_index_end) {
|
|
if (shards[shard_index_end].shard_info->offset >= needs_reshard_end) {
|
|
needs_reshard_end = shards[shard_index_end].shard_info->offset;
|
|
break;
|
|
}
|
|
}
|
|
if (shard_index_end == shards.size()) {
|
|
needs_reshard_end = OBJECT_MAX_SIZE;
|
|
}
|
|
dout(20) << __func__ << " shards [" << shard_index_begin << "," << shard_index_end << ")"
|
|
<< " over 0x[" << std::hex << needs_reshard_begin << ","
|
|
<< needs_reshard_end << ")" << std::dec << dendl;
|
|
} else {
|
|
// When sharding is not applied yet, it is an error to request reshard on range.
|
|
// The problem is that reshard() function will not touch any extent outside the range.
|
|
// Thus initial reshard() must encompass whole object.
|
|
needs_reshard_begin = 0;
|
|
needs_reshard_end = OBJECT_MAX_SIZE;
|
|
}
|
|
|
|
uint64_t data_reshard_end = needs_reshard_end;
|
|
if (needs_reshard_end == OBJECT_MAX_SIZE && !extent_map.empty()) {
|
|
data_reshard_end = extent_map.rbegin()->blob_end();
|
|
}
|
|
|
|
// we may need to fault in a larger interval later must have all
|
|
// referring extents for spanning blobs loaded in order to have
|
|
// accurate use_tracker values.
|
|
uint32_t spanning_scan_begin = needs_reshard_begin;
|
|
uint32_t spanning_scan_end = needs_reshard_end;
|
|
|
|
// calculate average extent size
|
|
unsigned bytes = 0;
|
|
unsigned extents = 0;
|
|
if (onode->onode.extent_map_shards.empty()) {
|
|
bytes = inline_bl.length();
|
|
extents = extent_map.size();
|
|
} else {
|
|
for (unsigned i = shard_index_begin; i < shard_index_end; ++i) {
|
|
bytes += shards[i].shard_info->bytes;
|
|
extents += shards[i].extents;
|
|
}
|
|
}
|
|
unsigned target = cct->_conf->bluestore_extent_map_shard_target_size;
|
|
unsigned slop = target *
|
|
cct->_conf->bluestore_extent_map_shard_target_size_slop;
|
|
unsigned extent_avg = bytes / std::max(1u, extents);
|
|
dout(20) << __func__ << " extent_avg " << extent_avg << ", target " << target
|
|
<< ", slop " << slop << dendl;
|
|
|
|
uint32_t next_boundary = segment_size;
|
|
uint32_t encoded_segment_estimate = 0;
|
|
if (segment_size != 0) {
|
|
if (data_reshard_end != needs_reshard_begin) {
|
|
encoded_segment_estimate = bytes * segment_size / (data_reshard_end - needs_reshard_begin);
|
|
} else {
|
|
derr << __func__ << " 0 reshard-range doing 0x" << std::hex << needs_reshard_begin
|
|
<< "-0x" << needs_reshard_end << std::dec << " on"
|
|
<< pretty_binary_string(onode->oid.hobj.to_str()) << dendl;
|
|
encoded_segment_estimate = 500; // just something, instead div0 ....
|
|
}
|
|
}
|
|
|
|
// reshard
|
|
unsigned estimate = 0;
|
|
unsigned offset = needs_reshard_begin;
|
|
vector<bluestore_onode_t::shard_info> new_shard_info;
|
|
unsigned max_blob_end = 0;
|
|
Extent dummy(needs_reshard_begin);
|
|
for (auto extent = extent_map.lower_bound(dummy);
|
|
extent != extent_map.end();
|
|
++extent) {
|
|
if (extent->logical_offset >= needs_reshard_end) {
|
|
break;
|
|
}
|
|
dout(30) << " extent " << *extent << dendl;
|
|
|
|
bool make_shard_here = false;
|
|
if (segment_size != 0) { //onode data has strict boundaries
|
|
if (extent->blob_start() >= next_boundary) {
|
|
// beginning of the extent is a place that might be a shard boundary
|
|
// we want to decide whether to continue streaming to the current shard
|
|
// or move to the next one
|
|
if (estimate + encoded_segment_estimate/2 >= target /*it is better to go undersize*/) {
|
|
make_shard_here = true;
|
|
}
|
|
next_boundary = p2roundup(extent->blob_end(), segment_size);
|
|
}
|
|
} else {
|
|
// disfavor shard boundaries that span a blob
|
|
bool would_span = (extent->logical_offset < max_blob_end) || (extent->blob_offset != 0);
|
|
if ((estimate > 0)
|
|
&& (estimate + extent_avg > target + (would_span ? slop : 0))) {
|
|
make_shard_here = true;
|
|
}
|
|
}
|
|
if (make_shard_here) {
|
|
// new shard
|
|
if (offset == needs_reshard_begin) {
|
|
new_shard_info.emplace_back(bluestore_onode_t::shard_info());
|
|
new_shard_info.back().offset = offset;
|
|
dout(20) << __func__ << " new shard 0x" << std::hex << offset
|
|
<< std::dec << dendl;
|
|
}
|
|
offset = extent->logical_offset;
|
|
new_shard_info.emplace_back(bluestore_onode_t::shard_info());
|
|
new_shard_info.back().offset = offset;
|
|
dout(20) << __func__ << " new shard 0x" << std::hex << offset
|
|
<< std::dec << dendl;
|
|
estimate = 0;
|
|
}
|
|
estimate += extent_avg;
|
|
unsigned blob_start = extent->blob_start();
|
|
if (blob_start < spanning_scan_begin) {
|
|
spanning_scan_begin = blob_start;
|
|
}
|
|
uint32_t blob_end = extent->blob_end();
|
|
if (blob_end > max_blob_end) {
|
|
max_blob_end = blob_end;
|
|
}
|
|
if (blob_end > spanning_scan_end) {
|
|
spanning_scan_end = blob_end;
|
|
}
|
|
}
|
|
if (new_shard_info.empty() && (shard_index_begin > 0 ||
|
|
shard_index_end < shards.size())) {
|
|
// we resharded a partial range; we must produce at least one output
|
|
// shard
|
|
new_shard_info.emplace_back(bluestore_onode_t::shard_info());
|
|
new_shard_info.back().offset = needs_reshard_begin;
|
|
dout(20) << __func__ << " new shard 0x" << std::hex << needs_reshard_begin
|
|
<< std::dec << " (singleton degenerate case)" << dendl;
|
|
}
|
|
|
|
auto& extent_map_shards = onode->onode.extent_map_shards;
|
|
dout(20) << __func__ << " new " << new_shard_info << dendl;
|
|
dout(20) << __func__ << " old " << extent_map_shards << dendl;
|
|
|
|
plan.shard_index_begin = shard_index_begin;
|
|
plan.shard_index_end = shard_index_end;
|
|
plan.spanning_scan_begin = spanning_scan_begin;
|
|
plan.spanning_scan_end = spanning_scan_end;
|
|
plan.new_shard_info = std::move(new_shard_info);
|
|
return plan;
|
|
}
|
|
|
|
|
|
void BlueStore::ExtentMap::reshard_action(
|
|
ReshardPlan& plan,
|
|
KeyValueDB *db,
|
|
KeyValueDB::Transaction t) {
|
|
auto cct = onode->c->store->cct; // For configuration and logging
|
|
|
|
std::vector<bluestore_onode_t::shard_info> new_shard_info = plan.new_shard_info;
|
|
unsigned shard_index_begin = plan.shard_index_begin;
|
|
unsigned shard_index_end = plan.shard_index_end;
|
|
uint32_t spanning_scan_begin = plan.spanning_scan_begin;
|
|
uint32_t spanning_scan_end = plan.spanning_scan_end;
|
|
|
|
dout(20) << __func__ << " applying plan with shards [" << shard_index_begin << ","
|
|
<< shard_index_end << ")" << dendl;
|
|
|
|
// Fault the range
|
|
if (db) {
|
|
fault_range(db, needs_reshard_begin, (needs_reshard_end - needs_reshard_begin));
|
|
}
|
|
|
|
// Remove old shard keys
|
|
string key;
|
|
for (unsigned i = shard_index_begin; t && i < shard_index_end; ++i) {
|
|
generate_extent_shard_key_and_apply(
|
|
onode->key, shards[i].shard_info->offset, &key,
|
|
[&](const string& final_key) {
|
|
t->rmkey(PREFIX_OBJ, final_key);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Update extent_map_shards and shards
|
|
auto& extent_map_shards = onode->onode.extent_map_shards;
|
|
if (extent_map_shards.empty()) {
|
|
// no old shards to keep
|
|
extent_map_shards.swap(new_shard_info);
|
|
init_shards(true, true);
|
|
} else {
|
|
// splice in new shards
|
|
extent_map_shards.erase(extent_map_shards.begin() + shard_index_begin, extent_map_shards.begin() + shard_index_end);
|
|
shards.erase(shards.begin() + shard_index_begin, shards.begin() + shard_index_end);
|
|
extent_map_shards.insert(
|
|
extent_map_shards.begin() + shard_index_begin,
|
|
new_shard_info.begin(),
|
|
new_shard_info.end());
|
|
shards.insert(shards.begin() + shard_index_begin, new_shard_info.size(), Shard());
|
|
shard_index_end = shard_index_begin + new_shard_info.size();
|
|
|
|
ceph_assert(extent_map_shards.size() == shards.size());
|
|
|
|
// note that we need to update every shard_info of shards here,
|
|
// as extent_map_shards might have been totally re-allocated above
|
|
for (unsigned i = 0; i < shards.size(); i++) {
|
|
shards[i].shard_info = &extent_map_shards[i];
|
|
}
|
|
|
|
// mark newly added shards as dirty
|
|
for (unsigned i = shard_index_begin; i < shard_index_end; ++i) {
|
|
shards[i].loaded = true;
|
|
shards[i].dirty = true;
|
|
}
|
|
}
|
|
dout(20) << __func__ << " fin " << extent_map_shards << dendl;
|
|
inline_bl.clear();
|
|
|
|
if (extent_map_shards.empty()) {
|
|
// no more shards; unspan all previously spanning blobs
|
|
auto spanning_blob_it = spanning_blob_map.begin();
|
|
while (spanning_blob_it != spanning_blob_map.end()) {
|
|
spanning_blob_it->second->id = -1;
|
|
dout(30) << __func__ << " un-spanning " << *spanning_blob_it->second << dendl;
|
|
spanning_blob_it = spanning_blob_map.erase(spanning_blob_it);
|
|
}
|
|
} else {
|
|
// identify new spanning blobs
|
|
dout(20) << __func__ << " checking spanning blobs 0x[" << std::hex
|
|
<< spanning_scan_begin << "," << spanning_scan_end << ")" << dendl;
|
|
if (db) {
|
|
if (spanning_scan_begin < needs_reshard_begin) {
|
|
fault_range(db, spanning_scan_begin,
|
|
needs_reshard_begin - spanning_scan_begin);
|
|
}
|
|
if (spanning_scan_end > needs_reshard_end) {
|
|
fault_range(db, needs_reshard_end,
|
|
spanning_scan_end - needs_reshard_end);
|
|
}
|
|
}
|
|
auto current_shard = extent_map_shards.begin() + shard_index_begin;
|
|
auto end_shard = extent_map_shards.end();
|
|
unsigned shard_start = current_shard->offset;
|
|
unsigned shard_end;
|
|
++current_shard;
|
|
if (current_shard == end_shard) {
|
|
shard_end = OBJECT_MAX_SIZE;
|
|
} else {
|
|
shard_end = current_shard->offset;
|
|
}
|
|
|
|
bool was_too_many_blobs_check = false;
|
|
auto too_many_blobs_threshold =
|
|
g_conf()->bluestore_debug_too_many_blobs_threshold;
|
|
auto& dumped_onodes = onode->c->onode_space.cache->dumped_onodes;
|
|
decltype(onode->c->onode_space.cache->dumped_onodes)::value_type* oid_slot = nullptr;
|
|
decltype(onode->c->onode_space.cache->dumped_onodes)::value_type* oldest_slot = nullptr;
|
|
|
|
for (auto extent = extent_map.lower_bound(Extent(needs_reshard_begin)); extent != extent_map.end(); ++extent) {
|
|
if (extent->logical_offset >= needs_reshard_end) {
|
|
break;
|
|
}
|
|
dout(30) << __func__ << " extent " << *extent << dendl;
|
|
while (extent->logical_offset >= shard_end) {
|
|
shard_start = shard_end;
|
|
ceph_assert(current_shard != end_shard);
|
|
++current_shard;
|
|
if (current_shard == end_shard) {
|
|
shard_end = OBJECT_MAX_SIZE;
|
|
} else {
|
|
shard_end = current_shard->offset;
|
|
}
|
|
dout(30) << __func__ << " shard 0x" << std::hex << shard_start
|
|
<< " to 0x" << shard_end << std::dec << dendl;
|
|
}
|
|
|
|
if (extent->blob_escapes_range(shard_start, shard_end - shard_start)) {
|
|
BlobRef b = extent->blob;
|
|
uint32_t bstart = extent->blob_start();
|
|
uint32_t bend = extent->blob_end();
|
|
if (!b->is_spanning()) {
|
|
// We have two options: (1) split the blob into pieces at the
|
|
// shard boundaries (and adjust extents accordingly), or (2)
|
|
// mark it spanning. We prefer to cut the blob if we can. Note that
|
|
// we may have to split it multiple times--potentially at every
|
|
// shard boundary.
|
|
auto _make_spanning = [&](BlobRef& b) {
|
|
auto bid = allocate_spanning_blob_id();
|
|
b->id = bid;
|
|
spanning_blob_map[b->id] = b;
|
|
dout(20) << __func__ << " adding spanning " << *b << dendl;
|
|
if (!was_too_many_blobs_check &&
|
|
too_many_blobs_threshold &&
|
|
spanning_blob_map.size() >= size_t(too_many_blobs_threshold)) {
|
|
|
|
was_too_many_blobs_check = true;
|
|
for (size_t i = 0; i < dumped_onodes.size(); ++i) {
|
|
if (dumped_onodes[i].first == onode->oid) {
|
|
oid_slot = &dumped_onodes[i];
|
|
break;
|
|
}
|
|
if (!oldest_slot || (oldest_slot &&
|
|
dumped_onodes[i].second < oldest_slot->second)) {
|
|
oldest_slot = &dumped_onodes[i];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
if (b->can_split()) {
|
|
auto bstart1 = bstart;
|
|
for (const auto& sh : shards) {
|
|
if (bstart1 < sh.shard_info->offset &&
|
|
bend > sh.shard_info->offset) {
|
|
uint32_t blob_offset = sh.shard_info->offset - bstart1;
|
|
if (b->can_split_at(blob_offset)) {
|
|
dout(20) << __func__ << " splitting blob, bstart 0x"
|
|
<< std::hex << bstart1 << " blob_offset 0x"
|
|
<< blob_offset << std::dec << " " << *b << dendl;
|
|
b = split_blob(b, blob_offset, sh.shard_info->offset);
|
|
// switch b to the new right-hand side, in case it
|
|
// *also* has to get split.
|
|
bstart1 = sh.shard_info->offset;
|
|
onode->c->store->logger->inc(l_bluestore_blob_split);
|
|
} else {
|
|
_make_spanning(b);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
_make_spanning(b);
|
|
}
|
|
} // if (!extent->blob->is_spanning())
|
|
// Make sure extent with a spanning blob doesn't span over shard boundary
|
|
if (extent->blob->is_spanning()) {
|
|
BlobRef b = extent->blob;
|
|
uint32_t bstart = extent->blob_start();
|
|
for (const auto& sh : shards) {
|
|
if (bstart < sh.shard_info->offset && bend > sh.shard_info->offset) {
|
|
uint32_t blob_offset = sh.shard_info->offset - bstart;
|
|
auto pos = sh.shard_info->offset;
|
|
if (extent->logical_offset < pos && extent->logical_end() > pos) {
|
|
// split extent
|
|
size_t left = pos - extent->logical_offset;
|
|
Extent* ne = new Extent(pos, blob_offset, extent->length - left, b);
|
|
extent_map.insert(*ne);
|
|
extent->length = left;
|
|
dout(20) << __func__ << " split " << *extent << dendl;
|
|
dout(20) << __func__ << " to " << *ne << dendl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (extent->blob->is_spanning()) {
|
|
spanning_blob_map.erase(extent->blob->id);
|
|
extent->blob->id = -1;
|
|
dout(20) << __func__ << " un-spanning " << *extent->blob << dendl;
|
|
}
|
|
}
|
|
}
|
|
bool do_dump = (!oid_slot && was_too_many_blobs_check) ||
|
|
(oid_slot &&
|
|
(mono_clock::now() - oid_slot->second >= make_timespan(5 * 60)));
|
|
if (do_dump) {
|
|
dout(0) << __func__
|
|
<< " spanning blob count exceeds threshold, "
|
|
<< spanning_blob_map.size() << " spanning blobs"
|
|
<< dendl;
|
|
_dump_onode<0>(cct, *onode);
|
|
if (oid_slot) {
|
|
oid_slot->second = mono_clock::now();
|
|
} else {
|
|
ceph_assert(oldest_slot);
|
|
oldest_slot->first = onode->oid;
|
|
oldest_slot->second = mono_clock::now();
|
|
}
|
|
}
|
|
}
|
|
|
|
clear_needs_reshard();
|
|
}
|
|
|
|
void BlueStore::ExtentMap::reshard(
|
|
KeyValueDB *db,
|
|
KeyValueDB::Transaction t,
|
|
uint32_t segment_size) {
|
|
auto plan = reshard_decision(segment_size);
|
|
reshard_action(plan, db, t);
|
|
}
|
|
|
|
bool BlueStore::ExtentMap::encode_some(
|
|
uint32_t offset,
|
|
uint32_t length,
|
|
bufferlist& bl,
|
|
unsigned *pn,
|
|
bool complain_extent_overlap,
|
|
bool complain_shard_spanning)
|
|
{
|
|
Extent dummy(offset);
|
|
auto start = extent_map.lower_bound(dummy);
|
|
uint32_t end = offset + length;
|
|
|
|
__u8 struct_v = 2; // Version 2 differs from v1 in blob's ref_map
|
|
// serialization only. Hence there is no specific
|
|
// handling at ExtentMap level.
|
|
|
|
unsigned n = 0;
|
|
size_t bound = 0;
|
|
uint32_t prev_offset_end = 0;
|
|
for (auto p = start;
|
|
p != extent_map.end() && p->logical_offset < end;
|
|
++p, ++n) {
|
|
ceph_assert(p->logical_offset >= offset);
|
|
if (complain_extent_overlap) {
|
|
if (p->logical_offset < prev_offset_end) {
|
|
using P = BlueStore::printer;
|
|
dout(-1) << __func__ << " extents overlap: "
|
|
<< std::hex << offset <<"~" << length
|
|
<< " " << p->logical_offset <<"~" << p->length
|
|
<< std::dec << std::endl
|
|
<< onode->print(P::NICK + P::SDISK + P::SUSE + P::SBUF)
|
|
<< dendl;
|
|
ceph_abort_msg("extents overlaps");
|
|
}
|
|
prev_offset_end = p->logical_end();
|
|
}
|
|
p->blob->last_encoded_id = -1;
|
|
if (!p->blob->is_spanning() && p->blob_escapes_range(offset, length)) {
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << " hit new spanning blob " << *p << dendl;
|
|
request_reshard(p->blob_start(), p->blob_end());
|
|
return true;
|
|
} else if (p->blob->is_spanning() && p->logical_end() > end) {
|
|
dout(20) << __func__ << std::hex << offset << "~" << length
|
|
<< std::dec << " extent stands out " << *p << dendl;
|
|
request_reshard(p->blob_start(), p->blob_end());
|
|
return true;
|
|
} else {
|
|
denc_varint(0, bound); // blobid
|
|
denc_varint(0, bound); // logical_offset
|
|
denc_varint(0, bound); // len
|
|
denc_varint(0, bound); // blob_offset
|
|
|
|
p->blob->bound_encode(
|
|
bound,
|
|
struct_v,
|
|
p->blob->get_sbid(),
|
|
false);
|
|
}
|
|
}
|
|
|
|
denc(struct_v, bound);
|
|
denc_varint(0, bound); // number of extents
|
|
|
|
{
|
|
auto app = bl.get_contiguous_appender(bound);
|
|
denc(struct_v, app);
|
|
denc_varint(n, app);
|
|
if (pn) {
|
|
*pn = n;
|
|
}
|
|
|
|
n = 0;
|
|
uint64_t pos = 0;
|
|
uint64_t prev_len = 0;
|
|
for (auto p = start;
|
|
p != extent_map.end() && p->logical_offset < end;
|
|
++p, ++n) {
|
|
unsigned blobid;
|
|
if (complain_shard_spanning) {
|
|
if (p->logical_end() > end) {
|
|
using P = BlueStore::printer;
|
|
dout(-1) << __func__ << " extent spans shard after reshard " << ": " << std::endl
|
|
<< onode->print(P::NICK + P::SDISK + P::SUSE + P::SBUF) << dendl;
|
|
ceph_abort();
|
|
}
|
|
}
|
|
bool include_blob = false;
|
|
if (p->blob->is_spanning()) {
|
|
blobid = p->blob->id << BLOBID_SHIFT_BITS;
|
|
blobid |= BLOBID_FLAG_SPANNING;
|
|
} else if (p->blob->last_encoded_id < 0) {
|
|
p->blob->last_encoded_id = n + 1; // so it is always non-zero
|
|
include_blob = true;
|
|
blobid = 0; // the decoder will infer the id from n
|
|
} else {
|
|
blobid = p->blob->last_encoded_id << BLOBID_SHIFT_BITS;
|
|
}
|
|
if (p->logical_offset == pos) {
|
|
blobid |= BLOBID_FLAG_CONTIGUOUS;
|
|
}
|
|
if (p->blob_offset == 0) {
|
|
blobid |= BLOBID_FLAG_ZEROOFFSET;
|
|
}
|
|
if (p->length == prev_len) {
|
|
blobid |= BLOBID_FLAG_SAMELENGTH;
|
|
} else {
|
|
prev_len = p->length;
|
|
}
|
|
denc_varint(blobid, app);
|
|
if ((blobid & BLOBID_FLAG_CONTIGUOUS) == 0) {
|
|
denc_varint_lowz(p->logical_offset - pos, app);
|
|
}
|
|
if ((blobid & BLOBID_FLAG_ZEROOFFSET) == 0) {
|
|
denc_varint_lowz(p->blob_offset, app);
|
|
}
|
|
if ((blobid & BLOBID_FLAG_SAMELENGTH) == 0) {
|
|
denc_varint_lowz(p->length, app);
|
|
}
|
|
pos = p->logical_end();
|
|
if (include_blob) {
|
|
p->blob->encode(app, struct_v, p->blob->get_sbid(), false);
|
|
}
|
|
}
|
|
}
|
|
/*derr << __func__ << bl << dendl;
|
|
derr << __func__ << ":";
|
|
bl.hexdump(*_dout);
|
|
*_dout << dendl;
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
/////////////////// BlueStore::ExtentMap::DecoderExtent ///////////
|
|
void BlueStore::ExtentMap::ExtentDecoder::decode_extent(
|
|
Extent* le,
|
|
__u8 struct_v,
|
|
bptr_c_it_t& p,
|
|
Collection* c)
|
|
{
|
|
uint64_t blobid;
|
|
denc_varint(blobid, p);
|
|
if ((blobid & BLOBID_FLAG_CONTIGUOUS) == 0) {
|
|
uint64_t gap;
|
|
denc_varint_lowz(gap, p);
|
|
pos += gap;
|
|
}
|
|
le->logical_offset = pos;
|
|
if ((blobid & BLOBID_FLAG_ZEROOFFSET) == 0) {
|
|
denc_varint_lowz(le->blob_offset, p);
|
|
} else {
|
|
le->blob_offset = 0;
|
|
}
|
|
if ((blobid & BLOBID_FLAG_SAMELENGTH) == 0) {
|
|
denc_varint_lowz(prev_len, p);
|
|
}
|
|
le->length = prev_len;
|
|
if (blobid & BLOBID_FLAG_SPANNING) {
|
|
consume_blobid(le, true, blobid >> BLOBID_SHIFT_BITS);
|
|
} else {
|
|
blobid >>= BLOBID_SHIFT_BITS;
|
|
if (blobid) {
|
|
consume_blobid(le, false, blobid - 1);
|
|
} else {
|
|
// dummy onodes might not have collections, we need a check for it.
|
|
BlobRef b = c ? c->new_blob() : new Blob(nullptr);
|
|
uint64_t sbid = 0;
|
|
b->decode(p, struct_v, &sbid, false, c);
|
|
consume_blob(le, extent_pos, sbid, b);
|
|
}
|
|
}
|
|
pos += prev_len;
|
|
++extent_pos;
|
|
}
|
|
|
|
unsigned BlueStore::ExtentMap::ExtentDecoder::decode_some(
|
|
const bufferlist& bl, Collection* c)
|
|
{
|
|
__u8 struct_v;
|
|
uint32_t num;
|
|
|
|
ceph_assert(bl.get_num_buffers() <= 1);
|
|
auto p = bl.front().begin_deep();
|
|
denc(struct_v, p);
|
|
// Version 2 differs from v1 in blob's ref_map
|
|
// serialization only. Hence there is no specific
|
|
// handling at ExtentMap level below.
|
|
ceph_assert(struct_v == 1 || struct_v == 2);
|
|
denc_varint(num, p);
|
|
|
|
extent_pos = 0;
|
|
while (!p.end()) {
|
|
Extent* le = get_next_extent();
|
|
decode_extent(le, struct_v, p, c);
|
|
add_extent(le);
|
|
}
|
|
ceph_assert(extent_pos == num);
|
|
return num;
|
|
}
|
|
|
|
void BlueStore::ExtentMap::ExtentDecoder::decode_spanning_blobs(
|
|
bptr_c_it_t& p, Collection* c)
|
|
{
|
|
__u8 struct_v;
|
|
denc(struct_v, p);
|
|
// Version 2 differs from v1 in blob's ref_map
|
|
// serialization only. Hence there is no specific
|
|
// handling at ExtentMap level.
|
|
ceph_assert(struct_v == 1 || struct_v == 2);
|
|
|
|
unsigned n;
|
|
denc_varint(n, p);
|
|
while (n--) {
|
|
BlobRef b = c ? c->new_blob() : new Blob(nullptr);
|
|
denc_varint(b->id, p);
|
|
uint64_t sbid = 0;
|
|
b->decode(p, struct_v, &sbid, true, c);
|
|
consume_spanning_blob(sbid, b);
|
|
}
|
|
}
|
|
|
|
/////////////////// BlueStore::ExtentMap::DecoderExtentFull ///////////
|
|
void BlueStore::ExtentMap::ExtentDecoderFull::consume_blobid(
|
|
BlueStore::Extent* le, bool spanning, uint64_t blobid) {
|
|
ceph_assert(le);
|
|
if (spanning) {
|
|
le->assign_blob(extent_map.get_spanning_blob(blobid));
|
|
} else {
|
|
ceph_assert(blobid < blobs.size());
|
|
le->assign_blob(blobs[blobid]);
|
|
// we build ref_map dynamically for non-spanning blobs
|
|
le->blob->get_ref(
|
|
extent_map.onode->c,
|
|
le->blob_offset,
|
|
le->length);
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentMap::ExtentDecoderFull::consume_blob(
|
|
BlueStore::Extent* le, uint64_t extent_no, uint64_t sbid, BlobRef b) {
|
|
ceph_assert(le);
|
|
blobs.resize(extent_no + 1);
|
|
blobs[extent_no] = b;
|
|
extent_map.onode->c->open_shared_blob(sbid, b);
|
|
le->assign_blob(b);
|
|
le->blob->get_ref(
|
|
extent_map.onode->c,
|
|
le->blob_offset,
|
|
le->length);
|
|
}
|
|
|
|
void BlueStore::ExtentMap::ExtentDecoderFull::consume_spanning_blob(
|
|
uint64_t sbid, BlueStore::BlobRef b) {
|
|
extent_map.spanning_blob_map[b->id] = b;
|
|
extent_map.onode->c->open_shared_blob(sbid, b);
|
|
}
|
|
|
|
BlueStore::Extent* BlueStore::ExtentMap::ExtentDecoderFull::get_next_extent()
|
|
{
|
|
return new Extent();
|
|
}
|
|
|
|
void BlueStore::ExtentMap::ExtentDecoderFull::add_extent(BlueStore::Extent* le)
|
|
{
|
|
extent_map.extent_map.insert(*le);
|
|
}
|
|
|
|
unsigned BlueStore::ExtentMap::decode_some(bufferlist& bl)
|
|
{
|
|
ExtentDecoderFull edecoder(*this);
|
|
unsigned n = edecoder.decode_some(bl, onode->c);
|
|
return n;
|
|
}
|
|
|
|
void BlueStore::ExtentMap::bound_encode_spanning_blobs(size_t& p)
|
|
{
|
|
// Version 2 differs from v1 in blob's ref_map
|
|
// serialization only. Hence there is no specific
|
|
// handling at ExtentMap level.
|
|
__u8 struct_v = 2;
|
|
|
|
denc(struct_v, p);
|
|
denc_varint((uint32_t)0, p);
|
|
size_t key_size = 0;
|
|
denc_varint((uint32_t)0, key_size);
|
|
p += spanning_blob_map.size() * key_size;
|
|
for (const auto& i : spanning_blob_map) {
|
|
i.second->bound_encode(p, struct_v, i.second->get_sbid(), true);
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentMap::encode_spanning_blobs(
|
|
bufferlist::contiguous_appender& p)
|
|
{
|
|
// Version 2 differs from v1 in blob's ref_map
|
|
// serialization only. Hence there is no specific
|
|
// handling at ExtentMap level.
|
|
__u8 struct_v = 2;
|
|
|
|
denc(struct_v, p);
|
|
denc_varint(spanning_blob_map.size(), p);
|
|
for (auto& i : spanning_blob_map) {
|
|
denc_varint(i.second->id, p);
|
|
i.second->encode(p, struct_v, i.second->get_sbid(), true);
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentMap::init_shards(bool loaded, bool dirty)
|
|
{
|
|
shards.resize(onode->onode.extent_map_shards.size());
|
|
unsigned i = 0;
|
|
for (auto &s : onode->onode.extent_map_shards) {
|
|
shards[i].shard_info = &s;
|
|
shards[i].loaded = loaded;
|
|
shards[i].dirty = dirty;
|
|
++i;
|
|
}
|
|
}
|
|
|
|
std::pair<uint32_t, uint32_t> BlueStore::ExtentMap::fault_range_ex(
|
|
KeyValueDB *db,
|
|
uint32_t offset,
|
|
uint32_t length)
|
|
{
|
|
dout(30) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
if (shards.size() == 0) {
|
|
// no sharding yet; everyting is loaded
|
|
return {0, OBJECT_MAX_SIZE};
|
|
}
|
|
auto start = seek_shard(offset);
|
|
auto last = seek_shard(offset + length);
|
|
maybe_load_shard(db, start, last);
|
|
uint32_t left_bound = shards[start].shard_info->offset;
|
|
uint32_t right_bound = (size_t)last + 1 < shards.size() ?
|
|
shards[last + 1].shard_info->offset : OBJECT_MAX_SIZE;
|
|
dout(20) << __func__ << " start=" << start << " last=" << last
|
|
<< " -> 0x" << std::hex << left_bound << "~" << right_bound
|
|
<< std::dec << dendl;
|
|
return {left_bound, right_bound};
|
|
}
|
|
|
|
void BlueStore::ExtentMap::fault_range(
|
|
KeyValueDB *db,
|
|
uint32_t offset,
|
|
uint32_t length)
|
|
{
|
|
dout(30) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
if (shards.size() == 0) {
|
|
// no sharding yet; everyting is loaded
|
|
return;
|
|
}
|
|
auto start = seek_shard(offset);
|
|
auto last = seek_shard(offset + length);
|
|
maybe_load_shard(db, start, last);
|
|
}
|
|
|
|
void BlueStore::ExtentMap::maybe_load_shard(
|
|
KeyValueDB *db,
|
|
int start,
|
|
int last)
|
|
{
|
|
ceph_assert(last >= start);
|
|
ceph_assert(start >= 0);
|
|
|
|
string key;
|
|
while (start <= last) {
|
|
ceph_assert((size_t)start < shards.size());
|
|
auto p = &shards[start];
|
|
if (!p->loaded) {
|
|
dout(30) << __func__ << " opening shard 0x" << std::hex
|
|
<< p->shard_info->offset << std::dec << dendl;
|
|
bufferlist v;
|
|
generate_extent_shard_key_and_apply(
|
|
onode->key, p->shard_info->offset, &key,
|
|
[&](const string& final_key) {
|
|
int r = db->get(PREFIX_OBJ, final_key, &v);
|
|
if (r < 0) {
|
|
derr << __func__ << " missing shard 0x" << std::hex
|
|
<< p->shard_info->offset << std::dec << " for " << onode->oid
|
|
<< dendl;
|
|
ceph_assert(r >= 0);
|
|
}
|
|
}
|
|
);
|
|
p->extents = decode_some(v);
|
|
p->loaded = true;
|
|
uint32_t shard_end =
|
|
(size_t)start + 1 < shards.size() ? (p + 1)->shard_info->offset : OBJECT_MAX_SIZE;
|
|
dout(20) << __func__ << " open shard for range 0x"
|
|
<< std::hex << p->shard_info->offset << "~" << shard_end << std::dec
|
|
<< " (" << v.length() << " bytes)" << dendl;
|
|
ceph_assert(p->dirty == false);
|
|
ceph_assert(v.length() == p->shard_info->bytes);
|
|
onode->c->store->logger->inc(l_bluestore_onode_shard_misses);
|
|
} else {
|
|
onode->c->store->logger->inc(l_bluestore_onode_shard_hits);
|
|
}
|
|
++start;
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentMap::dirty_range(
|
|
uint32_t offset,
|
|
uint32_t length)
|
|
{
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
if (shards.empty()) {
|
|
dout(20) << __func__ << " mark inline shard dirty" << dendl;
|
|
inline_bl.clear();
|
|
return;
|
|
}
|
|
auto start = seek_shard(offset);
|
|
if (length == 0) {
|
|
length = 1;
|
|
}
|
|
auto last = seek_shard(offset + length - 1);
|
|
if (start < 0)
|
|
return;
|
|
|
|
ceph_assert(last >= start);
|
|
while (start <= last) {
|
|
ceph_assert((size_t)start < shards.size());
|
|
auto p = &shards[start];
|
|
if (!p->loaded) {
|
|
derr << __func__ << "on write 0x" << std::hex << offset
|
|
<< "~" << length << " shard 0x" << p->shard_info->offset
|
|
<< std::dec << " is not loaded, can't mark dirty" << dendl;
|
|
ceph_abort_msg("can't mark unloaded shard dirty");
|
|
}
|
|
if (!p->dirty) {
|
|
dout(20) << __func__ << " mark shard 0x" << std::hex
|
|
<< p->shard_info->offset << std::dec << " dirty" << dendl;
|
|
p->dirty = true;
|
|
}
|
|
++start;
|
|
}
|
|
}
|
|
|
|
BlueStore::extent_map_t::iterator BlueStore::ExtentMap::find(
|
|
uint64_t offset)
|
|
{
|
|
Extent dummy(offset);
|
|
return extent_map.find(dummy);
|
|
}
|
|
|
|
BlueStore::extent_map_t::iterator BlueStore::ExtentMap::seek_lextent(
|
|
uint64_t offset)
|
|
{
|
|
Extent dummy(offset);
|
|
auto fp = extent_map.lower_bound(dummy);
|
|
if (fp != extent_map.begin()) {
|
|
--fp;
|
|
if (fp->logical_end() <= offset) {
|
|
++fp;
|
|
}
|
|
}
|
|
return fp;
|
|
}
|
|
|
|
BlueStore::extent_map_t::const_iterator BlueStore::ExtentMap::seek_lextent(
|
|
uint64_t offset) const
|
|
{
|
|
Extent dummy(offset);
|
|
auto fp = extent_map.lower_bound(dummy);
|
|
if (fp != extent_map.begin()) {
|
|
--fp;
|
|
if (fp->logical_end() <= offset) {
|
|
++fp;
|
|
}
|
|
}
|
|
return fp;
|
|
}
|
|
|
|
// Split extent at desired offset.
|
|
// Returns iterator to the right part.
|
|
BlueStore::extent_map_t::iterator BlueStore::ExtentMap::split_at(
|
|
BlueStore::extent_map_t::iterator p, uint32_t offset)
|
|
{
|
|
ceph_assert(p != extent_map.end());
|
|
ceph_assert(p->logical_offset < offset);
|
|
ceph_assert(offset < p->logical_end());
|
|
add(offset, p->blob_offset + (offset - p->logical_offset),
|
|
p->logical_end() - offset, p->blob);
|
|
p->length = offset - p->logical_offset;
|
|
++p;
|
|
return p;
|
|
}
|
|
|
|
// If inside extent split it, and return right part.
|
|
// If not inside extent return extent on right.
|
|
BlueStore::extent_map_t::iterator BlueStore::ExtentMap::maybe_split_at(uint32_t offset)
|
|
{
|
|
auto p = seek_lextent(offset);
|
|
if (p != extent_map.end()) {
|
|
if (p->logical_offset < offset && offset < p->logical_end()) {
|
|
// need to split
|
|
add(offset, p->blob_offset + (offset - p->logical_offset),
|
|
p->logical_end() - offset, p->blob);
|
|
p->length = offset - p->logical_offset;
|
|
++p;
|
|
// check that we moved to proper extent
|
|
ceph_assert(p->logical_offset == offset);
|
|
} else {
|
|
// the extent is either outside offset or exactly at
|
|
}
|
|
}
|
|
return p;
|
|
}
|
|
|
|
// If there exist extent at `offset` return it,
|
|
// otherwise return smallest that `offset < logical_offset`.
|
|
BlueStore::extent_map_t::iterator BlueStore::ExtentMap::seek_nextent(
|
|
uint64_t offset)
|
|
{
|
|
Extent dummy(offset);
|
|
auto p = extent_map.lower_bound(dummy);
|
|
return p;
|
|
}
|
|
|
|
bool BlueStore::ExtentMap::has_any_lextents(uint64_t offset, uint64_t length)
|
|
{
|
|
auto fp = seek_lextent(offset);
|
|
if (fp == extent_map.end() || fp->logical_offset >= offset + length) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int BlueStore::ExtentMap::compress_extent_map(
|
|
uint64_t offset,
|
|
uint64_t length)
|
|
{
|
|
if (extent_map.empty())
|
|
return 0;
|
|
int removed = 0;
|
|
auto p = seek_lextent(offset);
|
|
if (p != extent_map.begin()) {
|
|
--p; // start to the left of offset
|
|
}
|
|
// the caller should have just written to this region
|
|
ceph_assert(p != extent_map.end());
|
|
|
|
// identify the *next* shard
|
|
auto pshard = shards.begin();
|
|
while (pshard != shards.end() &&
|
|
p->logical_offset >= pshard->shard_info->offset) {
|
|
++pshard;
|
|
}
|
|
uint64_t shard_end;
|
|
if (pshard != shards.end()) {
|
|
shard_end = pshard->shard_info->offset;
|
|
} else {
|
|
shard_end = OBJECT_MAX_SIZE;
|
|
}
|
|
|
|
auto n = p;
|
|
for (++n; n != extent_map.end(); p = n++) {
|
|
if (n->logical_offset > offset + length) {
|
|
break; // stop after end
|
|
}
|
|
while (n != extent_map.end() &&
|
|
p->logical_end() == n->logical_offset &&
|
|
p->blob == n->blob &&
|
|
p->blob_offset + p->length == n->blob_offset &&
|
|
n->logical_offset < shard_end) {
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< " next shard 0x" << shard_end << std::dec
|
|
<< " merging " << *p << " and " << *n << dendl;
|
|
p->length += n->length;
|
|
rm(n++);
|
|
++removed;
|
|
}
|
|
if (n == extent_map.end()) {
|
|
break;
|
|
}
|
|
if (n->logical_offset >= shard_end) {
|
|
ceph_assert(pshard != shards.end());
|
|
++pshard;
|
|
if (pshard != shards.end()) {
|
|
shard_end = pshard->shard_info->offset;
|
|
} else {
|
|
shard_end = OBJECT_MAX_SIZE;
|
|
}
|
|
}
|
|
}
|
|
if (removed) {
|
|
onode->c->store->logger->inc(l_bluestore_extent_compress, removed);
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
void BlueStore::ExtentMap::punch_hole(
|
|
CollectionRef &c,
|
|
uint64_t offset,
|
|
uint64_t length,
|
|
old_extent_map_t *old_extents)
|
|
{
|
|
auto p = seek_lextent(offset);
|
|
uint64_t end = offset + length;
|
|
while (p != extent_map.end()) {
|
|
if (p->logical_offset >= end) {
|
|
break;
|
|
}
|
|
if (p->logical_offset < offset) {
|
|
if (p->logical_end() > end) {
|
|
// split and deref middle
|
|
uint64_t front = offset - p->logical_offset;
|
|
OldExtent* oe = OldExtent::create(c, offset, p->blob_offset + front,
|
|
length, p->blob);
|
|
old_extents->push_back(*oe);
|
|
add(end,
|
|
p->blob_offset + front + length,
|
|
p->length - front - length,
|
|
p->blob);
|
|
p->length = front;
|
|
break;
|
|
} else {
|
|
// deref tail
|
|
ceph_assert(p->logical_end() > offset); // else seek_lextent bug
|
|
uint64_t keep = offset - p->logical_offset;
|
|
OldExtent* oe = OldExtent::create(c, offset, p->blob_offset + keep,
|
|
p->length - keep, p->blob);
|
|
old_extents->push_back(*oe);
|
|
p->length = keep;
|
|
++p;
|
|
continue;
|
|
}
|
|
}
|
|
if (p->logical_offset + p->length <= end) {
|
|
// deref whole lextent
|
|
OldExtent* oe = OldExtent::create(c, p->logical_offset, p->blob_offset,
|
|
p->length, p->blob);
|
|
old_extents->push_back(*oe);
|
|
rm(p++);
|
|
continue;
|
|
}
|
|
// deref head
|
|
uint64_t keep = p->logical_end() - end;
|
|
OldExtent* oe = OldExtent::create(c, p->logical_offset, p->blob_offset,
|
|
p->length - keep, p->blob);
|
|
old_extents->push_back(*oe);
|
|
|
|
add(end, p->blob_offset + p->length - keep, keep, p->blob);
|
|
rm(p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
BlueStore::Extent *BlueStore::ExtentMap::set_lextent(
|
|
CollectionRef &c,
|
|
uint64_t logical_offset,
|
|
uint64_t blob_offset, uint64_t length, BlobRef b,
|
|
old_extent_map_t *old_extents)
|
|
{
|
|
// We need to have completely initialized Blob to increment its ref counters.
|
|
ceph_assert(b->get_blob().get_logical_length() != 0);
|
|
|
|
// Do get_ref prior to punch_hole to prevent from putting reused blob into
|
|
// old_extents list if we overwre the blob totally
|
|
// This might happen during WAL overwrite.
|
|
b->get_ref(onode->c, blob_offset, length);
|
|
|
|
if (old_extents) {
|
|
punch_hole(c, logical_offset, length, old_extents);
|
|
}
|
|
|
|
Extent *le = new Extent(logical_offset, blob_offset, length, b);
|
|
extent_map.insert(*le);
|
|
maybe_reshard(logical_offset, logical_offset + length);
|
|
return le;
|
|
}
|
|
|
|
BlueStore::BlobRef BlueStore::ExtentMap::split_blob(
|
|
BlobRef lb,
|
|
uint32_t blob_offset,
|
|
uint32_t pos)
|
|
{
|
|
uint32_t end_pos = pos + lb->get_blob().get_logical_length() - blob_offset;
|
|
dout(20) << __func__ << " 0x" << std::hex << pos << " end 0x" << end_pos
|
|
<< " blob_offset 0x" << blob_offset << std::dec << " " << *lb
|
|
<< dendl;
|
|
BlobRef rb = onode->c->new_blob();
|
|
lb->split(onode->c, blob_offset, rb.get());
|
|
|
|
for (auto ep = seek_lextent(pos);
|
|
ep != extent_map.end() && ep->logical_offset < end_pos;
|
|
++ep) {
|
|
if (ep->blob != lb) {
|
|
continue;
|
|
}
|
|
if (ep->logical_offset < pos) {
|
|
// split extent
|
|
size_t left = pos - ep->logical_offset;
|
|
Extent *ne = new Extent(pos, 0, ep->length - left, rb);
|
|
extent_map.insert(*ne);
|
|
ep->length = left;
|
|
dout(30) << __func__ << " split " << *ep << dendl;
|
|
dout(30) << __func__ << " to " << *ne << dendl;
|
|
} else {
|
|
// switch blob
|
|
ceph_assert(ep->blob_offset >= blob_offset);
|
|
|
|
ep->blob = rb;
|
|
ep->blob_offset -= blob_offset;
|
|
dout(30) << __func__ << " adjusted " << *ep << dendl;
|
|
}
|
|
}
|
|
return rb;
|
|
}
|
|
|
|
BlueStore::ExtentMap::debug_au_vector_t
|
|
BlueStore::ExtentMap::debug_list_disk_layout()
|
|
{
|
|
BlueStore::ExtentMap::debug_au_vector_t res;
|
|
uint32_t l_pos = 0;
|
|
for (auto ep = extent_map.begin(); ep != extent_map.end(); ++ep) {
|
|
if (l_pos < ep->logical_offset) {
|
|
// a hole in logical mapping, mark it
|
|
res.emplace_back(-1ULL, ep->logical_offset - l_pos, 0, 0);
|
|
}
|
|
l_pos = ep->logical_offset + ep->length;
|
|
const bluestore_blob_t& bblob = ep->blob->get_blob();
|
|
uint32_t chunk_size = bblob.get_chunk_size(onode->c->store->block_size);
|
|
uint32_t length_left = ep->length;
|
|
|
|
bluestore_extent_ref_map_t* ref_map = nullptr;
|
|
if (bblob.is_shared()) {
|
|
ceph_assert(ep->blob->is_shared_loaded());
|
|
bluestore_shared_blob_t* bsblob = ep->blob->get_shared_blob()->persistent;
|
|
ref_map = &bsblob->ref_map;
|
|
}
|
|
|
|
unsigned csum_i = 0;
|
|
size_t csum_cnt = 0;
|
|
uint32_t length;
|
|
if (bblob.has_csum()) {
|
|
csum_cnt = bblob.get_csum_count();
|
|
uint32_t csum_chunk_size = bblob.get_csum_chunk_size();
|
|
uint64_t csum_offset_align = p2align(ep->blob_offset, csum_chunk_size);
|
|
csum_i = csum_offset_align / csum_chunk_size;
|
|
// size of first chunk
|
|
length = p2align(ep->blob_offset + csum_chunk_size, csum_chunk_size) - ep->blob_offset;
|
|
length = std::min<uint32_t>(length_left, length);
|
|
if (csum_chunk_size < chunk_size) {
|
|
chunk_size = csum_chunk_size;
|
|
}
|
|
} else {
|
|
length = p2align(ep->blob_offset + chunk_size, chunk_size) - ep->blob_offset;
|
|
length = std::min<uint32_t>(length_left, length);
|
|
}
|
|
|
|
uint32_t bo = ep->blob_offset;
|
|
while (length_left > 0) {
|
|
uint64_t csum_val = 0;
|
|
if (bblob.has_csum()) {
|
|
ceph_assert(csum_cnt > csum_i);
|
|
csum_val = bblob.get_csum_item(csum_i);
|
|
++csum_i;
|
|
}
|
|
//extract AU from extents
|
|
uint64_t disk_extent_left; // length till the end of disk extent
|
|
uint64_t disk_offset = bblob.calc_offset(bo, &disk_extent_left);
|
|
bluestore_extent_ref_map_t::debug_len_cnt l_c = {0, std::numeric_limits<uint32_t>::max()};
|
|
if (bblob.is_shared()) {
|
|
l_c = ref_map->debug_peek(disk_offset);
|
|
if (l_c.len < length) {
|
|
length = l_c.len;
|
|
}
|
|
}
|
|
res.emplace_back(disk_offset, length, csum_val, l_c.cnt);
|
|
bo += length;
|
|
length_left -= length;
|
|
length = chunk_size;
|
|
};
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& out, const BlueStore::ExtentMap::debug_au_vector_t& auv)
|
|
{
|
|
out << "[";
|
|
for (size_t i = 0; i < auv.size(); ++i) {
|
|
if (i != 0) {
|
|
out << " ";
|
|
}
|
|
out << "0x" << std::hex;
|
|
if (auv[i].disk_offset != -1ULL) {
|
|
out << auv[i].disk_offset << "~" << auv[i].disk_length
|
|
<< "(" << std::dec << int32_t(auv[i].ref_cnts)
|
|
<< "):" << std::hex << auv[i].chksum;
|
|
} else {
|
|
out << "~" << auv[i].disk_length << std::dec;
|
|
}
|
|
}
|
|
out << "]" << std::dec;
|
|
return out;
|
|
}
|
|
|
|
// Onode
|
|
//
|
|
// Mapping blobs over Onode's logical offsets.
|
|
//
|
|
// Blob is always continous. Blobs may overlap.
|
|
// Non-mapped regions are "0" when read.
|
|
// 1 2 3
|
|
// 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
// <blob.a.blob.a><blob.b.blo> <blob.c.blob.c.blob.c.blob>
|
|
// <blob.d.blob.d.b> <blob.e.blob.e>
|
|
// blob.a starts at 0x0 length 0xe
|
|
// blob.b starts at 0xf length 0xb
|
|
// blob.c starts at 0x23 length 0x1b
|
|
// blob.d starts at 0x06 length 0x12
|
|
// blob.e starts at 0x2d length 0xf
|
|
//
|
|
// Blobs can have non-encoded parts:
|
|
// 1 2 3
|
|
// 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
// aaaaaa......aaabbbbb...bbbb ccccccccccccccc..........cc
|
|
// dddddd........ddd .....eeeeeeeeee
|
|
// "." - non-encoded parts of blob (holes)
|
|
//
|
|
// Mapping logical to blob:
|
|
// extent_map maps {Onode's logical offset, length}=>{Blob, in-blob offset}
|
|
// {0x0, 0x6}=>{blob.a, 0x0}
|
|
// {0x6, 0x6}=>{blob.d, 0x0}
|
|
// {0xc, 0x3}=>{blob.a, 0xc}
|
|
// {0xf, 0x5}=>{blob.b, 0x0}
|
|
// {0x14, 0x3}=>{blob.d, 0xe}
|
|
// {0x17, 0x4}=>{blob.b, 0x8}
|
|
// a hole here
|
|
// {0x23, 0xe}=>{blob.c, 0x0}
|
|
// and so on...
|
|
//
|
|
// Compressed blobs do not have non-encoded parts.
|
|
// Same example as above but all blobs are compressed:
|
|
// 1 2 3
|
|
// 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
|
// aaaaaaAAAAAAaaabbbbbBBBbbbb cccccccccccccccCCCCCCCCCCcc
|
|
// ddddddDDDDDDDDddd EEEEEeeeeeeeeee
|
|
// A-E: parts of blobs that are never used.
|
|
// This can happen when a compressed blob is overwritten partially.
|
|
// The target ranges are no longer used, but are left there because they are necessary
|
|
// for successful decompression.
|
|
//
|
|
// In compressed blobs PExtentVector and csum refer to actually occupied disk space.
|
|
// Blob's logical length is larger then occupied disk space.
|
|
// Mapping from extent_map always uses offsets of decompressed data.
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.onode(" << this << ")." << __func__ << " "
|
|
|
|
const std::string& BlueStore::Onode::calc_omap_prefix(uint8_t flags)
|
|
{
|
|
if (bluestore_onode_t::is_pgmeta_omap(flags)) {
|
|
return PREFIX_PGMETA_OMAP;
|
|
}
|
|
if (bluestore_onode_t::is_perpg_omap(flags)) {
|
|
return PREFIX_PERPG_OMAP;
|
|
}
|
|
if (bluestore_onode_t::is_perpool_omap(flags)) {
|
|
return PREFIX_PERPOOL_OMAP;
|
|
}
|
|
return PREFIX_OMAP;
|
|
}
|
|
|
|
// '-' < '.' < '~'
|
|
void BlueStore::Onode::calc_omap_header(
|
|
uint8_t flags,
|
|
const Onode* o,
|
|
std::string* out)
|
|
{
|
|
if (!bluestore_onode_t::is_pgmeta_omap(flags)) {
|
|
if (bluestore_onode_t::is_perpg_omap(flags)) {
|
|
_key_encode_u64(o->c->pool(), out);
|
|
_key_encode_u32(o->oid.hobj.get_bitwise_key_u32(), out);
|
|
} else if (bluestore_onode_t::is_perpool_omap(flags)) {
|
|
_key_encode_u64(o->c->pool(), out);
|
|
}
|
|
}
|
|
_key_encode_u64(o->onode.nid, out);
|
|
out->push_back('-');
|
|
}
|
|
|
|
void BlueStore::Onode::calc_omap_key(uint8_t flags,
|
|
const Onode* o,
|
|
const std::string& key,
|
|
std::string* out)
|
|
{
|
|
if (!bluestore_onode_t::is_pgmeta_omap(flags)) {
|
|
if (bluestore_onode_t::is_perpg_omap(flags)) {
|
|
_key_encode_u64(o->c->pool(), out);
|
|
_key_encode_u32(o->oid.hobj.get_bitwise_key_u32(), out);
|
|
} else if (bluestore_onode_t::is_perpool_omap(flags)) {
|
|
_key_encode_u64(o->c->pool(), out);
|
|
}
|
|
}
|
|
_key_encode_u64(o->onode.nid, out);
|
|
out->push_back('.');
|
|
out->append(key);
|
|
}
|
|
|
|
void BlueStore::Onode::calc_omap_tail(
|
|
uint8_t flags,
|
|
const Onode* o,
|
|
std::string* out)
|
|
{
|
|
if (!bluestore_onode_t::is_pgmeta_omap(flags)) {
|
|
if (bluestore_onode_t::is_perpg_omap(flags)) {
|
|
_key_encode_u64(o->c->pool(), out);
|
|
_key_encode_u32(o->oid.hobj.get_bitwise_key_u32(), out);
|
|
} else if (bluestore_onode_t::is_perpool_omap(flags)) {
|
|
_key_encode_u64(o->c->pool(), out);
|
|
}
|
|
}
|
|
_key_encode_u64(o->onode.nid, out);
|
|
out->push_back('~');
|
|
}
|
|
|
|
void BlueStore::Onode::get()
|
|
{
|
|
++nref;
|
|
++pin_nref;
|
|
}
|
|
void BlueStore::Onode::put()
|
|
{
|
|
if (--pin_nref == 1) {
|
|
c->get_onode_cache()->maybe_unpin(this);
|
|
}
|
|
if (--nref == 0) {
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void BlueStore::Onode::decode_raw(
|
|
BlueStore::Onode* on,
|
|
const bufferlist& v,
|
|
BlueStore::ExtentMap::ExtentDecoder& edecoder,
|
|
bool use_onode_segmentation)
|
|
{
|
|
on->exists = true;
|
|
auto p = v.front().begin_deep();
|
|
on->onode.decode(p, use_onode_segmentation ? 0 : bluestore_onode_t::FLAG_DEBUG_FORCE_V2);
|
|
|
|
// initialize extent_map
|
|
edecoder.decode_spanning_blobs(p, on->c);
|
|
ceph_assert(on->prev_spanning_cnt == 0);
|
|
if (on->c) {
|
|
on->prev_spanning_cnt = on->extent_map.spanning_blob_map.size();
|
|
if (on->prev_spanning_cnt != 0) {
|
|
on->c->store->logger->inc(l_bluestore_spanning_blobs, on->prev_spanning_cnt);
|
|
}
|
|
}
|
|
if (on->onode.extent_map_shards.empty()) {
|
|
denc(on->extent_map.inline_bl, p);
|
|
edecoder.decode_some(on->extent_map.inline_bl, on->c);
|
|
}
|
|
}
|
|
|
|
BlueStore::Onode* BlueStore::Onode::create_decode(
|
|
CollectionRef c,
|
|
const ghobject_t& oid,
|
|
const string& key,
|
|
const bufferlist& v,
|
|
bool allow_empty,
|
|
bool use_onode_segmentation)
|
|
{
|
|
ceph_assert(v.length() || allow_empty);
|
|
Onode* on = new Onode(c.get(), oid, (const mempool::bluestore_cache_meta::string)(key));
|
|
|
|
if (v.length()) {
|
|
ExtentMap::ExtentDecoderFull edecoder(on->extent_map);
|
|
decode_raw(on, v, edecoder, use_onode_segmentation);
|
|
|
|
for (auto& i : on->onode.attrs) {
|
|
i.second.reassign_to_mempool(mempool::mempool_bluestore_cache_meta);
|
|
}
|
|
|
|
// initialize extent_map
|
|
if (on->onode.extent_map_shards.empty()) {
|
|
on->extent_map.inline_bl.reassign_to_mempool(
|
|
mempool::mempool_bluestore_cache_data);
|
|
} else {
|
|
on->extent_map.init_shards(false, false);
|
|
}
|
|
} else {
|
|
// init segment_size
|
|
uint32_t segment_size = c->store->segment_size.load();
|
|
if (segment_size != 0 &&
|
|
c->comp_max_blob_size.has_value() &&
|
|
segment_size < c->comp_max_blob_size.value()) {
|
|
segment_size = c->comp_max_blob_size.value(); // compression larger than global segment_size, use it
|
|
}
|
|
on->onode.segment_size = segment_size;
|
|
}
|
|
return on;
|
|
}
|
|
|
|
void BlueStore::Onode::flush()
|
|
{
|
|
if (flushing_count.load()) {
|
|
ldout(c->store->cct, 20) << __func__ << " cnt:" << flushing_count << dendl;
|
|
waiting_count++;
|
|
std::unique_lock l(flush_lock);
|
|
while (flushing_count.load()) {
|
|
flush_cond.wait(l);
|
|
}
|
|
waiting_count--;
|
|
}
|
|
ldout(c->store->cct, 20) << __func__ << " done" << dendl;
|
|
}
|
|
|
|
void BlueStore::Onode::dump(Formatter* f) const
|
|
{
|
|
onode.dump(f);
|
|
extent_map.dump(f);
|
|
}
|
|
|
|
void BlueStore::Onode::rewrite_omap_key(const string& old, string *out)
|
|
{
|
|
if (!onode.is_pgmeta_omap()) {
|
|
if (onode.is_perpg_omap()) {
|
|
_key_encode_u64(c->pool(), out);
|
|
_key_encode_u32(oid.hobj.get_bitwise_key_u32(), out);
|
|
} else if (onode.is_perpool_omap()) {
|
|
_key_encode_u64(c->pool(), out);
|
|
}
|
|
}
|
|
_key_encode_u64(onode.nid, out);
|
|
out->append(old.c_str() + out->length(), old.size() - out->length());
|
|
}
|
|
|
|
size_t BlueStore::Onode::calc_userkey_offset_in_omap_key() const
|
|
{
|
|
size_t pos = sizeof(uint64_t) + 1;
|
|
if (!onode.is_pgmeta_omap()) {
|
|
if (onode.is_perpg_omap()) {
|
|
pos += sizeof(uint64_t) + sizeof(uint32_t);
|
|
} else if (onode.is_perpool_omap()) {
|
|
pos += sizeof(uint64_t);
|
|
}
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
void BlueStore::Onode::decode_omap_key(const string& key, string *user_key)
|
|
{
|
|
*user_key = key.substr(calc_userkey_offset_in_omap_key());
|
|
}
|
|
|
|
|
|
void BlueStore::Onode::finish_write(TransContext* txc, uint32_t offset, uint32_t length)
|
|
{
|
|
while (true) {
|
|
BufferCacheShard *cache = c->cache;
|
|
std::lock_guard l(cache->lock);
|
|
if (cache != c->cache) {
|
|
ldout(cache->cct, 20) << __func__
|
|
<< " raced with sb cache update, was " << cache
|
|
<< ", now " << c->cache << ", retrying"
|
|
<< dendl;
|
|
continue;
|
|
}
|
|
ldout(c->store->cct, 10) << __func__ << " txc " << txc << std::hex
|
|
<< " 0x" << offset << "~" << length << std::dec
|
|
<< dendl;
|
|
bc._finish_write(cache, txc, offset, length);
|
|
break;
|
|
}
|
|
ldout(c->store->cct, 10) << __func__ << " done " << txc << dendl;
|
|
}
|
|
|
|
// =======================================================
|
|
// WriteContext
|
|
|
|
/// Checks for writes to the same pextent within a blob
|
|
bool BlueStore::WriteContext::has_conflict(
|
|
BlobRef b,
|
|
uint64_t loffs,
|
|
uint64_t loffs_end,
|
|
uint64_t min_alloc_size)
|
|
{
|
|
ceph_assert((loffs % min_alloc_size) == 0);
|
|
ceph_assert((loffs_end % min_alloc_size) == 0);
|
|
for (auto w : writes) {
|
|
if (b == w.b) {
|
|
auto loffs2 = p2align(w.logical_offset, min_alloc_size);
|
|
auto loffs2_end = p2roundup(w.logical_offset + w.length0, min_alloc_size);
|
|
if ((loffs <= loffs2 && loffs_end > loffs2) ||
|
|
(loffs >= loffs2 && loffs < loffs2_end)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// =======================================================
|
|
|
|
// DeferredBatch
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.DeferredBatch(" << this << ") "
|
|
#undef dout_context
|
|
#define dout_context cct
|
|
|
|
void BlueStore::DeferredBatch::prepare_write(
|
|
CephContext *cct,
|
|
uint64_t seq, uint64_t offset, uint64_t length,
|
|
bufferlist::const_iterator& blp)
|
|
{
|
|
_discard(cct, offset, length);
|
|
auto i = iomap.insert(make_pair(offset, deferred_io()));
|
|
ceph_assert(i.second); // this should be a new insertion
|
|
i.first->second.seq = seq;
|
|
blp.copy(length, i.first->second.bl);
|
|
i.first->second.bl.reassign_to_mempool(
|
|
mempool::mempool_bluestore_writing_deferred);
|
|
dout(20) << __func__ << " seq " << seq
|
|
<< " 0x" << std::hex << offset << "~" << length
|
|
<< " crc " << i.first->second.bl.crc32c(-1)
|
|
<< std::dec << dendl;
|
|
seq_bytes[seq] += length;
|
|
#ifdef DEBUG_DEFERRED
|
|
_audit(cct);
|
|
#endif
|
|
}
|
|
|
|
void BlueStore::DeferredBatch::_discard(
|
|
CephContext *cct, uint64_t offset, uint64_t length)
|
|
{
|
|
generic_dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
auto p = iomap.lower_bound(offset);
|
|
if (p != iomap.begin()) {
|
|
--p;
|
|
auto end = p->first + p->second.bl.length();
|
|
if (end > offset) {
|
|
bufferlist head;
|
|
head.substr_of(p->second.bl, 0, offset - p->first);
|
|
dout(20) << __func__ << " keep head " << p->second.seq
|
|
<< " 0x" << std::hex << p->first << "~" << p->second.bl.length()
|
|
<< " -> 0x" << head.length() << std::dec << dendl;
|
|
auto i = seq_bytes.find(p->second.seq);
|
|
ceph_assert(i != seq_bytes.end());
|
|
if (end > offset + length) {
|
|
bufferlist tail;
|
|
tail.substr_of(p->second.bl, offset + length - p->first,
|
|
end - (offset + length));
|
|
dout(20) << __func__ << " keep tail " << p->second.seq
|
|
<< " 0x" << std::hex << p->first << "~" << p->second.bl.length()
|
|
<< " -> 0x" << tail.length() << std::dec << dendl;
|
|
auto &n = iomap[offset + length];
|
|
n.bl.swap(tail);
|
|
n.seq = p->second.seq;
|
|
i->second -= length;
|
|
} else {
|
|
i->second -= end - offset;
|
|
}
|
|
ceph_assert(i->second >= 0);
|
|
p->second.bl.swap(head);
|
|
}
|
|
++p;
|
|
}
|
|
while (p != iomap.end()) {
|
|
if (p->first >= offset + length) {
|
|
break;
|
|
}
|
|
auto i = seq_bytes.find(p->second.seq);
|
|
ceph_assert(i != seq_bytes.end());
|
|
auto end = p->first + p->second.bl.length();
|
|
if (end > offset + length) {
|
|
unsigned drop_front = offset + length - p->first;
|
|
unsigned keep_tail = end - (offset + length);
|
|
dout(20) << __func__ << " truncate front " << p->second.seq
|
|
<< " 0x" << std::hex << p->first << "~" << p->second.bl.length()
|
|
<< " drop_front 0x" << drop_front << " keep_tail 0x" << keep_tail
|
|
<< " to 0x" << (offset + length) << "~" << keep_tail
|
|
<< std::dec << dendl;
|
|
auto &s = iomap[offset + length];
|
|
s.seq = p->second.seq;
|
|
s.bl.substr_of(p->second.bl, drop_front, keep_tail);
|
|
i->second -= drop_front;
|
|
} else {
|
|
dout(20) << __func__ << " drop " << p->second.seq
|
|
<< " 0x" << std::hex << p->first << "~" << p->second.bl.length()
|
|
<< std::dec << dendl;
|
|
i->second -= p->second.bl.length();
|
|
}
|
|
ceph_assert(i->second >= 0);
|
|
p = iomap.erase(p);
|
|
}
|
|
}
|
|
|
|
void BlueStore::DeferredBatch::_audit(CephContext *cct)
|
|
{
|
|
map<uint64_t,int> sb;
|
|
for (auto p : seq_bytes) {
|
|
sb[p.first] = 0; // make sure we have the same set of keys
|
|
}
|
|
uint64_t pos = 0;
|
|
for (auto& p : iomap) {
|
|
ceph_assert(p.first >= pos);
|
|
sb[p.second.seq] += p.second.bl.length();
|
|
pos = p.first + p.second.bl.length();
|
|
}
|
|
ceph_assert(sb == seq_bytes);
|
|
}
|
|
|
|
|
|
// Collection
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore(" << store->path << ").collection(" << cid << " " << this << ") "
|
|
|
|
BlueStore::Collection::Collection(BlueStore *store_, OnodeCacheShard *oc, BufferCacheShard *bc, coll_t cid)
|
|
: CollectionImpl(store_->cct, cid),
|
|
store(store_),
|
|
cache(bc),
|
|
exists(true),
|
|
onode_space(oc),
|
|
commit_queue(nullptr)
|
|
{
|
|
}
|
|
|
|
bool BlueStore::Collection::flush_commit(Context *c)
|
|
{
|
|
return osr->flush_commit(c);
|
|
}
|
|
|
|
void BlueStore::Collection::flush()
|
|
{
|
|
osr->flush();
|
|
}
|
|
|
|
void BlueStore::Collection::flush_all_but_last()
|
|
{
|
|
osr->flush_all_but_last();
|
|
}
|
|
|
|
void BlueStore::Collection::open_shared_blob(uint64_t sbid, BlobRef b)
|
|
{
|
|
ceph_assert(!b->get_shared_blob());
|
|
const bluestore_blob_t& blob = b->get_blob();
|
|
if (!blob.is_shared()) {
|
|
return;
|
|
}
|
|
|
|
SharedBlobRef sb = shared_blob_set.lookup(sbid);
|
|
if (sb) {
|
|
b->set_shared_blob(sb);
|
|
ldout(store->cct, 10) << __func__ << " sbid 0x" << std::hex << sbid
|
|
<< std::dec << " had " << *b->get_shared_blob() << dendl;
|
|
} else {
|
|
b->set_shared_blob(new SharedBlob(sbid, this));
|
|
shared_blob_set.add(this, b->get_shared_blob().get());
|
|
ldout(store->cct, 10) << __func__ << " sbid 0x" << std::hex << sbid
|
|
<< std::dec << " opened " << *b->get_shared_blob()
|
|
<< dendl;
|
|
}
|
|
}
|
|
|
|
void BlueStore::Collection::load_shared_blob(SharedBlobRef sb)
|
|
{
|
|
if (!sb->is_loaded()) {
|
|
|
|
bufferlist v;
|
|
string key;
|
|
auto sbid = sb->get_sbid();
|
|
get_shared_blob_key(sbid, &key);
|
|
int r = store->db->get(PREFIX_SHARED_BLOB, key, &v);
|
|
if (r < 0) {
|
|
lderr(store->cct) << __func__ << " sbid 0x" << std::hex << sbid
|
|
<< std::dec << " not found at key "
|
|
<< pretty_binary_string(key) << dendl;
|
|
ceph_abort_msg("uh oh, missing shared_blob");
|
|
}
|
|
|
|
sb->loaded = true;
|
|
sb->persistent = new bluestore_shared_blob_t(sbid);
|
|
auto p = v.cbegin();
|
|
decode(*(sb->persistent), p);
|
|
ldout(store->cct, 10) << __func__ << " sbid 0x" << std::hex << sbid
|
|
<< std::dec << " loaded shared_blob " << *sb << dendl;
|
|
}
|
|
}
|
|
|
|
void BlueStore::Collection::make_blob_shared(uint64_t sbid, BlobRef b)
|
|
{
|
|
ldout(store->cct, 10) << __func__ << " " << *b << dendl;
|
|
|
|
// update blob
|
|
bluestore_blob_t& blob = b->dirty_blob();
|
|
blob.set_flag(bluestore_blob_t::FLAG_SHARED);
|
|
// drop any unused parts, unlikely we could use them in future
|
|
blob.clear_flag(bluestore_blob_t::FLAG_HAS_UNUSED);
|
|
// update shared blob
|
|
b->set_shared_blob(new SharedBlob(sbid, this));
|
|
b->get_shared_blob()->loaded = true;
|
|
b->get_shared_blob()->persistent = new bluestore_shared_blob_t(sbid);
|
|
shared_blob_set.add(this, b->get_shared_blob().get());
|
|
for (auto p : blob.get_extents()) {
|
|
if (p.is_valid()) {
|
|
b->get_shared_blob()->get_ref(
|
|
p.offset,
|
|
p.length);
|
|
}
|
|
}
|
|
ldout(store->cct, 20) << __func__ << " now " << *b << dendl;
|
|
}
|
|
|
|
uint64_t BlueStore::Collection::make_blob_unshared(SharedBlob *sb)
|
|
{
|
|
ldout(store->cct, 10) << __func__ << " " << *sb << dendl;
|
|
ceph_assert(sb->is_loaded());
|
|
|
|
uint64_t sbid = sb->get_sbid();
|
|
shared_blob_set.remove(sb);
|
|
sb->loaded = false;
|
|
delete sb->persistent;
|
|
sb->sbid_unloaded = 0;
|
|
ldout(store->cct, 20) << __func__ << " now " << *sb << dendl;
|
|
return sbid;
|
|
}
|
|
|
|
BlueStore::OnodeRef BlueStore::Collection::get_onode(
|
|
const ghobject_t& oid,
|
|
bool create,
|
|
bool is_createop)
|
|
{
|
|
ceph_assert(create ? ceph_mutex_is_wlocked(lock) : ceph_mutex_is_locked(lock));
|
|
|
|
spg_t pgid;
|
|
if (cid.is_pg(&pgid)) {
|
|
if (!oid.match(cnode.bits, pgid.ps())) {
|
|
lderr(store->cct) << __func__ << " oid " << oid << " not part of "
|
|
<< pgid << " bits " << cnode.bits << dendl;
|
|
ceph_abort();
|
|
}
|
|
}
|
|
|
|
OnodeRef o = onode_space.lookup(oid);
|
|
if (o)
|
|
return o;
|
|
|
|
string key;
|
|
get_object_key(store->cct, oid, &key);
|
|
|
|
ldout(store->cct, 20) << __func__ << " oid " << oid << " key "
|
|
<< pretty_binary_string(key) << dendl;
|
|
|
|
bufferlist v;
|
|
int r = -ENOENT;
|
|
Onode *on;
|
|
if (!is_createop) {
|
|
r = store->db->get(PREFIX_OBJ, key.c_str(), key.size(), &v);
|
|
ldout(store->cct, 20) << " r " << r << " v.len " << v.length() << dendl;
|
|
}
|
|
if (v.length() == 0) {
|
|
ceph_assert(r == -ENOENT);
|
|
if (!create)
|
|
return OnodeRef();
|
|
} else {
|
|
ceph_assert(r >= 0);
|
|
}
|
|
|
|
// new object, load onode if available
|
|
on = Onode::create_decode(this, oid, key, v, true, store->segment_size != 0);
|
|
o.reset(on);
|
|
return onode_space.add_onode(oid, o);
|
|
}
|
|
|
|
void BlueStore::Collection::split_cache(
|
|
Collection *dest)
|
|
{
|
|
ldout(store->cct, 10) << __func__ << " to " << dest << dendl;
|
|
|
|
auto *ocache = get_onode_cache();
|
|
auto *ocache_dest = dest->get_onode_cache();
|
|
|
|
// lock cache shards
|
|
std::lock(ocache->lock, ocache_dest->lock, cache->lock, dest->cache->lock);
|
|
std::lock_guard l(ocache->lock, std::adopt_lock);
|
|
std::lock_guard l2(ocache_dest->lock, std::adopt_lock);
|
|
std::lock_guard l3(cache->lock, std::adopt_lock);
|
|
std::lock_guard l4(dest->cache->lock, std::adopt_lock);
|
|
|
|
int destbits = dest->cnode.bits;
|
|
spg_t destpg;
|
|
bool is_pg = dest->cid.is_pg(&destpg);
|
|
ceph_assert(is_pg);
|
|
|
|
auto p = onode_space.onode_map.begin();
|
|
while (p != onode_space.onode_map.end()) {
|
|
OnodeRef o = p->second;
|
|
if (!p->second->oid.match(destbits, destpg.pgid.ps())) {
|
|
// onode does not belong to this child
|
|
ldout(store->cct, 20) << __func__ << " not moving " << o << " " << o->oid
|
|
<< dendl;
|
|
++p;
|
|
} else {
|
|
ldout(store->cct, 20) << __func__ << " moving " << o << " " << o->oid
|
|
<< dendl;
|
|
|
|
// ensuring that nref is always >= 2 and hence onode is pinned
|
|
OnodeRef o_pin = o;
|
|
|
|
p = onode_space.onode_map.erase(p);
|
|
dest->onode_space.onode_map[o->oid] = o;
|
|
if (o->cached) {
|
|
get_onode_cache()->_move_pinned(dest->get_onode_cache(), o.get());
|
|
}
|
|
o->c = dest;
|
|
|
|
// move over shared blobs and buffers. cover shared blobs from
|
|
// both extent map and spanning blob map (the full extent map
|
|
// may not be faulted in)
|
|
|
|
auto rehome_blob = [&](Blob* b) {
|
|
cache->rm_blob();
|
|
dest->cache->add_blob();
|
|
SharedBlob* sb = b->get_shared_blob().get();
|
|
b->collection = dest;
|
|
if (sb) {
|
|
if (sb->collection == dest) {
|
|
ldout(store->cct, 20) << __func__ << " already moved " << *sb
|
|
<< dendl;
|
|
return;
|
|
}
|
|
ldout(store->cct, 20) << __func__ << " moving " << *b << dendl;
|
|
ldout(store->cct, 20) << __func__ << " moving " << *sb << dendl;
|
|
shared_blob_set.remove(sb);
|
|
dest->shared_blob_set.add(dest, sb);
|
|
sb->collection = dest;
|
|
}
|
|
};
|
|
|
|
for (auto& e : o->extent_map.extent_map) {
|
|
e.blob->last_encoded_id = -1;
|
|
}
|
|
for (auto& b : o->extent_map.spanning_blob_map) {
|
|
b.second->last_encoded_id = -1;
|
|
}
|
|
|
|
for (auto& b : o->bc.buffer_map) {
|
|
ceph_assert(!b.is_writing());
|
|
ldout(store->cct, 1)
|
|
<< __func__ << " moving " << b << dendl;
|
|
dest->cache->_move(cache, &b);
|
|
}
|
|
for (auto& e : o->extent_map.extent_map) {
|
|
cache->rm_extent();
|
|
dest->cache->add_extent();
|
|
Blob* tb = e.blob.get();
|
|
if (tb->last_encoded_id == -1) {
|
|
rehome_blob(tb);
|
|
tb->last_encoded_id = 0;
|
|
}
|
|
}
|
|
for (auto& b : o->extent_map.spanning_blob_map) {
|
|
Blob* tb = b.second.get();
|
|
if (tb->last_encoded_id == -1) {
|
|
// Having blob in spanning but not mapped is an error.
|
|
// It will be dropped during encode_some(),
|
|
// but in the meantime we want cache to be consistent.
|
|
ldout(store->cct, 10) << __func__ << " spanning blob not in map " << *tb << dendl;
|
|
rehome_blob(tb);
|
|
tb->last_encoded_id = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dest->cache->_trim();
|
|
}
|
|
// =======================================================
|
|
|
|
// MempoolThread
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore.MempoolThread "
|
|
#undef dout_context
|
|
#define dout_context store->cct
|
|
|
|
void *BlueStore::MempoolThread::entry()
|
|
{
|
|
std::unique_lock l{lock};
|
|
|
|
uint32_t prev_config_change = store->config_changed.load();
|
|
uint64_t base = store->osd_memory_base;
|
|
double fragmentation = store->osd_memory_expected_fragmentation;
|
|
uint64_t target = store->osd_memory_target;
|
|
uint64_t min = store->osd_memory_cache_min;
|
|
uint64_t max = min;
|
|
|
|
// When setting the maximum amount of memory to use for cache, first
|
|
// assume some base amount of memory for the OSD and then fudge in
|
|
// some overhead for fragmentation that scales with cache usage.
|
|
uint64_t ltarget = (1.0 - fragmentation) * target;
|
|
if (ltarget > base + min) {
|
|
max = ltarget - base;
|
|
}
|
|
|
|
binned_kv_cache = store->db->get_priority_cache();
|
|
binned_kv_onode_cache = store->db->get_priority_cache(PREFIX_OBJ);
|
|
if (store->cache_autotune && binned_kv_cache != nullptr) {
|
|
pcm = std::make_shared<PriorityCache::Manager>(
|
|
store->cct, min, max, target, true, "bluestore-pricache");
|
|
pcm->insert("kv", binned_kv_cache, true);
|
|
pcm->insert("meta", meta_cache, true);
|
|
pcm->insert("data", data_cache, true);
|
|
if (binned_kv_onode_cache != nullptr) {
|
|
pcm->insert("kv_onode", binned_kv_onode_cache, true);
|
|
}
|
|
}
|
|
|
|
utime_t next_balance = ceph_clock_now();
|
|
utime_t next_resize = ceph_clock_now();
|
|
utime_t next_bin_rotation = ceph_clock_now();
|
|
utime_t next_deferred_force_submit = ceph_clock_now();
|
|
utime_t alloc_stats_dump_clock = ceph_clock_now();
|
|
|
|
bool interval_stats_trim = false;
|
|
while (!stop) {
|
|
// Update pcm cache settings if related configuration was changed
|
|
uint32_t cur_config_change = store->config_changed.load();
|
|
if (cur_config_change != prev_config_change) {
|
|
_update_cache_settings();
|
|
prev_config_change = cur_config_change;
|
|
}
|
|
|
|
// define various intervals for background work
|
|
double age_bin_interval = store->cache_age_bin_interval;
|
|
double autotune_interval = store->cache_autotune_interval;
|
|
double resize_interval = store->osd_memory_cache_resize_interval;
|
|
double max_defer_interval = store->max_defer_interval;
|
|
double alloc_stats_dump_interval =
|
|
store->cct->_conf->bluestore_alloc_stats_dump_interval;
|
|
|
|
// alloc stats dump
|
|
if (alloc_stats_dump_interval > 0 &&
|
|
alloc_stats_dump_clock + alloc_stats_dump_interval < ceph_clock_now()) {
|
|
store->_record_allocation_stats();
|
|
alloc_stats_dump_clock = ceph_clock_now();
|
|
}
|
|
// cache age binning
|
|
if (age_bin_interval > 0 && next_bin_rotation < ceph_clock_now()) {
|
|
if (binned_kv_cache != nullptr) {
|
|
binned_kv_cache->import_bins(store->kv_bins);
|
|
}
|
|
if (binned_kv_onode_cache != nullptr) {
|
|
binned_kv_onode_cache->import_bins(store->kv_onode_bins);
|
|
}
|
|
meta_cache->import_bins(store->meta_bins);
|
|
data_cache->import_bins(store->data_bins);
|
|
|
|
if (pcm != nullptr) {
|
|
pcm->shift_bins();
|
|
}
|
|
next_bin_rotation = ceph_clock_now();
|
|
next_bin_rotation += age_bin_interval;
|
|
}
|
|
// cache balancing
|
|
if (autotune_interval > 0 && next_balance < ceph_clock_now()) {
|
|
if (binned_kv_cache != nullptr) {
|
|
binned_kv_cache->set_cache_ratio(store->cache_kv_ratio);
|
|
}
|
|
if (binned_kv_onode_cache != nullptr) {
|
|
binned_kv_onode_cache->set_cache_ratio(store->cache_kv_onode_ratio);
|
|
}
|
|
meta_cache->set_cache_ratio(store->cache_meta_ratio);
|
|
data_cache->set_cache_ratio(store->cache_data_ratio);
|
|
|
|
// Log events at 5 instead of 20 when balance happens.
|
|
interval_stats_trim = true;
|
|
|
|
if (pcm != nullptr) {
|
|
pcm->balance();
|
|
}
|
|
|
|
next_balance = ceph_clock_now();
|
|
next_balance += autotune_interval;
|
|
}
|
|
// memory resizing (ie autotuning)
|
|
if (resize_interval > 0 && next_resize < ceph_clock_now()) {
|
|
if (ceph_using_tcmalloc() && pcm != nullptr) {
|
|
pcm->tune_memory();
|
|
}
|
|
next_resize = ceph_clock_now();
|
|
next_resize += resize_interval;
|
|
}
|
|
// deferred force submit
|
|
if (max_defer_interval > 0 &&
|
|
next_deferred_force_submit < ceph_clock_now()) {
|
|
if (store->get_deferred_last_submitted() + max_defer_interval <
|
|
ceph_clock_now()) {
|
|
store->deferred_try_submit();
|
|
}
|
|
next_deferred_force_submit = ceph_clock_now();
|
|
next_deferred_force_submit += max_defer_interval/3;
|
|
}
|
|
|
|
// Now Resize the shards
|
|
_resize_shards(interval_stats_trim);
|
|
interval_stats_trim = false;
|
|
|
|
store->refresh_perf_counters();
|
|
uint64_t period = store->cct->_conf.get_val<uint64_t>("bluestore_fragmentation_check_period");
|
|
if (period != 0 && store->alloc) {
|
|
auto now = mono_clock::now();
|
|
timespan elapsed = now - last_fragmentation_check;
|
|
if (elapsed > make_timespan(period)) {
|
|
last_fragmentation_check = now;
|
|
double score;
|
|
score = store->alloc->get_fragmentation_score();
|
|
store->logger->set(l_bluestore_fragmentation, score * 1e6);
|
|
now = mono_clock::now();
|
|
elapsed = now - last_fragmentation_check;
|
|
auto seconds = elapsed.count() * 1e-9;
|
|
dout(0) << std::fixed << std::setprecision(6)
|
|
<< "fragmentation_score=" << score << " took=" << seconds << "s" << dendl;
|
|
}
|
|
}
|
|
|
|
auto wait = ceph::make_timespan(
|
|
store->cct->_conf->bluestore_cache_trim_interval);
|
|
cond.wait_for(l, wait);
|
|
}
|
|
// do final dump
|
|
store->_record_allocation_stats();
|
|
stop = false;
|
|
pcm = nullptr;
|
|
return NULL;
|
|
}
|
|
|
|
void BlueStore::MempoolThread::_resize_shards(bool interval_stats)
|
|
{
|
|
size_t onode_shards = store->onode_cache_shards.size();
|
|
size_t buffer_shards = store->buffer_cache_shards.size();
|
|
int64_t kv_used = store->db->get_cache_usage();
|
|
int64_t kv_onode_used = store->db->get_cache_usage(PREFIX_OBJ);
|
|
int64_t meta_used = meta_cache->_get_used_bytes();
|
|
int64_t data_used = data_cache->_get_used_bytes();
|
|
|
|
uint64_t cache_size = store->cache_size;
|
|
int64_t kv_alloc =
|
|
static_cast<int64_t>(store->cache_kv_ratio * cache_size);
|
|
int64_t kv_onode_alloc =
|
|
static_cast<int64_t>(store->cache_kv_onode_ratio * cache_size);
|
|
int64_t meta_alloc =
|
|
static_cast<int64_t>(store->cache_meta_ratio * cache_size);
|
|
int64_t data_alloc =
|
|
static_cast<int64_t>(store->cache_data_ratio * cache_size);
|
|
|
|
if (pcm != nullptr && binned_kv_cache != nullptr) {
|
|
cache_size = pcm->get_tuned_mem();
|
|
kv_alloc = binned_kv_cache->get_committed_size();
|
|
meta_alloc = meta_cache->get_committed_size();
|
|
data_alloc = data_cache->get_committed_size();
|
|
if (binned_kv_onode_cache != nullptr) {
|
|
kv_onode_alloc = binned_kv_onode_cache->get_committed_size();
|
|
}
|
|
}
|
|
|
|
if (interval_stats) {
|
|
dout(5) << __func__ << " cache_size: " << cache_size
|
|
<< " kv_alloc: " << kv_alloc
|
|
<< " kv_used: " << kv_used
|
|
<< " kv_onode_alloc: " << kv_onode_alloc
|
|
<< " kv_onode_used: " << kv_onode_used
|
|
<< " meta_alloc: " << meta_alloc
|
|
<< " meta_used: " << meta_used
|
|
<< " data_alloc: " << data_alloc
|
|
<< " data_used: " << data_used << dendl;
|
|
} else {
|
|
dout(20) << __func__ << " cache_size: " << cache_size
|
|
<< " kv_alloc: " << kv_alloc
|
|
<< " kv_used: " << kv_used
|
|
<< " kv_onode_alloc: " << kv_onode_alloc
|
|
<< " kv_onode_used: " << kv_onode_used
|
|
<< " meta_alloc: " << meta_alloc
|
|
<< " meta_used: " << meta_used
|
|
<< " data_alloc: " << data_alloc
|
|
<< " data_used: " << data_used << dendl;
|
|
}
|
|
|
|
uint64_t max_shard_onodes = static_cast<uint64_t>(
|
|
(meta_alloc / (double) onode_shards) / meta_cache->get_bytes_per_onode());
|
|
uint64_t max_shard_buffer = static_cast<uint64_t>(data_alloc / buffer_shards);
|
|
|
|
dout(30) << __func__ << " max_shard_onodes: " << max_shard_onodes
|
|
<< " max_shard_buffer: " << max_shard_buffer << dendl;
|
|
|
|
for (auto i : store->onode_cache_shards) {
|
|
i->set_max(max_shard_onodes);
|
|
}
|
|
for (auto i : store->buffer_cache_shards) {
|
|
i->set_max(max_shard_buffer);
|
|
}
|
|
}
|
|
|
|
void BlueStore::MempoolThread::_update_cache_settings()
|
|
{
|
|
// Nothing to do if pcm is not used.
|
|
if (pcm == nullptr) {
|
|
return;
|
|
}
|
|
|
|
uint64_t target = store->osd_memory_target;
|
|
uint64_t base = store->osd_memory_base;
|
|
uint64_t min = store->osd_memory_cache_min;
|
|
uint64_t max = min;
|
|
double fragmentation = store->osd_memory_expected_fragmentation;
|
|
|
|
uint64_t ltarget = (1.0 - fragmentation) * target;
|
|
if (ltarget > base + min) {
|
|
max = ltarget - base;
|
|
}
|
|
|
|
// set pcm cache levels
|
|
pcm->set_target_memory(target);
|
|
pcm->set_min_memory(min);
|
|
pcm->set_max_memory(max);
|
|
|
|
dout(5) << __func__ << " updated pcm target: " << target
|
|
<< " pcm min: " << min
|
|
<< " pcm max: " << max
|
|
<< dendl;
|
|
}
|
|
|
|
// =====================================
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore(" << path << ") "
|
|
#undef dout_context
|
|
#define dout_context cct
|
|
|
|
|
|
static void aio_cb(void *priv, void *priv2)
|
|
{
|
|
BlueStore *store = static_cast<BlueStore*>(priv);
|
|
BlueStore::AioContext *c = static_cast<BlueStore::AioContext*>(priv2);
|
|
c->aio_finish(store);
|
|
}
|
|
|
|
static void discard_cb(void *priv, void *priv2)
|
|
{
|
|
BlueStore *store = static_cast<BlueStore*>(priv);
|
|
interval_set<uint64_t> *tmp = static_cast<interval_set<uint64_t>*>(priv2);
|
|
store->handle_discard(*tmp);
|
|
}
|
|
|
|
void BlueStore::handle_discard(interval_set<uint64_t>& to_release)
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
ceph_assert(alloc);
|
|
alloc->release(to_release);
|
|
}
|
|
|
|
BlueStore::BlueStore(CephContext *cct, const string& path)
|
|
: BlueStore(cct, path, 0) {}
|
|
|
|
BlueStore::BlueStore(CephContext *cct,
|
|
const string& path,
|
|
uint64_t _min_alloc_size)
|
|
: ObjectStore(cct, path),
|
|
throttle(cct),
|
|
finisher(cct, "commit_finisher", "cfin"),
|
|
kv_sync_thread(this),
|
|
kv_finalize_thread(this),
|
|
min_alloc_size(_min_alloc_size),
|
|
min_alloc_size_order(std::countr_zero(_min_alloc_size)),
|
|
mempool_thread(this)
|
|
{
|
|
_init_logger();
|
|
cct->_conf.add_observer(this);
|
|
set_cache_shards(1);
|
|
if (cct->_conf->bluefs_cache_enable) {
|
|
bluefscache = std::make_shared<BlueFSLRUCache>(cct->_conf->bluefs_cache_size, cct->_conf->bluefs_cache_evict_size, cct);
|
|
}
|
|
bluestore_bdev_label_require_all = cct->_conf.get_val<bool>("bluestore_bdev_label_require_all");
|
|
asok_hook = new SocketHook(*this);
|
|
}
|
|
|
|
BlueStore::~BlueStore()
|
|
{
|
|
delete asok_hook;
|
|
asok_hook = nullptr;
|
|
cct->_conf.remove_observer(this);
|
|
_shutdown_logger();
|
|
ceph_assert(!mounted);
|
|
ceph_assert(db == NULL);
|
|
ceph_assert(bluefs == NULL);
|
|
ceph_assert(fsid_fd < 0);
|
|
ceph_assert(path_fd < 0);
|
|
for (auto i : onode_cache_shards) {
|
|
delete i;
|
|
}
|
|
for (auto i : buffer_cache_shards) {
|
|
delete i;
|
|
}
|
|
onode_cache_shards.clear();
|
|
buffer_cache_shards.clear();
|
|
}
|
|
|
|
std::vector<std::string> BlueStore::get_tracked_keys() const noexcept
|
|
{
|
|
return {
|
|
"bluestore_csum_type"s,
|
|
"bluestore_compression_mode"s,
|
|
"bluestore_compression_algorithm"s,
|
|
"bluestore_compression_min_blob_size"s,
|
|
"bluestore_compression_min_blob_size_ssd"s,
|
|
"bluestore_compression_min_blob_size_hdd"s,
|
|
"bluestore_compression_max_blob_size"s,
|
|
"bluestore_compression_max_blob_size_ssd"s,
|
|
"bluestore_compression_max_blob_size_hdd"s,
|
|
"bluestore_compression_required_ratio"s,
|
|
"bluestore_max_alloc_size"s,
|
|
"bluestore_prefer_deferred_size"s,
|
|
"bluestore_prefer_deferred_size_hdd"s,
|
|
"bluestore_prefer_deferred_size_ssd"s,
|
|
"bluestore_deferred_batch_ops"s,
|
|
"bluestore_deferred_batch_ops_hdd"s,
|
|
"bluestore_deferred_batch_ops_ssd"s,
|
|
"bluestore_throttle_bytes"s,
|
|
"bluestore_throttle_deferred_bytes"s,
|
|
"bluestore_throttle_cost_per_io_hdd"s,
|
|
"bluestore_throttle_cost_per_io_ssd"s,
|
|
"bluestore_throttle_cost_per_io"s,
|
|
"bluestore_max_blob_size"s,
|
|
"bluestore_max_blob_size_ssd"s,
|
|
"bluestore_max_blob_size_hdd"s,
|
|
"osd_memory_target"s,
|
|
"osd_memory_target_cgroup_limit_ratio"s,
|
|
"osd_memory_base"s,
|
|
"osd_memory_cache_min"s,
|
|
"osd_memory_expected_fragmentation"s,
|
|
"bluestore_cache_autotune"s,
|
|
"bluestore_cache_autotune_interval"s,
|
|
"bluestore_cache_age_bin_interval"s,
|
|
"bluestore_cache_kv_age_bins"s,
|
|
"bluestore_cache_kv_onode_age_bins"s,
|
|
"bluestore_cache_meta_age_bins"s,
|
|
"bluestore_cache_data_age_bins"s,
|
|
"bluestore_warn_on_legacy_statfs"s,
|
|
"bluestore_warn_on_no_per_pool_omap"s,
|
|
"bluestore_warn_on_no_per_pg_omap"s,
|
|
"bluestore_max_defer_interval"s,
|
|
"bluestore_onode_segment_size"s,
|
|
"bluestore_allocator_lookup_policy"s,
|
|
"bluestore_volume_selection_reserved_factor"s,
|
|
"bluestore_volume_selection_reserved"s
|
|
};
|
|
}
|
|
|
|
void BlueStore::handle_conf_change(const ConfigProxy& conf,
|
|
const std::set<std::string> &changed)
|
|
{
|
|
if (changed.count("bluestore_warn_on_legacy_statfs")) {
|
|
_check_legacy_statfs_alert();
|
|
}
|
|
if (changed.count("bluestore_warn_on_no_per_pool_omap") ||
|
|
changed.count("bluestore_warn_on_no_per_pg_omap")) {
|
|
_check_no_per_pg_or_pool_omap_alert();
|
|
}
|
|
|
|
if (changed.count("bluestore_csum_type")) {
|
|
_set_csum();
|
|
}
|
|
if (changed.count("bluestore_compression_mode") ||
|
|
changed.count("bluestore_compression_algorithm") ||
|
|
changed.count("bluestore_compression_min_blob_size") ||
|
|
changed.count("bluestore_compression_max_blob_size")) {
|
|
if (bdev) {
|
|
_set_compression();
|
|
}
|
|
}
|
|
if (changed.count("bluestore_onode_segment_size")) {
|
|
segment_size = (cct->_conf.get_val<Option::size_t>("bluestore_onode_segment_size"));
|
|
}
|
|
if (changed.count("bluestore_max_blob_size") ||
|
|
changed.count("bluestore_max_blob_size_ssd") ||
|
|
changed.count("bluestore_max_blob_size_hdd")) {
|
|
if (bdev) {
|
|
// only after startup
|
|
_set_blob_size();
|
|
}
|
|
}
|
|
if (changed.count("bluestore_prefer_deferred_size") ||
|
|
changed.count("bluestore_prefer_deferred_size_hdd") ||
|
|
changed.count("bluestore_prefer_deferred_size_ssd") ||
|
|
changed.count("bluestore_max_alloc_size") ||
|
|
changed.count("bluestore_deferred_batch_ops") ||
|
|
changed.count("bluestore_deferred_batch_ops_hdd") ||
|
|
changed.count("bluestore_deferred_batch_ops_ssd")) {
|
|
if (bdev) {
|
|
// only after startup
|
|
_set_alloc_sizes();
|
|
}
|
|
}
|
|
if (changed.count("bluestore_throttle_cost_per_io") ||
|
|
changed.count("bluestore_throttle_cost_per_io_hdd") ||
|
|
changed.count("bluestore_throttle_cost_per_io_ssd")) {
|
|
if (bdev) {
|
|
_set_throttle_params();
|
|
}
|
|
}
|
|
if (changed.count("bluestore_throttle_bytes") ||
|
|
changed.count("bluestore_throttle_deferred_bytes") ||
|
|
changed.count("bluestore_throttle_trace_rate")) {
|
|
throttle.reset_throttle(conf);
|
|
}
|
|
if (changed.count("bluestore_max_defer_interval")) {
|
|
if (bdev) {
|
|
_set_max_defer_interval();
|
|
}
|
|
}
|
|
if (changed.count("osd_memory_target") ||
|
|
changed.count("osd_memory_base") ||
|
|
changed.count("osd_memory_cache_min") ||
|
|
changed.count("osd_memory_expected_fragmentation")) {
|
|
_update_osd_memory_options();
|
|
}
|
|
if (changed.count("bluestore_allocator_lookup_policy")) {
|
|
_update_allocator_lookup_policy();
|
|
}
|
|
if (changed.count("bluestore_volume_selection_reserved_factor") ||
|
|
changed.count("bluestore_volume_selection_reserved")) {
|
|
if (bluefs)
|
|
bluefs->update_volume_selector_from_config();
|
|
}
|
|
}
|
|
|
|
void BlueStore::_set_compression()
|
|
{
|
|
auto m = Compressor::get_comp_mode_type(cct->_conf->bluestore_compression_mode);
|
|
if (m) {
|
|
_clear_compression_alert();
|
|
comp_mode = *m;
|
|
} else {
|
|
derr << __func__ << " unrecognized value '"
|
|
<< cct->_conf->bluestore_compression_mode
|
|
<< "' for bluestore_compression_mode, reverting to 'none'"
|
|
<< dendl;
|
|
comp_mode = Compressor::COMP_NONE;
|
|
string s("unknown mode: ");
|
|
s += cct->_conf->bluestore_compression_mode;
|
|
_set_compression_alert(true, s.c_str());
|
|
}
|
|
|
|
if (cct->_conf->bluestore_compression_min_blob_size) {
|
|
comp_min_blob_size = cct->_conf->bluestore_compression_min_blob_size;
|
|
} else {
|
|
ceph_assert(bdev);
|
|
if (_use_rotational_settings()) {
|
|
comp_min_blob_size = cct->_conf->bluestore_compression_min_blob_size_hdd;
|
|
} else {
|
|
comp_min_blob_size = cct->_conf->bluestore_compression_min_blob_size_ssd;
|
|
}
|
|
}
|
|
|
|
if (cct->_conf->bluestore_compression_max_blob_size) {
|
|
comp_max_blob_size = cct->_conf->bluestore_compression_max_blob_size;
|
|
} else {
|
|
ceph_assert(bdev);
|
|
if (_use_rotational_settings()) {
|
|
comp_max_blob_size = cct->_conf->bluestore_compression_max_blob_size_hdd;
|
|
} else {
|
|
comp_max_blob_size = cct->_conf->bluestore_compression_max_blob_size_ssd;
|
|
}
|
|
}
|
|
if (compressors.size() == 0) {
|
|
compressors.resize(Compressor::COMP_ALG_LAST);
|
|
compressors[Compressor::COMP_ALG_NONE] = nullptr;
|
|
int alg = Compressor::COMP_ALG_NONE + 1;
|
|
while (alg < Compressor::COMP_ALG_LAST) {
|
|
compressors[alg] = Compressor::create(cct, alg);
|
|
++alg;
|
|
}
|
|
}
|
|
auto alg_name = cct->_conf->bluestore_compression_algorithm;
|
|
CompressorRef c =
|
|
!alg_name.empty() ? Compressor::create(cct, alg_name) : CompressorRef();
|
|
if (c) {
|
|
ceph_assert(c->get_type() < int(compressors.size()));
|
|
def_compressor_alg = c->get_type();
|
|
alg_name = c->get_type_name(); // let's use actual resulting alg name
|
|
} else {
|
|
if (!alg_name.empty()) {
|
|
derr << __func__ << " unable to initialize " << alg_name.c_str() << " compressor"
|
|
<< dendl;
|
|
_set_compression_alert(false, alg_name.c_str());
|
|
} else {
|
|
_clear_compression_alert();
|
|
}
|
|
def_compressor_alg = Compressor::COMP_ALG_NONE;
|
|
alg_name = "(none)";
|
|
}
|
|
dout(10) << __func__ << " mode " << Compressor::get_comp_mode_name(comp_mode)
|
|
<< " alg " << alg_name
|
|
<< " min_blob " << comp_min_blob_size
|
|
<< " max_blob " << comp_max_blob_size
|
|
<< " segment_size " << segment_size
|
|
<< dendl;
|
|
}
|
|
|
|
void BlueStore::_set_csum()
|
|
{
|
|
csum_type = Checksummer::CSUM_NONE;
|
|
int t = Checksummer::get_csum_string_type(cct->_conf->bluestore_csum_type);
|
|
if (t > Checksummer::CSUM_NONE)
|
|
csum_type = t;
|
|
|
|
dout(10) << __func__ << " csum_type "
|
|
<< Checksummer::get_csum_type_string(csum_type)
|
|
<< dendl;
|
|
}
|
|
|
|
void BlueStore::_set_throttle_params()
|
|
{
|
|
if (cct->_conf->bluestore_throttle_cost_per_io) {
|
|
throttle_cost_per_io = cct->_conf->bluestore_throttle_cost_per_io;
|
|
} else {
|
|
ceph_assert(bdev);
|
|
if (_use_rotational_settings()) {
|
|
throttle_cost_per_io = cct->_conf->bluestore_throttle_cost_per_io_hdd;
|
|
} else {
|
|
throttle_cost_per_io = cct->_conf->bluestore_throttle_cost_per_io_ssd;
|
|
}
|
|
}
|
|
|
|
dout(10) << __func__ << " throttle_cost_per_io " << throttle_cost_per_io
|
|
<< dendl;
|
|
}
|
|
void BlueStore::_set_blob_size()
|
|
{
|
|
if (cct->_conf->bluestore_max_blob_size) {
|
|
max_blob_size = cct->_conf->bluestore_max_blob_size;
|
|
} else {
|
|
ceph_assert(bdev);
|
|
if (_use_rotational_settings()) {
|
|
max_blob_size = cct->_conf->bluestore_max_blob_size_hdd;
|
|
} else {
|
|
max_blob_size = cct->_conf->bluestore_max_blob_size_ssd;
|
|
}
|
|
}
|
|
dout(10) << __func__ << " max_blob_size 0x" << std::hex << max_blob_size
|
|
<< std::dec << dendl;
|
|
}
|
|
|
|
void BlueStore::_update_osd_memory_options()
|
|
{
|
|
osd_memory_target = cct->_conf.get_val<Option::size_t>("osd_memory_target");
|
|
osd_memory_base = cct->_conf.get_val<Option::size_t>("osd_memory_base");
|
|
osd_memory_expected_fragmentation = cct->_conf.get_val<double>("osd_memory_expected_fragmentation");
|
|
osd_memory_cache_min = cct->_conf.get_val<Option::size_t>("osd_memory_cache_min");
|
|
config_changed++;
|
|
dout(10) << __func__
|
|
<< " osd_memory_target " << osd_memory_target
|
|
<< " osd_memory_base " << osd_memory_base
|
|
<< " osd_memory_expected_fragmentation " << osd_memory_expected_fragmentation
|
|
<< " osd_memory_cache_min " << osd_memory_cache_min
|
|
<< dendl;
|
|
}
|
|
|
|
|
|
void BlueStore::_update_allocator_lookup_policy()
|
|
{
|
|
auto policy = cct->_conf.get_val<string>("bluestore_allocator_lookup_policy");
|
|
if (policy == "hdd_optimized") {
|
|
use_last_allocator_lookup_position = true;
|
|
} else if (policy == "ssd_optimized") {
|
|
use_last_allocator_lookup_position = false;
|
|
} else {
|
|
// Apply "auto" policy for everything else.
|
|
// Which means reusing last lookup position for hdds.
|
|
use_last_allocator_lookup_position = _use_rotational_settings();
|
|
}
|
|
dout(5) << __func__
|
|
<< " use_last_lookup_position " << use_last_allocator_lookup_position
|
|
<< dendl;
|
|
}
|
|
|
|
int BlueStore::_set_cache_sizes()
|
|
{
|
|
ceph_assert(bdev);
|
|
cache_autotune = cct->_conf.get_val<bool>("bluestore_cache_autotune");
|
|
cache_autotune_interval =
|
|
cct->_conf.get_val<double>("bluestore_cache_autotune_interval");
|
|
cache_age_bin_interval =
|
|
cct->_conf.get_val<double>("bluestore_cache_age_bin_interval");
|
|
auto _set_bin = [&](std::string conf_name, std::vector<uint64_t>* intervals)
|
|
{
|
|
std::string intervals_str = cct->_conf.get_val<std::string>(conf_name);
|
|
std::istringstream interval_stream(intervals_str);
|
|
std::copy(
|
|
std::istream_iterator<uint64_t>(interval_stream),
|
|
std::istream_iterator<uint64_t>(),
|
|
std::back_inserter(*intervals));
|
|
};
|
|
_set_bin("bluestore_cache_age_bins_kv", &kv_bins);
|
|
_set_bin("bluestore_cache_age_bins_kv_onode", &kv_onode_bins);
|
|
_set_bin("bluestore_cache_age_bins_meta", &meta_bins);
|
|
_set_bin("bluestore_cache_age_bins_data", &data_bins);
|
|
|
|
osd_memory_target = cct->_conf.get_val<Option::size_t>("osd_memory_target");
|
|
osd_memory_base = cct->_conf.get_val<Option::size_t>("osd_memory_base");
|
|
osd_memory_expected_fragmentation =
|
|
cct->_conf.get_val<double>("osd_memory_expected_fragmentation");
|
|
osd_memory_cache_min = cct->_conf.get_val<Option::size_t>("osd_memory_cache_min");
|
|
osd_memory_cache_resize_interval =
|
|
cct->_conf.get_val<double>("osd_memory_cache_resize_interval");
|
|
|
|
if (cct->_conf->bluestore_cache_size) {
|
|
cache_size = cct->_conf->bluestore_cache_size;
|
|
} else {
|
|
// choose global cache size based on backend type
|
|
if (_use_rotational_settings()) {
|
|
cache_size = cct->_conf->bluestore_cache_size_hdd;
|
|
} else {
|
|
cache_size = cct->_conf->bluestore_cache_size_ssd;
|
|
}
|
|
}
|
|
|
|
cache_meta_ratio = cct->_conf.get_val<double>("bluestore_cache_meta_ratio");
|
|
if (cache_meta_ratio < 0 || cache_meta_ratio > 1.0) {
|
|
derr << __func__ << " bluestore_cache_meta_ratio (" << cache_meta_ratio
|
|
<< ") must be in range [0,1.0]" << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
cache_kv_ratio = cct->_conf.get_val<double>("bluestore_cache_kv_ratio");
|
|
if (cache_kv_ratio < 0 || cache_kv_ratio > 1.0) {
|
|
derr << __func__ << " bluestore_cache_kv_ratio (" << cache_kv_ratio
|
|
<< ") must be in range [0,1.0]" << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
cache_kv_onode_ratio = cct->_conf.get_val<double>("bluestore_cache_kv_onode_ratio");
|
|
if (cache_kv_onode_ratio < 0 || cache_kv_onode_ratio > 1.0) {
|
|
derr << __func__ << " bluestore_cache_kv_onode_ratio (" << cache_kv_onode_ratio
|
|
<< ") must be in range [0,1.0]" << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cache_meta_ratio + cache_kv_ratio + cache_kv_onode_ratio > 1.0) {
|
|
derr << __func__ << " bluestore_cache_meta_ratio (" << cache_meta_ratio
|
|
<< ") + bluestore_cache_kv_ratio (" << cache_kv_ratio
|
|
<< ") + bluestore_cache_kv_onode_ratio (" << cache_kv_onode_ratio
|
|
<< ") = " << cache_meta_ratio + cache_kv_ratio + cache_kv_onode_ratio << "; must be <= 1.0"
|
|
<< dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
cache_data_ratio = (double)1.0 -
|
|
(double)cache_meta_ratio -
|
|
(double)cache_kv_ratio -
|
|
(double)cache_kv_onode_ratio;
|
|
if (cache_data_ratio < 0) {
|
|
// deal with floating point imprecision
|
|
cache_data_ratio = 0;
|
|
}
|
|
|
|
dout(1) << __func__ << " cache_size " << cache_size
|
|
<< " meta " << cache_meta_ratio
|
|
<< " kv " << cache_kv_ratio
|
|
<< " kv_onode " << cache_kv_onode_ratio
|
|
<< " data " << cache_data_ratio
|
|
<< dendl;
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::write_meta(const std::string& key, const std::string& value)
|
|
{
|
|
if (!bdev || !bdev->supported_bdev_label()) {
|
|
// skip bdev label section if not supported
|
|
return ObjectStore::write_meta(key, value);
|
|
}
|
|
string p = path + "/block";
|
|
if (bdev_label_valid_locations.empty()) {
|
|
_read_multi_bdev_label(cct, bdev, p, fsid, &bdev_label, &bdev_label_valid_locations,
|
|
&bdev_label_multi, &bdev_label_epoch);
|
|
}
|
|
if (!bdev_label_valid_locations.empty()) {
|
|
bdev_label.meta[key] = value;
|
|
if (bdev_label_multi) {
|
|
bdev_label_epoch++;
|
|
bdev_label.meta["epoch"] = std::to_string(bdev_label_epoch);
|
|
}
|
|
int r = _write_bdev_label(cct, bdev, p, bdev_label, bdev_label_valid_locations);
|
|
ceph_assert(r == 0);
|
|
}
|
|
return ObjectStore::write_meta(key, value);
|
|
}
|
|
|
|
int BlueStore::read_meta(const std::string& key, std::string *value)
|
|
{
|
|
BlockDevice* local_bdev = bdev;
|
|
auto close_bdev = make_scope_guard([&] {
|
|
if (!bdev && local_bdev) {
|
|
local_bdev->close();
|
|
delete local_bdev;
|
|
}
|
|
});
|
|
if (!local_bdev) {
|
|
string p = path + "/block";
|
|
local_bdev = BlockDevice::create(cct, p, nullptr, nullptr, nullptr, nullptr);
|
|
int r = local_bdev->open(p);
|
|
if (r < 0) {
|
|
delete local_bdev;
|
|
local_bdev = nullptr;
|
|
}
|
|
}
|
|
if (!local_bdev || !local_bdev->supported_bdev_label()) {
|
|
// skip bdev label section if not supported
|
|
return ObjectStore::read_meta(key, value);
|
|
}
|
|
string p = path + "/block";
|
|
if (bdev_label_valid_locations.empty()) {
|
|
_read_multi_bdev_label(cct, local_bdev, p, fsid, &bdev_label, &bdev_label_valid_locations,
|
|
&bdev_label_multi, &bdev_label_epoch);
|
|
}
|
|
if (!bdev_label_valid_locations.empty()) {
|
|
auto i = bdev_label.meta.find(key);
|
|
if (i != bdev_label.meta.end()) {
|
|
*value = i->second;
|
|
return 0;
|
|
}
|
|
}
|
|
return ObjectStore::read_meta(key, value);
|
|
}
|
|
|
|
|
|
// Reads configuration.
|
|
// Validates values.
|
|
//
|
|
// In future this should be the only place that reads meta,
|
|
// except initialization of components, like BlueFS, FreeListManager
|
|
//
|
|
// NOTE: Any configuration settings that affect data layout on disk
|
|
// must be persisted to meta.
|
|
int BlueStore::read_meta_conf_check_env()
|
|
{
|
|
int r = 0;
|
|
std::string esb;
|
|
r = read_meta("elastic_shared_blobs",&esb);
|
|
if (r == 0) {
|
|
if (esb != "1" && esb != "0") {
|
|
derr << __func__ << " wrong meta.elastic_shared_blobs=" << esb << dendl;
|
|
r = -EIO;
|
|
} else {
|
|
elastic_shared_blobs = esb == "1";
|
|
}
|
|
} else {
|
|
if (r == -ENOENT) {
|
|
dout(1) << __func__ << " meta.elastic_shared_blobs not set, using legacy mode" << dendl;
|
|
elastic_shared_blobs = false;
|
|
r = 0;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
|
|
void BlueStore::_init_logger()
|
|
{
|
|
PerfCountersBuilder b(cct, "bluestore",
|
|
l_bluestore_first, l_bluestore_last);
|
|
|
|
// space utilization stats
|
|
//****************************************
|
|
b.add_u64(l_bluestore_allocated, "allocated",
|
|
"Sum for allocated bytes",
|
|
"al_b",
|
|
PerfCountersBuilder::PRIO_CRITICAL,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64(l_bluestore_stored, "stored",
|
|
"Sum for stored bytes",
|
|
"st_b",
|
|
PerfCountersBuilder::PRIO_CRITICAL,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64(l_bluestore_fragmentation, "fragmentation_micros",
|
|
"How fragmented bluestore free space is (free extents / max possible number of free extents) * 1000",
|
|
"fbss",
|
|
PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64(l_bluestore_alloc_unit, "alloc_unit",
|
|
"allocation unit size in bytes",
|
|
"au_b",
|
|
PerfCountersBuilder::PRIO_CRITICAL,
|
|
unit_t(UNIT_BYTES));
|
|
//****************************************
|
|
|
|
// Update op processing state latencies
|
|
//****************************************
|
|
b.add_time_avg(l_bluestore_state_prepare_lat, "state_prepare_lat",
|
|
"Average prepare state latency",
|
|
"sprl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_aio_wait_lat, "state_aio_wait_lat",
|
|
"Average aio_wait state latency",
|
|
"sawl", PerfCountersBuilder::PRIO_INTERESTING);
|
|
b.add_time_avg(l_bluestore_state_io_done_lat, "state_io_done_lat",
|
|
"Average io_done state latency",
|
|
"sidl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_kv_queued_lat, "state_kv_queued_lat",
|
|
"Average kv_queued state latency",
|
|
"skql", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_kv_committing_lat, "state_kv_commiting_lat",
|
|
"Average kv_commiting state latency",
|
|
"skcl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_kv_done_lat, "state_kv_done_lat",
|
|
"Average kv_done state latency",
|
|
"skdl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_finishing_lat, "state_finishing_lat",
|
|
"Average finishing state latency",
|
|
"sfnl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_done_lat, "state_done_lat",
|
|
"Average done state latency",
|
|
"sdnl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_deferred_queued_lat, "state_deferred_queued_lat",
|
|
"Average deferred_queued state latency",
|
|
"sdql", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_deferred_aio_wait_lat, "state_deferred_aio_wait_lat",
|
|
"Average aio_wait state latency",
|
|
"sdal", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_state_deferred_cleanup_lat, "state_deferred_cleanup_lat",
|
|
"Average cleanup state latency",
|
|
"sdcl", PerfCountersBuilder::PRIO_USEFUL);
|
|
//****************************************
|
|
|
|
// Update Transaction stats
|
|
//****************************************
|
|
b.add_time_avg(l_bluestore_throttle_lat, "txc_throttle_lat",
|
|
"Average submit throttle latency",
|
|
"th_l", PerfCountersBuilder::PRIO_CRITICAL);
|
|
b.add_time_avg(l_bluestore_submit_lat, "txc_submit_lat",
|
|
"Average submit latency",
|
|
"s_l", PerfCountersBuilder::PRIO_CRITICAL);
|
|
b.add_time_avg(l_bluestore_commit_lat, "txc_commit_lat",
|
|
"Average commit latency",
|
|
"c_l", PerfCountersBuilder::PRIO_CRITICAL);
|
|
b.add_u64_counter(l_bluestore_txc, "txc_count", "Transactions committed");
|
|
//****************************************
|
|
|
|
// Read op stats
|
|
//****************************************
|
|
b.add_time_avg(l_bluestore_read_onode_meta_lat, "read_onode_meta_lat",
|
|
"Average read onode metadata latency",
|
|
"roml", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_read_wait_aio_lat, "read_wait_aio_lat",
|
|
"Average read I/O waiting latency",
|
|
"rwal", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_csum_lat, "csum_lat",
|
|
"Average checksum latency",
|
|
"csml", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_read_eio, "read_eio",
|
|
"Read EIO errors propagated to high level callers");
|
|
b.add_u64_counter(l_bluestore_reads_with_retries, "reads_with_retries",
|
|
"Read operations that required at least one retry due to failed checksum validation",
|
|
"rd_r", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_read_lat, "read_lat",
|
|
"Average read latency",
|
|
"r_l", PerfCountersBuilder::PRIO_CRITICAL);
|
|
//****************************************
|
|
|
|
// kv_thread latencies
|
|
//****************************************
|
|
b.add_time_avg(l_bluestore_kv_flush_lat, "kv_flush_lat",
|
|
"Average kv_thread flush latency",
|
|
"kfsl", PerfCountersBuilder::PRIO_INTERESTING);
|
|
b.add_time_avg(l_bluestore_kv_commit_lat, "kv_commit_lat",
|
|
"Average kv_thread commit latency",
|
|
"kcol", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_kv_sync_lat, "kv_sync_lat",
|
|
"Average kv_sync thread latency",
|
|
"kscl", PerfCountersBuilder::PRIO_INTERESTING);
|
|
b.add_time_avg(l_bluestore_kv_final_lat, "kv_final_lat",
|
|
"Average kv_finalize thread latency",
|
|
"kfll", PerfCountersBuilder::PRIO_INTERESTING);
|
|
//****************************************
|
|
|
|
// write op stats
|
|
//****************************************
|
|
b.add_time_avg(l_bluestore_write_lat, "write_lat",
|
|
"write_op average execution time",
|
|
"aw", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_write_big, "write_big",
|
|
"Large aligned writes into fresh blobs");
|
|
b.add_u64_counter(l_bluestore_write_big_bytes, "write_big_bytes",
|
|
"Large aligned writes into fresh blobs (bytes)",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64_counter(l_bluestore_write_big_blobs, "write_big_blobs",
|
|
"Large aligned writes into fresh blobs (blobs)");
|
|
b.add_u64_counter(l_bluestore_write_big_deferred,
|
|
"write_big_deferred",
|
|
"Big overwrites using deferred");
|
|
|
|
b.add_u64_counter(l_bluestore_write_small, "write_small",
|
|
"Small writes into existing or sparse small blobs");
|
|
b.add_u64_counter(l_bluestore_write_small_bytes, "write_small_bytes",
|
|
"Small writes into existing or sparse small blobs (bytes)",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64_counter(l_bluestore_write_small_unused,
|
|
"write_small_unused",
|
|
"Small writes into unused portion of existing blob");
|
|
b.add_u64_counter(l_bluestore_write_small_pre_read,
|
|
"write_small_pre_read",
|
|
"Small writes that required we read some data (possibly "
|
|
"cached) to fill out the block");
|
|
|
|
b.add_u64_counter(l_bluestore_write_pad_bytes, "write_pad_bytes",
|
|
"Sum for write-op padded bytes",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64_counter(l_bluestore_write_penalty_read_ops, "write_penalty_read_ops",
|
|
"Sum for write penalty read ops");
|
|
b.add_u64_counter(l_bluestore_write_new, "write_new",
|
|
"Write into new blob");
|
|
|
|
b.add_u64_counter(l_bluestore_issued_deferred_writes,
|
|
"issued_deferred_writes",
|
|
"Total deferred writes issued");
|
|
b.add_u64_counter(l_bluestore_issued_deferred_write_bytes,
|
|
"issued_deferred_write_bytes",
|
|
"Total bytes in issued deferred writes",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64_counter(l_bluestore_submitted_deferred_writes,
|
|
"submitted_deferred_writes",
|
|
"Total deferred writes submitted to disk");
|
|
b.add_u64_counter(l_bluestore_submitted_deferred_write_bytes,
|
|
"submitted_deferred_write_bytes",
|
|
"Total bytes submitted to disk by deferred writes",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
|
|
b.add_u64_counter(l_bluestore_write_big_skipped_blobs,
|
|
"write_big_skipped_blobs",
|
|
"Large aligned writes into fresh blobs skipped due to zero detection (blobs)");
|
|
b.add_u64_counter(l_bluestore_write_big_skipped_bytes,
|
|
"write_big_skipped_bytes",
|
|
"Large aligned writes into fresh blobs skipped due to zero detection (bytes)");
|
|
b.add_u64_counter(l_bluestore_write_small_skipped,
|
|
"write_small_skipped",
|
|
"Small writes into existing or sparse small blobs skipped due to zero detection");
|
|
b.add_u64_counter(l_bluestore_write_small_skipped_bytes,
|
|
"write_small_skipped_bytes",
|
|
"Small writes into existing or sparse small blobs skipped due to zero detection (bytes)");
|
|
//****************************************
|
|
|
|
// compressions stats
|
|
//****************************************
|
|
b.add_u64(l_bluestore_compressed, "compressed",
|
|
"Sum for stored compressed bytes",
|
|
"c", PerfCountersBuilder::PRIO_USEFUL, unit_t(UNIT_BYTES));
|
|
b.add_u64(l_bluestore_compressed_allocated, "compressed_allocated",
|
|
"Sum for bytes allocated for compressed data",
|
|
"c_a", PerfCountersBuilder::PRIO_USEFUL, unit_t(UNIT_BYTES));
|
|
b.add_u64(l_bluestore_compressed_original, "compressed_original",
|
|
"Sum for original bytes that were compressed",
|
|
"c_o", PerfCountersBuilder::PRIO_USEFUL, unit_t(UNIT_BYTES));
|
|
b.add_time_avg(l_bluestore_compress_lat, "compress_lat",
|
|
"Average compress latency",
|
|
"_cpl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_decompress_lat, "decompress_lat",
|
|
"Average decompress latency",
|
|
"dcpl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_compress_success_count, "compress_success_count",
|
|
"Sum for beneficial compress ops");
|
|
b.add_u64_counter(l_bluestore_compress_rejected_count, "compress_rejected_count",
|
|
"Sum for compress ops rejected due to low net gain of space");
|
|
//****************************************
|
|
|
|
// onode cache stats
|
|
//****************************************
|
|
b.add_u64(l_bluestore_onodes, "onodes",
|
|
"Number of onodes in cache");
|
|
b.add_u64(l_bluestore_pinned_onodes, "onodes_pinned",
|
|
"Number of pinned onodes in cache");
|
|
b.add_u64_counter(l_bluestore_onode_hits, "onode_hits",
|
|
"Count of onode cache lookup hits",
|
|
"o_ht", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_onode_misses, "onode_misses",
|
|
"Count of onode cache lookup misses",
|
|
"o_ms", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_onode_shard_hits, "onode_shard_hits",
|
|
"Count of onode shard cache lookups hits");
|
|
b.add_u64_counter(l_bluestore_onode_shard_misses,
|
|
"onode_shard_misses",
|
|
"Count of onode shard cache lookups misses");
|
|
b.add_u64(l_bluestore_extents, "onode_extents",
|
|
"Number of extents in cache");
|
|
b.add_u64(l_bluestore_blobs, "onode_blobs",
|
|
"Number of blobs in cache");
|
|
b.add_u64(l_bluestore_spanning_blobs, "onode_spanning_blobs",
|
|
"Number of spanning blobs in loaded onodes");
|
|
//****************************************
|
|
|
|
// buffer cache stats
|
|
//****************************************
|
|
b.add_u64(l_bluestore_buffers, "buffers",
|
|
"Number of buffers in cache");
|
|
b.add_u64(l_bluestore_buffer_bytes, "buffer_bytes",
|
|
"Number of buffer bytes in cache",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64_counter(l_bluestore_buffer_hit_bytes, "buffer_hit_bytes",
|
|
"Sum for bytes of read hit in the cache",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
b.add_u64_counter(l_bluestore_buffer_miss_bytes, "buffer_miss_bytes",
|
|
"Sum for bytes of read missed in the cache",
|
|
NULL,
|
|
PerfCountersBuilder::PRIO_DEBUGONLY,
|
|
unit_t(UNIT_BYTES));
|
|
//****************************************
|
|
|
|
// internal stats
|
|
//****************************************
|
|
b.add_u64_counter(l_bluestore_onode_reshard, "onode_reshard",
|
|
"Onode extent map reshard events");
|
|
b.add_u64_counter(l_bluestore_blob_split, "blob_split",
|
|
"Sum for blob splitting due to resharding");
|
|
b.add_u64_counter(l_bluestore_extent_compress, "extent_compress",
|
|
"Sum for extents that have been removed due to compression");
|
|
b.add_u64_counter(l_bluestore_gc_merged, "gc_merged",
|
|
"Sum for extents that have been merged due to garbage "
|
|
"collection");
|
|
//****************************************
|
|
// misc
|
|
//****************************************
|
|
b.add_u64_counter(l_bluestore_omap_iterator_count, "omap_iterator_count",
|
|
"Open omap iterators count");
|
|
b.add_u64_counter(l_bluestore_omap_setheader_count, "omap_setheader_count",
|
|
"amount of omap setheader calls");
|
|
b.add_u64_counter(l_bluestore_omap_setheader_bytes, "omap_setheader_bytes",
|
|
"amount of bytes set by omap setheader calls");
|
|
b.add_u64_counter(l_bluestore_omap_setkeys_count, "omap_setkeys_count",
|
|
"amount of omap setkeys calls");
|
|
b.add_u64_counter(l_bluestore_omap_setkeys_records, "omap_setkeys_records",
|
|
"amount of keys set by omap setkeys calls");
|
|
b.add_u64_counter(l_bluestore_omap_setkeys_bytes, "omap_setkeys_bytes",
|
|
"amount of bytes set by omap setkeys calls");
|
|
b.add_u64_counter(l_bluestore_omap_rmkeys_count, "omap_rmkeys_count",
|
|
"amount of omap keys removed via rmkeys");
|
|
b.add_u64_counter(l_bluestore_omap_rmkey_ranges_count, "omap_rmkey_range_count",
|
|
"amount of omap key ranges removed via rmkeys");
|
|
|
|
//****************************************
|
|
// other client ops latencies
|
|
//****************************************
|
|
b.add_time_avg(l_bluestore_omap_upper_bound_lat, "omap_upper_bound_lat",
|
|
"Average omap iterator upper_bound call latency",
|
|
"oubl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_omap_lower_bound_lat, "omap_lower_bound_lat",
|
|
"Average omap iterator lower_bound call latency",
|
|
"olbl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_omap_next_lat, "omap_next_lat",
|
|
"Average omap iterator next call latency",
|
|
"onxl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_omap_get_keys_lat, "omap_get_keys_lat",
|
|
"Average omap get_keys call latency",
|
|
"ogkl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_omap_get_values_lat, "omap_get_values_lat",
|
|
"Average omap get_values call latency",
|
|
"ogvl", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_omap_clear_lat, "omap_clear_lat",
|
|
"Average omap clear call latency");
|
|
b.add_time_avg(l_bluestore_clist_lat, "clist_lat",
|
|
"Average collection listing latency",
|
|
"cl_l", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_remove_lat, "remove_lat",
|
|
"Average removal latency",
|
|
"rm_l", PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_time_avg(l_bluestore_truncate_lat, "truncate_lat",
|
|
"Average truncate latency",
|
|
"tr_l", PerfCountersBuilder::PRIO_USEFUL);
|
|
//****************************************
|
|
|
|
// slow op count
|
|
//****************************************
|
|
b.add_u64_counter(l_bluestore_slow_aio_wait_count,
|
|
"slow_aio_wait_count",
|
|
"Slow op count for aio wait",
|
|
"sawc",
|
|
PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_slow_committed_kv_count,
|
|
"slow_committed_kv_count",
|
|
"Slow op count for committed kv",
|
|
"sckc",
|
|
PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_slow_read_onode_meta_count,
|
|
"slow_read_onode_meta_count",
|
|
"Slow op count for read onode meta",
|
|
"sroc",
|
|
PerfCountersBuilder::PRIO_USEFUL);
|
|
b.add_u64_counter(l_bluestore_slow_read_wait_aio_count,
|
|
"slow_read_wait_aio_count",
|
|
"Slow op count for read wait aio",
|
|
"srwc",
|
|
PerfCountersBuilder::PRIO_USEFUL);
|
|
|
|
// Resulting size axis configuration for op histograms, values are in bytes
|
|
PerfHistogramCommon::axis_config_d alloc_hist_x_axis_config{
|
|
"Given size (bytes)",
|
|
PerfHistogramCommon::SCALE_LOG2, ///< Request size in logarithmic scale
|
|
0, ///< Start at 0
|
|
4096, ///< Quantization unit
|
|
13, ///< Enough to cover 4+M requests
|
|
};
|
|
// Req size axis configuration for op histograms, values are in bytes
|
|
PerfHistogramCommon::axis_config_d alloc_hist_y_axis_config{
|
|
"Request size (bytes)",
|
|
PerfHistogramCommon::SCALE_LOG2, ///< Request size in logarithmic scale
|
|
0, ///< Start at 0
|
|
4096, ///< Quantization unit
|
|
13, ///< Enough to cover 4+M requests
|
|
};
|
|
b.add_u64_counter_histogram(
|
|
l_bluestore_allocate_hist, "allocate_histogram",
|
|
alloc_hist_x_axis_config, alloc_hist_y_axis_config,
|
|
"Histogram of requested block allocations vs. given ones");
|
|
b.add_time_avg(l_bluestore_allocator_lat, "allocator_lat",
|
|
"Average bluestore allocator latency",
|
|
"bsal",
|
|
PerfCountersBuilder::PRIO_USEFUL);
|
|
|
|
logger = b.create_perf_counters();
|
|
cct->get_perfcounters_collection()->add(logger);
|
|
}
|
|
|
|
int BlueStore::_reload_logger()
|
|
{
|
|
struct store_statfs_t store_statfs;
|
|
int r = statfs(&store_statfs);
|
|
if (r >= 0) {
|
|
logger->set(l_bluestore_allocated, store_statfs.allocated);
|
|
logger->set(l_bluestore_stored, store_statfs.data_stored);
|
|
logger->set(l_bluestore_compressed, store_statfs.data_compressed);
|
|
logger->set(l_bluestore_compressed_allocated, store_statfs.data_compressed_allocated);
|
|
logger->set(l_bluestore_compressed_original, store_statfs.data_compressed_original);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_shutdown_logger()
|
|
{
|
|
cct->get_perfcounters_collection()->remove(logger);
|
|
delete logger;
|
|
}
|
|
|
|
int BlueStore::get_block_device_fsid(CephContext* cct, const string& path,
|
|
uuid_d *fsid)
|
|
{
|
|
bluestore_bdev_label_t label;
|
|
string bdev_path = path + "/block";
|
|
unique_ptr<BlockDevice> bdev(BlockDevice::create(cct, bdev_path, nullptr, nullptr, nullptr, nullptr));
|
|
int r = bdev->open(bdev_path);
|
|
if (r == 0) {
|
|
r = _read_multi_bdev_label(cct, bdev.get(), bdev_path, uuid_d(), &label);
|
|
}
|
|
if (r == 0) {
|
|
*fsid = label.osd_uuid;
|
|
}
|
|
bdev->close();
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_open_path()
|
|
{
|
|
// sanity check(s)
|
|
ceph_assert(path_fd < 0);
|
|
path_fd = TEMP_FAILURE_RETRY(::open(path.c_str(), O_DIRECTORY|O_CLOEXEC));
|
|
if (path_fd < 0) {
|
|
int r = -errno;
|
|
derr << __func__ << " unable to open " << path << ": " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_close_path()
|
|
{
|
|
VOID_TEMP_FAILURE_RETRY(::close(path_fd));
|
|
path_fd = -1;
|
|
}
|
|
|
|
int BlueStore::_write_bdev_label(
|
|
CephContext *cct,
|
|
BlockDevice* bdev,
|
|
const string &path,
|
|
bluestore_bdev_label_t label,
|
|
std::vector<uint64_t> locations)
|
|
{
|
|
dout(10) << __func__ << " path " << path << " label " << label
|
|
<< " locations " << locations << dendl;
|
|
bufferlist bl;
|
|
encode(label, bl);
|
|
uint32_t crc = bl.crc32c(-1);
|
|
encode(crc, bl);
|
|
ceph_assert(bl.length() <= BDEV_LABEL_BLOCK_SIZE);
|
|
bufferptr z(BDEV_LABEL_BLOCK_SIZE - bl.length());
|
|
z.zero();
|
|
bl.append(std::move(z));
|
|
|
|
bl.rebuild_aligned_size_and_memory(BDEV_LABEL_BLOCK_SIZE, BDEV_LABEL_BLOCK_SIZE, IOV_MAX);
|
|
int r = 0;
|
|
ceph_assert(locations.size() > 0);
|
|
uint64_t dev_size = bdev->get_size();
|
|
int failed_r = 0;
|
|
bool wrote_at_least_one = false;
|
|
for (uint64_t position : locations) {
|
|
if (position + BDEV_LABEL_BLOCK_SIZE <= dev_size) {
|
|
r = bdev->write(position, bl, false);
|
|
if (r == 0) {
|
|
wrote_at_least_one = true;
|
|
} else {
|
|
derr << __func__ << " failed to write to " << path
|
|
<< " at location 0x" << std::hex << position << std::dec
|
|
<< ": " << cpp_strerror(r) << dendl;
|
|
failed_r = r;
|
|
}
|
|
}
|
|
}
|
|
if (!wrote_at_least_one) {
|
|
derr << __func__ << " failed to write to any of bdev locations" << dendl;
|
|
r = failed_r;
|
|
goto out;
|
|
}
|
|
r = bdev->flush();
|
|
if (r < 0) {
|
|
derr << __func__ << " failed to fsync " << path
|
|
<< ": " << cpp_strerror(r) << dendl;
|
|
}
|
|
out:
|
|
return r;
|
|
}
|
|
/*
|
|
Reads bdev label at specific position.
|
|
|
|
Returns:
|
|
0 - label read successful
|
|
1 - position outside device
|
|
<0 - error
|
|
*/
|
|
int BlueStore::_read_bdev_label(
|
|
CephContext* cct,
|
|
BlockDevice* bdev,
|
|
const std::string &path,
|
|
bluestore_bdev_label_t *label,
|
|
uint64_t disk_position)
|
|
{
|
|
dout(10) << __func__ << " position=0x" << std::hex << disk_position << std::dec << dendl;
|
|
ceph_assert(bdev);
|
|
bufferlist bl;
|
|
unique_ptr<char[]> buf(new char[BDEV_LABEL_BLOCK_SIZE]);
|
|
uint64_t dev_size = bdev->get_size();
|
|
if (dev_size < disk_position + BDEV_LABEL_BLOCK_SIZE) {
|
|
dout(10) << __func__ << " position=0x" << std::hex << disk_position
|
|
<< " dev size=0x" << dev_size << std::dec << dendl;
|
|
return 1;
|
|
}
|
|
int r = bdev->read_random(disk_position, BDEV_LABEL_BLOCK_SIZE, buf.get(), false);
|
|
if (r < 0) {
|
|
derr << __func__ << " failed to read from " << path
|
|
<< " at 0x" << std::hex << disk_position << std::dec
|
|
<<": " << cpp_strerror(r) << dendl;
|
|
return -EIO;
|
|
}
|
|
|
|
bl.append(buf.get(), BDEV_LABEL_BLOCK_SIZE);
|
|
uint32_t crc, expected_crc;
|
|
auto p = bl.cbegin();
|
|
try {
|
|
decode(*label, p);
|
|
bufferlist t;
|
|
t.substr_of(bl, 0, p.get_off());
|
|
crc = t.crc32c(-1);
|
|
decode(expected_crc, p);
|
|
}
|
|
catch (ceph::buffer::error& e) {
|
|
// We can still get here in non-erroneous scenarios,
|
|
// hence do not log that as an error
|
|
dout(0) << __func__ << " " << path.c_str() << " data at 0x" << std::hex << disk_position
|
|
<< std::dec << ", " << "unable to decode label "
|
|
<< dendl;
|
|
return -ENOENT;
|
|
}
|
|
if (crc != expected_crc) {
|
|
// We can still get here in non-erroneousscenarios,
|
|
// hence do not log that as an error
|
|
dout(0) << __func__ << " bad crc on label, expected " << expected_crc
|
|
<< " != actual " << crc << dendl;
|
|
return -EIO;
|
|
}
|
|
dout(10) << __func__ << " got " << *label << dendl;
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Reads device label.
|
|
fsid - Fsid to look for. If zero, accept any.
|
|
*out_label - Filled if reading of label is considered successful.
|
|
*out_valid_positions - List of locations that contained valid labels.
|
|
*out_is_multi - Whether the label is regular or multi label with epoch.
|
|
*out_epoch - Epoch of label.
|
|
|
|
Returns:
|
|
0 When all label are read
|
|
1 When some, but not all labels are read
|
|
-ENOENT Otherwise
|
|
*/
|
|
int BlueStore::_read_multi_bdev_label(
|
|
CephContext* cct,
|
|
BlockDevice* bdev,
|
|
const string& path,
|
|
uuid_d fsid,
|
|
bluestore_bdev_label_t *out_label,
|
|
std::vector<uint64_t>* out_valid_positions,
|
|
bool* out_is_multi,
|
|
int64_t* out_epoch)
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
ceph_assert(bdev);
|
|
ceph_assert(out_label);
|
|
// go and try read all possible bdev labels.
|
|
// if only first bdev label is correct, it must not have "multi=yes" key.
|
|
bool any_fsid = cct->_conf->bluestore_debug_permit_any_bdev_label;
|
|
int64_t epoch = -1;
|
|
bool all_labels_valid = true;
|
|
for (uint64_t position : bdev_label_positions) {
|
|
bluestore_bdev_label_t label;
|
|
int r = _read_bdev_label(cct, bdev, path, &label, position);
|
|
if (r == 0 && (fsid.is_zero() || label.osd_uuid == fsid || any_fsid)) {
|
|
auto i = label.meta.find("multi");
|
|
bool is_multi = i != label.meta.end() && i->second == "yes";
|
|
if (position == BDEV_FIRST_LABEL_POSITION && !is_multi) {
|
|
// we have a single-label case
|
|
*out_label = label;
|
|
if (out_is_multi) {
|
|
*out_is_multi = false;
|
|
}
|
|
if(out_valid_positions) {
|
|
out_valid_positions->push_back(position);
|
|
}
|
|
goto done;
|
|
}
|
|
if (!is_multi) {
|
|
// for not base bdev position, it has to be cloned to be considered
|
|
continue;
|
|
}
|
|
//from now on, only accept same fsid, unless overriden by config
|
|
fsid = label.osd_uuid;
|
|
i = label.meta.find("epoch");
|
|
if (i != label.meta.end()) {
|
|
int64_t v = atoll(i->second.c_str());
|
|
if (v > epoch) {
|
|
epoch = v;
|
|
*out_label = label;
|
|
if(out_valid_positions) {
|
|
// clear out old versions
|
|
out_valid_positions->clear();
|
|
}
|
|
} else if (v < epoch) {
|
|
derr << __func__ << " label at 0x" << std::hex << position << std::dec
|
|
<< " has outdated epoch=" << v << " current=" << epoch << dendl;
|
|
}
|
|
if(v == epoch && out_valid_positions) {
|
|
out_valid_positions->push_back(position);
|
|
}
|
|
} else {
|
|
derr << __func__ << " label at 0x" << std::hex << position << std::dec
|
|
<< " is multi=yes but no epoch" << dendl;
|
|
}
|
|
} else if (r == 0) {
|
|
derr << __func__ << " label at 0x" << std::hex << position << std::dec
|
|
<< " correct, but osd_uuid=" << label.osd_uuid << " need=" << fsid << dendl;
|
|
all_labels_valid = false;
|
|
} else if (r == 1) {
|
|
// tried to read but no disk
|
|
} else {
|
|
all_labels_valid = false;
|
|
}
|
|
}
|
|
if (epoch != -1) {
|
|
if (out_epoch) {
|
|
*out_epoch = epoch;
|
|
}
|
|
if (out_is_multi) {
|
|
*out_is_multi = true;
|
|
}
|
|
} else {
|
|
// not even one label read properly
|
|
derr << "No valid bdev label found" << dendl;
|
|
return -ENOENT;
|
|
}
|
|
done:
|
|
dout(10) << __func__ << " got " << *out_label
|
|
<< (all_labels_valid ? " all labels valid " : " some labels missing ")
|
|
<< dendl;
|
|
return all_labels_valid ? 0 : 1;
|
|
}
|
|
|
|
void BlueStore::_main_bdev_label_try_reserve()
|
|
{
|
|
// Try to mark bdev label locations as used.
|
|
// This is possible if location is not allocated.
|
|
// If location us used, remove it from list of places to write label.
|
|
// We operate on BlueStore's main device allocator `alloc`.
|
|
ceph_assert(alloc);
|
|
ceph_assert(bdev);
|
|
ceph_assert(bdev_label_multi == true);
|
|
vector<uint64_t> candidate_positions;
|
|
vector<uint64_t> accepted_positions;
|
|
dout(20) << __func__ << " input " << bdev_label_valid_locations << dendl;
|
|
uint64_t lsize = std::max(BDEV_LABEL_BLOCK_SIZE, min_alloc_size);
|
|
for (uint64_t location : bdev_label_valid_locations) {
|
|
if (location != BDEV_FIRST_LABEL_POSITION) {
|
|
candidate_positions.push_back(location);
|
|
}
|
|
}
|
|
auto look_for_bdev = [&](uint64_t free_location, uint64_t free_length) {
|
|
for (size_t i = 0; i < candidate_positions.size();) {
|
|
uint64_t location = candidate_positions[i];
|
|
if (free_location <= location &&
|
|
location + lsize <= free_location + free_length) {
|
|
accepted_positions.push_back(location);
|
|
candidate_positions.erase(candidate_positions.begin() + i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
};
|
|
// Iterating over free is very inefficient.
|
|
// We can do it here only because its only on init, otherwise it would be unacceptable.
|
|
// Here we could use some API like: alloc->allocate_at().
|
|
// When we create it, replace code.
|
|
alloc->foreach(look_for_bdev);
|
|
for (auto& location : accepted_positions) {
|
|
alloc->init_rm_free(location, lsize);
|
|
}
|
|
|
|
for (size_t i = 0; i < candidate_positions.size(); i++) {
|
|
uint64_t location = candidate_positions[i];
|
|
derr << __func__ << " bdev label location 0x" << std::hex << location << std::dec
|
|
<< " occupied by BlueStore object or BlueFS file, disabling" << dendl;
|
|
std::erase(bdev_label_valid_locations, candidate_positions[i]);
|
|
}
|
|
dout(20) << __func__ << " result " << bdev_label_valid_locations << dendl;
|
|
}
|
|
|
|
void BlueStore::_main_bdev_label_remove(Allocator* an_alloc)
|
|
{
|
|
ceph_assert(bdev_label_multi == true);
|
|
uint64_t lsize = std::max(BDEV_LABEL_BLOCK_SIZE, min_alloc_size);
|
|
for (size_t location : bdev_label_valid_locations) {
|
|
if (location != BDEV_FIRST_LABEL_POSITION)
|
|
an_alloc->init_add_free(location, lsize);
|
|
}
|
|
}
|
|
|
|
int BlueStore::_check_or_set_bdev_label(
|
|
BlockDevice* bdev, const string& path,
|
|
const string& desc, bool create)
|
|
{
|
|
bluestore_bdev_label_t label;
|
|
if (create) {
|
|
label.osd_uuid = fsid;
|
|
label.size = bdev->get_size();
|
|
label.btime = ceph_clock_now();
|
|
label.description = desc;
|
|
int r = _write_bdev_label(cct, bdev, path, label);
|
|
if (r < 0)
|
|
return r;
|
|
} else {
|
|
int r = _read_bdev_label(cct, bdev, path, &label);
|
|
if (r < 0)
|
|
return r;
|
|
if (cct->_conf->bluestore_debug_permit_any_bdev_label) {
|
|
dout(20) << __func__ << " bdev " << path << " fsid " << label.osd_uuid
|
|
<< " and fsid " << fsid << " check bypassed" << dendl;
|
|
} else if (label.osd_uuid != fsid) {
|
|
derr << __func__ << " bdev " << path << " fsid " << label.osd_uuid
|
|
<< " does not match our fsid " << fsid << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_set_main_bdev_label()
|
|
{
|
|
uint64_t size = bdev->get_size();
|
|
bdev_label_valid_locations.clear();
|
|
bluestore_bdev_label_t& label = bdev_label;
|
|
label.osd_uuid = fsid;
|
|
label.size = size;
|
|
label.btime = ceph_clock_now();
|
|
label.description = "main";
|
|
if (cct->_conf.get_val<bool>("bluestore_bdev_label_multi")) {
|
|
label.meta["multi"] = "yes";
|
|
label.meta["epoch"] = "1";
|
|
bdev_label_multi = true;
|
|
bdev_label_epoch = 1;
|
|
for (uint64_t position : bdev_label_positions) {
|
|
if (position + BDEV_LABEL_BLOCK_SIZE <= size) {
|
|
bdev_label_valid_locations.push_back(position);
|
|
}
|
|
}
|
|
} else {
|
|
bdev_label_valid_locations.push_back(BDEV_FIRST_LABEL_POSITION);
|
|
}
|
|
int r = _write_bdev_label(cct, bdev, path + "/block", label, bdev_label_valid_locations);
|
|
if (r < 0)
|
|
return r;
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_check_main_bdev_label()
|
|
{
|
|
string block_path = path + "/block";
|
|
int r = _read_multi_bdev_label(cct, bdev, block_path, fsid, &bdev_label,
|
|
&bdev_label_valid_locations, &bdev_label_multi, &bdev_label_epoch);
|
|
if (r < 0)
|
|
return r;
|
|
if (cct->_conf->bluestore_debug_permit_any_bdev_label) {
|
|
dout(20) << __func__ << " bdev " << block_path << " fsid " << bdev_label.osd_uuid
|
|
<< " and fsid " << fsid << " check bypassed" << dendl;
|
|
} else if (bdev_label.osd_uuid != fsid) {
|
|
derr << __func__ << " bdev " << block_path << " fsid " << bdev_label.osd_uuid
|
|
<< " does not match our fsid " << fsid << dendl;
|
|
return -EIO;
|
|
}
|
|
if (bluestore_bdev_label_require_all && r != 0) {
|
|
// We are about to complain that some labels failed.
|
|
// But in case if we expanded block device some labels will not be good.
|
|
uint64_t lsize = std::max(BDEV_LABEL_BLOCK_SIZE, min_alloc_size);
|
|
uint32_t valid_locations = 0;
|
|
for (uint64_t loc : bdev_label_positions) {
|
|
if (loc + lsize <= bdev_label.size) {
|
|
++valid_locations;
|
|
}
|
|
}
|
|
if (valid_locations > bdev_label_valid_locations.size()) {
|
|
derr << __func__ << " not all labels read properly, "
|
|
<< valid_locations << "!=" << bdev_label_valid_locations.size()
|
|
<< dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::read_bdev_label_at_pos(
|
|
CephContext* cct,
|
|
const std::string &bdev_path,
|
|
uint64_t disk_position,
|
|
bluestore_bdev_label_t *label)
|
|
{
|
|
unique_ptr<BlockDevice> bdev(BlockDevice::create(
|
|
cct, bdev_path, nullptr, nullptr, nullptr, nullptr));
|
|
if (!bdev) {
|
|
return -EIO;
|
|
}
|
|
bdev->set_no_exclusive_lock();
|
|
int r = bdev->open(bdev_path);
|
|
if (r < 0)
|
|
return r;
|
|
r = _read_bdev_label(cct, bdev.get(), bdev_path, label, disk_position);
|
|
bdev->close();
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::read_bdev_label(
|
|
CephContext* cct,
|
|
const std::string &path,
|
|
bluestore_bdev_label_t* out_label,
|
|
std::vector<uint64_t>* out_valid_positions,
|
|
bool* out_is_multi,
|
|
int64_t* out_epoch)
|
|
{
|
|
unique_ptr<BlockDevice> bdev(BlockDevice::create(
|
|
cct, path, nullptr, nullptr, nullptr, nullptr));
|
|
if (!bdev) {
|
|
return -EIO;
|
|
}
|
|
bdev->set_no_exclusive_lock();
|
|
int r = bdev->open(path);
|
|
if (r < 0)
|
|
return r;
|
|
uuid_d fsid;
|
|
r = BlueStore::_read_multi_bdev_label(
|
|
cct, bdev.get(), path, fsid, out_label, out_valid_positions, out_is_multi, out_epoch);
|
|
bdev->close();
|
|
return r;
|
|
}
|
|
int BlueStore::write_bdev_label(
|
|
CephContext* cct, const std::string &path,
|
|
const bluestore_bdev_label_t& label, uint64_t disk_position)
|
|
{
|
|
unique_ptr<BlockDevice> bdev(BlockDevice::create(
|
|
cct, path, nullptr, nullptr, nullptr, nullptr));
|
|
int r = bdev->open(path);
|
|
if (r < 0)
|
|
return r;
|
|
r = BlueStore::_write_bdev_label(
|
|
cct, bdev.get(), path, label, {disk_position});
|
|
bdev->close();
|
|
return r;
|
|
}
|
|
|
|
|
|
void BlueStore::_set_alloc_sizes(void)
|
|
{
|
|
max_alloc_size = cct->_conf->bluestore_max_alloc_size;
|
|
|
|
if (cct->_conf->bluestore_prefer_deferred_size) {
|
|
prefer_deferred_size = cct->_conf->bluestore_prefer_deferred_size;
|
|
} else {
|
|
if (_use_rotational_settings()) {
|
|
prefer_deferred_size = cct->_conf->bluestore_prefer_deferred_size_hdd;
|
|
} else {
|
|
prefer_deferred_size = cct->_conf->bluestore_prefer_deferred_size_ssd;
|
|
}
|
|
}
|
|
|
|
if (cct->_conf->bluestore_deferred_batch_ops) {
|
|
deferred_batch_ops = cct->_conf->bluestore_deferred_batch_ops;
|
|
} else {
|
|
if (_use_rotational_settings()) {
|
|
deferred_batch_ops = cct->_conf->bluestore_deferred_batch_ops_hdd;
|
|
} else {
|
|
deferred_batch_ops = cct->_conf->bluestore_deferred_batch_ops_ssd;
|
|
}
|
|
}
|
|
|
|
dout(10) << __func__ << " min_alloc_size 0x" << std::hex << min_alloc_size
|
|
<< std::dec << " order " << (int)min_alloc_size_order
|
|
<< " max_alloc_size 0x" << std::hex << max_alloc_size
|
|
<< " prefer_deferred_size 0x" << prefer_deferred_size
|
|
<< std::dec
|
|
<< " deferred_batch_ops " << deferred_batch_ops
|
|
<< dendl;
|
|
}
|
|
|
|
int BlueStore::_open_bdev(bool create)
|
|
{
|
|
ceph_assert(bdev == NULL);
|
|
string p = path + "/block";
|
|
bdev = BlockDevice::create(cct, p, aio_cb, static_cast<void*>(this), discard_cb, static_cast<void*>(this), "bluestore");
|
|
int r = bdev->open(p);
|
|
if (r < 0)
|
|
goto fail;
|
|
|
|
if (create &&
|
|
cct->_conf.get_val<bool>("bluestore_discard_on_mkfs")) {
|
|
interval_set<uint64_t> whole_device;
|
|
dout(5) << __func__ << " trimming device:" << p << dendl;
|
|
whole_device.insert(0, bdev->get_size());
|
|
bdev->try_discard(whole_device, false, true);
|
|
dout(5) << __func__ << " trimmed device:" << p << dendl;
|
|
}
|
|
|
|
if (bdev->supported_bdev_label()) {
|
|
if (create) {
|
|
r = _set_main_bdev_label();
|
|
} else {
|
|
r = _check_main_bdev_label();
|
|
}
|
|
if (r < 0)
|
|
goto fail_close;
|
|
}
|
|
|
|
// initialize global block parameters
|
|
block_size = bdev->get_block_size();
|
|
block_mask = ~(block_size - 1);
|
|
block_size_order = std::countr_zero(block_size);
|
|
ceph_assert(block_size == 1u << block_size_order);
|
|
_set_max_defer_interval();
|
|
// and set cache_size based on device type
|
|
r = _set_cache_sizes();
|
|
if (r < 0) {
|
|
goto fail_close;
|
|
}
|
|
// get block dev optimal io size
|
|
optimal_io_size = bdev->get_optimal_io_size();
|
|
|
|
return 0;
|
|
|
|
fail_close:
|
|
bdev->close();
|
|
fail:
|
|
delete bdev;
|
|
bdev = NULL;
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_validate_bdev()
|
|
{
|
|
ceph_assert(bdev);
|
|
uint64_t dev_size = bdev->get_size();
|
|
ceph_assert(dev_size > _get_ondisk_reserved());
|
|
}
|
|
|
|
void BlueStore::_close_bdev()
|
|
{
|
|
ceph_assert(bdev);
|
|
bdev->close();
|
|
delete bdev;
|
|
bdev = NULL;
|
|
}
|
|
|
|
int BlueStore::_open_fm(KeyValueDB::Transaction t,
|
|
bool read_only,
|
|
bool db_avail,
|
|
bool fm_restore)
|
|
{
|
|
int r;
|
|
|
|
dout(5) << __func__ << "::NCB::freelist_type=" << freelist_type << dendl;
|
|
ceph_assert(fm == NULL);
|
|
// fm_restore means we are transitioning from null-fm to bitmap-fm
|
|
ceph_assert(!fm_restore || (freelist_type != "null"));
|
|
// fm restore must pass in a valid transaction
|
|
ceph_assert(!fm_restore || (t != nullptr));
|
|
|
|
// when function is called in repair mode (to_repair=true) we skip db->open()/create()
|
|
bool can_have_null_fm = !is_db_rotational() &&
|
|
!read_only &&
|
|
db_avail &&
|
|
cct->_conf->bluestore_allocation_from_file;
|
|
|
|
// When allocation-info is stored in a single file we set freelist_type to "null"
|
|
if (can_have_null_fm) {
|
|
freelist_type = "null";
|
|
need_to_destage_allocation_file = true;
|
|
}
|
|
fm = FreelistManager::create(cct, freelist_type, PREFIX_ALLOC);
|
|
ceph_assert(fm);
|
|
if (t) {
|
|
// create mode. initialize freespace
|
|
dout(20) << __func__ << " initializing freespace" << dendl;
|
|
{
|
|
bufferlist bl;
|
|
bl.append(freelist_type);
|
|
t->set(PREFIX_SUPER, "freelist_type", bl);
|
|
}
|
|
// being able to allocate in units less than bdev block size
|
|
// seems to be a bad idea.
|
|
ceph_assert(cct->_conf->bdev_block_size <= min_alloc_size);
|
|
|
|
uint64_t alloc_size = min_alloc_size;
|
|
if (!bdev->is_smr() && freelist_type == "zoned") {
|
|
derr << "non-SMR device (or SMR support not built-in) but freelist_type = zoned"
|
|
<< dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
fm->create(bdev->get_size(), alloc_size, t);
|
|
|
|
auto reserved = _get_ondisk_reserved();
|
|
if (fm_restore) {
|
|
// we need to allocate the full space in restore case
|
|
// as later we will add free-space marked in the allocator file
|
|
fm->allocate(0, bdev->get_size(), t);
|
|
} else {
|
|
// allocate bdev label + bluefs superblock reserved space.
|
|
fm->allocate(BDEV_FIRST_LABEL_POSITION, reserved, t);
|
|
// we do not mark other label positions
|
|
}
|
|
r = _write_out_fm_meta(0);
|
|
ceph_assert(r == 0);
|
|
} else {
|
|
if (can_have_null_fm) {
|
|
commit_to_null_manager();
|
|
}
|
|
r = fm->init(db, read_only,
|
|
[&](const std::string& key, std::string* result) {
|
|
return read_meta(key, result);
|
|
});
|
|
if (r < 0) {
|
|
derr << __func__ << " failed: " << cpp_strerror(r) << dendl;
|
|
delete fm;
|
|
fm = NULL;
|
|
return r;
|
|
}
|
|
}
|
|
dout(1) << __func__ << " effective freelist_type = " << freelist_type << std::hex
|
|
<< ", freelist_alloc_size = 0x" << fm->get_alloc_size()
|
|
<< ", min_alloc_size = 0x" << min_alloc_size
|
|
<< std::dec << dendl;
|
|
if (!fm->validate(min_alloc_size)) {
|
|
derr << __func__ << " freelist validation failed, unable to proceed." << dendl;
|
|
ceph_assert(false);
|
|
}
|
|
// if space size tracked by free list manager is that higher than actual
|
|
// dev size one can hit out-of-space allocation which will result
|
|
// in data loss and/or assertions
|
|
// Probably user altered the device size somehow.
|
|
// The only fix for now is to redeploy OSD.
|
|
if (fm->get_size() >= bdev->get_size() + min_alloc_size) {
|
|
ostringstream ss;
|
|
ss << "slow device size mismatch detected, "
|
|
<< " fm size(" << fm->get_size()
|
|
<< ") > slow device size(" << bdev->get_size()
|
|
<< "), Please stop using this OSD as it might cause data loss.";
|
|
_set_disk_size_mismatch_alert(ss.str());
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_close_fm()
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
ceph_assert(fm);
|
|
fm->shutdown();
|
|
delete fm;
|
|
fm = NULL;
|
|
}
|
|
|
|
int BlueStore::_write_out_fm_meta(uint64_t target_size)
|
|
{
|
|
int r = 0;
|
|
string p = path + "/block";
|
|
|
|
std::vector<std::pair<string, string>> fm_meta;
|
|
fm->get_meta(target_size, &fm_meta);
|
|
|
|
for (auto& m : fm_meta) {
|
|
r = write_meta(m.first, m.second);
|
|
ceph_assert(r == 0);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_create_alloc()
|
|
{
|
|
ceph_assert(alloc == NULL);
|
|
ceph_assert(shared_alloc.a == NULL);
|
|
ceph_assert(bdev->get_size());
|
|
|
|
uint64_t alloc_size = min_alloc_size;
|
|
|
|
std::string allocator_type = cct->_conf->bluestore_allocator;
|
|
|
|
alloc = Allocator::create(
|
|
cct, allocator_type,
|
|
bdev->get_size(),
|
|
alloc_size,
|
|
"block");
|
|
if (!alloc) {
|
|
lderr(cct) << __func__ << " failed to create " << allocator_type << " allocator"
|
|
<< dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
// BlueFS will share the same allocator
|
|
shared_alloc.set(alloc, alloc_size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_init_alloc()
|
|
{
|
|
int r = _create_alloc();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
ceph_assert(alloc != NULL);
|
|
|
|
uint64_t num = 0, bytes = 0;
|
|
utime_t start_time = ceph_clock_now();
|
|
if (!fm->is_null_manager()) {
|
|
// This is the original path - loading allocation map from RocksDB and feeding into the allocator
|
|
dout(5) << __func__ << "::NCB::loading allocation from FM -> alloc" << dendl;
|
|
// initialize from freelist
|
|
fm->enumerate_reset();
|
|
uint64_t offset, length;
|
|
while (fm->enumerate_next(db, &offset, &length)) {
|
|
alloc->init_add_free(offset, length);
|
|
++num;
|
|
bytes += length;
|
|
}
|
|
fm->enumerate_reset();
|
|
|
|
utime_t duration = ceph_clock_now() - start_time;
|
|
dout(5) << __func__ << "::num_entries=" << num << " free_size=" << bytes << " alloc_size=" <<
|
|
alloc->get_capacity() - bytes << " time=" << duration << " seconds" << dendl;
|
|
} else {
|
|
// This is the new path reading the allocation map from a flat bluefs file and feeding them into the allocator
|
|
|
|
if (!cct->_conf->bluestore_allocation_from_file) {
|
|
derr << __func__ << "::NCB::cct->_conf->bluestore_allocation_from_file is set to FALSE with an active NULL-FM" << dendl;
|
|
derr << __func__ << "::NCB::Please change the value of bluestore_allocation_from_file to TRUE in your ceph.conf file" << dendl;
|
|
return -ENOTSUP; // Operation not supported
|
|
}
|
|
if (restore_allocator(alloc, &num, &bytes) == 0) {
|
|
dout(5) << __func__ << "::NCB::restore_allocator() completed successfully alloc=" << alloc << dendl;
|
|
} else {
|
|
// This must mean that we had an unplanned shutdown and didn't manage to destage the allocator
|
|
dout(0) << __func__ << "::NCB::restore_allocator() failed! Run Full Recovery from ONodes (might take a while) ..." << dendl;
|
|
// if failed must recover from on-disk ONode internal state
|
|
if (read_allocation_from_drive_on_startup() != 0) {
|
|
derr << __func__ << "::NCB::Failed Recovery" << dendl;
|
|
derr << __func__ << "::NCB::Ceph-OSD won't start, make sure your drives are connected and readable" << dendl;
|
|
derr << __func__ << "::NCB::If no HW fault is found, please report failure and consider redeploying OSD" << dendl;
|
|
return -ENOTRECOVERABLE;
|
|
}
|
|
}
|
|
if (before_expansion_bdev_size > 0 &&
|
|
before_expansion_bdev_size < bdev_label.size) {
|
|
// we grow the allocation range, must reflect it in the allocation file
|
|
alloc->init_add_free(before_expansion_bdev_size,
|
|
bdev_label.size - before_expansion_bdev_size);
|
|
need_to_destage_allocation_file = true;
|
|
}
|
|
}
|
|
before_expansion_bdev_size = 0;
|
|
|
|
dout(1) << __func__
|
|
<< " loaded " << byte_u_t(bytes) << " in " << num << " extents"
|
|
<< std::hex
|
|
<< ", allocator type " << alloc->get_type()
|
|
<< ", capacity 0x" << alloc->get_capacity()
|
|
<< ", block size 0x" << alloc->get_block_size()
|
|
<< ", free 0x" << alloc->get_free()
|
|
<< ", fragmentation " << alloc->get_fragmentation()
|
|
<< std::dec << dendl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_post_init_alloc()
|
|
{
|
|
int r = 0;
|
|
if (fm->is_null_manager()) {
|
|
// Now that we load the allocation map we need to invalidate the file as new allocation won't be reflected
|
|
// Changes to the allocation map (alloc/release) are not updated inline and will only be stored on umount()
|
|
// This means that we should not use the existing file on failure case (unplanned shutdown) and must resort
|
|
// to recovery from RocksDB::ONodes
|
|
r = invalidate_allocation_file_on_bluefs();
|
|
}
|
|
ceph_assert(r >= 0);
|
|
}
|
|
|
|
void BlueStore::_close_alloc()
|
|
{
|
|
ceph_assert(bdev);
|
|
bdev->discard_drain();
|
|
|
|
ceph_assert(alloc);
|
|
alloc->shutdown();
|
|
delete alloc;
|
|
|
|
ceph_assert(shared_alloc.a);
|
|
if (alloc != shared_alloc.a) {
|
|
shared_alloc.a->shutdown();
|
|
delete shared_alloc.a;
|
|
}
|
|
|
|
shared_alloc.reset();
|
|
alloc = nullptr;
|
|
}
|
|
|
|
int BlueStore::_open_fsid(bool create)
|
|
{
|
|
ceph_assert(fsid_fd < 0);
|
|
int flags = O_RDWR|O_CLOEXEC;
|
|
if (create)
|
|
flags |= O_CREAT;
|
|
fsid_fd = ::openat(path_fd, "fsid", flags, 0644);
|
|
if (fsid_fd < 0) {
|
|
int err = -errno;
|
|
derr << __func__ << " " << cpp_strerror(err) << dendl;
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_read_fsid(uuid_d *uuid)
|
|
{
|
|
char fsid_str[40];
|
|
memset(fsid_str, 0, sizeof(fsid_str));
|
|
int ret = safe_read(fsid_fd, fsid_str, sizeof(fsid_str));
|
|
if (ret < 0) {
|
|
derr << __func__ << " failed: " << cpp_strerror(ret) << dendl;
|
|
return ret;
|
|
}
|
|
if (ret > 36)
|
|
fsid_str[36] = 0;
|
|
else
|
|
fsid_str[ret] = 0;
|
|
if (!uuid->parse(fsid_str)) {
|
|
derr << __func__ << " unparsable uuid " << fsid_str << dendl;
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_write_fsid()
|
|
{
|
|
int r = ::ftruncate(fsid_fd, 0);
|
|
if (r < 0) {
|
|
r = -errno;
|
|
derr << __func__ << " fsid truncate failed: " << cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
string str = stringify(fsid) + "\n";
|
|
r = safe_write(fsid_fd, str.c_str(), str.length());
|
|
if (r < 0) {
|
|
derr << __func__ << " fsid write failed: " << cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
r = ::fsync(fsid_fd);
|
|
if (r < 0) {
|
|
r = -errno;
|
|
derr << __func__ << " fsid fsync failed: " << cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_close_fsid()
|
|
{
|
|
VOID_TEMP_FAILURE_RETRY(::close(fsid_fd));
|
|
fsid_fd = -1;
|
|
}
|
|
|
|
int BlueStore::_lock_fsid()
|
|
{
|
|
struct flock l;
|
|
memset(&l, 0, sizeof(l));
|
|
l.l_type = F_WRLCK;
|
|
l.l_whence = SEEK_SET;
|
|
int r = ::fcntl(fsid_fd, F_SETLK, &l);
|
|
if (r < 0) {
|
|
int err = errno;
|
|
derr << __func__ << " failed to lock " << path << "/fsid"
|
|
<< " (is another ceph-osd still running?)"
|
|
<< cpp_strerror(err) << dendl;
|
|
return -err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool BlueStore::is_rotational()
|
|
{
|
|
if (bdev) {
|
|
return bdev->is_rotational();
|
|
}
|
|
|
|
bool rotational = true;
|
|
int r = _open_path();
|
|
if (r < 0)
|
|
goto out;
|
|
r = _open_fsid(false);
|
|
if (r < 0)
|
|
goto out_path;
|
|
r = _read_fsid(&fsid);
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
r = _lock_fsid();
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
r = _open_bdev(false);
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
rotational = bdev->is_rotational();
|
|
_close_bdev();
|
|
out_fsid:
|
|
_close_fsid();
|
|
out_path:
|
|
_close_path();
|
|
out:
|
|
return rotational;
|
|
}
|
|
|
|
bool BlueStore::is_journal_rotational()
|
|
{
|
|
if (!bluefs) {
|
|
dout(5) << __func__ << " bluefs disabled, default to store media type"
|
|
<< dendl;
|
|
return is_rotational();
|
|
}
|
|
dout(10) << __func__ << " " << (int)bluefs->wal_is_rotational() << dendl;
|
|
return bluefs->wal_is_rotational();
|
|
}
|
|
|
|
bool BlueStore::is_db_rotational()
|
|
{
|
|
if (!bluefs) {
|
|
dout(5) << __func__ << " bluefs disabled, default to store media type"
|
|
<< dendl;
|
|
return is_rotational();
|
|
}
|
|
dout(10) << __func__ << " " << (int)bluefs->db_is_rotational() << dendl;
|
|
return bluefs->db_is_rotational();
|
|
}
|
|
|
|
bool BlueStore::_use_rotational_settings()
|
|
{
|
|
if (cct->_conf->bluestore_debug_enforce_settings == "hdd") {
|
|
return true;
|
|
}
|
|
if (cct->_conf->bluestore_debug_enforce_settings == "ssd") {
|
|
return false;
|
|
}
|
|
return bdev->is_rotational();
|
|
}
|
|
|
|
bool BlueStore::is_statfs_recoverable() const
|
|
{
|
|
// abuse fm for now
|
|
return has_null_manager();
|
|
}
|
|
|
|
bool BlueStore::test_mount_in_use()
|
|
{
|
|
// most error conditions mean the mount is not in use (e.g., because
|
|
// it doesn't exist). only if we fail to lock do we conclude it is
|
|
// in use.
|
|
bool ret = false;
|
|
int r = _open_path();
|
|
if (r < 0)
|
|
return false;
|
|
r = _open_fsid(false);
|
|
if (r < 0)
|
|
goto out_path;
|
|
r = _lock_fsid();
|
|
if (r < 0)
|
|
ret = true; // if we can't lock, it is in use
|
|
_close_fsid();
|
|
out_path:
|
|
_close_path();
|
|
return ret;
|
|
}
|
|
|
|
int BlueStore::_minimal_open_bluefs(bool create)
|
|
{
|
|
int r;
|
|
|
|
bluefs = new BlueFS(cct, bluefscache);
|
|
|
|
string bfn;
|
|
struct stat st;
|
|
|
|
bfn = path + "/block.db";
|
|
bool trim_db_wal =
|
|
create && cct->_conf.get_val<bool>("bluestore_discard_on_mkfs");
|
|
if (::stat(bfn.c_str(), &st) == 0) {
|
|
r = bluefs->add_block_device(BlueFS::BDEV_DB, bfn, trim_db_wal);
|
|
if (r < 0) {
|
|
derr << __func__ << " add block device(" << bfn << ") returned: "
|
|
<< cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
|
|
if (bluefs->bdev_support_label(BlueFS::BDEV_DB)) {
|
|
r = _check_or_set_bdev_label(
|
|
bluefs->get_block_device(BlueFS::BDEV_DB),
|
|
bfn,
|
|
"bluefs db", create);
|
|
if (r < 0) {
|
|
derr << __func__ << " check block device(" << bfn
|
|
<< ") label returned: " << cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
}
|
|
bluefs_layout.shared_bdev = BlueFS::BDEV_SLOW;
|
|
bluefs_layout.dedicated_db = true;
|
|
} else {
|
|
r = -errno;
|
|
if (::lstat(bfn.c_str(), &st) == -1) {
|
|
r = 0;
|
|
bluefs_layout.shared_bdev = BlueFS::BDEV_DB;
|
|
} else {
|
|
derr << __func__ << " " << bfn << " symlink exists but target unusable: "
|
|
<< cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
}
|
|
|
|
// shared device
|
|
bfn = path + "/block";
|
|
// never trim here
|
|
r = bluefs->add_block_device(bluefs_layout.shared_bdev, bfn, false,
|
|
&shared_alloc);
|
|
if (r < 0) {
|
|
derr << __func__ << " add block device(" << bfn << ") returned: "
|
|
<< cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
|
|
bfn = path + "/block.wal";
|
|
if (::stat(bfn.c_str(), &st) == 0) {
|
|
r = bluefs->add_block_device(BlueFS::BDEV_WAL, bfn, trim_db_wal);
|
|
if (r < 0) {
|
|
derr << __func__ << " add block device(" << bfn << ") returned: "
|
|
<< cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
|
|
if (bluefs->bdev_support_label(BlueFS::BDEV_WAL)) {
|
|
r = _check_or_set_bdev_label(
|
|
bluefs->get_block_device(BlueFS::BDEV_WAL),
|
|
bfn,
|
|
"bluefs wal", create);
|
|
if (r < 0) {
|
|
derr << __func__ << " check block device(" << bfn
|
|
<< ") label returned: " << cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
}
|
|
|
|
bluefs_layout.dedicated_wal = true;
|
|
} else {
|
|
r = 0;
|
|
if (::lstat(bfn.c_str(), &st) != -1) {
|
|
r = -errno;
|
|
derr << __func__ << " " << bfn << " symlink exists but target unusable: "
|
|
<< cpp_strerror(r) << dendl;
|
|
goto free_bluefs;
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
free_bluefs:
|
|
ceph_assert(bluefs);
|
|
delete bluefs;
|
|
bluefs = NULL;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_open_bluefs(bool create, bool read_only)
|
|
{
|
|
int r = _minimal_open_bluefs(create);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
BlueFSVolumeSelector* vselector = nullptr;
|
|
if (bluefs_layout.shared_bdev == BlueFS::BDEV_SLOW ||
|
|
cct->_conf->bluestore_volume_selection_policy == "use_some_extra_enforced" ||
|
|
cct->_conf->bluestore_volume_selection_policy == "fit_to_fast") {
|
|
|
|
string options = cct->_conf->bluestore_rocksdb_options;
|
|
string options_annex = cct->_conf->bluestore_rocksdb_options_annex;
|
|
if (!options_annex.empty()) {
|
|
if (!options.empty() &&
|
|
*options.rbegin() != ',') {
|
|
options += ',';
|
|
}
|
|
options += options_annex;
|
|
}
|
|
|
|
rocksdb::Options rocks_opts;
|
|
r = RocksDBStore::ParseOptionsFromStringStatic(
|
|
cct,
|
|
options,
|
|
rocks_opts,
|
|
nullptr);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
if (cct->_conf->bluestore_volume_selection_policy == "fit_to_fast") {
|
|
vselector = new FitToFastVolumeSelector(
|
|
bluefs->get_block_device_size(BlueFS::BDEV_WAL) * 95 / 100,
|
|
bluefs->get_block_device_size(BlueFS::BDEV_DB) * 95 / 100,
|
|
bluefs->get_block_device_size(BlueFS::BDEV_SLOW) * 95 / 100);
|
|
} else {
|
|
vselector = new RocksDBBlueFSVolumeSelector(
|
|
bluefs->get_block_device_size(BlueFS::BDEV_WAL) * 95 / 100,
|
|
bluefs->get_block_device_size(BlueFS::BDEV_DB) * 95 / 100,
|
|
bluefs->get_block_device_size(BlueFS::BDEV_SLOW) * 95 / 100,
|
|
rocks_opts.write_buffer_size * rocks_opts.max_write_buffer_number,
|
|
rocks_opts.max_bytes_for_level_base,
|
|
rocks_opts.max_bytes_for_level_multiplier,
|
|
cct->_conf->bluestore_volume_selection_policy.find("use_some_extra") == 0);
|
|
}
|
|
vselector->update_from_config(cct);
|
|
}
|
|
if (create) {
|
|
bluefs->mkfs(fsid, bluefs_layout);
|
|
}
|
|
bluefs->set_volume_selector(vselector);
|
|
r = bluefs->mount();
|
|
if (r < 0) {
|
|
derr << __func__ << " failed bluefs mount: " << cpp_strerror(r) << dendl;
|
|
}
|
|
ceph_assert_always(bluefs->maybe_verify_layout(bluefs_layout) == 0);
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_close_bluefs()
|
|
{
|
|
bluefs->umount(db_was_opened_read_only);
|
|
_minimal_close_bluefs();
|
|
}
|
|
|
|
void BlueStore::_minimal_close_bluefs()
|
|
{
|
|
delete bluefs;
|
|
bluefs = NULL;
|
|
}
|
|
|
|
int BlueStore::_is_bluefs(bool create, bool* ret)
|
|
{
|
|
if (create) {
|
|
*ret = cct->_conf->bluestore_bluefs;
|
|
} else {
|
|
string s;
|
|
int r = read_meta("bluefs", &s);
|
|
if (r < 0) {
|
|
derr << __func__ << " unable to read 'bluefs' meta" << dendl;
|
|
return -EIO;
|
|
}
|
|
if (s == "1") {
|
|
*ret = true;
|
|
} else if (s == "0") {
|
|
*ret = false;
|
|
} else {
|
|
derr << __func__ << " bluefs = " << s << " : not 0 or 1, aborting"
|
|
<< dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* opens both DB and dependant super_meta, FreelistManager and allocator
|
|
* in the proper order
|
|
*/
|
|
int BlueStore::_open_db_and_around(bool read_only, bool to_repair)
|
|
{
|
|
dout(5) << __func__ << "::NCB::read_only=" << read_only << ", to_repair=" << to_repair << dendl;
|
|
{
|
|
string type;
|
|
int r = read_meta("type", &type);
|
|
if (r < 0) {
|
|
derr << __func__ << " failed to load os-type: " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
if (type != "bluestore") {
|
|
derr << __func__ << " expected bluestore, but type is " << type << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
int r = _open_path();
|
|
if (r < 0)
|
|
return r;
|
|
r = _open_fsid(false);
|
|
if (r < 0)
|
|
goto out_path;
|
|
|
|
r = _read_fsid(&fsid);
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
|
|
r = _lock_fsid();
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
|
|
r = _open_bdev(false);
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
|
|
// GBH: can probably skip open_db step in REad-Only mode when operating in NULL-FM mode
|
|
// (might need to open if failed to restore from file)
|
|
|
|
// open in read-only first to read FM list and init allocator
|
|
// as they might be needed for some BlueFS procedures
|
|
r = _open_db(false, false, true);
|
|
if (r < 0)
|
|
goto out_bdev;
|
|
|
|
r = _open_super_meta();
|
|
if (r < 0) {
|
|
goto out_db;
|
|
}
|
|
|
|
r = _open_fm(nullptr, true, false);
|
|
if (r < 0)
|
|
goto out_db;
|
|
|
|
r = _init_alloc();
|
|
if (r < 0)
|
|
goto out_fm;
|
|
|
|
if (bdev_label_multi) {
|
|
_main_bdev_label_try_reserve();
|
|
}
|
|
|
|
// Re-open in the proper mode(s).
|
|
|
|
// Can't simply bypass second open for read-only mode as we need to
|
|
// load allocated extents from bluefs into allocator.
|
|
// And now it's time to do that
|
|
//
|
|
_close_db();
|
|
r = _open_db(false, to_repair, read_only);
|
|
if (r < 0) {
|
|
goto out_alloc;
|
|
}
|
|
|
|
if (!read_only) {
|
|
_post_init_alloc();
|
|
}
|
|
|
|
// when function is called in repair mode (to_repair=true) we skip db->open()/create()
|
|
// we can't change bluestore allocation so no need to invlidate allocation-file
|
|
if (fm->is_null_manager() && !read_only && !to_repair) {
|
|
// Now that we load the allocation map we need to invalidate the file as new allocation won't be reflected
|
|
// Changes to the allocation map (alloc/release) are not updated inline and will only be stored on umount()
|
|
// This means that we should not use the existing file on failure case (unplanned shutdown) and must resort
|
|
// to recovery from RocksDB::ONodes
|
|
r = invalidate_allocation_file_on_bluefs();
|
|
if (r != 0) {
|
|
derr << __func__ << "::NCB::invalidate_allocation_file_on_bluefs() failed!" << dendl;
|
|
goto out_alloc;
|
|
}
|
|
}
|
|
|
|
// when function is called in repair mode (to_repair=true) we skip db->open()/create()
|
|
if (!is_db_rotational() && !read_only && !to_repair && cct->_conf->bluestore_allocation_from_file) {
|
|
dout(5) << __func__ << "::NCB::Commit to Null-Manager" << dendl;
|
|
commit_to_null_manager();
|
|
need_to_destage_allocation_file = true;
|
|
dout(10) << __func__ << "::NCB::need_to_destage_allocation_file was set" << dendl;
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_alloc:
|
|
_close_alloc();
|
|
out_fm:
|
|
_close_fm();
|
|
out_db:
|
|
_close_db();
|
|
out_bdev:
|
|
_close_bdev();
|
|
out_fsid:
|
|
_close_fsid();
|
|
out_path:
|
|
_close_path();
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_close_db_and_around()
|
|
{
|
|
if (db) {
|
|
_close_db();
|
|
}
|
|
_close_around_db();
|
|
}
|
|
|
|
void BlueStore::_close_around_db()
|
|
{
|
|
if (bluefs) {
|
|
_close_bluefs();
|
|
}
|
|
_close_fm();
|
|
_close_alloc();
|
|
_close_bdev();
|
|
_close_fsid();
|
|
_close_path();
|
|
}
|
|
|
|
int BlueStore::open_db_environment(KeyValueDB **pdb, bool read_only, bool to_repair)
|
|
{
|
|
_kv_only = true;
|
|
int r = _open_db_and_around(read_only, to_repair);
|
|
if (r == 0) {
|
|
*pdb = db;
|
|
} else {
|
|
*pdb = nullptr;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::close_db_environment()
|
|
{
|
|
if (db) {
|
|
delete db;
|
|
db = nullptr;
|
|
}
|
|
_close_around_db();
|
|
return 0;
|
|
}
|
|
|
|
/* gets access to bluefs supporting RocksDB */
|
|
BlueFS* BlueStore::get_bluefs() {
|
|
return bluefs;
|
|
}
|
|
|
|
int BlueStore::_prepare_db_environment(bool create, bool read_only,
|
|
std::string* _fn, std::string* _kv_backend)
|
|
{
|
|
int r;
|
|
ceph_assert(!db);
|
|
std::string& fn=*_fn;
|
|
std::string& kv_backend=*_kv_backend;
|
|
fn = path + "/db";
|
|
std::shared_ptr<Int64ArrayMergeOperator> merge_op(new Int64ArrayMergeOperator);
|
|
|
|
if (create) {
|
|
kv_backend = cct->_conf->bluestore_kvbackend;
|
|
} else {
|
|
r = read_meta("kv_backend", &kv_backend);
|
|
if (r < 0) {
|
|
derr << __func__ << " unable to read 'kv_backend' meta" << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
dout(10) << __func__ << " kv_backend = " << kv_backend << dendl;
|
|
|
|
bool do_bluefs;
|
|
r = _is_bluefs(create, &do_bluefs);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
dout(10) << __func__ << " do_bluefs = " << do_bluefs << dendl;
|
|
|
|
map<string,string> kv_options;
|
|
// force separate wal dir for all new deployments.
|
|
kv_options["separate_wal_dir"] = 1;
|
|
rocksdb::Env *env = NULL;
|
|
if (do_bluefs) {
|
|
dout(10) << __func__ << " initializing bluefs" << dendl;
|
|
if (kv_backend != "rocksdb") {
|
|
derr << " backend must be rocksdb to use bluefs" << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
r = _open_bluefs(create, read_only);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
if (cct->_conf->bluestore_bluefs_env_mirror) {
|
|
rocksdb::Env* a = new BlueRocksEnv(bluefs);
|
|
rocksdb::Env* b = rocksdb::Env::Default();
|
|
if (create) {
|
|
string cmd = "rm -rf " + path + "/db " +
|
|
path + "/db.slow " +
|
|
path + "/db.wal";
|
|
int r = system(cmd.c_str());
|
|
(void)r;
|
|
}
|
|
env = new rocksdb::EnvMirror(b, a, false, true);
|
|
} else {
|
|
env = new BlueRocksEnv(bluefs);
|
|
|
|
// simplify the dir names, too, as "seen" by rocksdb
|
|
fn = "db";
|
|
}
|
|
BlueFSVolumeSelector::paths paths;
|
|
bluefs->get_vselector_paths(fn, paths);
|
|
|
|
{
|
|
ostringstream db_paths;
|
|
bool first = true;
|
|
for (auto& p : paths) {
|
|
if (!first) {
|
|
db_paths << " ";
|
|
}
|
|
first = false;
|
|
db_paths << p.first << "," << p.second;
|
|
|
|
}
|
|
kv_options["db_paths"] = db_paths.str();
|
|
dout(1) << __func__ << " set db_paths to " << db_paths.str() << dendl;
|
|
}
|
|
|
|
if (create) {
|
|
for (auto& p : paths) {
|
|
env->CreateDir(p.first);
|
|
}
|
|
// Selectors don't provide wal path so far hence create explicitly
|
|
env->CreateDir(fn + ".wal");
|
|
} else {
|
|
std::vector<std::string> res;
|
|
// check for dir presence
|
|
auto r = env->GetChildren(fn+".wal", &res);
|
|
if (r.IsNotFound()) {
|
|
kv_options.erase("separate_wal_dir");
|
|
}
|
|
}
|
|
} else {
|
|
string walfn = path + "/db.wal";
|
|
|
|
if (create) {
|
|
int r = ::mkdir(fn.c_str(), 0755);
|
|
if (r < 0)
|
|
r = -errno;
|
|
if (r < 0 && r != -EEXIST) {
|
|
derr << __func__ << " failed to create " << fn << ": " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
// wal_dir, too!
|
|
r = ::mkdir(walfn.c_str(), 0755);
|
|
if (r < 0)
|
|
r = -errno;
|
|
if (r < 0 && r != -EEXIST) {
|
|
derr << __func__ << " failed to create " << walfn
|
|
<< ": " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
} else {
|
|
struct stat st;
|
|
r = ::stat(walfn.c_str(), &st);
|
|
if (r < 0 && errno == ENOENT) {
|
|
kv_options.erase("separate_wal_dir");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
db = KeyValueDB::create(cct,
|
|
kv_backend,
|
|
fn,
|
|
kv_options,
|
|
static_cast<void*>(env));
|
|
if (!db) {
|
|
derr << __func__ << " error creating db" << dendl;
|
|
if (bluefs) {
|
|
_close_bluefs();
|
|
}
|
|
// delete env manually here since we can't depend on db to do this
|
|
// under this case
|
|
delete env;
|
|
env = NULL;
|
|
return -EIO;
|
|
}
|
|
|
|
FreelistManager::setup_merge_operators(db, freelist_type);
|
|
db->set_merge_operator(PREFIX_STAT, merge_op);
|
|
db->set_cache_size(cache_kv_ratio * cache_size);
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_open_db(bool create, bool to_repair_db, bool read_only)
|
|
{
|
|
int r;
|
|
ceph_assert(!(create && read_only));
|
|
string options;
|
|
string options_annex;
|
|
stringstream err;
|
|
string kv_dir_fn;
|
|
string kv_backend;
|
|
std::string sharding_def;
|
|
// prevent write attempts to BlueFS in case we failed before BlueFS was opened
|
|
db_was_opened_read_only = true;
|
|
r = _prepare_db_environment(create, read_only, &kv_dir_fn, &kv_backend);
|
|
if (r < 0) {
|
|
derr << __func__ << " failed to prepare db environment: " << err.str() << dendl;
|
|
return -EIO;
|
|
}
|
|
// if reached here then BlueFS is already opened
|
|
db_was_opened_read_only = read_only;
|
|
dout(10) << __func__ << "::db_was_opened_read_only was set to " << read_only << dendl;
|
|
if (kv_backend == "rocksdb") {
|
|
options = cct->_conf->bluestore_rocksdb_options;
|
|
options_annex = cct->_conf->bluestore_rocksdb_options_annex;
|
|
if (!options_annex.empty()) {
|
|
if (!options.empty() &&
|
|
*options.rbegin() != ',') {
|
|
options += ',';
|
|
}
|
|
options += options_annex;
|
|
}
|
|
|
|
if (cct->_conf.get_val<bool>("bluestore_rocksdb_cf")) {
|
|
sharding_def = cct->_conf.get_val<std::string>("bluestore_rocksdb_cfs");
|
|
}
|
|
}
|
|
|
|
db->init(options);
|
|
if (to_repair_db)
|
|
return 0;
|
|
if (create) {
|
|
r = db->create_and_open(err, sharding_def);
|
|
} else {
|
|
// we pass in cf list here, but it is only used if the db already has
|
|
// column families created.
|
|
r = read_only ?
|
|
db->open_read_only(err, sharding_def) :
|
|
db->open(err, sharding_def);
|
|
}
|
|
if (r) {
|
|
derr << __func__ << " erroring opening db: " << err.str() << dendl;
|
|
_close_db();
|
|
return -EIO;
|
|
}
|
|
dout(1) << __func__ << " opened " << kv_backend
|
|
<< " path " << kv_dir_fn << " options " << options << dendl;
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_close_db()
|
|
{
|
|
dout(10) << __func__ << ":read_only=" << db_was_opened_read_only
|
|
<< " fm=" << fm
|
|
<< " destage_alloc_file=" << need_to_destage_allocation_file
|
|
<< " per_pool=" << per_pool_stat_collection
|
|
<< " pool stats=" << osd_pools.size()
|
|
<< dendl;
|
|
bool do_destage = !db_was_opened_read_only && need_to_destage_allocation_file;
|
|
if (do_destage && is_statfs_recoverable()) {
|
|
auto t = db->get_transaction();
|
|
store_statfs_t s;
|
|
if (per_pool_stat_collection) {
|
|
KeyValueDB::Iterator it = db->get_iterator(PREFIX_STAT, KeyValueDB::ITERATOR_NOCACHE);
|
|
uint64_t pool_id;
|
|
for (it->upper_bound(string()); it->valid(); it->next()) {
|
|
int r = get_key_pool_stat(it->key(), &pool_id);
|
|
if (r >= 0) {
|
|
dout(10) << __func__ << " wiping statfs for: " << pool_id << dendl;
|
|
} else {
|
|
derr << __func__ << " wiping invalid statfs key: " << it->key() << dendl;
|
|
}
|
|
t->rmkey(PREFIX_STAT, it->key());
|
|
}
|
|
|
|
std::lock_guard l(vstatfs_lock);
|
|
for(auto &p : osd_pools) {
|
|
string key;
|
|
get_pool_stat_key(p.first, &key);
|
|
bufferlist bl;
|
|
if (!p.second.is_empty()) {
|
|
p.second.encode(bl);
|
|
p.second.publish(&s);
|
|
t->set(PREFIX_STAT, key, bl);
|
|
dout(10) << __func__ << " persisting: "
|
|
<< p.first << "->" << s
|
|
<< dendl;
|
|
}
|
|
}
|
|
} else {
|
|
bufferlist bl;
|
|
{
|
|
std::lock_guard l(vstatfs_lock);
|
|
vstatfs.encode(bl);
|
|
vstatfs.publish(&s);
|
|
}
|
|
t->set(PREFIX_STAT, BLUESTORE_GLOBAL_STATFS_KEY, bl);
|
|
dout(10) << __func__ << "persisting: " << s << dendl;
|
|
}
|
|
int r = db->submit_transaction_sync(t);
|
|
dout(10) << __func__ << " statfs persisted." << dendl;
|
|
ceph_assert(r >= 0);
|
|
}
|
|
ceph_assert(db);
|
|
delete db;
|
|
db = nullptr;
|
|
|
|
if (do_destage && fm && fm->is_null_manager()) {
|
|
if (cct->_conf->osd_fast_shutdown) {
|
|
interval_set<uint64_t> discard_queued;
|
|
bdev->swap_discard_queued(discard_queued);
|
|
dout(10) << __func__ << "::discard_drain: size=" << discard_queued.size()
|
|
<< " num_intervals=" << discard_queued.num_intervals() << dendl;
|
|
// copy discard_queued to the allocator before storing it
|
|
for (auto p = discard_queued.begin(); p != discard_queued.end(); ++p) {
|
|
dout(20) << __func__ << "::discarded-extent=[" << p.get_start()
|
|
<< ", " << p.get_len() << "]" << dendl;
|
|
alloc->init_add_free(p.get_start(), p.get_len());
|
|
}
|
|
}
|
|
|
|
// When we reach here it is either a graceful shutdown (so can drain the full discards-queue)
|
|
// or it was a fast shutdown, but we already moved the main discards-queue to the allocator
|
|
// and only need to wait for the threads local discard_processing queues to drain
|
|
bdev->discard_drain();
|
|
int ret = store_allocator(alloc);
|
|
if (unlikely(ret != 0)) {
|
|
derr << __func__ << "::NCB::store_allocator() failed (we will need to rebuild it on startup)" << dendl;
|
|
}
|
|
}
|
|
|
|
if (bluefs) {
|
|
_close_bluefs();
|
|
}
|
|
}
|
|
|
|
void BlueStore::_dump_alloc_on_failure()
|
|
{
|
|
auto dump_interval =
|
|
cct->_conf->bluestore_bluefs_alloc_failure_dump_interval;
|
|
if (dump_interval > 0 &&
|
|
next_dump_on_bluefs_alloc_failure <= ceph_clock_now()) {
|
|
shared_alloc.a->dump();
|
|
next_dump_on_bluefs_alloc_failure = ceph_clock_now();
|
|
next_dump_on_bluefs_alloc_failure += dump_interval;
|
|
}
|
|
}
|
|
|
|
int BlueStore::_open_collections()
|
|
{
|
|
if (!coll_map.empty()) {
|
|
// could be opened from another path
|
|
dout(20) << __func__ << "::NCB::collections are already opened, nothing to do" << dendl;
|
|
return 0;
|
|
}
|
|
|
|
dout(10) << __func__ << dendl;
|
|
collections_had_errors = false;
|
|
KeyValueDB::Iterator it = db->get_iterator(PREFIX_COLL);
|
|
size_t load_cnt = 0;
|
|
for (it->upper_bound(string());
|
|
it->valid();
|
|
it->next()) {
|
|
coll_t cid;
|
|
if (cid.parse(it->key())) {
|
|
auto c = ceph::make_ref<Collection>(
|
|
this,
|
|
onode_cache_shards[cid.hash_to_shard(onode_cache_shards.size())],
|
|
buffer_cache_shards[cid.hash_to_shard(buffer_cache_shards.size())],
|
|
cid);
|
|
bufferlist bl = it->value();
|
|
auto p = bl.cbegin();
|
|
try {
|
|
decode(c->cnode, p);
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " failed to decode cnode, key:"
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
return -EIO;
|
|
}
|
|
dout(20) << __func__ << " opened " << cid << " " << c
|
|
<< " " << c->cnode << dendl;
|
|
_osr_attach(c.get());
|
|
coll_map[cid] = c;
|
|
load_cnt++;
|
|
} else {
|
|
derr << __func__ << " unrecognized collection " << it->key() << dendl;
|
|
collections_had_errors = true;
|
|
}
|
|
}
|
|
dout(10) << __func__ << " collections loaded: " << load_cnt
|
|
<< dendl;
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_fsck_collections(int64_t* errors)
|
|
{
|
|
if (collections_had_errors) {
|
|
dout(10) << __func__ << dendl;
|
|
KeyValueDB::Iterator it = db->get_iterator(PREFIX_COLL, KeyValueDB::ITERATOR_NOCACHE);
|
|
for (it->upper_bound(string());
|
|
it->valid();
|
|
it->next()) {
|
|
coll_t cid;
|
|
if (!cid.parse(it->key())) {
|
|
derr << __func__ << " unrecognized collection " << it->key() << dendl;
|
|
if (errors) {
|
|
(*errors)++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_set_per_pool_omap()
|
|
{
|
|
per_pool_omap = OMAP_BULK;
|
|
bufferlist bl;
|
|
db->get(PREFIX_SUPER, "per_pool_omap", &bl);
|
|
if (bl.length()) {
|
|
auto s = bl.to_str();
|
|
if (s == stringify(OMAP_PER_POOL)) {
|
|
per_pool_omap = OMAP_PER_POOL;
|
|
} else if (s == stringify(OMAP_PER_PG)) {
|
|
per_pool_omap = OMAP_PER_PG;
|
|
} else {
|
|
ceph_assert(s == stringify(OMAP_BULK));
|
|
}
|
|
dout(10) << __func__ << " per_pool_omap = " << per_pool_omap << dendl;
|
|
} else {
|
|
dout(10) << __func__ << " per_pool_omap not present" << dendl;
|
|
}
|
|
_check_no_per_pg_or_pool_omap_alert();
|
|
}
|
|
|
|
void BlueStore::_open_statfs()
|
|
{
|
|
osd_pools.clear();
|
|
vstatfs.reset();
|
|
|
|
bufferlist bl;
|
|
int r = db->get(PREFIX_STAT, BLUESTORE_GLOBAL_STATFS_KEY, &bl);
|
|
if (r >= 0) {
|
|
per_pool_stat_collection = false;
|
|
if (size_t(bl.length()) >= sizeof(vstatfs.values)) {
|
|
auto it = bl.cbegin();
|
|
vstatfs.decode(it);
|
|
dout(10) << __func__ << " store_statfs is found" << dendl;
|
|
} else {
|
|
dout(10) << __func__ << " store_statfs is corrupt, using empty" << dendl;
|
|
}
|
|
_check_legacy_statfs_alert();
|
|
} else {
|
|
per_pool_stat_collection = true;
|
|
dout(10) << __func__ << " per-pool statfs is enabled" << dendl;
|
|
KeyValueDB::Iterator it = db->get_iterator(PREFIX_STAT, KeyValueDB::ITERATOR_NOCACHE);
|
|
for (it->upper_bound(string());
|
|
it->valid();
|
|
it->next()) {
|
|
|
|
uint64_t pool_id;
|
|
int r = get_key_pool_stat(it->key(), &pool_id);
|
|
ceph_assert(r == 0);
|
|
|
|
bufferlist bl;
|
|
bl = it->value();
|
|
auto p = bl.cbegin();
|
|
auto& st = osd_pools[pool_id];
|
|
try {
|
|
st.decode(p);
|
|
vstatfs += st;
|
|
|
|
dout(10) << __func__ << " pool " << std::hex << pool_id
|
|
<< " statfs(hex) " << st
|
|
<< std::dec << dendl;
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " failed to decode pool stats, key:"
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
}
|
|
}
|
|
}
|
|
dout(10) << __func__ << " statfs " << std::hex
|
|
<< vstatfs << std::dec << dendl;
|
|
|
|
}
|
|
|
|
int BlueStore::_setup_block_symlink_or_file(
|
|
string name,
|
|
string epath,
|
|
uint64_t size,
|
|
bool create)
|
|
{
|
|
dout(20) << __func__ << " name " << name << " path " << epath
|
|
<< " size " << size << " create=" << (int)create << dendl;
|
|
int r = 0;
|
|
int flags = O_RDWR|O_CLOEXEC;
|
|
if (create)
|
|
flags |= O_CREAT;
|
|
if (epath.length()) {
|
|
r = ::symlinkat(epath.c_str(), path_fd, name.c_str());
|
|
if (r < 0) {
|
|
r = -errno;
|
|
derr << __func__ << " failed to create " << name << " symlink to "
|
|
<< epath << ": " << cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
|
|
if (!epath.compare(0, strlen(SPDK_PREFIX), SPDK_PREFIX)) {
|
|
int fd = ::openat(path_fd, epath.c_str(), flags, 0644);
|
|
if (fd < 0) {
|
|
r = -errno;
|
|
derr << __func__ << " failed to open " << epath << " file: "
|
|
<< cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
// write the Transport ID of the NVMe device
|
|
// a transport id for PCIe looks like: "trtype:PCIe traddr:0000:02:00.0"
|
|
// where "0000:02:00.0" is the selector of a PCI device, see
|
|
// the first column of "lspci -mm -n -D"
|
|
// a transport id for tcp looks like: "trype:TCP adrfam:IPv4 traddr:172.31.89.152 trsvcid:4420"
|
|
string trid = epath.substr(strlen(SPDK_PREFIX));
|
|
r = ::write(fd, trid.c_str(), trid.size());
|
|
ceph_assert(r == static_cast<int>(trid.size()));
|
|
dout(1) << __func__ << " created " << name << " symlink to "
|
|
<< epath << dendl;
|
|
VOID_TEMP_FAILURE_RETRY(::close(fd));
|
|
}
|
|
}
|
|
if (size) {
|
|
int fd = ::openat(path_fd, name.c_str(), flags, 0644);
|
|
if (fd >= 0) {
|
|
// block file is present
|
|
struct stat st;
|
|
int r = ::fstat(fd, &st);
|
|
if (r == 0 &&
|
|
S_ISREG(st.st_mode) && // if it is a regular file
|
|
st.st_size == 0) { // and is 0 bytes
|
|
r = ::ftruncate(fd, size);
|
|
if (r < 0) {
|
|
r = -errno;
|
|
derr << __func__ << " failed to resize " << name << " file to "
|
|
<< size << ": " << cpp_strerror(r) << dendl;
|
|
VOID_TEMP_FAILURE_RETRY(::close(fd));
|
|
return r;
|
|
}
|
|
|
|
if (cct->_conf->bluestore_block_preallocate_file) {
|
|
r = ::ceph_posix_fallocate(fd, 0, size);
|
|
if (r > 0) {
|
|
derr << __func__ << " failed to prefallocate " << name << " file to "
|
|
<< size << ": " << cpp_strerror(r) << dendl;
|
|
VOID_TEMP_FAILURE_RETRY(::close(fd));
|
|
return -r;
|
|
}
|
|
}
|
|
dout(1) << __func__ << " resized " << name << " file to "
|
|
<< byte_u_t(size) << dendl;
|
|
}
|
|
VOID_TEMP_FAILURE_RETRY(::close(fd));
|
|
} else {
|
|
int r = -errno;
|
|
if (r != -ENOENT) {
|
|
derr << __func__ << " failed to open " << name << " file: "
|
|
<< cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::mkfs()
|
|
{
|
|
dout(1) << __func__ << " path " << path << dendl;
|
|
int r;
|
|
uuid_d old_fsid;
|
|
uint64_t reserved;
|
|
if (cct->_conf->osd_max_object_size > OBJECT_MAX_SIZE) {
|
|
derr << __func__ << " osd_max_object_size "
|
|
<< cct->_conf->osd_max_object_size << " > bluestore max "
|
|
<< OBJECT_MAX_SIZE << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
{
|
|
string done;
|
|
r = read_meta("mkfs_done", &done);
|
|
if (r == 0) {
|
|
dout(1) << __func__ << " already created" << dendl;
|
|
if (cct->_conf->bluestore_fsck_on_mkfs) {
|
|
r = fsck(cct->_conf->bluestore_fsck_on_mkfs_deep);
|
|
if (r < 0) {
|
|
derr << __func__ << " fsck found fatal error: " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
if (r > 0) {
|
|
derr << __func__ << " fsck found " << r << " errors" << dendl;
|
|
r = -EIO;
|
|
}
|
|
}
|
|
return r; // idempotent
|
|
}
|
|
}
|
|
r = _open_path();
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = _open_fsid(true);
|
|
if (r < 0)
|
|
goto out_path_fd;
|
|
|
|
r = _lock_fsid();
|
|
if (r < 0)
|
|
goto out_close_fsid;
|
|
|
|
r = _read_fsid(&old_fsid);
|
|
if (r < 0 || old_fsid.is_zero()) {
|
|
if (fsid.is_zero()) {
|
|
fsid.generate_random();
|
|
dout(1) << __func__ << " generated fsid " << fsid << dendl;
|
|
} else {
|
|
dout(1) << __func__ << " using provided fsid " << fsid << dendl;
|
|
}
|
|
// we'll write it later.
|
|
} else {
|
|
if (!fsid.is_zero() && fsid != old_fsid) {
|
|
derr << __func__ << " on-disk fsid " << old_fsid
|
|
<< " != provided " << fsid << dendl;
|
|
r = -EINVAL;
|
|
goto out_close_fsid;
|
|
}
|
|
fsid = old_fsid;
|
|
}
|
|
|
|
r = _setup_block_symlink_or_file("block", cct->_conf->bluestore_block_path,
|
|
cct->_conf->bluestore_block_size,
|
|
cct->_conf->bluestore_block_create);
|
|
if (r < 0)
|
|
goto out_close_fsid;
|
|
if (cct->_conf->bluestore_bluefs) {
|
|
r = _setup_block_symlink_or_file("block.wal", cct->_conf->bluestore_block_wal_path,
|
|
cct->_conf->bluestore_block_wal_size,
|
|
cct->_conf->bluestore_block_wal_create);
|
|
if (r < 0)
|
|
goto out_close_fsid;
|
|
r = _setup_block_symlink_or_file("block.db", cct->_conf->bluestore_block_db_path,
|
|
cct->_conf->bluestore_block_db_size,
|
|
cct->_conf->bluestore_block_db_create);
|
|
if (r < 0)
|
|
goto out_close_fsid;
|
|
}
|
|
|
|
r = _open_bdev(true);
|
|
if (r < 0)
|
|
goto out_close_fsid;
|
|
{
|
|
string type;
|
|
r = read_meta("type", &type);
|
|
if (r == 0) {
|
|
if (type != "bluestore") {
|
|
derr << __func__ << " expected bluestore, but type is " << type << dendl;
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
r = write_meta("type", "bluestore");
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
}
|
|
|
|
freelist_type = "bitmap";
|
|
dout(10) << " freelist_type " << freelist_type << dendl;
|
|
|
|
// choose min_alloc_size
|
|
dout(5) << __func__ << " optimal_io_size 0x" << std::hex << optimal_io_size
|
|
<< " block_size: 0x" << block_size << std::dec << dendl;
|
|
if ((cct->_conf->bluestore_use_optimal_io_size_for_min_alloc_size) && (optimal_io_size != 0)) {
|
|
dout(5) << __func__ << " optimal_io_size 0x" << std::hex << optimal_io_size
|
|
<< " for min_alloc_size 0x" << min_alloc_size << std::dec << dendl;
|
|
min_alloc_size = optimal_io_size;
|
|
}
|
|
else if (cct->_conf->bluestore_min_alloc_size) {
|
|
min_alloc_size = cct->_conf->bluestore_min_alloc_size;
|
|
} else {
|
|
ceph_assert(bdev);
|
|
if (_use_rotational_settings()) {
|
|
min_alloc_size = cct->_conf->bluestore_min_alloc_size_hdd;
|
|
} else {
|
|
min_alloc_size = cct->_conf->bluestore_min_alloc_size_ssd;
|
|
}
|
|
}
|
|
_validate_bdev();
|
|
|
|
// make sure min_alloc_size is power of 2 aligned.
|
|
if (!std::has_single_bit(min_alloc_size)) {
|
|
derr << __func__ << " min_alloc_size 0x"
|
|
<< std::hex << min_alloc_size << std::dec
|
|
<< " is not power of 2 aligned!"
|
|
<< dendl;
|
|
r = -EINVAL;
|
|
goto out_close_bdev;
|
|
}
|
|
|
|
// make sure min_alloc_size is >= and aligned with block size
|
|
if (min_alloc_size % block_size != 0) {
|
|
derr << __func__ << " min_alloc_size 0x"
|
|
<< std::hex << min_alloc_size
|
|
<< " is less or not aligned with block_size: 0x"
|
|
<< block_size << std::dec << dendl;
|
|
r = -EINVAL;
|
|
goto out_close_bdev;
|
|
}
|
|
|
|
r = _create_alloc();
|
|
if (r < 0) {
|
|
goto out_close_bdev;
|
|
}
|
|
|
|
// initialize alloc, remove regions taken
|
|
reserved = _get_ondisk_reserved();
|
|
// full free
|
|
alloc->init_add_free(0, p2align(bdev->get_size(), min_alloc_size));
|
|
// allocate bdev label + bluefs superblock reserved space.
|
|
alloc->init_rm_free(BDEV_FIRST_LABEL_POSITION, reserved);
|
|
|
|
// take possible bdev locations, so it will not be used
|
|
if (cct->_conf.get_val<bool>("bluestore_bdev_label_multi")) {
|
|
// take space for other bdev label copies
|
|
for (size_t i = 1; i < bdev_label_positions.size(); i++) {
|
|
uint64_t location = bdev_label_positions[i];
|
|
uint64_t size = p2roundup(BDEV_LABEL_BLOCK_SIZE, min_alloc_size);
|
|
if (location + size > bdev->get_size()) continue;
|
|
ceph_assert(p2align(location, min_alloc_size) == location);
|
|
alloc->init_rm_free(location, size);
|
|
}
|
|
}
|
|
|
|
r = _open_db(true);
|
|
if (r < 0)
|
|
goto out_close_alloc;
|
|
|
|
{
|
|
KeyValueDB::Transaction t = db->get_transaction();
|
|
r = _open_fm(t, false, true);
|
|
if (r < 0)
|
|
goto out_close_db;
|
|
{
|
|
bufferlist bl;
|
|
encode((uint64_t)0, bl);
|
|
t->set(PREFIX_SUPER, "nid_max", bl);
|
|
t->set(PREFIX_SUPER, "blobid_max", bl);
|
|
}
|
|
|
|
{
|
|
bufferlist bl;
|
|
encode((uint64_t)min_alloc_size, bl);
|
|
t->set(PREFIX_SUPER, "min_alloc_size", bl);
|
|
}
|
|
{
|
|
bufferlist bl;
|
|
if (cct->_conf.get_val<bool>("bluestore_debug_legacy_omap")) {
|
|
bl.append(stringify(OMAP_BULK));
|
|
} else {
|
|
bl.append(stringify(OMAP_PER_PG));
|
|
}
|
|
t->set(PREFIX_SUPER, "per_pool_omap", bl);
|
|
}
|
|
|
|
ondisk_format = latest_ondisk_format;
|
|
_prepare_ondisk_format_super(t);
|
|
db->submit_transaction_sync(t);
|
|
}
|
|
|
|
r = write_meta("kv_backend", cct->_conf->bluestore_kvbackend);
|
|
if (r < 0)
|
|
goto out_close_fm;
|
|
|
|
r = write_meta("bluefs", stringify(bluefs ? 1 : 0));
|
|
if (r < 0)
|
|
goto out_close_fm;
|
|
|
|
r = write_meta("elastic_shared_blobs",
|
|
cct->_conf.get_val<bool>("bluestore_elastic_shared_blobs") ? "1" : "0");
|
|
if (r < 0)
|
|
goto out_close_fm;
|
|
|
|
if (fsid != old_fsid) {
|
|
r = _write_fsid();
|
|
if (r < 0) {
|
|
derr << __func__ << " error writing fsid: " << cpp_strerror(r) << dendl;
|
|
goto out_close_fm;
|
|
}
|
|
}
|
|
|
|
out_close_fm:
|
|
_close_fm();
|
|
out_close_db:
|
|
_close_db();
|
|
out_close_alloc:
|
|
_close_alloc();
|
|
out_close_bdev:
|
|
_close_bdev();
|
|
out_close_fsid:
|
|
_close_fsid();
|
|
out_path_fd:
|
|
_close_path();
|
|
|
|
if (r == 0 &&
|
|
cct->_conf->bluestore_fsck_on_mkfs) {
|
|
int rc = fsck(cct->_conf->bluestore_fsck_on_mkfs_deep);
|
|
if (rc < 0)
|
|
return rc;
|
|
if (rc > 0) {
|
|
derr << __func__ << " fsck found " << rc << " errors" << dendl;
|
|
r = -EIO;
|
|
}
|
|
}
|
|
|
|
if (r == 0) {
|
|
// indicate success by writing the 'mkfs_done' file
|
|
r = write_meta("mkfs_done", "yes");
|
|
}
|
|
|
|
if (r < 0) {
|
|
derr << __func__ << " failed, " << cpp_strerror(r) << dendl;
|
|
} else {
|
|
dout(0) << __func__ << " success" << dendl;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::add_new_bluefs_device(int id, const string& dev_path)
|
|
{
|
|
dout(10) << __func__ << " path " << dev_path << " id:" << id << dendl;
|
|
int r;
|
|
ceph_assert(path_fd < 0);
|
|
|
|
ceph_assert(id == BlueFS::BDEV_NEWWAL || id == BlueFS::BDEV_NEWDB);
|
|
|
|
if (!cct->_conf->bluestore_bluefs) {
|
|
derr << __func__ << " bluefs isn't configured, can't add new device " << dendl;
|
|
return -EIO;
|
|
}
|
|
dout(5) << __func__ << "::NCB::calling open_db_and_around(read-only)" << dendl;
|
|
r = _open_db_and_around(true);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
if (id == BlueFS::BDEV_NEWWAL) {
|
|
string p = path + "/block.wal";
|
|
r = _setup_block_symlink_or_file("block.wal", dev_path,
|
|
cct->_conf->bluestore_block_wal_size,
|
|
true);
|
|
ceph_assert(r == 0);
|
|
|
|
r = bluefs->add_block_device(BlueFS::BDEV_NEWWAL, p,
|
|
cct->_conf->bdev_enable_discard);
|
|
ceph_assert(r == 0);
|
|
|
|
if (bluefs->bdev_support_label(BlueFS::BDEV_NEWWAL)) {
|
|
r = _check_or_set_bdev_label(
|
|
bluefs->get_block_device(BlueFS::BDEV_NEWWAL),
|
|
p,
|
|
"bluefs wal",
|
|
true);
|
|
ceph_assert(r == 0);
|
|
}
|
|
|
|
bluefs_layout.dedicated_wal = true;
|
|
} else if (id == BlueFS::BDEV_NEWDB) {
|
|
string p = path + "/block.db";
|
|
r = _setup_block_symlink_or_file("block.db", dev_path,
|
|
cct->_conf->bluestore_block_db_size,
|
|
true);
|
|
ceph_assert(r == 0);
|
|
|
|
r = bluefs->add_block_device(BlueFS::BDEV_NEWDB, p,
|
|
cct->_conf->bdev_enable_discard);
|
|
ceph_assert(r == 0);
|
|
|
|
if (bluefs->bdev_support_label(BlueFS::BDEV_NEWDB)) {
|
|
r = _check_or_set_bdev_label(
|
|
bluefs->get_block_device(BlueFS::BDEV_NEWDB),
|
|
p,
|
|
"bluefs db",
|
|
true);
|
|
ceph_assert(r == 0);
|
|
}
|
|
bluefs_layout.shared_bdev = BlueFS::BDEV_SLOW;
|
|
bluefs_layout.dedicated_db = true;
|
|
}
|
|
bluefs->umount();
|
|
bluefs->mount();
|
|
|
|
r = bluefs->prepare_new_device(id, bluefs_layout);
|
|
ceph_assert(r == 0);
|
|
|
|
if (r < 0) {
|
|
derr << __func__ << " failed, " << cpp_strerror(r) << dendl;
|
|
} else {
|
|
dout(0) << __func__ << " success" << dendl;
|
|
}
|
|
|
|
_close_db_and_around();
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::migrate_to_existing_bluefs_device(const set<int>& devs_source,
|
|
int id)
|
|
{
|
|
dout(10) << __func__ << " id:" << id << dendl;
|
|
ceph_assert(path_fd < 0);
|
|
|
|
ceph_assert(id == BlueFS::BDEV_SLOW || id == BlueFS::BDEV_DB);
|
|
|
|
if (!cct->_conf->bluestore_bluefs) {
|
|
derr << __func__ << " bluefs isn't configured, can't add new device " << dendl;
|
|
return -EIO;
|
|
}
|
|
|
|
int r = _open_db_and_around(true);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
auto close_db = make_scope_guard([&] {
|
|
_close_db_and_around();
|
|
});
|
|
uint64_t used_space = 0;
|
|
for(auto src_id : devs_source) {
|
|
used_space += bluefs->get_used(src_id);
|
|
}
|
|
uint64_t target_free = bluefs->get_free(id);
|
|
if (target_free < used_space) {
|
|
derr << __func__
|
|
<< " can't migrate, free space at target: " << target_free
|
|
<< " is less than required space: " << used_space
|
|
<< dendl;
|
|
return -ENOSPC;
|
|
}
|
|
if (devs_source.count(BlueFS::BDEV_DB)) {
|
|
bluefs_layout.shared_bdev = BlueFS::BDEV_DB;
|
|
bluefs_layout.dedicated_db = false;
|
|
}
|
|
if (devs_source.count(BlueFS::BDEV_WAL)) {
|
|
bluefs_layout.dedicated_wal = false;
|
|
}
|
|
r = bluefs->device_migrate_to_existing(cct, devs_source, id, bluefs_layout);
|
|
if (r < 0) {
|
|
derr << __func__ << " failed during BlueFS migration, " << cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
|
|
if (devs_source.count(BlueFS::BDEV_DB)) {
|
|
r = unlink(string(path + "/block.db").c_str());
|
|
ceph_assert(r == 0);
|
|
}
|
|
if (devs_source.count(BlueFS::BDEV_WAL)) {
|
|
r = unlink(string(path + "/block.wal").c_str());
|
|
ceph_assert(r == 0);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::migrate_to_new_bluefs_device(const set<int>& devs_source,
|
|
int id,
|
|
const string& dev_path)
|
|
{
|
|
dout(10) << __func__ << " path " << dev_path << " id:" << id << dendl;
|
|
ceph_assert(path_fd < 0);
|
|
|
|
ceph_assert(id == BlueFS::BDEV_NEWWAL || id == BlueFS::BDEV_NEWDB);
|
|
|
|
if (!cct->_conf->bluestore_bluefs) {
|
|
derr << __func__ << " bluefs isn't configured, can't add new device " << dendl;
|
|
return -EIO;
|
|
}
|
|
|
|
int r = _open_db_and_around(true);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
auto close_db = make_scope_guard([&] {
|
|
_close_db_and_around();
|
|
});
|
|
|
|
string link_db;
|
|
string link_wal;
|
|
if (devs_source.count(BlueFS::BDEV_DB) &&
|
|
bluefs_layout.shared_bdev != BlueFS::BDEV_DB) {
|
|
link_db = path + "/block.db";
|
|
bluefs_layout.shared_bdev = BlueFS::BDEV_DB;
|
|
bluefs_layout.dedicated_db = false;
|
|
}
|
|
if (devs_source.count(BlueFS::BDEV_WAL)) {
|
|
link_wal = path + "/block.wal";
|
|
bluefs_layout.dedicated_wal = false;
|
|
}
|
|
|
|
size_t target_size = 0;
|
|
string target_name;
|
|
if (id == BlueFS::BDEV_NEWWAL) {
|
|
target_name = "block.wal";
|
|
target_size = cct->_conf->bluestore_block_wal_size;
|
|
bluefs_layout.dedicated_wal = true;
|
|
|
|
r = bluefs->add_block_device(BlueFS::BDEV_NEWWAL, dev_path,
|
|
cct->_conf->bdev_enable_discard);
|
|
ceph_assert(r == 0);
|
|
|
|
if (bluefs->bdev_support_label(BlueFS::BDEV_NEWWAL)) {
|
|
r = _check_or_set_bdev_label(
|
|
bluefs->get_block_device(BlueFS::BDEV_NEWWAL),
|
|
dev_path,
|
|
"bluefs wal",
|
|
true);
|
|
ceph_assert(r == 0);
|
|
}
|
|
} else if (id == BlueFS::BDEV_NEWDB) {
|
|
target_name = "block.db";
|
|
target_size = cct->_conf->bluestore_block_db_size;
|
|
bluefs_layout.shared_bdev = BlueFS::BDEV_SLOW;
|
|
bluefs_layout.dedicated_db = true;
|
|
|
|
r = bluefs->add_block_device(BlueFS::BDEV_NEWDB, dev_path,
|
|
cct->_conf->bdev_enable_discard);
|
|
ceph_assert(r == 0);
|
|
|
|
if (bluefs->bdev_support_label(BlueFS::BDEV_NEWDB)) {
|
|
r = _check_or_set_bdev_label(
|
|
bluefs->get_block_device(BlueFS::BDEV_NEWDB),
|
|
dev_path,
|
|
"bluefs db",
|
|
true);
|
|
ceph_assert(r == 0);
|
|
}
|
|
}
|
|
|
|
bluefs->umount();
|
|
bluefs->mount();
|
|
|
|
r = bluefs->device_migrate_to_new(cct, devs_source, id, bluefs_layout);
|
|
|
|
if (r < 0) {
|
|
derr << __func__ << " failed during BlueFS migration, " << cpp_strerror(r) << dendl;
|
|
return r;
|
|
}
|
|
|
|
if (!link_db.empty()) {
|
|
r = unlink(link_db.c_str());
|
|
ceph_assert(r == 0);
|
|
}
|
|
if (!link_wal.empty()) {
|
|
r = unlink(link_wal.c_str());
|
|
ceph_assert(r == 0);
|
|
}
|
|
r = _setup_block_symlink_or_file(
|
|
target_name,
|
|
dev_path,
|
|
target_size,
|
|
true);
|
|
ceph_assert(r == 0);
|
|
dout(0) << __func__ << " success" << dendl;
|
|
|
|
return r;
|
|
}
|
|
|
|
string BlueStore::get_device_path(unsigned id)
|
|
{
|
|
string res;
|
|
if (id < BlueFS::MAX_BDEV) {
|
|
switch (id) {
|
|
case BlueFS::BDEV_WAL:
|
|
res = path + "/block.wal";
|
|
break;
|
|
case BlueFS::BDEV_DB:
|
|
if (id == bluefs_layout.shared_bdev) {
|
|
res = path + "/block";
|
|
} else {
|
|
res = path + "/block.db";
|
|
}
|
|
break;
|
|
case BlueFS::BDEV_SLOW:
|
|
res = path + "/block";
|
|
break;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
int BlueStore::expand_devices(ostream& out)
|
|
{
|
|
// let's open in read-only mode first to be able to recover
|
|
// from the out-of-space state at DB/shared volume(s)
|
|
// Opening in R/W mode might cause extra space allocation
|
|
// which is effectively a show stopper for volume expansion.
|
|
int r = _open_db_and_around(true);
|
|
ceph_assert(r == 0);
|
|
bluefs->dump_block_extents(out);
|
|
out << "Expanding DB/WAL..." << std::endl;
|
|
// updating dedicated devices first
|
|
for (auto devid : { BlueFS::BDEV_WAL, BlueFS::BDEV_DB}) {
|
|
if (devid == bluefs_layout.shared_bdev) {
|
|
continue;
|
|
}
|
|
auto my_bdev = bluefs->get_block_device(devid);
|
|
uint64_t size = my_bdev ? my_bdev->get_size() : 0;
|
|
if (size == 0) {
|
|
// no bdev
|
|
continue;
|
|
}
|
|
if (my_bdev->supported_bdev_label()) {
|
|
string my_path = get_device_path(devid);
|
|
bluestore_bdev_label_t my_label;
|
|
int r = _read_bdev_label(cct, my_bdev, my_path, &my_label);
|
|
if (r < 0) {
|
|
derr << "unable to read label for " << my_path << ": "
|
|
<< cpp_strerror(r) << dendl;
|
|
continue;
|
|
} else {
|
|
if (size == my_label.size) {
|
|
// no need to expand
|
|
out << devid
|
|
<< " : nothing to do, skipped"
|
|
<< std::endl;
|
|
continue;
|
|
} else if (size < my_label.size) {
|
|
// something weird in bdev label
|
|
out << devid
|
|
<<" : ERROR: bdev label is above device size, skipped"
|
|
<< std::endl;
|
|
continue;
|
|
} else {
|
|
my_label.size = size;
|
|
out << devid
|
|
<< " : Expanding to 0x" << std::hex << size
|
|
<< std::dec << "(" << byte_u_t(size) << ")"
|
|
<< std::endl;
|
|
r = _write_bdev_label(cct, my_bdev, my_path, my_label);
|
|
if (r < 0) {
|
|
derr << "unable to write label for " << my_path << ": "
|
|
<< cpp_strerror(r) << dendl;
|
|
} else {
|
|
out << devid
|
|
<< " : size updated to 0x" << std::hex << size
|
|
<< std::dec << "(" << byte_u_t(size) << ")"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// now proceed with a shared device
|
|
uint64_t size0 = fm->get_size();
|
|
uint64_t size = bdev->get_size();
|
|
auto devid = bluefs_layout.shared_bdev;
|
|
auto aligned_size = p2align(size, min_alloc_size);
|
|
if (aligned_size == size0) {
|
|
// no need to expand
|
|
out << devid
|
|
<< " : nothing to do, skipped"
|
|
<< std::endl;
|
|
} else if (aligned_size < size0) {
|
|
// something weird in bdev label
|
|
out << devid
|
|
<< " : ERROR: previous device size is above the current one, skipped"
|
|
<< std::endl;
|
|
} else {
|
|
auto my_path = get_device_path(devid);
|
|
out << devid
|
|
<<" : Expanding to 0x" << std::hex << size
|
|
<< std::dec << "(" << byte_u_t(size) << ")"
|
|
<< std::endl;
|
|
r = _write_out_fm_meta(size);
|
|
if (r != 0) {
|
|
derr << "unable to write out fm meta for " << my_path << ": "
|
|
<< cpp_strerror(r) << dendl;
|
|
} else if (bdev->supported_bdev_label()) {
|
|
bdev_label.size = size;
|
|
uint64_t lsize = std::max(BDEV_LABEL_BLOCK_SIZE, min_alloc_size);
|
|
for (uint64_t loc : bdev_label_positions) {
|
|
if ((loc >= size0) && (loc + lsize <= size)) {
|
|
bdev_label_valid_locations.push_back(loc);
|
|
if (!bdev_label_multi) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
r = _write_bdev_label(cct, bdev, my_path,
|
|
bdev_label, bdev_label_valid_locations);
|
|
if (r != 0) {
|
|
derr << "unable to write label(s) for " << my_path << ": "
|
|
<< cpp_strerror(r) << dendl;
|
|
}
|
|
}
|
|
if (r == 0) {
|
|
out << devid
|
|
<< " : size updated to 0x" << std::hex << size
|
|
<< std::dec << "(" << byte_u_t(size) << ")"
|
|
<< std::endl;
|
|
_close_db_and_around();
|
|
|
|
//
|
|
// Mount in read/write to sync expansion changes
|
|
// and make sure everything is all right.
|
|
//
|
|
before_expansion_bdev_size = size0; // preserve orignal size to permit
|
|
// following _db_open_and_around()
|
|
// do some post-init stuff on opened
|
|
// allocator.
|
|
|
|
r = _open_db_and_around(false);
|
|
ceph_assert(r == 0);
|
|
}
|
|
}
|
|
_close_db_and_around();
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::dump_bluefs_sizes(ostream& out)
|
|
{
|
|
int r = _open_db_and_around(true);
|
|
ceph_assert(r == 0);
|
|
bluefs->dump_block_extents(out);
|
|
_close_db_and_around();
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::trim_free_space(const string& type, std::ostream& outss)
|
|
{
|
|
auto iterated_allocation = [&](size_t off, size_t len) {
|
|
ceph_assert(len > 0);
|
|
interval_set<uint64_t> to_discard;
|
|
to_discard.union_insert(off, len);
|
|
bdev->try_discard(to_discard, false);
|
|
};
|
|
if (type == "bdev-block") {
|
|
if (!bdev->is_discard_supported()) {
|
|
outss << "device " << type << " does not support trim";
|
|
return;
|
|
}
|
|
shared_alloc.a->foreach(iterated_allocation);
|
|
outss << "device " << type << " trim done";
|
|
} else {
|
|
bluefs->trim_free_space(type, outss);
|
|
}
|
|
}
|
|
|
|
int BlueStore::zap_device(CephContext* cct, const string& dev)
|
|
{
|
|
string path = dev; // dummy var for dout
|
|
uint64_t brush_size;
|
|
dout(5) << __func__ << " " << dev << dendl;
|
|
unique_ptr<BlockDevice>
|
|
_bdev(BlockDevice::create(cct, dev, nullptr, nullptr, nullptr, nullptr));
|
|
int r = _bdev->open(dev);
|
|
if (r < 0)
|
|
goto fail;
|
|
brush_size = std::max(_bdev->get_block_size(), BDEV_LABEL_BLOCK_SIZE);
|
|
|
|
for (auto off : bdev_label_positions) {
|
|
uint64_t end = std::min(off + brush_size, _bdev->get_size());
|
|
if (end > off) {
|
|
uint64_t l = end - off;
|
|
bufferlist bl;
|
|
bl.append_zero(l);
|
|
dout(10) << __func__ << " writing 0x"
|
|
<< std::hex << off << "~" << l
|
|
<< std::dec << " to " << dev
|
|
<< dendl;
|
|
r = _bdev->write(off, bl, false);
|
|
if (r < 0) {
|
|
derr << __func__ << " error writing 0x"
|
|
<< std::hex << off << "~" << l
|
|
<< std::dec << " to " << dev
|
|
<< " : " << cpp_strerror(r) << dendl;
|
|
break;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
_bdev->close();
|
|
|
|
fail:
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::set_cache_shards(unsigned num)
|
|
{
|
|
dout(10) << __func__ << " " << num << dendl;
|
|
size_t oold = onode_cache_shards.size();
|
|
size_t bold = buffer_cache_shards.size();
|
|
ceph_assert(num >= oold && num >= bold);
|
|
onode_cache_shards.resize(num);
|
|
buffer_cache_shards.resize(num);
|
|
for (unsigned i = oold; i < num; ++i) {
|
|
onode_cache_shards[i] =
|
|
OnodeCacheShard::create(cct, cct->_conf->bluestore_cache_type,
|
|
logger);
|
|
}
|
|
for (unsigned i = bold; i < num; ++i) {
|
|
buffer_cache_shards[i] =
|
|
BufferCacheShard::create(this, cct->_conf->bluestore_cache_type,
|
|
logger);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------
|
|
bool BlueStore::has_null_manager() const
|
|
{
|
|
return (fm && fm->is_null_manager());
|
|
}
|
|
|
|
int BlueStore::mount_readonly()
|
|
{
|
|
int r = _mount_readonly();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
r = _open_collections();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
auto shutdown_cache = make_scope_guard([&] {
|
|
if (!mounted) {
|
|
_shutdown_cache();
|
|
}
|
|
});
|
|
|
|
_kv_start();
|
|
auto stop_kv = make_scope_guard([&] {
|
|
if (!mounted) {
|
|
_kv_stop();
|
|
}
|
|
});
|
|
|
|
r = _deferred_replay();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
mempool_thread.init();
|
|
mounted = true;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_mount_readonly()
|
|
{
|
|
dout(5) << __func__ << dendl;
|
|
{
|
|
string type;
|
|
int r = read_meta("type", &type);
|
|
if (r < 0) {
|
|
derr << __func__ << " failed to load os-type: " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
if (type != "bluestore") {
|
|
derr << __func__ << " expected bluestore, but type is " << type << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
int r = _open_path();
|
|
if (r < 0)
|
|
return r;
|
|
r = _open_fsid(false);
|
|
if (r < 0)
|
|
goto out_path;
|
|
|
|
r = _read_fsid(&fsid);
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
|
|
r = _lock_fsid();
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
|
|
r = _open_bdev(false);
|
|
if (r < 0)
|
|
goto out_fsid;
|
|
|
|
r = _open_db(false, false, true);
|
|
if (r < 0)
|
|
goto out_bdev;
|
|
|
|
r = _open_super_meta();
|
|
if (r < 0) {
|
|
goto out_db;
|
|
}
|
|
return 0;
|
|
|
|
out_db:
|
|
_close_db();
|
|
out_bdev:
|
|
_close_bdev();
|
|
out_fsid:
|
|
_close_fsid();
|
|
out_path:
|
|
_close_path();
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::umount_readonly()
|
|
{
|
|
ceph_assert(_kv_only || mounted);
|
|
_osr_drain_all();
|
|
|
|
mounted = false;
|
|
|
|
if (!_kv_only) {
|
|
mempool_thread.shutdown();
|
|
dout(20) << __func__ << " stopping kv thread" << dendl;
|
|
_kv_stop();
|
|
// skip cache cleanup step on fast shutdown
|
|
if (likely(!m_fast_shutdown)) {
|
|
_shutdown_cache();
|
|
}
|
|
dout(20) << __func__ << " closing" << dendl;
|
|
}
|
|
return _umount_readonly();
|
|
}
|
|
|
|
int BlueStore::_umount_readonly()
|
|
{
|
|
dout(5) << __func__ << dendl;
|
|
if (db) {
|
|
_close_db();
|
|
}
|
|
if (bluefs) {
|
|
_close_bluefs();
|
|
}
|
|
_close_bdev();
|
|
_close_fsid();
|
|
_close_path();
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_mount()
|
|
{
|
|
dout(5) << __func__ << " path " << path << dendl;
|
|
|
|
{
|
|
int r = read_meta_conf_check_env();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
}
|
|
use_write_v2 = cct->_conf.get_val<bool>("bluestore_write_v2");
|
|
if (cct->_conf.get_val<bool>("bluestore_write_v2_random")) {
|
|
srand(time(NULL) * 11 + 3);
|
|
use_write_v2 = rand() % 2;
|
|
}
|
|
segment_size = (cct->_conf.get_val<Option::size_t>("bluestore_onode_segment_size"));
|
|
if (cct->_conf.get_val<bool>("bluestore_debug_onode_segmentation_random")) {
|
|
srand(time(NULL) * 13 + 5);
|
|
if (rand() % 2) {
|
|
segment_size = 0;
|
|
}
|
|
}
|
|
debug_extent_map_encode_check = cct->_conf.get_val<bool>("bluestore_debug_extent_map_encode_check");
|
|
_kv_only = false;
|
|
if (cct->_conf->bluestore_fsck_on_mount) {
|
|
int rc = fsck(cct->_conf->bluestore_fsck_on_mount_deep);
|
|
if (rc < 0)
|
|
return rc;
|
|
if (rc > 0) {
|
|
derr << __func__ << " fsck found " << rc << " errors" << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
if (cct->_conf->osd_max_object_size > OBJECT_MAX_SIZE) {
|
|
derr << __func__ << " osd_max_object_size "
|
|
<< cct->_conf->osd_max_object_size << " > bluestore max "
|
|
<< OBJECT_MAX_SIZE << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
dout(5) << __func__ << "::NCB::calling open_db_and_around(read/write)" << dendl;
|
|
int r = _open_db_and_around(false);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
auto close_db = make_scope_guard([&] {
|
|
if (!mounted) {
|
|
_close_db_and_around();
|
|
}
|
|
});
|
|
|
|
r = _upgrade_super();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
// The recovery process for allocation-map needs to open collection early
|
|
r = _open_collections();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
auto shutdown_cache = make_scope_guard([&] {
|
|
if (!mounted) {
|
|
_shutdown_cache();
|
|
}
|
|
});
|
|
|
|
r = _reload_logger();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
_kv_start();
|
|
auto stop_kv = make_scope_guard([&] {
|
|
if (!mounted) {
|
|
_kv_stop();
|
|
}
|
|
});
|
|
|
|
r = _deferred_replay();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
mempool_thread.init();
|
|
|
|
if ((!per_pool_stat_collection || per_pool_omap != OMAP_PER_PG) &&
|
|
cct->_conf->bluestore_fsck_quick_fix_on_mount == true) {
|
|
|
|
auto was_per_pool_omap = per_pool_omap;
|
|
|
|
dout(1) << __func__ << " quick-fix on mount" << dendl;
|
|
_fsck_on_open(FSCK_SHALLOW, true);
|
|
|
|
//set again as hopefully it has been fixed
|
|
if (was_per_pool_omap != OMAP_PER_PG) {
|
|
_set_per_pool_omap();
|
|
}
|
|
}
|
|
|
|
mounted = true;
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::umount()
|
|
{
|
|
dout(5) << __func__ << dendl;
|
|
ceph_assert(_kv_only || mounted);
|
|
_osr_drain_all();
|
|
|
|
mounted = false;
|
|
|
|
ceph_assert(alloc);
|
|
|
|
if (!_kv_only) {
|
|
mempool_thread.shutdown();
|
|
dout(20) << __func__ << " stopping kv thread" << dendl;
|
|
_kv_stop();
|
|
// skip cache cleanup step on fast shutdown
|
|
if (likely(!m_fast_shutdown)) {
|
|
_shutdown_cache();
|
|
}
|
|
dout(20) << __func__ << " closing" << dendl;
|
|
}
|
|
_close_db_and_around();
|
|
// disable fsck on fast-shutdown
|
|
if (cct->_conf->bluestore_fsck_on_umount && !m_fast_shutdown) {
|
|
int rc = fsck(cct->_conf->bluestore_fsck_on_umount_deep);
|
|
if (rc < 0)
|
|
return rc;
|
|
if (rc > 0) {
|
|
derr << __func__ << " fsck found " << rc << " errors" << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::cold_open()
|
|
{
|
|
return _open_db_and_around(true);
|
|
}
|
|
|
|
int BlueStore::cold_close()
|
|
{
|
|
_close_db_and_around();
|
|
return 0;
|
|
}
|
|
|
|
// derr wrapper to limit enormous output and avoid log flooding.
|
|
// Of limited use where such output is expected for now
|
|
#define fsck_derr(err_cnt, threshold) \
|
|
if (err_cnt <= threshold) { \
|
|
bool need_skip_print = err_cnt == threshold; \
|
|
derr
|
|
|
|
#define fsck_dendl \
|
|
dendl; \
|
|
if (need_skip_print) \
|
|
derr << "more error lines skipped..." << dendl; \
|
|
}
|
|
|
|
int _fsck_sum_extents(
|
|
const PExtentVector& extents,
|
|
bool compressed,
|
|
store_statfs_t& expected_statfs,
|
|
BlueStore::pool_fsck_stats_t& pool_fsck_stat)
|
|
{
|
|
for (auto e : extents) {
|
|
if (!e.is_valid())
|
|
continue;
|
|
expected_statfs.allocated += e.length;
|
|
pool_fsck_stat.allocated += e.length;
|
|
if (compressed) {
|
|
expected_statfs.data_compressed_allocated += e.length;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_fsck_check_extents(
|
|
std::string_view ctx_descr,
|
|
const PExtentVector& extents,
|
|
bool compressed,
|
|
mempool_dynamic_bitset &used_blocks,
|
|
uint64_t granularity,
|
|
BlueStoreRepairer* repairer,
|
|
store_statfs_t& expected_statfs,
|
|
BlueStore::pool_fsck_stats_t& pool_fsck_stat,
|
|
FSCKDepth depth)
|
|
{
|
|
dout(30) << __func__ << " " << ctx_descr << ", extents " << extents << dendl;
|
|
int errors = 0;
|
|
for (auto e : extents) {
|
|
if (!e.is_valid())
|
|
continue;
|
|
expected_statfs.allocated += e.length;
|
|
pool_fsck_stat.allocated += e.length;
|
|
if (compressed) {
|
|
expected_statfs.data_compressed_allocated += e.length;
|
|
}
|
|
if (depth != FSCK_SHALLOW) {
|
|
bool already = false;
|
|
apply_for_bitset_range(
|
|
e.offset, e.length, granularity, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset &bs) {
|
|
if (bs.test(pos)) {
|
|
if (repairer) {
|
|
repairer->note_misreference(
|
|
pos * min_alloc_size, min_alloc_size, !already);
|
|
}
|
|
if (!already) {
|
|
derr << __func__ << "::fsck error: " << ctx_descr << ", extent " << e
|
|
<< " or a subset is already allocated (misreferenced)" << dendl;
|
|
++errors;
|
|
already = true;
|
|
}
|
|
}
|
|
else
|
|
bs.set(pos);
|
|
});
|
|
|
|
if (e.end() > bdev->get_size()) {
|
|
derr << "fsck error: " << ctx_descr << ", extent " << e
|
|
<< " past end of block device" << dendl;
|
|
++errors;
|
|
}
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
void BlueStore::_fsck_check_statfs(
|
|
const store_statfs_t& expected_statfs,
|
|
const per_pool_statfs& expected_pool_statfs,
|
|
int64_t& errors,
|
|
int64_t& warnings,
|
|
BlueStoreRepairer* repairer)
|
|
{
|
|
string key;
|
|
store_statfs_t actual_statfs;
|
|
store_statfs_t s;
|
|
{
|
|
// make a copy
|
|
per_pool_statfs my_expected_pool_statfs(expected_pool_statfs);
|
|
auto op = osd_pools.begin();
|
|
while (op != osd_pools.end()) {
|
|
get_pool_stat_key(op->first, &key);
|
|
op->second.publish(&s);
|
|
auto it_expected = my_expected_pool_statfs.find(op->first);
|
|
if (it_expected == my_expected_pool_statfs.end()) {
|
|
auto op0 = op++;
|
|
if (op0->second.is_empty()) {
|
|
// It's OK to lack relevant empty statfs record
|
|
continue;
|
|
}
|
|
derr << __func__ << "::fsck error: " << std::hex
|
|
<< "pool " << op0->first << " has got no statfs to match against: "
|
|
<< s
|
|
<< std::dec << dendl;
|
|
++errors;
|
|
if (repairer) {
|
|
osd_pools.erase(op0);
|
|
repairer->remove_key(db, PREFIX_STAT, key);
|
|
}
|
|
} else {
|
|
if (!(s == it_expected->second)) {
|
|
derr << "fsck error: actual " << s
|
|
<< " != expected " << it_expected->second
|
|
<< " for pool "
|
|
<< std::hex << op->first << std::dec << dendl;
|
|
++errors;
|
|
if (repairer) {
|
|
// repair in-memory in a hope this would be flushed properly on shutdown
|
|
s = it_expected->second;
|
|
op->second = it_expected->second;
|
|
repairer->fix_statfs(db, key, it_expected->second);
|
|
}
|
|
}
|
|
actual_statfs.add(s);
|
|
my_expected_pool_statfs.erase(it_expected);
|
|
++op;
|
|
}
|
|
}
|
|
// check stats that lack matching entities in osd_pools
|
|
for (auto &p : my_expected_pool_statfs) {
|
|
if (p.second.is_zero()) {
|
|
// It's OK to lack relevant empty statfs record
|
|
continue;
|
|
}
|
|
get_pool_stat_key(p.first, &key);
|
|
derr << __func__ << "::fsck error: " << std::hex
|
|
<< "pool " << p.first << " has got no actual statfs: "
|
|
<< std::dec << p.second
|
|
<< dendl;
|
|
++errors;
|
|
if (repairer) {
|
|
osd_pools[p.first] = p.second;
|
|
repairer->fix_statfs(db, key, p.second);
|
|
actual_statfs.add(p.second);
|
|
}
|
|
}
|
|
}
|
|
// process global statfs
|
|
if (repairer) {
|
|
if (!per_pool_stat_collection) {
|
|
// by virtue of running this method, we correct the top-level
|
|
// error of having global stats
|
|
repairer->remove_key(db, PREFIX_STAT, BLUESTORE_GLOBAL_STATFS_KEY);
|
|
per_pool_stat_collection = true;
|
|
}
|
|
vstatfs = actual_statfs;
|
|
dout(20) << __func__ << " setting vstatfs to " << actual_statfs << dendl;
|
|
} else if (!per_pool_stat_collection) {
|
|
// check global stats only if fscking (not repairing) w/o per-pool stats
|
|
vstatfs.publish(&s);
|
|
if (!(s == expected_statfs)) {
|
|
derr << "fsck error: actual " << s
|
|
<< " != expected " << expected_statfs << dendl;
|
|
++errors;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_fsck_foreach_shared_blob(
|
|
std::function< bool (coll_t, ghobject_t, uint64_t, const bluestore_blob_t&)> cb) {
|
|
auto it = db->get_iterator(PREFIX_OBJ, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
CollectionRef c;
|
|
spg_t pgid;
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
dout(30) << __func__ << " key "
|
|
<< pretty_binary_string(it->key())
|
|
<< dendl;
|
|
if (is_extent_shard_key(it->key())) {
|
|
continue;
|
|
}
|
|
|
|
ghobject_t oid;
|
|
int r = get_key_object(it->key(), &oid);
|
|
if (r < 0) {
|
|
continue;
|
|
}
|
|
|
|
if (!c ||
|
|
oid.shard_id != pgid.shard ||
|
|
oid.hobj.get_logical_pool() != (int64_t)pgid.pool() ||
|
|
!c->contains(oid)) {
|
|
c = nullptr;
|
|
for (auto& p : coll_map) {
|
|
if (p.second->contains(oid)) {
|
|
c = p.second;
|
|
break;
|
|
}
|
|
}
|
|
if (!c) {
|
|
continue;
|
|
}
|
|
}
|
|
dout(20) << __func__
|
|
<< " inspecting shared blob refs for col:" << c->cid
|
|
<< " obj:" << oid
|
|
<< dendl;
|
|
|
|
OnodeRef o;
|
|
o.reset(Onode::create_decode(c, oid, it->key(), it->value(), false, segment_size != 0));
|
|
o->extent_map.fault_range(db, 0, OBJECT_MAX_SIZE);
|
|
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
mempool::bluestore_fsck::set<BlobRef> passed_sbs;
|
|
for (auto& e : o->extent_map.extent_map) {
|
|
auto& b = e.blob->get_blob();
|
|
if (b.is_shared() && passed_sbs.count(e.blob) == 0) {
|
|
auto sbid = e.blob->get_sbid();
|
|
if (cb(c->cid, oid, sbid, b) == false) {
|
|
goto stop_iterating;
|
|
}
|
|
passed_sbs.emplace(e.blob);
|
|
}
|
|
} // for ... extent_map
|
|
} // for ... it->valid
|
|
} //if (it(PREFIX_OBJ))
|
|
stop_iterating:;
|
|
}
|
|
|
|
void BlueStore::_fsck_repair_shared_blobs(
|
|
BlueStoreRepairer& repairer,
|
|
shared_blob_2hash_tracker_t& sb_ref_counts,
|
|
sb_info_space_efficient_map_t& sb_info)
|
|
{
|
|
auto sb_ref_mismatches = sb_ref_counts.count_non_zero();
|
|
dout(1) << __func__ << " repairing shared_blobs, ref mismatch estimate: "
|
|
<< sb_ref_mismatches << dendl;
|
|
if (!sb_ref_mismatches) // not expected to succeed, just in case
|
|
return;
|
|
|
|
mempool::bluestore_fsck::map<uint64_t, bluestore_extent_ref_map_t> refs_map;
|
|
|
|
// first iteration over objects to identify all the broken sbids
|
|
_fsck_foreach_shared_blob( [&](coll_t cid,
|
|
ghobject_t oid,
|
|
uint64_t sbid,
|
|
const bluestore_blob_t& b) {
|
|
auto it = refs_map.lower_bound(sbid);
|
|
if(it != refs_map.end() && it->first == sbid) {
|
|
return true;
|
|
}
|
|
for (auto& p : b.get_extents()) {
|
|
if (p.is_valid() &&
|
|
!sb_ref_counts.test_all_zero_range(sbid,
|
|
p.offset,
|
|
p.length)) {
|
|
refs_map.emplace_hint(it, sbid, bluestore_extent_ref_map_t());
|
|
dout(20) << __func__
|
|
<< " broken shared blob found for col:" << cid
|
|
<< " obj:" << oid
|
|
<< " sbid 0x" << std::hex << sbid << std::dec
|
|
<< dendl;
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// second iteration over objects to build new ref map for the broken sbids
|
|
_fsck_foreach_shared_blob( [&](coll_t cid,
|
|
ghobject_t oid,
|
|
uint64_t sbid,
|
|
const bluestore_blob_t& b) {
|
|
auto it = refs_map.find(sbid);
|
|
if(it == refs_map.end()) {
|
|
return true;
|
|
}
|
|
for (auto& p : b.get_extents()) {
|
|
if (p.is_valid()) {
|
|
it->second.get(p.offset, p.length);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// update shared blob records
|
|
auto ref_it = refs_map.begin();
|
|
while (ref_it != refs_map.end()) {
|
|
size_t cnt = 0;
|
|
const size_t max_transactions = 4096;
|
|
KeyValueDB::Transaction txn = db->get_transaction();
|
|
for (cnt = 0;
|
|
cnt < max_transactions && ref_it != refs_map.end();
|
|
ref_it++) {
|
|
auto sbid = ref_it->first;
|
|
dout(20) << __func__ << " repaired shared_blob 0x"
|
|
<< std::hex << sbid << std::dec
|
|
<< ref_it->second << dendl;
|
|
repairer.fix_shared_blob(txn, sbid, &ref_it->second, 0);
|
|
cnt++;
|
|
}
|
|
if (cnt) {
|
|
db->submit_transaction_sync(txn);
|
|
cnt = 0;
|
|
}
|
|
}
|
|
// remove stray shared blob records
|
|
size_t cnt = 0;
|
|
const size_t max_transactions = 4096;
|
|
KeyValueDB::Transaction txn = db->get_transaction();
|
|
sb_info.foreach_stray([&](const sb_info_t& sbi) {
|
|
auto sbid = sbi.get_sbid();
|
|
dout(20) << __func__ << " removing stray shared_blob 0x"
|
|
<< std::hex << sbid << std::dec
|
|
<< dendl;
|
|
repairer.fix_shared_blob(txn, sbid, nullptr, 0);
|
|
cnt++;
|
|
if (cnt >= max_transactions) {}
|
|
db->submit_transaction_sync(txn);
|
|
txn = db->get_transaction();
|
|
cnt = 0;
|
|
});
|
|
if (cnt > 0) {
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
// amount of repairs to report to be equal to previously
|
|
// determined error estimation, not the actual number of updated shared blobs
|
|
repairer.inc_repaired(sb_ref_mismatches);
|
|
}
|
|
|
|
BlueStore::OnodeRef BlueStore::fsck_check_objects_shallow(
|
|
BlueStore::FSCKDepth depth,
|
|
int64_t pool_id,
|
|
BlueStore::CollectionRef c,
|
|
const ghobject_t& oid,
|
|
const string& key,
|
|
const bufferlist& value,
|
|
mempool::bluestore_fsck::list<string>* expecting_shards,
|
|
map<BlobRef, bluestore_blob_t::unused_t>* referenced,
|
|
BlueStore::FSCK_ObjectCtx& ctx)
|
|
{
|
|
auto& errors = ctx.errors;
|
|
auto& num_objects = ctx.num_objects;
|
|
auto& num_extents = ctx.num_extents;
|
|
auto& num_blobs = ctx.num_blobs;
|
|
auto& num_sharded_objects = ctx.num_sharded_objects;
|
|
auto& num_spanning_blobs = ctx.num_spanning_blobs;
|
|
auto used_blocks = ctx.used_blocks;
|
|
auto sb_info_lock = ctx.sb_info_lock;
|
|
auto& sb_info = ctx.sb_info;
|
|
auto& sb_ref_counts = ctx.sb_ref_counts;
|
|
auto repairer = ctx.repairer;
|
|
pool_fsck_stats_t* pool_fsck_stat =
|
|
&ctx.per_pool_fsck_stats[c->cid.is_pg() ? c->cid.pool() : META_POOL_ID];
|
|
|
|
store_statfs_t* res_statfs = (per_pool_stat_collection || repairer) ?
|
|
&ctx.expected_pool_statfs[pool_id] :
|
|
&ctx.expected_store_statfs;
|
|
|
|
|
|
dout(10) << __func__ << " " << oid << dendl;
|
|
OnodeRef o;
|
|
o.reset(Onode::create_decode(c, oid, key, value, false, segment_size != 0));
|
|
++num_objects;
|
|
++pool_fsck_stat->num_objects;
|
|
num_spanning_blobs += o->extent_map.spanning_blob_map.size();
|
|
|
|
o->extent_map.fault_range(db, 0, OBJECT_MAX_SIZE);
|
|
_dump_onode<30>(cct, *o);
|
|
// shards
|
|
if (!o->extent_map.shards.empty()) {
|
|
++num_sharded_objects;
|
|
if (depth != FSCK_SHALLOW) {
|
|
ceph_assert(expecting_shards);
|
|
for (auto& s : o->extent_map.shards) {
|
|
dout(20) << __func__ << " shard " << *s.shard_info << dendl;
|
|
expecting_shards->push_back(string());
|
|
get_extent_shard_key(o->key, s.shard_info->offset,
|
|
&expecting_shards->back());
|
|
if (s.shard_info->offset >= o->onode.size) {
|
|
derr << "fsck error: " << oid << " shard 0x" << std::hex
|
|
<< s.shard_info->offset << " past EOF at 0x" << o->onode.size
|
|
<< std::dec << dendl;
|
|
++errors;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// lextents
|
|
uint64_t pos = 0;
|
|
mempool::bluestore_fsck::map<BlobRef,
|
|
bluestore_blob_use_tracker_t> ref_map;
|
|
for (auto& l : o->extent_map.extent_map) {
|
|
dout(20) << __func__ << " " << l << dendl;
|
|
if (l.logical_offset < pos) {
|
|
derr << "fsck error: " << oid << " lextent at 0x"
|
|
<< std::hex << l.logical_offset
|
|
<< " overlaps with the previous, which ends at 0x" << pos
|
|
<< std::dec << dendl;
|
|
++errors;
|
|
}
|
|
if (depth != FSCK_SHALLOW &&
|
|
o->extent_map.spans_shard(l.logical_offset, l.length)) {
|
|
derr << "fsck error: " << oid << " lextent at 0x"
|
|
<< std::hex << l.logical_offset << "~" << l.length
|
|
<< " spans a shard boundary"
|
|
<< std::dec << dendl;
|
|
++errors;
|
|
}
|
|
pos = l.logical_offset + l.length;
|
|
res_statfs->data_stored += l.length;
|
|
pool_fsck_stat->stored += l.length;
|
|
ceph_assert(l.blob);
|
|
const bluestore_blob_t& blob = l.blob->get_blob();
|
|
|
|
auto& ref = ref_map[l.blob];
|
|
if (ref.is_empty()) {
|
|
uint32_t min_release_size = blob.get_release_size(min_alloc_size);
|
|
uint32_t l = blob.get_logical_length();
|
|
ref.init(l, min_release_size);
|
|
}
|
|
ref.get(
|
|
l.blob_offset,
|
|
l.length);
|
|
++num_extents;
|
|
if (depth != FSCK_SHALLOW &&
|
|
blob.has_unused()) {
|
|
ceph_assert(referenced);
|
|
auto p = referenced->find(l.blob);
|
|
bluestore_blob_t::unused_t* pu;
|
|
if (p == referenced->end()) {
|
|
pu = &(*referenced)[l.blob];
|
|
}
|
|
else {
|
|
pu = &p->second;
|
|
}
|
|
uint64_t blob_len = blob.get_logical_length();
|
|
ceph_assert((blob_len % (sizeof(*pu) * 8)) == 0);
|
|
ceph_assert(l.blob_offset + l.length <= blob_len);
|
|
uint64_t chunk_size = blob_len / (sizeof(*pu) * 8);
|
|
uint64_t start = l.blob_offset / chunk_size;
|
|
uint64_t end =
|
|
round_up_to(l.blob_offset + l.length, chunk_size) / chunk_size;
|
|
for (auto i = start; i < end; ++i) {
|
|
(*pu) |= (1u << i);
|
|
}
|
|
}
|
|
} //for (auto& l : o->extent_map.extent_map)
|
|
|
|
for (auto& i : ref_map) {
|
|
++num_blobs;
|
|
const bluestore_blob_t& blob = i.first->get_blob();
|
|
bool equal =
|
|
depth == FSCK_SHALLOW ? true :
|
|
i.first->get_blob_use_tracker().equal(i.second);
|
|
if (!equal) {
|
|
derr << "fsck error: " << oid << " blob " << *i.first
|
|
<< " doesn't match expected ref_map " << i.second << dendl;
|
|
++errors;
|
|
}
|
|
if (blob.is_compressed()) {
|
|
res_statfs->data_compressed += blob.get_compressed_payload_length();
|
|
res_statfs->data_compressed_original +=
|
|
i.first->get_referenced_bytes();
|
|
}
|
|
if (depth != FSCK_SHALLOW && repairer) {
|
|
for (auto e : blob.get_extents()) {
|
|
if (!e.is_valid())
|
|
continue;
|
|
repairer->set_space_used(e.offset, e.length, c->cid, oid);
|
|
}
|
|
}
|
|
if (blob.is_shared()) {
|
|
if (i.first->get_sbid() > blobid_max) {
|
|
derr << "fsck error: " << oid << " blob " << blob
|
|
<< " sbid " << i.first->get_sbid() << " > blobid_max "
|
|
<< blobid_max << dendl;
|
|
++errors;
|
|
} else if (i.first->get_sbid() == 0) {
|
|
derr << "fsck error: " << oid << " blob " << blob
|
|
<< " marked as shared but has uninitialized sbid"
|
|
<< dendl;
|
|
++errors;
|
|
}
|
|
// the below lock is optional and provided in multithreading mode only
|
|
if (sb_info_lock) {
|
|
sb_info_lock->lock();
|
|
}
|
|
auto sbid = i.first->get_sbid();
|
|
sb_info_t& sbi = sb_info.add_or_adopt(i.first->get_sbid());
|
|
ceph_assert(sbi.pool_id == sb_info_t::INVALID_POOL_ID ||
|
|
sbi.pool_id == oid.hobj.get_logical_pool());
|
|
sbi.pool_id = oid.hobj.get_logical_pool();
|
|
bool compressed = blob.is_compressed();
|
|
for (auto e : blob.get_extents()) {
|
|
if (e.is_valid()) {
|
|
if (compressed) {
|
|
ceph_assert(sbi.allocated_chunks <= 0);
|
|
sbi.allocated_chunks -= (e.length >> min_alloc_size_order);
|
|
} else {
|
|
ceph_assert(sbi.allocated_chunks >= 0);
|
|
sbi.allocated_chunks += (e.length >> min_alloc_size_order);
|
|
}
|
|
sb_ref_counts.inc_range(sbid, e.offset, e.length, 1);
|
|
}
|
|
}
|
|
if (sb_info_lock) {
|
|
sb_info_lock->unlock();
|
|
}
|
|
} else if (depth != FSCK_SHALLOW) {
|
|
ceph_assert(used_blocks);
|
|
string ctx_descr = " oid " + stringify(oid);
|
|
errors += _fsck_check_extents(ctx_descr,
|
|
blob.get_extents(),
|
|
blob.is_compressed(),
|
|
*used_blocks,
|
|
fm->get_alloc_size(),
|
|
repairer,
|
|
*res_statfs,
|
|
*pool_fsck_stat,
|
|
depth);
|
|
} else {
|
|
errors += _fsck_sum_extents(
|
|
blob.get_extents(),
|
|
blob.is_compressed(),
|
|
*res_statfs,
|
|
*pool_fsck_stat);
|
|
}
|
|
} // for (auto& i : ref_map)
|
|
|
|
{
|
|
auto &sbm = o->extent_map.spanning_blob_map;
|
|
size_t broken = 0;
|
|
BlobRef first_broken;
|
|
for (auto it = sbm.begin(); it != sbm.end();) {
|
|
auto it1 = it++;
|
|
if (ref_map.count(it1->second) == 0) {
|
|
if (!broken) {
|
|
first_broken = it1->second;
|
|
++errors;
|
|
derr << "fsck error:" << " stray spanning blob found:" << it1->first
|
|
<< dendl;
|
|
}
|
|
broken++;
|
|
if (repairer) {
|
|
sbm.erase(it1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (broken) {
|
|
derr << "fsck error: " << oid << " - " << broken
|
|
<< " zombie spanning blob(s) found, the first one: "
|
|
<< *first_broken << dendl;
|
|
if(repairer) {
|
|
repairer->fix_spanning_blobs(
|
|
db,
|
|
[&](KeyValueDB::Transaction txn) {
|
|
_record_onode(o, txn);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (o->onode.has_omap()) {
|
|
_fsck_check_object_omap(depth, o, ctx);
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
class ShallowFSCKThreadPool : public ThreadPool
|
|
{
|
|
public:
|
|
ShallowFSCKThreadPool(CephContext* cct_, std::string nm, std::string tn, int n) :
|
|
ThreadPool(cct_, nm, tn, n) {
|
|
}
|
|
void worker(ThreadPool::WorkThread* wt) override {
|
|
int next_wq = 0;
|
|
while (!_stop) {
|
|
next_wq %= work_queues.size();
|
|
WorkQueue_ *wq = work_queues[next_wq++];
|
|
|
|
void* item = wq->_void_dequeue();
|
|
if (item) {
|
|
processing++;
|
|
TPHandle tp_handle(cct, nullptr, wq->timeout_interval.load(), wq->suicide_interval.load());
|
|
wq->_void_process(item, tp_handle);
|
|
processing--;
|
|
}
|
|
}
|
|
}
|
|
template <size_t BatchLen>
|
|
struct FSCKWorkQueue : public ThreadPool::WorkQueue_
|
|
{
|
|
struct Entry {
|
|
int64_t pool_id;
|
|
BlueStore::CollectionRef c;
|
|
ghobject_t oid;
|
|
string key;
|
|
bufferlist value;
|
|
};
|
|
struct Batch {
|
|
std::atomic<size_t> running = { 0 };
|
|
size_t entry_count = 0;
|
|
std::array<Entry, BatchLen> entries;
|
|
|
|
int64_t errors = 0;
|
|
int64_t warnings = 0;
|
|
uint64_t num_objects = 0;
|
|
uint64_t num_extents = 0;
|
|
uint64_t num_blobs = 0;
|
|
uint64_t num_sharded_objects = 0;
|
|
uint64_t num_spanning_blobs = 0;
|
|
store_statfs_t expected_store_statfs;
|
|
BlueStore::per_pool_statfs expected_pool_statfs;
|
|
BlueStore::per_pool_fsck_stats_t per_pool_fsck_stats;
|
|
};
|
|
|
|
size_t batchCount;
|
|
BlueStore* store = nullptr;
|
|
|
|
ceph::mutex* sb_info_lock = nullptr;
|
|
sb_info_space_efficient_map_t* sb_info = nullptr;
|
|
shared_blob_2hash_tracker_t* sb_ref_counts = nullptr;
|
|
BlueStoreRepairer* repairer = nullptr;
|
|
|
|
Batch* batches = nullptr;
|
|
size_t last_batch_pos = 0;
|
|
bool batch_acquired = false;
|
|
|
|
FSCKWorkQueue(std::string n,
|
|
size_t _batchCount,
|
|
BlueStore* _store,
|
|
ceph::mutex* _sb_info_lock,
|
|
sb_info_space_efficient_map_t& _sb_info,
|
|
shared_blob_2hash_tracker_t& _sb_ref_counts,
|
|
BlueStoreRepairer* _repairer) :
|
|
WorkQueue_(n, ceph::timespan::zero(), ceph::timespan::zero()),
|
|
batchCount(_batchCount),
|
|
store(_store),
|
|
sb_info_lock(_sb_info_lock),
|
|
sb_info(&_sb_info),
|
|
sb_ref_counts(&_sb_ref_counts),
|
|
repairer(_repairer)
|
|
{
|
|
batches = new Batch[batchCount];
|
|
}
|
|
~FSCKWorkQueue() {
|
|
delete[] batches;
|
|
}
|
|
|
|
/// Remove all work items from the queue.
|
|
void _clear() override {
|
|
//do nothing
|
|
}
|
|
/// Check whether there is anything to do.
|
|
bool _empty() override {
|
|
ceph_abort();
|
|
}
|
|
|
|
/// Get the next work item to process.
|
|
void* _void_dequeue() override {
|
|
size_t pos = rand() % batchCount;
|
|
size_t pos0 = pos;
|
|
do {
|
|
auto& batch = batches[pos];
|
|
if (batch.running.fetch_add(1) == 0) {
|
|
if (batch.entry_count) {
|
|
return &batch;
|
|
}
|
|
}
|
|
batch.running--;
|
|
pos++;
|
|
pos %= batchCount;
|
|
} while (pos != pos0);
|
|
return nullptr;
|
|
}
|
|
/** @brief Process the work item.
|
|
* This function will be called several times in parallel
|
|
* and must therefore be thread-safe. */
|
|
void _void_process(void* item, TPHandle& handle) override {
|
|
Batch* batch = (Batch*)item;
|
|
|
|
BlueStore::FSCK_ObjectCtx ctx(
|
|
batch->errors,
|
|
batch->warnings,
|
|
batch->num_objects,
|
|
batch->num_extents,
|
|
batch->num_blobs,
|
|
batch->num_sharded_objects,
|
|
batch->num_spanning_blobs,
|
|
nullptr, // used_blocks
|
|
nullptr, //used_omap_head
|
|
nullptr,
|
|
sb_info_lock,
|
|
*sb_info,
|
|
*sb_ref_counts,
|
|
batch->expected_store_statfs,
|
|
batch->expected_pool_statfs,
|
|
batch->per_pool_fsck_stats,
|
|
repairer);
|
|
|
|
for (size_t i = 0; i < batch->entry_count; i++) {
|
|
auto& entry = batch->entries[i];
|
|
|
|
store->fsck_check_objects_shallow(
|
|
BlueStore::FSCK_SHALLOW,
|
|
entry.pool_id,
|
|
entry.c,
|
|
entry.oid,
|
|
entry.key,
|
|
entry.value,
|
|
nullptr, // expecting_shards - this will need a protection if passed
|
|
nullptr, // referenced
|
|
ctx);
|
|
}
|
|
batch->entry_count = 0;
|
|
batch->running--;
|
|
}
|
|
/** @brief Synchronously finish processing a work item.
|
|
* This function is called after _void_process with the global thread pool lock held,
|
|
* so at most one copy will execute simultaneously for a given thread pool.
|
|
* It can be used for non-thread-safe finalization. */
|
|
void _void_process_finish(void*) override {
|
|
ceph_abort();
|
|
}
|
|
|
|
bool queue(
|
|
int64_t pool_id,
|
|
BlueStore::CollectionRef c,
|
|
const ghobject_t& oid,
|
|
const string& key,
|
|
const bufferlist& value) {
|
|
bool res = false;
|
|
size_t pos0 = last_batch_pos;
|
|
if (!batch_acquired) {
|
|
do {
|
|
auto& batch = batches[last_batch_pos];
|
|
if (batch.running.fetch_add(1) == 0) {
|
|
if (batch.entry_count < BatchLen) {
|
|
batch_acquired = true;
|
|
break;
|
|
}
|
|
}
|
|
batch.running.fetch_sub(1);
|
|
last_batch_pos++;
|
|
last_batch_pos %= batchCount;
|
|
} while (last_batch_pos != pos0);
|
|
}
|
|
if (batch_acquired) {
|
|
auto& batch = batches[last_batch_pos];
|
|
ceph_assert(batch.running);
|
|
ceph_assert(batch.entry_count < BatchLen);
|
|
|
|
auto& entry = batch.entries[batch.entry_count];
|
|
entry.pool_id = pool_id;
|
|
entry.c = c;
|
|
entry.oid = oid;
|
|
entry.key = key;
|
|
entry.value = value;
|
|
|
|
++batch.entry_count;
|
|
if (batch.entry_count == BatchLen) {
|
|
batch_acquired = false;
|
|
batch.running.fetch_sub(1);
|
|
last_batch_pos++;
|
|
last_batch_pos %= batchCount;
|
|
}
|
|
res = true;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void finalize(ThreadPool& tp,
|
|
BlueStore::FSCK_ObjectCtx& ctx) {
|
|
if (batch_acquired) {
|
|
auto& batch = batches[last_batch_pos];
|
|
ceph_assert(batch.running);
|
|
batch.running.fetch_sub(1);
|
|
}
|
|
tp.stop();
|
|
|
|
for (size_t i = 0; i < batchCount; i++) {
|
|
auto& batch = batches[i];
|
|
|
|
//process leftovers if any
|
|
if (batch.entry_count) {
|
|
TPHandle tp_handle(store->cct,
|
|
nullptr,
|
|
timeout_interval.load(),
|
|
suicide_interval.load());
|
|
ceph_assert(batch.running == 0);
|
|
|
|
batch.running++; // just to be on-par with the regular call
|
|
_void_process(&batch, tp_handle);
|
|
}
|
|
ceph_assert(batch.entry_count == 0);
|
|
|
|
ctx.errors += batch.errors;
|
|
ctx.warnings += batch.warnings;
|
|
ctx.num_objects += batch.num_objects;
|
|
ctx.num_extents += batch.num_extents;
|
|
ctx.num_blobs += batch.num_blobs;
|
|
ctx.num_sharded_objects += batch.num_sharded_objects;
|
|
ctx.num_spanning_blobs += batch.num_spanning_blobs;
|
|
|
|
ctx.expected_store_statfs.add(batch.expected_store_statfs);
|
|
|
|
for (auto it = batch.expected_pool_statfs.begin();
|
|
it != batch.expected_pool_statfs.end();
|
|
it++) {
|
|
ctx.expected_pool_statfs[it->first].add(it->second);
|
|
}
|
|
for (auto it = batch.per_pool_fsck_stats.begin();
|
|
it != batch.per_pool_fsck_stats.end();
|
|
it++) {
|
|
ctx.per_pool_fsck_stats[it->first].add(it->second);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
void BlueStore::_fsck_check_object_omap(FSCKDepth depth,
|
|
OnodeRef& o,
|
|
const BlueStore::FSCK_ObjectCtx& ctx)
|
|
{
|
|
auto& errors = ctx.errors;
|
|
auto& warnings = ctx.warnings;
|
|
auto repairer = ctx.repairer;
|
|
|
|
ceph_assert(o->onode.has_omap());
|
|
if (!o->onode.is_perpool_omap() && !o->onode.is_pgmeta_omap()) {
|
|
if (per_pool_omap == OMAP_PER_POOL) {
|
|
fsck_derr(errors, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck error: " << o->oid
|
|
<< " has omap that is not per-pool or pgmeta"
|
|
<< fsck_dendl;
|
|
++errors;
|
|
} else {
|
|
const char* w;
|
|
int64_t num;
|
|
if (cct->_conf->bluestore_fsck_error_on_no_per_pool_omap) {
|
|
++errors;
|
|
num = errors;
|
|
w = "error";
|
|
} else {
|
|
++warnings;
|
|
num = warnings;
|
|
w = "warning";
|
|
}
|
|
fsck_derr(num, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck " << w << ": " << o->oid
|
|
<< " has omap that is not per-pool or pgmeta"
|
|
<< fsck_dendl;
|
|
}
|
|
} else if (!o->onode.is_perpg_omap() && !o->onode.is_pgmeta_omap()) {
|
|
if (per_pool_omap == OMAP_PER_PG) {
|
|
fsck_derr(errors, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck error: " << o->oid
|
|
<< " has omap that is not per-pg or pgmeta"
|
|
<< fsck_dendl;
|
|
++errors;
|
|
} else {
|
|
const char* w;
|
|
int64_t num;
|
|
if (cct->_conf->bluestore_fsck_error_on_no_per_pg_omap) {
|
|
++errors;
|
|
num = errors;
|
|
w = "error";
|
|
} else {
|
|
++warnings;
|
|
num = warnings;
|
|
w = "warning";
|
|
}
|
|
fsck_derr(num, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck " << w << ": " << o->oid
|
|
<< " has omap that is not per-pg or pgmeta"
|
|
<< fsck_dendl;
|
|
}
|
|
}
|
|
if (repairer &&
|
|
!o->onode.is_perpg_omap() &&
|
|
!o->onode.is_pgmeta_omap()) {
|
|
dout(10) << "fsck converting " << o->oid << " omap to per-pg" << dendl;
|
|
bufferlist header;
|
|
map<string, bufferlist> kv;
|
|
{
|
|
KeyValueDB::Transaction txn = db->get_transaction();
|
|
uint64_t txn_cost = 0;
|
|
const string& prefix = Onode::calc_omap_prefix(o->onode.flags);
|
|
uint8_t new_flags = o->onode.flags |
|
|
bluestore_onode_t::FLAG_PERPOOL_OMAP |
|
|
bluestore_onode_t::FLAG_PERPG_OMAP;
|
|
const string& new_omap_prefix = Onode::calc_omap_prefix(new_flags);
|
|
|
|
KeyValueDB::Iterator it = db->get_iterator(prefix);
|
|
string head, tail;
|
|
o->get_omap_header(&head);
|
|
o->get_omap_tail(&tail);
|
|
it->lower_bound(head);
|
|
// head
|
|
if (it->valid() && it->key() == head) {
|
|
dout(30) << __func__ << " got header" << dendl;
|
|
header = it->value();
|
|
if (header.length()) {
|
|
string new_head;
|
|
Onode::calc_omap_header(new_flags, o.get(), &new_head);
|
|
txn->set(new_omap_prefix, new_head, header);
|
|
txn_cost += new_head.length() + header.length();
|
|
}
|
|
it->next();
|
|
}
|
|
// tail
|
|
{
|
|
string new_tail;
|
|
Onode::calc_omap_tail(new_flags, o.get(), &new_tail);
|
|
bufferlist empty;
|
|
txn->set(new_omap_prefix, new_tail, empty);
|
|
txn_cost += new_tail.length() + new_tail.length();
|
|
}
|
|
// values
|
|
string final_key;
|
|
Onode::calc_omap_key(new_flags, o.get(), string(), &final_key);
|
|
size_t base_key_len = final_key.size();
|
|
while (it->valid() && it->key() < tail) {
|
|
string user_key;
|
|
o->decode_omap_key(it->key(), &user_key);
|
|
dout(20) << __func__ << " got " << pretty_binary_string(it->key())
|
|
<< " -> " << user_key << dendl;
|
|
|
|
final_key.resize(base_key_len);
|
|
final_key += user_key;
|
|
auto v = it->value();
|
|
txn->set(new_omap_prefix, final_key, v);
|
|
txn_cost += final_key.length() + v.length();
|
|
|
|
// submit a portion if cost exceeds 16MB
|
|
if (txn_cost >= 16 * (1 << 20) ) {
|
|
db->submit_transaction_sync(txn);
|
|
txn = db->get_transaction();
|
|
txn_cost = 0;
|
|
}
|
|
it->next();
|
|
}
|
|
if (txn_cost > 0) {
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
}
|
|
// finalize: remove legacy data
|
|
{
|
|
KeyValueDB::Transaction txn = db->get_transaction();
|
|
// remove old keys
|
|
const string& old_omap_prefix = o->get_omap_prefix();
|
|
string old_head, old_tail;
|
|
o->get_omap_header(&old_head);
|
|
o->get_omap_tail(&old_tail);
|
|
txn->rm_range_keys(old_omap_prefix, old_head, old_tail);
|
|
txn->rmkey(old_omap_prefix, old_tail);
|
|
// set flag
|
|
o->onode.set_flag(bluestore_onode_t::FLAG_PERPOOL_OMAP | bluestore_onode_t::FLAG_PERPG_OMAP);
|
|
_record_onode(o, txn);
|
|
db->submit_transaction_sync(txn);
|
|
repairer->inc_repaired();
|
|
repairer->request_compaction();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_fsck_check_objects(
|
|
FSCKDepth depth,
|
|
BlueStore::FSCK_ObjectCtx& ctx)
|
|
{
|
|
auto& errors = ctx.errors;
|
|
auto sb_info_lock = ctx.sb_info_lock;
|
|
auto& sb_info = ctx.sb_info;
|
|
auto& sb_ref_counts = ctx.sb_ref_counts;
|
|
auto repairer = ctx.repairer;
|
|
|
|
uint64_t_btree_t used_nids;
|
|
|
|
size_t processed_myself = 0;
|
|
|
|
auto it = db->get_iterator(PREFIX_OBJ, KeyValueDB::ITERATOR_NOCACHE);
|
|
mempool::bluestore_fsck::list<string> expecting_shards;
|
|
if (it) {
|
|
const size_t thread_count = cct->_conf->bluestore_fsck_quick_fix_threads;
|
|
typedef ShallowFSCKThreadPool::FSCKWorkQueue<256> WQ;
|
|
std::unique_ptr<WQ> wq(
|
|
new WQ(
|
|
"FSCKWorkQueue",
|
|
(thread_count ? : 1) * 32,
|
|
this,
|
|
sb_info_lock,
|
|
sb_info,
|
|
sb_ref_counts,
|
|
repairer));
|
|
|
|
ShallowFSCKThreadPool thread_pool(cct, "ShallowFSCKThreadPool", "ShallowFSCK", thread_count);
|
|
|
|
thread_pool.add_work_queue(wq.get());
|
|
if (depth == FSCK_SHALLOW && thread_count > 0) {
|
|
//not the best place but let's check anyway
|
|
ceph_assert(sb_info_lock);
|
|
thread_pool.start();
|
|
}
|
|
|
|
// fill global if not overriden below
|
|
CollectionRef c;
|
|
int64_t pool_id = -1;
|
|
spg_t pgid;
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
dout(30) << __func__ << " key "
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
if (is_extent_shard_key(it->key())) {
|
|
if (depth == FSCK_SHALLOW) {
|
|
continue;
|
|
}
|
|
while (!expecting_shards.empty() &&
|
|
expecting_shards.front() < it->key()) {
|
|
derr << "fsck error: missing shard key "
|
|
<< pretty_binary_string(expecting_shards.front())
|
|
<< dendl;
|
|
++errors;
|
|
expecting_shards.pop_front();
|
|
}
|
|
if (!expecting_shards.empty() &&
|
|
expecting_shards.front() == it->key()) {
|
|
// all good
|
|
expecting_shards.pop_front();
|
|
continue;
|
|
}
|
|
|
|
uint32_t offset;
|
|
string okey;
|
|
get_key_extent_shard(it->key(), &okey, &offset);
|
|
derr << "fsck error: stray shard 0x" << std::hex << offset
|
|
<< std::dec << dendl;
|
|
if (expecting_shards.empty()) {
|
|
derr << "fsck error: " << pretty_binary_string(it->key())
|
|
<< " is unexpected" << dendl;
|
|
++errors;
|
|
continue;
|
|
}
|
|
while (expecting_shards.front() > it->key()) {
|
|
derr << "fsck error: saw " << pretty_binary_string(it->key())
|
|
<< dendl;
|
|
derr << "fsck error: exp "
|
|
<< pretty_binary_string(expecting_shards.front()) << dendl;
|
|
++errors;
|
|
expecting_shards.pop_front();
|
|
if (expecting_shards.empty()) {
|
|
break;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
ghobject_t oid;
|
|
int r = get_key_object(it->key(), &oid);
|
|
if (r < 0) {
|
|
derr << "fsck error: bad object key "
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
++errors;
|
|
continue;
|
|
}
|
|
if (!c ||
|
|
oid.shard_id != pgid.shard ||
|
|
oid.hobj.get_logical_pool() != (int64_t)pgid.pool() ||
|
|
!c->contains(oid)) {
|
|
c = nullptr;
|
|
for (auto& p : coll_map) {
|
|
if (p.second->contains(oid)) {
|
|
c = p.second;
|
|
break;
|
|
}
|
|
}
|
|
if (!c) {
|
|
derr << "fsck error: stray object " << oid
|
|
<< " not owned by any collection" << dendl;
|
|
++errors;
|
|
continue;
|
|
}
|
|
pool_id = c->cid.is_pg(&pgid) ? pgid.pool() : META_POOL_ID;
|
|
dout(20) << __func__ << " collection " << c->cid << " " << c->cnode
|
|
<< dendl;
|
|
}
|
|
|
|
if (depth != FSCK_SHALLOW &&
|
|
!expecting_shards.empty()) {
|
|
for (auto& k : expecting_shards) {
|
|
derr << "fsck error: missing shard key "
|
|
<< pretty_binary_string(k) << dendl;
|
|
}
|
|
++errors;
|
|
expecting_shards.clear();
|
|
}
|
|
|
|
bool queued = false;
|
|
if (depth == FSCK_SHALLOW && thread_count > 0) {
|
|
queued = wq->queue(
|
|
pool_id,
|
|
c,
|
|
oid,
|
|
it->key(),
|
|
it->value());
|
|
}
|
|
OnodeRef o;
|
|
map<BlobRef, bluestore_blob_t::unused_t> referenced;
|
|
|
|
if (!queued) {
|
|
++processed_myself;
|
|
o = fsck_check_objects_shallow(
|
|
depth,
|
|
pool_id,
|
|
c,
|
|
oid,
|
|
it->key(),
|
|
it->value(),
|
|
&expecting_shards,
|
|
&referenced,
|
|
ctx);
|
|
}
|
|
|
|
if (depth != FSCK_SHALLOW) {
|
|
ceph_assert(o != nullptr);
|
|
if (o->onode.nid) {
|
|
if (o->onode.nid > nid_max) {
|
|
derr << "fsck error: " << oid << " nid " << o->onode.nid
|
|
<< " > nid_max " << nid_max << dendl;
|
|
++errors;
|
|
}
|
|
if (used_nids.count(o->onode.nid)) {
|
|
derr << "fsck error: " << oid << " nid " << o->onode.nid
|
|
<< " already in use" << dendl;
|
|
++errors;
|
|
continue; // go for next object
|
|
}
|
|
used_nids.insert(o->onode.nid);
|
|
}
|
|
for (auto& i : referenced) {
|
|
dout(20) << __func__ << " referenced 0x" << std::hex << i.second
|
|
<< std::dec << " for " << *i.first << dendl;
|
|
const bluestore_blob_t& blob = i.first->get_blob();
|
|
if (i.second & blob.unused) {
|
|
derr << "fsck error: " << oid << " blob claims unused 0x"
|
|
<< std::hex << blob.unused
|
|
<< " but extents reference 0x" << i.second << std::dec
|
|
<< " on blob " << *i.first << dendl;
|
|
++errors;
|
|
}
|
|
if (blob.has_csum()) {
|
|
uint64_t blob_len = blob.get_logical_length();
|
|
uint64_t unused_chunk_size = blob_len / (sizeof(blob.unused) * 8);
|
|
unsigned csum_count = blob.get_csum_count();
|
|
unsigned csum_chunk_size = blob.get_csum_chunk_size();
|
|
for (unsigned p = 0; p < csum_count; ++p) {
|
|
unsigned pos = p * csum_chunk_size;
|
|
unsigned firstbit = pos / unused_chunk_size; // [firstbit,lastbit]
|
|
unsigned lastbit = (pos + csum_chunk_size - 1) / unused_chunk_size;
|
|
unsigned mask = 1u << firstbit;
|
|
for (unsigned b = firstbit + 1; b <= lastbit; ++b) {
|
|
mask |= 1u << b;
|
|
}
|
|
if ((blob.unused & mask) == mask) {
|
|
// this csum chunk region is marked unused
|
|
if (blob.get_csum_item(p) != 0) {
|
|
derr << "fsck error: " << oid
|
|
<< " blob claims csum chunk 0x" << std::hex << pos
|
|
<< "~" << csum_chunk_size
|
|
<< " is unused (mask 0x" << mask << " of unused 0x"
|
|
<< blob.unused << ") but csum is non-zero 0x"
|
|
<< blob.get_csum_item(p) << std::dec << " on blob "
|
|
<< *i.first << dendl;
|
|
++errors;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// omap
|
|
if (o->onode.has_omap()) {
|
|
ceph_assert(ctx.used_omap_head);
|
|
if (ctx.used_omap_head->count(o->onode.nid)) {
|
|
derr << "fsck error: " << o->oid << " omap_head " << o->onode.nid
|
|
<< " already in use" << dendl;
|
|
++errors;
|
|
} else {
|
|
ctx.used_omap_head->insert(o->onode.nid);
|
|
}
|
|
} // if (o->onode.has_omap())
|
|
if (depth == FSCK_DEEP) {
|
|
bufferlist bl;
|
|
uint64_t max_read_block = cct->_conf->bluestore_fsck_read_bytes_cap;
|
|
uint64_t offset = 0;
|
|
do {
|
|
uint64_t l = std::min(uint64_t(o->onode.size - offset), max_read_block);
|
|
int r = _do_read(c.get(), o, offset, l, bl,
|
|
CEPH_OSD_OP_FLAG_FADVISE_NOCACHE);
|
|
if (r < 0) {
|
|
++errors;
|
|
derr << "fsck error: " << oid << std::hex
|
|
<< " error during read: "
|
|
<< " " << offset << "~" << l
|
|
<< " " << cpp_strerror(r) << std::dec
|
|
<< dendl;
|
|
break;
|
|
}
|
|
offset += l;
|
|
} while (offset < o->onode.size);
|
|
} // deep
|
|
} //if (depth != FSCK_SHALLOW)
|
|
} // for (it->lower_bound(string()); it->valid(); it->next())
|
|
if (depth == FSCK_SHALLOW && thread_count > 0) {
|
|
wq->finalize(thread_pool, ctx);
|
|
if (processed_myself) {
|
|
// may be needs more threads?
|
|
dout(0) << __func__ << " partial offload"
|
|
<< ", done myself " << processed_myself
|
|
<< " of " << ctx.num_objects
|
|
<< "objects, threads " << thread_count
|
|
<< dendl;
|
|
}
|
|
}
|
|
} // if (it)
|
|
}
|
|
/**
|
|
An overview for currently implemented repair logics
|
|
performed in fsck in two stages: detection(+preparation) and commit.
|
|
Detection stage (in processing order):
|
|
(Issue -> Repair action to schedule)
|
|
- Detect undecodable keys for Shared Blobs -> Remove
|
|
- Detect undecodable records for Shared Blobs -> Remove
|
|
(might trigger missed Shared Blob detection below)
|
|
- Detect stray records for Shared Blobs -> Remove
|
|
- Detect misreferenced pextents -> Fix
|
|
Prepare Bloom-like filter to track cid/oid -> pextent
|
|
Prepare list of extents that are improperly referenced
|
|
Enumerate Onode records that might use 'misreferenced' pextents
|
|
(Bloom-like filter applied to reduce computation)
|
|
Per each questinable Onode enumerate all blobs and identify broken ones
|
|
(i.e. blobs having 'misreferences')
|
|
Rewrite each broken blob data by allocating another extents and
|
|
copying data there
|
|
If blob is shared - unshare it and mark corresponding Shared Blob
|
|
for removal
|
|
Release previously allocated space
|
|
Update Extent Map
|
|
- Detect missed Shared Blobs -> Recreate
|
|
- Detect undecodable deferred transaction -> Remove
|
|
- Detect Freelist Manager's 'false free' entries -> Mark as used
|
|
- Detect Freelist Manager's leaked entries -> Mark as free
|
|
- Detect statfs inconsistency - Update
|
|
Commit stage (separate DB commit per each step):
|
|
- Apply leaked FM entries fix
|
|
- Apply 'false free' FM entries fix
|
|
- Apply 'Remove' actions
|
|
- Apply fix for misreference pextents
|
|
- Apply Shared Blob recreate
|
|
(can be merged with the step above if misreferences were dectected)
|
|
- Apply StatFS update
|
|
*/
|
|
int BlueStore::_fsck(BlueStore::FSCKDepth depth, bool repair)
|
|
{
|
|
dout(5) << __func__
|
|
<< (repair ? " repair" : " check")
|
|
<< (depth == FSCK_DEEP ? " (deep)" :
|
|
depth == FSCK_SHALLOW ? " (shallow)" : " (regular)")
|
|
<< dendl;
|
|
|
|
// hack - sanitize check for bdev label
|
|
bluestore_bdev_label_require_all = false;
|
|
auto restore_option = make_scope_guard([&] {
|
|
bluestore_bdev_label_require_all = cct->_conf.get_val<bool>("bluestore_bdev_label_require_all");
|
|
});
|
|
|
|
// in deep mode we need R/W write access to be able to replay deferred ops
|
|
const bool read_only = !(repair || depth == FSCK_DEEP);
|
|
int r = _open_db_and_around(read_only);
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
auto close_db = make_scope_guard([&] {
|
|
_close_db_and_around();
|
|
});
|
|
|
|
if (!read_only) {
|
|
r = _upgrade_super();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// NullFreelistManager needs to open collection early
|
|
r = _open_collections();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
|
|
mempool_thread.init();
|
|
auto stop_mempool = make_scope_guard([&] {
|
|
mempool_thread.shutdown();
|
|
_shutdown_cache();
|
|
});
|
|
// we need finisher and kv_{sync,finalize}_thread *just* for replay
|
|
// enable in repair or deep mode modes only
|
|
if (!read_only) {
|
|
_kv_start();
|
|
r = _deferred_replay();
|
|
_kv_stop();
|
|
}
|
|
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
return _fsck_on_open(depth, repair);
|
|
}
|
|
|
|
int BlueStore::revert_wal_to_plain() {
|
|
int r = cold_open();
|
|
if (r != 0) {
|
|
dout(1) << __func__ << "failed to open db / allocator" << dendl;
|
|
goto out;
|
|
}
|
|
bluefs->revert_wal_to_plain();
|
|
cold_close();
|
|
out:
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_fsck_on_open(BlueStore::FSCKDepth depth, bool repair)
|
|
{
|
|
uint64_t sb_hash_size = uint64_t(
|
|
cct->_conf.get_val<Option::size_t>("osd_memory_target") *
|
|
cct->_conf.get_val<double>(
|
|
"bluestore_fsck_shared_blob_tracker_size"));
|
|
|
|
dout(1) << __func__
|
|
<< " <<<START>>>"
|
|
<< (repair ? " repair" : " check")
|
|
<< (depth == FSCK_DEEP ? " (deep)" :
|
|
depth == FSCK_SHALLOW ? " (shallow)" : " (regular)")
|
|
<< " start sb_tracker_hash_size:" << sb_hash_size
|
|
<< dendl;
|
|
int64_t errors = 0;
|
|
int64_t warnings = 0;
|
|
unsigned repaired = 0;
|
|
|
|
std::vector<uint64_t> bdev_labels_broken;
|
|
std::vector<uint64_t> bdev_labels_in_repair;
|
|
uint64_t_btree_t used_omap_head;
|
|
uint64_t_btree_t used_sbids;
|
|
|
|
mempool_dynamic_bitset used_blocks, bluefs_used_blocks;
|
|
KeyValueDB::Iterator it;
|
|
store_statfs_t expected_store_statfs;
|
|
per_pool_statfs expected_pool_statfs;
|
|
per_pool_fsck_stats_t per_pool_fsck_stats;
|
|
|
|
sb_info_space_efficient_map_t sb_info;
|
|
shared_blob_2hash_tracker_t sb_ref_counts(
|
|
sb_hash_size,
|
|
min_alloc_size);
|
|
size_t sb_ref_mismatches = 0;
|
|
|
|
/// map of oid -> (first_)offset for each zone
|
|
std::vector<std::unordered_map<ghobject_t, uint64_t>> zone_refs; // FIXME: this may be a lot of RAM!
|
|
|
|
uint64_t num_objects = 0;
|
|
uint64_t num_extents = 0;
|
|
uint64_t num_blobs = 0;
|
|
uint64_t num_spanning_blobs = 0;
|
|
uint64_t num_shared_blobs = 0;
|
|
uint64_t num_sharded_objects = 0;
|
|
BlueStoreRepairer repairer;
|
|
|
|
auto alloc_size = fm->get_alloc_size();
|
|
|
|
if (bdev->supported_bdev_label() && bdev_label_multi
|
|
&& bdev_label_valid_locations.empty()) {
|
|
derr << __func__ << " fsck error: no valid block device label found" << dendl;
|
|
return -EIO;
|
|
}
|
|
// Delayed action, we could not do it in _fsck().
|
|
if (bdev->supported_bdev_label() && repair && !bdev_label_multi &&
|
|
cct->_conf.get_val<bool>("bluestore_bdev_label_multi_upgrade")) {
|
|
// upgrade to multi
|
|
bdev_label.meta["multi"] = "yes";
|
|
bdev_label.meta["epoch"] = "1";
|
|
bdev_label_multi = true;
|
|
bdev_labels_broken.push_back(BDEV_FIRST_LABEL_POSITION);
|
|
errors++;
|
|
}
|
|
if (bdev->supported_bdev_label() && bdev_label_multi) {
|
|
for (size_t i = 0; i < bdev_label_positions.size(); i++) {
|
|
uint64_t location = bdev_label_positions[i];
|
|
if (location + BDEV_LABEL_BLOCK_SIZE > bdev_label.size) {
|
|
continue;
|
|
}
|
|
if (std::find(
|
|
bdev_label_valid_locations.begin(),
|
|
bdev_label_valid_locations.end(),
|
|
location) == bdev_label_valid_locations.end()) {
|
|
derr << "fsck error: bdev label at 0x" << std::hex << location << std::dec
|
|
<< " corrupted" << dendl;
|
|
errors++;
|
|
bdev_labels_broken.push_back(location);
|
|
}
|
|
}
|
|
// We have to wait for allocations check to know if we can fix.
|
|
}
|
|
|
|
utime_t start = ceph_clock_now();
|
|
|
|
_fsck_collections(&errors);
|
|
used_blocks.resize(fm->get_alloc_units());
|
|
|
|
if (bluefs) {
|
|
interval_set<uint64_t> bluefs_extents;
|
|
bluefs->foreach_block_extents(
|
|
bluefs_layout.shared_bdev,
|
|
[&](uint64_t start, uint32_t len) {
|
|
apply_for_bitset_range(start, len, alloc_size, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset& bs) {
|
|
ceph_assert(pos < bs.size());
|
|
bs.set(pos);
|
|
}
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
bluefs_used_blocks = used_blocks;
|
|
|
|
if (bdev->supported_bdev_label() && bdev_label_multi) {
|
|
// Forcibly mark regions of bdev label clones as used.
|
|
// If an object happens to be using it we will get an error and a repair applied.
|
|
// We can move away data only if it was allocated for object in BlueStore,
|
|
// we are unable to move away BlueFS data.
|
|
|
|
// skip first bdev label in this check
|
|
for (uint64_t position : bdev_labels_broken) {
|
|
uint64_t length = std::max<uint64_t>(BDEV_LABEL_BLOCK_SIZE, alloc_size);
|
|
bool is_taken_by_bluefs = false;
|
|
apply_for_bitset_range(position, length, alloc_size, bluefs_used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset& bs) {
|
|
is_taken_by_bluefs |= bs.test(pos);
|
|
}
|
|
);
|
|
if (is_taken_by_bluefs) {
|
|
// We are unable to fix it.
|
|
dout(1) << "fsck bdev label at 0x" << std::hex << position << std::dec
|
|
<< "taken by bluefs, cannot be fixed" << dendl;
|
|
} else {
|
|
// Mark blocks so we could move offending objects away.
|
|
bdev_labels_in_repair.push_back(position);
|
|
}
|
|
}
|
|
// Mark locations of those bdev labels that are not taken by bluefs.
|
|
for (size_t i = 0; i < bdev_label_positions.size(); i++) {
|
|
uint64_t position = bdev_label_positions[i];
|
|
uint64_t length = std::max<uint64_t>(BDEV_LABEL_BLOCK_SIZE, alloc_size);
|
|
if (position + length <= bdev_label.size) {
|
|
apply_for_bitset_range(position, length, alloc_size, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset& bs) {
|
|
bs.set(pos);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
apply_for_bitset_range(
|
|
BDEV_FIRST_LABEL_POSITION, std::max<uint64_t>(min_alloc_size, SUPER_RESERVED), alloc_size, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset &bs) {
|
|
bs.set(pos);
|
|
}
|
|
);
|
|
|
|
|
|
if (repair) {
|
|
repairer.init_space_usage_tracker(
|
|
bdev->get_size(),
|
|
min_alloc_size);
|
|
}
|
|
|
|
if (bluefs) {
|
|
int r = bluefs->fsck();
|
|
if (r < 0) {
|
|
return r;
|
|
}
|
|
if (r > 0)
|
|
errors += r;
|
|
}
|
|
|
|
if (!per_pool_stat_collection) {
|
|
const char *w;
|
|
if (cct->_conf->bluestore_fsck_error_on_no_per_pool_stats) {
|
|
w = "error";
|
|
++errors;
|
|
} else {
|
|
w = "warning";
|
|
++warnings;
|
|
}
|
|
derr << "fsck " << w << ": store not yet converted to per-pool stats"
|
|
<< dendl;
|
|
}
|
|
if (per_pool_omap != OMAP_PER_PG) {
|
|
const char *w;
|
|
if (cct->_conf->bluestore_fsck_error_on_no_per_pool_omap) {
|
|
w = "error";
|
|
++errors;
|
|
} else {
|
|
w = "warning";
|
|
++warnings;
|
|
}
|
|
derr << "fsck " << w << ": store not yet converted to per-pg omap"
|
|
<< dendl;
|
|
}
|
|
|
|
if (g_conf()->bluestore_debug_fsck_abort) {
|
|
dout(1) << __func__ << " debug abort" << dendl;
|
|
goto out_scan;
|
|
}
|
|
|
|
dout(1) << __func__ << " checking shared_blobs (phase 1)" << dendl;
|
|
it = db->get_iterator(PREFIX_SHARED_BLOB, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
string key = it->key();
|
|
uint64_t sbid;
|
|
if (get_key_shared_blob(key, &sbid) < 0) {
|
|
// Failed to parse the key.
|
|
// This gonna to be handled at the second stage
|
|
continue;
|
|
}
|
|
bluestore_shared_blob_t shared_blob(sbid);
|
|
bufferlist bl = it->value();
|
|
auto blp = bl.cbegin();
|
|
try {
|
|
decode(shared_blob, blp);
|
|
}
|
|
catch (ceph::buffer::error& e) {
|
|
// this gonna to be handled at the second stage
|
|
continue;
|
|
}
|
|
dout(20) << __func__ << " " << shared_blob << dendl;
|
|
auto& sbi = sb_info.add_maybe_stray(sbid);
|
|
|
|
// primarily to silent the 'unused' warning
|
|
ceph_assert(sbi.pool_id == sb_info_t::INVALID_POOL_ID);
|
|
|
|
for (auto& r : shared_blob.ref_map.ref_map) {
|
|
sb_ref_counts.inc_range(
|
|
sbid,
|
|
r.first,
|
|
r.second.length,
|
|
-r.second.refs);
|
|
}
|
|
}
|
|
} // if (it) //checking shared_blobs (phase1)
|
|
|
|
// walk PREFIX_OBJ
|
|
{
|
|
dout(1) << __func__ << " walking object keyspace" << dendl;
|
|
ceph::mutex sb_info_lock = ceph::make_mutex("BlueStore::fsck::sbinfo_lock");
|
|
BlueStore::FSCK_ObjectCtx ctx(
|
|
errors,
|
|
warnings,
|
|
num_objects,
|
|
num_extents,
|
|
num_blobs,
|
|
num_sharded_objects,
|
|
num_spanning_blobs,
|
|
&used_blocks,
|
|
&used_omap_head,
|
|
&zone_refs,
|
|
//no need for the below lock when in non-shallow mode as
|
|
// there is no multithreading in this case
|
|
depth == FSCK_SHALLOW ? &sb_info_lock : nullptr,
|
|
sb_info,
|
|
sb_ref_counts,
|
|
expected_store_statfs,
|
|
expected_pool_statfs,
|
|
per_pool_fsck_stats,
|
|
repair ? &repairer : nullptr);
|
|
|
|
_fsck_check_objects(depth, ctx);
|
|
}
|
|
|
|
sb_ref_mismatches = sb_ref_counts.count_non_zero();
|
|
if (sb_ref_mismatches != 0) {
|
|
derr << "fsck error:" << "*" << sb_ref_mismatches
|
|
<< " shared blob references aren't matching, at least "
|
|
<< sb_ref_mismatches << " found" << dendl;
|
|
errors += sb_ref_mismatches;
|
|
if (!repair) {
|
|
uint32_t cnts = 0;
|
|
_fsck_foreach_shared_blob( [&](coll_t cid,
|
|
ghobject_t oid,
|
|
uint64_t sbid,
|
|
const bluestore_blob_t& b) {
|
|
for (auto& p : b.get_extents()) {
|
|
if (p.is_valid() &&
|
|
!sb_ref_counts.test_all_zero_range(sbid,
|
|
p.offset,
|
|
p.length)) {
|
|
derr << "fsck possibly broken shared blob found for col:" << cid
|
|
<< " obj:" << oid
|
|
<< " sbid 0x" << std::hex << sbid << std::dec
|
|
<< " " << p
|
|
<< dendl;
|
|
++cnts;
|
|
break;
|
|
}
|
|
}
|
|
return cnts <= MAX_FSCK_ERROR_LINES;
|
|
});
|
|
}
|
|
}
|
|
|
|
if (depth != FSCK_SHALLOW && repair) {
|
|
_fsck_repair_shared_blobs(repairer, sb_ref_counts, sb_info);
|
|
}
|
|
dout(1) << __func__ << " checking shared_blobs (phase 2)" << dendl;
|
|
it = db->get_iterator(PREFIX_SHARED_BLOB, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
// FIXME minor: perhaps simplify for shallow mode?
|
|
// fill global if not overriden below
|
|
auto expected_statfs = &expected_store_statfs;
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
string key = it->key();
|
|
uint64_t sbid;
|
|
if (get_key_shared_blob(key, &sbid)) {
|
|
derr << "fsck error: bad key '" << key
|
|
<< "' in shared blob namespace" << dendl;
|
|
if (repair) {
|
|
repairer.remove_key(db, PREFIX_SHARED_BLOB, key);
|
|
}
|
|
++errors;
|
|
continue;
|
|
}
|
|
auto p = sb_info.find(sbid);
|
|
if (p == sb_info.end()) {
|
|
if (sb_ref_mismatches > 0) {
|
|
// highly likely this has been already reported before, ignoring...
|
|
dout(5) << __func__ << " found duplicate(?) stray shared blob data for sbid 0x"
|
|
<< std::hex << sbid << std::dec << dendl;
|
|
} else {
|
|
derr<< "fsck error: found stray shared blob data for sbid 0x"
|
|
<< std::hex << sbid << std::dec << dendl;
|
|
++errors;
|
|
if (repair) {
|
|
repairer.remove_key(db, PREFIX_SHARED_BLOB, key);
|
|
}
|
|
}
|
|
} else {
|
|
++num_shared_blobs;
|
|
sb_info_t& sbi = *p;
|
|
bluestore_shared_blob_t shared_blob(sbid);
|
|
bufferlist bl = it->value();
|
|
auto blp = bl.cbegin();
|
|
try {
|
|
decode(shared_blob, blp);
|
|
}
|
|
catch (ceph::buffer::error& e) {
|
|
++errors;
|
|
|
|
derr << "fsck error: failed to decode Shared Blob"
|
|
<< pretty_binary_string(key) << dendl;
|
|
if (repair) {
|
|
dout(20) << __func__ << " undecodable Shared Blob, key:'"
|
|
<< pretty_binary_string(key)
|
|
<< "', removing" << dendl;
|
|
repairer.remove_key(db, PREFIX_SHARED_BLOB, key);
|
|
}
|
|
continue;
|
|
}
|
|
dout(20) << __func__ << " " << shared_blob << dendl;
|
|
PExtentVector extents;
|
|
for (auto& r : shared_blob.ref_map.ref_map) {
|
|
extents.emplace_back(bluestore_pextent_t(r.first, r.second.length));
|
|
}
|
|
if (sbi.pool_id != sb_info_t::INVALID_POOL_ID &&
|
|
(per_pool_stat_collection || repair)) {
|
|
expected_statfs = &expected_pool_statfs[sbi.pool_id];
|
|
}
|
|
std::stringstream ss;
|
|
ss << "sbid 0x" << std::hex << sbid << std::dec;
|
|
|
|
pool_fsck_stats_t& ppfs = per_pool_fsck_stats[sbi.pool_id];
|
|
ppfs.shared_blobs++;
|
|
errors += _fsck_check_extents(ss.str(),
|
|
extents,
|
|
sbi.allocated_chunks < 0,
|
|
used_blocks,
|
|
fm->get_alloc_size(),
|
|
repair ? &repairer : nullptr,
|
|
*expected_statfs,
|
|
ppfs,
|
|
depth);
|
|
}
|
|
}
|
|
} // if (it) /* checking shared_blobs (phase 2)*/
|
|
|
|
if (repair && repairer.preprocess_misreference(db)) {
|
|
|
|
dout(1) << __func__ << " sorting out misreferenced extents" << dendl;
|
|
auto& misref_extents = repairer.get_misreferences();
|
|
interval_set<uint64_t> to_release;
|
|
it = db->get_iterator(PREFIX_OBJ, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
// fill global if not overriden below
|
|
auto expected_statfs = &expected_store_statfs;
|
|
|
|
CollectionRef c;
|
|
spg_t pgid;
|
|
KeyValueDB::Transaction txn = repairer.get_fix_misreferences_txn();
|
|
bool bypass_rest = false;
|
|
for (it->lower_bound(string()); it->valid() && !bypass_rest;
|
|
it->next()) {
|
|
dout(30) << __func__ << " key "
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
if (is_extent_shard_key(it->key())) {
|
|
continue;
|
|
}
|
|
|
|
ghobject_t oid;
|
|
int r = get_key_object(it->key(), &oid);
|
|
if (r < 0 || !repairer.is_used(oid)) {
|
|
continue;
|
|
}
|
|
|
|
if (!c ||
|
|
oid.shard_id != pgid.shard ||
|
|
oid.hobj.get_logical_pool() != (int64_t)pgid.pool() ||
|
|
!c->contains(oid)) {
|
|
c = nullptr;
|
|
for (auto& p : coll_map) {
|
|
if (p.second->contains(oid)) {
|
|
c = p.second;
|
|
break;
|
|
}
|
|
}
|
|
if (!c) {
|
|
continue;
|
|
}
|
|
if (per_pool_stat_collection || repair) {
|
|
auto pool_id = c->cid.is_pg(&pgid) ? pgid.pool() : META_POOL_ID;
|
|
expected_statfs = &expected_pool_statfs[pool_id];
|
|
}
|
|
}
|
|
if (!repairer.is_used(c->cid)) {
|
|
continue;
|
|
}
|
|
|
|
dout(20) << __func__ << " check misreference for col:" << c->cid
|
|
<< " obj:" << oid << dendl;
|
|
|
|
OnodeRef o;
|
|
o.reset(Onode::create_decode(c, oid, it->key(), it->value(), false, segment_size != 0));
|
|
o->extent_map.fault_range(db, 0, OBJECT_MAX_SIZE);
|
|
mempool::bluestore_fsck::set<BlobRef> blobs;
|
|
|
|
for (auto& e : o->extent_map.extent_map) {
|
|
blobs.insert(e.blob);
|
|
}
|
|
bool need_onode_update = false;
|
|
bool first_dump = true;
|
|
for(auto b : blobs) {
|
|
bool broken_blob = false;
|
|
auto& pextents = b->dirty_blob().dirty_extents();
|
|
for (auto& e : pextents) {
|
|
if (!e.is_valid()) {
|
|
continue;
|
|
}
|
|
// for the sake of simplicity and proper shared blob handling
|
|
// always rewrite the whole blob even when it's partially
|
|
// misreferenced.
|
|
if (misref_extents.intersects(e.offset, e.length)) {
|
|
if (first_dump) {
|
|
first_dump = false;
|
|
_dump_onode<10>(cct, *o);
|
|
}
|
|
broken_blob = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!broken_blob)
|
|
continue;
|
|
bool compressed = b->get_blob().is_compressed();
|
|
need_onode_update = true;
|
|
dout(10) << __func__
|
|
<< " fix misreferences in oid:" << oid
|
|
<< " " << *b << dendl;
|
|
uint64_t b_off = 0;
|
|
PExtentVector pext_to_release;
|
|
pext_to_release.reserve(pextents.size());
|
|
// rewriting all valid pextents
|
|
for (auto e = pextents.begin(); e != pextents.end();
|
|
e++) {
|
|
auto b_off_cur = b_off;
|
|
b_off += e->length;
|
|
if (!e->is_valid()) {
|
|
continue;
|
|
}
|
|
PExtentVector exts;
|
|
dout(5) << __func__ << "::NCB::(F)alloc=" << alloc << ", length=" << e->length << dendl;
|
|
int64_t alloc_len =
|
|
alloc->allocate(e->length, min_alloc_size,
|
|
0, -1, &exts);
|
|
if (alloc_len < 0 || alloc_len < (int64_t)e->length) {
|
|
derr << __func__
|
|
<< " failed to allocate 0x" << std::hex << e->length
|
|
<< " allocated 0x " << (alloc_len < 0 ? 0 : alloc_len)
|
|
<< " min_alloc_size 0x" << min_alloc_size
|
|
<< " available 0x " << alloc->get_free()
|
|
<< std::dec << dendl;
|
|
if (exts.size()) {
|
|
alloc->release(exts);
|
|
}
|
|
bypass_rest = true;
|
|
break;
|
|
}
|
|
expected_statfs->allocated += e->length;
|
|
if (compressed) {
|
|
expected_statfs->data_compressed_allocated += e->length;
|
|
}
|
|
|
|
bufferlist bl;
|
|
IOContext ioc(cct, NULL, !cct->_conf->bluestore_fail_eio);
|
|
r = bdev->read(e->offset, e->length, &bl, &ioc, false);
|
|
if (r < 0) {
|
|
derr << __func__ << " failed to read from 0x" << std::hex << e->offset
|
|
<<"~" << e->length << std::dec << dendl;
|
|
ceph_abort_msg("read failed, wtf");
|
|
}
|
|
pext_to_release.push_back(*e);
|
|
e = pextents.erase(e);
|
|
e = pextents.insert(e, exts.begin(), exts.end());
|
|
b->get_blob().map_bl(
|
|
b_off_cur, bl,
|
|
[&](uint64_t offset, bufferlist& t) {
|
|
int r = bdev->write(offset, t, false);
|
|
ceph_assert(r == 0);
|
|
});
|
|
e += exts.size() - 1;
|
|
for (auto& p : exts) {
|
|
fm->allocate(p.offset, p.length, txn);
|
|
}
|
|
} // for (auto e = pextents.begin(); e != pextents.end(); e++) {
|
|
|
|
if (b->get_blob().is_shared()) {
|
|
b->dirty_blob().clear_flag(bluestore_blob_t::FLAG_SHARED);
|
|
|
|
auto sbid = b->get_sbid();
|
|
auto sb_it = sb_info.find(sbid);
|
|
ceph_assert(sb_it != sb_info.end());
|
|
sb_info_t& sbi = *sb_it;
|
|
|
|
if (sbi.allocated_chunks < 0) {
|
|
// NB: it's crucial to use compressed_allocated_chunks from sb_info_t
|
|
// as we originally used that value while accumulating
|
|
// expected_statfs
|
|
expected_statfs->allocated -= uint64_t(-sbi.allocated_chunks) << min_alloc_size_order;
|
|
expected_statfs->data_compressed_allocated -=
|
|
uint64_t(-sbi.allocated_chunks) << min_alloc_size_order;
|
|
} else {
|
|
expected_statfs->allocated -= uint64_t(sbi.allocated_chunks) << min_alloc_size_order;
|
|
}
|
|
sbi.allocated_chunks = 0;
|
|
repairer.fix_shared_blob(txn, sbid, nullptr, 0);
|
|
|
|
// relying on blob's pextents to decide what to release.
|
|
for (auto& p : pext_to_release) {
|
|
to_release.union_insert(p.offset, p.length);
|
|
}
|
|
} else {
|
|
for (auto& p : pext_to_release) {
|
|
expected_statfs->allocated -= p.length;
|
|
if (compressed) {
|
|
expected_statfs->data_compressed_allocated -= p.length;
|
|
}
|
|
to_release.union_insert(p.offset, p.length);
|
|
}
|
|
}
|
|
if (bypass_rest) {
|
|
break;
|
|
}
|
|
} // for(auto b : blobs)
|
|
if (need_onode_update) {
|
|
o->extent_map.dirty_range(0, OBJECT_MAX_SIZE);
|
|
_record_onode(o, txn);
|
|
}
|
|
} // for (it->lower_bound(string()); it->valid(); it->next())
|
|
|
|
for (auto it = to_release.begin(); it != to_release.end(); ++it) {
|
|
dout(10) << __func__ << " release 0x" << std::hex << it.get_start()
|
|
<< "~" << it.get_len() << std::dec << dendl;
|
|
fm->release(it.get_start(), it.get_len(), txn);
|
|
}
|
|
alloc->release(to_release);
|
|
to_release.clear();
|
|
} // if (it) {
|
|
} //if (repair && repairer.preprocess_misreference()) {
|
|
sb_info.clear();
|
|
sb_ref_counts.reset();
|
|
|
|
dout(1) << __func__ << " checking pool_statfs" << dendl;
|
|
_fsck_check_statfs(expected_store_statfs, expected_pool_statfs,
|
|
errors, warnings, repair ? &repairer : nullptr);
|
|
if (depth != FSCK_SHALLOW) {
|
|
dout(1) << __func__ << " checking for stray omap data " << dendl;
|
|
it = db->get_iterator(PREFIX_OMAP, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
uint64_t last_omap_head = 0;
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
uint64_t omap_head;
|
|
|
|
_key_decode_u64(it->key().c_str(), &omap_head);
|
|
|
|
if (used_omap_head.count(omap_head) == 0 &&
|
|
omap_head != last_omap_head) {
|
|
pair<string,string> rk = it->raw_key();
|
|
fsck_derr(errors, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck error: found stray omap data on omap_head "
|
|
<< omap_head << " " << last_omap_head
|
|
<< " prefix/key: " << url_escape(rk.first)
|
|
<< " " << url_escape(rk.second)
|
|
<< fsck_dendl;
|
|
++errors;
|
|
last_omap_head = omap_head;
|
|
}
|
|
}
|
|
}
|
|
it = db->get_iterator(PREFIX_PGMETA_OMAP, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
uint64_t last_omap_head = 0;
|
|
pool_fsck_stats_t& ppfs = per_pool_fsck_stats[META_POOL_ID];
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
uint64_t omap_head;
|
|
_key_decode_u64(it->key().c_str(), &omap_head);
|
|
ppfs.omaps++;
|
|
ppfs.omap_key_size += it->key().size();
|
|
ppfs.omap_val_size += it->value().length();
|
|
if (used_omap_head.count(omap_head) == 0 &&
|
|
omap_head != last_omap_head) {
|
|
pair<string,string> rk = it->raw_key();
|
|
fsck_derr(errors, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck error: found stray (pgmeta) omap data on omap_head "
|
|
<< omap_head << " " << last_omap_head
|
|
<< " prefix/key: " << url_escape(rk.first)
|
|
<< " " << url_escape(rk.second)
|
|
<< fsck_dendl;
|
|
last_omap_head = omap_head;
|
|
++errors;
|
|
}
|
|
}
|
|
}
|
|
it = db->get_iterator(PREFIX_PERPOOL_OMAP, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
uint64_t last_omap_head = 0;
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
uint64_t pool;
|
|
uint64_t omap_head;
|
|
string k = it->key();
|
|
const char *c = k.c_str();
|
|
c = _key_decode_u64(c, &pool);
|
|
c = _key_decode_u64(c, &omap_head);
|
|
auto p =
|
|
pool > 0 ? pool : META_POOL_ID; // we erroneously use pool==0 for
|
|
// meta (aka pool==-1) objects
|
|
// (see #64153)
|
|
// hence treat it as meta
|
|
pool_fsck_stats_t& ppfs = per_pool_fsck_stats[p];
|
|
ppfs.omaps++;
|
|
ppfs.omap_key_size += it->key().size();
|
|
ppfs.omap_val_size += it->value().length();
|
|
if (used_omap_head.count(omap_head) == 0 &&
|
|
omap_head != last_omap_head) {
|
|
pair<string,string> rk = it->raw_key();
|
|
fsck_derr(errors, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck error: found stray (per-pool) omap data on omap_head "
|
|
<< omap_head << " " << last_omap_head
|
|
<< " prefix/key: " << url_escape(rk.first)
|
|
<< " " << url_escape(rk.second)
|
|
<< fsck_dendl;
|
|
++errors;
|
|
last_omap_head = omap_head;
|
|
}
|
|
}
|
|
}
|
|
it = db->get_iterator(PREFIX_PERPG_OMAP, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
uint64_t last_omap_head = 0;
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
uint64_t pool;
|
|
uint32_t hash;
|
|
uint64_t omap_head;
|
|
string k = it->key();
|
|
const char* c = k.c_str();
|
|
c = _key_decode_u64(c, &pool);
|
|
c = _key_decode_u32(c, &hash);
|
|
c = _key_decode_u64(c, &omap_head);
|
|
auto p =
|
|
pool > 0 ? pool : META_POOL_ID; // we erroneously use pool==0 for
|
|
// meta (aka pool==-1) objects
|
|
// (see #64153)
|
|
// hence treat it as meta
|
|
pool_fsck_stats_t& ppfs = per_pool_fsck_stats[p];
|
|
ppfs.omaps++;
|
|
ppfs.omap_key_size += it->key().size();
|
|
ppfs.omap_val_size += it->value().length();
|
|
if (used_omap_head.count(omap_head) == 0 &&
|
|
omap_head != last_omap_head) {
|
|
fsck_derr(errors, MAX_FSCK_ERROR_LINES)
|
|
<< "fsck error: found stray (per-pg) omap data on omap_head "
|
|
<< " key " << pretty_binary_string(it->key())
|
|
<< omap_head << " " << last_omap_head << " " << used_omap_head.count(omap_head) << fsck_dendl;
|
|
++errors;
|
|
last_omap_head = omap_head;
|
|
}
|
|
}
|
|
}
|
|
dout(1) << __func__ << " checking deferred events" << dendl;
|
|
it = db->get_iterator(PREFIX_DEFERRED, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (it) {
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
bufferlist bl = it->value();
|
|
auto p = bl.cbegin();
|
|
bluestore_deferred_transaction_t wt;
|
|
try {
|
|
decode(wt, p);
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << "fsck error: failed to decode deferred txn "
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
if (repair) {
|
|
dout(20) << __func__ << " undecodable deferred TXN record, key: '"
|
|
<< pretty_binary_string(it->key())
|
|
<< "', removing" << dendl;
|
|
repairer.remove_key(db, PREFIX_DEFERRED, it->key());
|
|
}
|
|
continue;
|
|
}
|
|
dout(20) << __func__ << " deferred " << wt.seq
|
|
<< " ops " << wt.ops.size()
|
|
<< " released 0x" << std::hex << wt.released << std::dec << dendl;
|
|
for (auto e = wt.released.begin(); e != wt.released.end(); ++e) {
|
|
apply_for_bitset_range(
|
|
e.get_start(), e.get_len(), alloc_size, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset &bs) {
|
|
bs.set(pos);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip freelist vs allocated compare when we have Null fm
|
|
if (!fm->is_null_manager()) {
|
|
dout(1) << __func__ << " checking freelist vs allocated" << dendl;
|
|
if (!bdev->supported_bdev_label()) {
|
|
// it should be 0 labels if labels are not supported
|
|
ceph_assert(bdev_label_valid_locations.empty());
|
|
}
|
|
//unmark extra bdev copies, will collide with the check
|
|
|
|
std::vector<uint64_t> sum = bdev_label_valid_locations;
|
|
sum.insert(sum.end(), bdev_labels_in_repair.begin(), bdev_labels_in_repair.end());
|
|
for (uint64_t location : sum) {
|
|
uint64_t length = std::max<uint64_t>(BDEV_LABEL_BLOCK_SIZE, alloc_size);
|
|
if (location != BDEV_FIRST_LABEL_POSITION) {
|
|
apply_for_bitset_range(location, length, alloc_size, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset& bs) {
|
|
bs.reset(pos);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
fm->enumerate_reset();
|
|
uint64_t offset, length;
|
|
while (fm->enumerate_next(db, &offset, &length)) {
|
|
bool intersects = false;
|
|
apply_for_bitset_range(
|
|
offset, length, alloc_size, used_blocks,
|
|
[&](uint64_t pos, mempool_dynamic_bitset &bs) {
|
|
ceph_assert(pos < bs.size());
|
|
if (bs.test(pos) && !bluefs_used_blocks.test(pos)) {
|
|
if (offset == SUPER_RESERVED &&
|
|
length == min_alloc_size - SUPER_RESERVED) {
|
|
// this is due to the change just after luminous to min_alloc_size
|
|
// granularity allocations, and our baked in assumption at the top
|
|
// of _fsck that 0~round_up_to(SUPER_RESERVED,min_alloc_size) is used
|
|
// (vs luminous's round_up_to(SUPER_RESERVED,block_size)). harmless,
|
|
// since we will never allocate this region below min_alloc_size.
|
|
dout(10) << __func__ << " ignoring free extent between SUPER_RESERVED"
|
|
<< " and min_alloc_size, 0x" << std::hex << offset << "~"
|
|
<< length << std::dec << dendl;
|
|
} else {
|
|
intersects = true;
|
|
if (repair) {
|
|
repairer.fix_false_free(db, fm,
|
|
pos * min_alloc_size,
|
|
min_alloc_size);
|
|
}
|
|
}
|
|
} else {
|
|
bs.set(pos);
|
|
}
|
|
}
|
|
);
|
|
if (intersects) {
|
|
derr << "fsck error: free extent 0x" << std::hex << offset
|
|
<< "~" << length << std::dec
|
|
<< " intersects allocated blocks" << dendl;
|
|
++errors;
|
|
}
|
|
}
|
|
fm->enumerate_reset();
|
|
|
|
// check for leaked extents
|
|
size_t count = used_blocks.count();
|
|
if (used_blocks.size() != count) {
|
|
ceph_assert(used_blocks.size() > count);
|
|
used_blocks.flip();
|
|
size_t start = used_blocks.find_first();
|
|
while (start != decltype(used_blocks)::npos) {
|
|
size_t cur = start;
|
|
while (true) {
|
|
size_t next = used_blocks.find_next(cur);
|
|
if (next != cur + 1) {
|
|
++errors;
|
|
derr << "fsck error: leaked extent 0x" << std::hex
|
|
<< ((uint64_t)start * fm->get_alloc_size()) << "~"
|
|
<< ((cur + 1 - start) * fm->get_alloc_size()) << std::dec
|
|
<< dendl;
|
|
if (repair) {
|
|
repairer.fix_leaked(db,
|
|
fm,
|
|
start * min_alloc_size,
|
|
(cur + 1 - start) * min_alloc_size);
|
|
}
|
|
start = next;
|
|
break;
|
|
}
|
|
cur = next;
|
|
}
|
|
}
|
|
used_blocks.flip();
|
|
}
|
|
}
|
|
}
|
|
if (repair) {
|
|
if (per_pool_omap != OMAP_PER_PG) {
|
|
dout(5) << __func__ << " fixing per_pg_omap" << dendl;
|
|
repairer.fix_per_pool_omap(db, OMAP_PER_PG);
|
|
}
|
|
|
|
dout(5) << __func__ << " applying repair results" << dendl;
|
|
repaired = repairer.apply(db);
|
|
dout(5) << __func__ << " repair applied" << dendl;
|
|
}
|
|
if (repair && bdev_labels_in_repair.size() > 0) {
|
|
// should not happen when labels are disabled
|
|
ceph_assert(bdev->supported_bdev_label());
|
|
// Now fix bdev_labels that were detected to be broken & repairable.
|
|
string p = path + "/block";
|
|
_write_bdev_label(cct, bdev, p, bdev_label, bdev_labels_in_repair);
|
|
for (uint64_t pos : bdev_labels_in_repair) {
|
|
bdev_label_valid_locations.push_back(pos);
|
|
}
|
|
repaired += bdev_labels_in_repair.size();
|
|
}
|
|
|
|
out_scan:
|
|
dout(2) << __func__ << " " << num_objects << " objects, "
|
|
<< num_sharded_objects << " of them sharded. "
|
|
<< dendl;
|
|
dout(2) << __func__ << " " << num_extents << " extents to "
|
|
<< num_blobs << " blobs, "
|
|
<< num_spanning_blobs << " spanning, "
|
|
<< num_shared_blobs << " shared."
|
|
<< dendl;
|
|
dout(2) << __func__ << " Per-pool stats:"
|
|
<< dendl;
|
|
for (auto& p : per_pool_fsck_stats) {
|
|
dout(2) << __func__
|
|
<< " pool "
|
|
<< p.first << " -> " << p.second
|
|
<< dendl;
|
|
}
|
|
|
|
utime_t duration = ceph_clock_now() - start;
|
|
dout(1) << __func__ << " <<<FINISH>>> with " << errors << " errors, "
|
|
<< warnings << " warnings, "
|
|
<< repaired << " repaired, "
|
|
<< (errors + warnings - (int)repaired) << " remaining in "
|
|
<< duration << " seconds" << dendl;
|
|
|
|
// In non-repair mode we should return error count only as
|
|
// it indicates if store status is OK.
|
|
// In repair mode both errors and warnings are taken into account
|
|
// since repaired counter relates to them both.
|
|
return repair ? errors + warnings - (int)repaired : errors;
|
|
}
|
|
|
|
int BlueStore::get_shared_blob(const string& key,
|
|
bufferlist& bl)
|
|
{
|
|
return db->get(PREFIX_SHARED_BLOB, key, &bl);
|
|
};
|
|
|
|
/// methods to inject various errors fsck can repair
|
|
void BlueStore::inject_broken_shared_blob_key(const string& key,
|
|
const bufferlist& bl)
|
|
{
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
txn->set(PREFIX_SHARED_BLOB, key, bl);
|
|
db->submit_transaction_sync(txn);
|
|
};
|
|
|
|
void BlueStore::inject_no_shared_blob_key()
|
|
{
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
ceph_assert(blobid_last > 0);
|
|
// kill the last used sbid, this can be broken due to blobid preallocation
|
|
// in rare cases, leaving as-is for the sake of simplicity
|
|
uint64_t sbid = blobid_last;
|
|
|
|
string key;
|
|
dout(5) << __func__<< " " << sbid << dendl;
|
|
get_shared_blob_key(sbid, &key);
|
|
txn->rmkey(PREFIX_SHARED_BLOB, key);
|
|
db->submit_transaction_sync(txn);
|
|
};
|
|
|
|
void BlueStore::inject_stray_shared_blob_key(uint64_t sbid)
|
|
{
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
|
|
dout(5) << __func__ << " " << sbid << dendl;
|
|
|
|
string key;
|
|
get_shared_blob_key(sbid, &key);
|
|
bluestore_shared_blob_t persistent(sbid);
|
|
persistent.ref_map.get(0xdead0000, min_alloc_size);
|
|
bufferlist bl;
|
|
encode(persistent, bl);
|
|
dout(20) << __func__ << " sbid " << sbid
|
|
<< " takes " << bl.length() << " bytes, updating"
|
|
<< dendl;
|
|
|
|
txn->set(PREFIX_SHARED_BLOB, key, bl);
|
|
db->submit_transaction_sync(txn);
|
|
};
|
|
|
|
|
|
void BlueStore::inject_leaked(uint64_t len)
|
|
{
|
|
PExtentVector exts;
|
|
int64_t alloc_len = alloc->allocate(len, min_alloc_size,
|
|
min_alloc_size * 256, -1, &exts);
|
|
ceph_assert(alloc_len >= 0); // generally we do not expect any errors
|
|
if (fm->is_null_manager()) {
|
|
return;
|
|
}
|
|
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
|
|
ceph_assert(alloc_len >= (int64_t)len);
|
|
for (auto& p : exts) {
|
|
fm->allocate(p.offset, p.length, txn);
|
|
}
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_false_free(coll_t cid, ghobject_t oid)
|
|
{
|
|
ceph_assert(!fm->is_null_manager());
|
|
|
|
KeyValueDB::Transaction txn;
|
|
OnodeRef o;
|
|
CollectionRef c = _get_collection(cid);
|
|
ceph_assert(c);
|
|
{
|
|
std::unique_lock l{c->lock}; // just to avoid internal asserts
|
|
o = c->get_onode(oid, false);
|
|
ceph_assert(o);
|
|
o->extent_map.fault_range(db, 0, OBJECT_MAX_SIZE);
|
|
}
|
|
|
|
bool injected = false;
|
|
txn = db->get_transaction();
|
|
auto& em = o->extent_map.extent_map;
|
|
std::vector<const PExtentVector*> v;
|
|
if (em.size()) {
|
|
v.push_back(&em.begin()->blob->get_blob().get_extents());
|
|
}
|
|
if (em.size() > 1) {
|
|
auto it = em.end();
|
|
--it;
|
|
v.push_back(&(it->blob->get_blob().get_extents()));
|
|
}
|
|
for (auto pext : v) {
|
|
if (pext->size()) {
|
|
auto p = pext->begin();
|
|
while (p != pext->end()) {
|
|
if (p->is_valid()) {
|
|
dout(20) << __func__ << " release 0x" << std::hex << p->offset
|
|
<< "~" << p->length << std::dec << dendl;
|
|
fm->release(p->offset, p->length, txn);
|
|
injected = true;
|
|
break;
|
|
}
|
|
++p;
|
|
}
|
|
}
|
|
}
|
|
ceph_assert(injected);
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_legacy_omap()
|
|
{
|
|
dout(1) << __func__ << dendl;
|
|
per_pool_omap = OMAP_BULK;
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
txn->rmkey(PREFIX_SUPER, "per_pool_omap");
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_legacy_omap(coll_t cid, ghobject_t oid)
|
|
{
|
|
dout(1) << __func__ << " "
|
|
<< cid << " " << oid
|
|
<<dendl;
|
|
KeyValueDB::Transaction txn;
|
|
OnodeRef o;
|
|
CollectionRef c = _get_collection(cid);
|
|
ceph_assert(c);
|
|
{
|
|
std::unique_lock l{ c->lock }; // just to avoid internal asserts
|
|
o = c->get_onode(oid, false);
|
|
ceph_assert(o);
|
|
}
|
|
o->onode.clear_flag(
|
|
bluestore_onode_t::FLAG_PERPG_OMAP |
|
|
bluestore_onode_t::FLAG_PERPOOL_OMAP |
|
|
bluestore_onode_t::FLAG_PGMETA_OMAP);
|
|
txn = db->get_transaction();
|
|
_record_onode(o, txn);
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_stray_omap(uint64_t head, const string& name)
|
|
{
|
|
dout(1) << __func__ << dendl;
|
|
KeyValueDB::Transaction txn = db->get_transaction();
|
|
|
|
string key;
|
|
bufferlist bl;
|
|
_key_encode_u64(head, &key);
|
|
key.append(name);
|
|
txn->set(PREFIX_OMAP, key, bl);
|
|
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_statfs(const string& key, const store_statfs_t& new_statfs)
|
|
{
|
|
BlueStoreRepairer repairer;
|
|
repairer.fix_statfs(db, key, new_statfs);
|
|
repairer.apply(db);
|
|
}
|
|
|
|
void BlueStore::inject_global_statfs(const store_statfs_t& new_statfs)
|
|
{
|
|
KeyValueDB::Transaction t = db->get_transaction();
|
|
volatile_statfs v;
|
|
v = new_statfs;
|
|
bufferlist bl;
|
|
v.encode(bl);
|
|
t->set(PREFIX_STAT, BLUESTORE_GLOBAL_STATFS_KEY, bl);
|
|
db->submit_transaction_sync(t);
|
|
// must set these; are needed at _close_db() statfs persisting
|
|
per_pool_stat_collection = false;
|
|
vstatfs = new_statfs;
|
|
}
|
|
|
|
void BlueStore::inject_misreference(coll_t cid1, ghobject_t oid1,
|
|
coll_t cid2, ghobject_t oid2,
|
|
uint64_t offset)
|
|
{
|
|
OnodeRef o1;
|
|
CollectionRef c1 = _get_collection(cid1);
|
|
ceph_assert(c1);
|
|
{
|
|
std::unique_lock l{c1->lock}; // just to avoid internal asserts
|
|
o1 = c1->get_onode(oid1, false);
|
|
ceph_assert(o1);
|
|
o1->extent_map.fault_range(db, offset, OBJECT_MAX_SIZE);
|
|
}
|
|
OnodeRef o2;
|
|
CollectionRef c2 = _get_collection(cid2);
|
|
ceph_assert(c2);
|
|
{
|
|
std::unique_lock l{c2->lock}; // just to avoid internal asserts
|
|
o2 = c2->get_onode(oid2, false);
|
|
ceph_assert(o2);
|
|
o2->extent_map.fault_range(db, offset, OBJECT_MAX_SIZE);
|
|
}
|
|
Extent& e1 = *(o1->extent_map.seek_lextent(offset));
|
|
Extent& e2 = *(o2->extent_map.seek_lextent(offset));
|
|
|
|
// require onode/extent layout to be the same (and simple)
|
|
// to make things easier
|
|
ceph_assert(o1->onode.extent_map_shards.empty());
|
|
ceph_assert(o2->onode.extent_map_shards.empty());
|
|
ceph_assert(o1->extent_map.spanning_blob_map.size() == 0);
|
|
ceph_assert(o2->extent_map.spanning_blob_map.size() == 0);
|
|
ceph_assert(e1.logical_offset == e2.logical_offset);
|
|
ceph_assert(e1.length == e2.length);
|
|
ceph_assert(e1.blob_offset == e2.blob_offset);
|
|
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
|
|
// along with misreference error this will create space leaks errors
|
|
e2.blob->dirty_blob() = e1.blob->get_blob();
|
|
o2->extent_map.dirty_range(offset, e2.length);
|
|
o2->extent_map.update(txn, false);
|
|
|
|
_record_onode(o2, txn);
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_zombie_spanning_blob(coll_t cid, ghobject_t oid,
|
|
int16_t blob_id)
|
|
{
|
|
OnodeRef o;
|
|
CollectionRef c = _get_collection(cid);
|
|
ceph_assert(c);
|
|
{
|
|
std::unique_lock l{ c->lock }; // just to avoid internal asserts
|
|
o = c->get_onode(oid, false);
|
|
ceph_assert(o);
|
|
o->extent_map.fault_range(db, 0, OBJECT_MAX_SIZE);
|
|
}
|
|
|
|
BlobRef b = c ? c->new_blob() : new Blob(nullptr);
|
|
b->id = blob_id;
|
|
o->extent_map.spanning_blob_map[blob_id] = b;
|
|
|
|
KeyValueDB::Transaction txn;
|
|
txn = db->get_transaction();
|
|
|
|
_record_onode(o, txn);
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
|
|
void BlueStore::inject_bluefs_file(std::string_view dir, std::string_view name, size_t new_size)
|
|
{
|
|
ceph_assert(bluefs);
|
|
|
|
BlueFS::FileWriter* p_handle = nullptr;
|
|
auto ret = bluefs->open_for_write(dir, name, &p_handle, false);
|
|
ceph_assert(ret == 0);
|
|
|
|
std::string s(new_size, '0');
|
|
bufferlist bl;
|
|
bl.append(s);
|
|
p_handle->append(bl);
|
|
|
|
bluefs->fsync(p_handle);
|
|
bluefs->close_writer(p_handle);
|
|
}
|
|
|
|
int BlueStore::compact()
|
|
{
|
|
int r = 0;
|
|
ceph_assert(db);
|
|
if (cct->_conf.get_val<bool>("bluestore_async_db_compaction")) {
|
|
dout(1) << __func__ << " starting async.." << dendl;
|
|
db->compact_async();
|
|
r = -EINPROGRESS;
|
|
} else {
|
|
dout(1) << __func__ << " starting sync.." << dendl;
|
|
db->compact();
|
|
dout(1) << __func__ << " finished." << dendl;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::collect_metadata(map<string,string> *pm)
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
bdev->collect_metadata("bluestore_bdev_", pm);
|
|
if (bluefs) {
|
|
(*pm)["bluefs"] = "1";
|
|
// this value is for backward compatibility only
|
|
(*pm)["bluefs_single_shared_device"] = \
|
|
stringify((int)bluefs_layout.single_shared_device());
|
|
(*pm)["bluefs_dedicated_db"] = \
|
|
stringify((int)bluefs_layout.dedicated_db);
|
|
(*pm)["bluefs_dedicated_wal"] = \
|
|
stringify((int)bluefs_layout.dedicated_wal);
|
|
bluefs->collect_metadata(pm, bluefs_layout.shared_bdev);
|
|
} else {
|
|
(*pm)["bluefs"] = "0";
|
|
}
|
|
|
|
// report numa mapping for underlying devices
|
|
int node = -1;
|
|
set<int> nodes;
|
|
set<string> failed;
|
|
int r = get_numa_node(&node, &nodes, &failed);
|
|
if (r >= 0) {
|
|
if (!failed.empty()) {
|
|
(*pm)["objectstore_numa_unknown_devices"] = stringify(failed);
|
|
}
|
|
if (!nodes.empty()) {
|
|
dout(1) << __func__ << " devices span numa nodes " << nodes << dendl;
|
|
(*pm)["objectstore_numa_nodes"] = stringify(nodes);
|
|
}
|
|
if (node >= 0) {
|
|
(*pm)["objectstore_numa_node"] = stringify(node);
|
|
}
|
|
}
|
|
(*pm)["bluestore_min_alloc_size"] = stringify(min_alloc_size);
|
|
(*pm)["bluestore_allocation_from_file"] = stringify(fm && fm->is_null_manager());
|
|
(*pm)["bluestore_allocator"] = alloc ? alloc->get_type() : "null";
|
|
(*pm)["bluestore_write_mode"] = use_write_v2 ? "new" : "classic";
|
|
(*pm)["bluestore_onode_segmentation"] = segment_size == 0 ? "inactive" : "active";
|
|
}
|
|
|
|
int BlueStore::get_numa_node(
|
|
int *final_node,
|
|
set<int> *out_nodes,
|
|
set<string> *out_failed)
|
|
{
|
|
int node = -1;
|
|
set<string> devices;
|
|
get_devices(&devices);
|
|
set<int> nodes;
|
|
set<string> failed;
|
|
for (auto& devname : devices) {
|
|
int n;
|
|
BlkDev bdev(devname);
|
|
int r = bdev.get_numa_node(&n);
|
|
if (r < 0) {
|
|
dout(10) << __func__ << " bdev " << devname << " can't detect numa_node"
|
|
<< dendl;
|
|
failed.insert(devname);
|
|
continue;
|
|
}
|
|
dout(10) << __func__ << " bdev " << devname << " on numa_node " << n
|
|
<< dendl;
|
|
nodes.insert(n);
|
|
if (node < 0) {
|
|
node = n;
|
|
}
|
|
}
|
|
if (node >= 0 && nodes.size() == 1 && failed.empty()) {
|
|
*final_node = node;
|
|
}
|
|
if (out_nodes) {
|
|
*out_nodes = nodes;
|
|
}
|
|
if (out_failed) {
|
|
*out_failed = failed;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::prepare_for_fast_shutdown()
|
|
{
|
|
m_fast_shutdown = true;
|
|
}
|
|
|
|
int BlueStore::get_devices(set<string> *ls)
|
|
{
|
|
if (bdev) {
|
|
bdev->get_devices(ls);
|
|
if (bluefs) {
|
|
bluefs->get_devices(ls);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// grumble, we haven't started up yet.
|
|
if (int r = _open_path(); r < 0) {
|
|
return r;
|
|
}
|
|
auto close_path = make_scope_guard([&] {
|
|
_close_path();
|
|
});
|
|
if (int r = _open_fsid(false); r < 0) {
|
|
return r;
|
|
}
|
|
auto close_fsid = make_scope_guard([&] {
|
|
_close_fsid();
|
|
});
|
|
if (int r = _read_fsid(&fsid); r < 0) {
|
|
return r;
|
|
}
|
|
if (int r = _lock_fsid(); r < 0) {
|
|
return r;
|
|
}
|
|
if (int r = _open_bdev(false); r < 0) {
|
|
return r;
|
|
}
|
|
auto close_bdev = make_scope_guard([&] {
|
|
_close_bdev();
|
|
});
|
|
if (int r = _minimal_open_bluefs(false); r < 0) {
|
|
return r;
|
|
}
|
|
bdev->get_devices(ls);
|
|
if (bluefs) {
|
|
bluefs->get_devices(ls);
|
|
}
|
|
_minimal_close_bluefs();
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_get_statfs_overall(struct store_statfs_t *buf)
|
|
{
|
|
buf->reset();
|
|
|
|
auto prefix = per_pool_omap == OMAP_BULK ?
|
|
PREFIX_OMAP :
|
|
per_pool_omap == OMAP_PER_POOL ?
|
|
PREFIX_PERPOOL_OMAP :
|
|
PREFIX_PERPG_OMAP;
|
|
buf->omap_allocated =
|
|
db->estimate_prefix_size(prefix, string());
|
|
|
|
uint64_t bfree = alloc->get_free();
|
|
|
|
if (bluefs) {
|
|
buf->internally_reserved = 0;
|
|
// include dedicated db, too, if that isn't the shared device.
|
|
if (bluefs_layout.shared_bdev != BlueFS::BDEV_DB) {
|
|
buf->total += bluefs->get_block_device_size(BlueFS::BDEV_DB);
|
|
}
|
|
// call any non-omap bluefs space "internal metadata"
|
|
buf->internal_metadata =
|
|
bluefs->get_used()
|
|
- buf->omap_allocated;
|
|
}
|
|
|
|
ExtBlkDevState ebd_state;
|
|
int rc = bdev->get_ebd_state(ebd_state);
|
|
if (rc == 0) {
|
|
buf->total += ebd_state.get_physical_total();
|
|
|
|
// we are limited by both the size of the virtual device and the
|
|
// underlying physical device.
|
|
bfree = std::min(bfree, ebd_state.get_physical_avail());
|
|
|
|
buf->allocated = ebd_state.get_physical_total() - ebd_state.get_physical_avail();;
|
|
} else {
|
|
buf->total += bdev->get_size();
|
|
}
|
|
buf->available = bfree;
|
|
}
|
|
|
|
int BlueStore::statfs(struct store_statfs_t *buf,
|
|
osd_alert_list_t* alerts)
|
|
{
|
|
if (alerts) {
|
|
alerts->clear();
|
|
_log_alerts(*alerts);
|
|
}
|
|
_get_statfs_overall(buf);
|
|
{
|
|
std::lock_guard l(vstatfs_lock);
|
|
buf->allocated = vstatfs.allocated();
|
|
buf->data_stored = vstatfs.stored();
|
|
buf->data_compressed = vstatfs.compressed();
|
|
buf->data_compressed_original = vstatfs.compressed_original();
|
|
buf->data_compressed_allocated = vstatfs.compressed_allocated();
|
|
}
|
|
|
|
dout(20) << __func__ << " " << *buf << dendl;
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::pool_statfs(uint64_t pool_id, struct store_statfs_t *buf,
|
|
bool *out_per_pool_omap)
|
|
{
|
|
dout(20) << __func__ << " pool " << pool_id<< dendl;
|
|
|
|
if (!per_pool_stat_collection) {
|
|
dout(20) << __func__ << " not supported in legacy mode " << dendl;
|
|
return -ENOTSUP;
|
|
}
|
|
buf->reset();
|
|
|
|
{
|
|
std::lock_guard l(vstatfs_lock);
|
|
osd_pools[pool_id].publish(buf);
|
|
}
|
|
|
|
string key_prefix;
|
|
_key_encode_u64(pool_id, &key_prefix);
|
|
*out_per_pool_omap = per_pool_omap != OMAP_BULK;
|
|
// stop calls after db was closed
|
|
if (*out_per_pool_omap && db) {
|
|
auto prefix = per_pool_omap == OMAP_PER_POOL ?
|
|
PREFIX_PERPOOL_OMAP :
|
|
PREFIX_PERPG_OMAP;
|
|
buf->omap_allocated = db->estimate_prefix_size(prefix, key_prefix);
|
|
}
|
|
|
|
dout(10) << __func__ << *buf << dendl;
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_check_legacy_statfs_alert()
|
|
{
|
|
string s;
|
|
if (!per_pool_stat_collection &&
|
|
cct->_conf->bluestore_warn_on_legacy_statfs) {
|
|
s = "legacy statfs reporting detected, "
|
|
"suggest to run store repair to get consistent statistic reports";
|
|
}
|
|
std::lock_guard l(qlock);
|
|
legacy_statfs_alert = s;
|
|
}
|
|
|
|
void BlueStore::_check_no_per_pg_or_pool_omap_alert()
|
|
{
|
|
string per_pg, per_pool;
|
|
if (per_pool_omap != OMAP_PER_PG) {
|
|
if (cct->_conf->bluestore_warn_on_no_per_pg_omap) {
|
|
per_pg = "legacy (not per-pg) omap detected, "
|
|
"suggest to run store repair to benefit from faster PG removal";
|
|
}
|
|
if (per_pool_omap != OMAP_PER_POOL) {
|
|
if (cct->_conf->bluestore_warn_on_no_per_pool_omap) {
|
|
per_pool = "legacy (not per-pool) omap detected, "
|
|
"suggest to run store repair to benefit from per-pool omap usage statistics";
|
|
}
|
|
}
|
|
}
|
|
std::lock_guard l(qlock);
|
|
no_per_pg_omap_alert = per_pg;
|
|
no_per_pool_omap_alert = per_pool;
|
|
}
|
|
|
|
// ---------------
|
|
// cache
|
|
|
|
BlueStore::CollectionRef BlueStore::_get_collection(const coll_t& cid)
|
|
{
|
|
std::shared_lock l(coll_lock);
|
|
auto cp = coll_map.find(cid);
|
|
if (cp == coll_map.end())
|
|
return CollectionRef();
|
|
return cp->second;
|
|
}
|
|
|
|
BlueStore::CollectionRef BlueStore::_get_collection_by_oid(const ghobject_t& oid)
|
|
{
|
|
std::shared_lock l(coll_lock);
|
|
|
|
// FIXME: we must replace this with something more efficient
|
|
|
|
for (auto& i : coll_map) {
|
|
spg_t spgid;
|
|
if (i.first.is_pg(&spgid) &&
|
|
i.second->contains(oid)) {
|
|
return i.second;
|
|
}
|
|
}
|
|
return CollectionRef();
|
|
}
|
|
|
|
void BlueStore::_queue_reap_collection(CollectionRef& c)
|
|
{
|
|
dout(10) << __func__ << " " << c << " " << c->cid << dendl;
|
|
// _reap_collections and this in the same thread,
|
|
// so no need a lock.
|
|
removed_collections.push_back(c);
|
|
}
|
|
|
|
void BlueStore::_reap_collections()
|
|
{
|
|
|
|
list<CollectionRef> removed_colls;
|
|
{
|
|
// _queue_reap_collection and this in the same thread.
|
|
// So no need a lock.
|
|
if (!removed_collections.empty())
|
|
removed_colls.swap(removed_collections);
|
|
else
|
|
return;
|
|
}
|
|
|
|
list<CollectionRef>::iterator p = removed_colls.begin();
|
|
while (p != removed_colls.end()) {
|
|
CollectionRef c = *p;
|
|
dout(10) << __func__ << " " << c << " " << c->cid << dendl;
|
|
if (c->onode_space.map_any([&](Onode* o) {
|
|
ceph_assert(!o->exists);
|
|
if (o->flushing_count.load()) {
|
|
dout(10) << __func__ << " " << c << " " << c->cid << " " << o->oid
|
|
<< " flush_txns " << o->flushing_count << dendl;
|
|
return true;
|
|
}
|
|
return false;
|
|
})) {
|
|
++p;
|
|
continue;
|
|
}
|
|
c->onode_space.clear();
|
|
p = removed_colls.erase(p);
|
|
dout(10) << __func__ << " " << c << " " << c->cid << " done" << dendl;
|
|
}
|
|
if (removed_colls.empty()) {
|
|
dout(10) << __func__ << " all reaped" << dendl;
|
|
} else {
|
|
removed_collections.splice(removed_collections.begin(), removed_colls);
|
|
}
|
|
}
|
|
|
|
void BlueStore::refresh_perf_counters()
|
|
{
|
|
uint64_t num_onodes = 0;
|
|
uint64_t num_pinned_onodes = 0;
|
|
uint64_t num_extents = 0;
|
|
uint64_t num_blobs = 0;
|
|
uint64_t num_buffers = 0;
|
|
uint64_t num_buffer_bytes = 0;
|
|
for (auto c : onode_cache_shards) {
|
|
c->add_stats(&num_onodes, &num_pinned_onodes);
|
|
}
|
|
for (auto c : buffer_cache_shards) {
|
|
c->add_stats(&num_extents, &num_blobs,
|
|
&num_buffers, &num_buffer_bytes);
|
|
}
|
|
logger->set(l_bluestore_onodes, num_onodes);
|
|
logger->set(l_bluestore_pinned_onodes, num_pinned_onodes);
|
|
logger->set(l_bluestore_extents, num_extents);
|
|
logger->set(l_bluestore_blobs, num_blobs);
|
|
logger->set(l_bluestore_buffers, num_buffers);
|
|
logger->set(l_bluestore_buffer_bytes, num_buffer_bytes);
|
|
}
|
|
|
|
// ---------------
|
|
// read operations
|
|
|
|
ObjectStore::CollectionHandle BlueStore::open_collection(const coll_t& cid)
|
|
{
|
|
return _get_collection(cid);
|
|
}
|
|
|
|
ObjectStore::CollectionHandle BlueStore::create_new_collection(
|
|
const coll_t& cid)
|
|
{
|
|
std::unique_lock l{coll_lock};
|
|
auto c = ceph::make_ref<Collection>(
|
|
this,
|
|
onode_cache_shards[cid.hash_to_shard(onode_cache_shards.size())],
|
|
buffer_cache_shards[cid.hash_to_shard(buffer_cache_shards.size())],
|
|
cid);
|
|
new_coll_map[cid] = c;
|
|
_osr_attach(c.get());
|
|
return c;
|
|
}
|
|
|
|
void BlueStore::set_collection_commit_queue(
|
|
const coll_t& cid,
|
|
ContextQueue *commit_queue)
|
|
{
|
|
if (commit_queue) {
|
|
std::shared_lock l(coll_lock);
|
|
if (coll_map.count(cid)) {
|
|
coll_map[cid]->commit_queue = commit_queue;
|
|
} else if (new_coll_map.count(cid)) {
|
|
new_coll_map[cid]->commit_queue = commit_queue;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool BlueStore::exists(CollectionHandle &c_, const ghobject_t& oid)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(10) << __func__ << " " << c->cid << " " << oid << dendl;
|
|
if (!c->exists)
|
|
return false;
|
|
|
|
bool r = true;
|
|
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists)
|
|
r = false;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::stat(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
struct stat *st,
|
|
bool allow_eio)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
dout(10) << __func__ << " " << c->get_cid() << " " << oid << dendl;
|
|
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists)
|
|
return -ENOENT;
|
|
st->st_size = o->onode.size;
|
|
st->st_blksize = 4096;
|
|
st->st_blocks = (st->st_size + st->st_blksize - 1) / st->st_blksize;
|
|
st->st_nlink = 1;
|
|
}
|
|
|
|
int r = 0;
|
|
if (_debug_mdata_eio(oid)) {
|
|
r = -EIO;
|
|
derr << __func__ << " " << c->cid << " " << oid << " INJECT EIO" << dendl;
|
|
}
|
|
return r;
|
|
}
|
|
int BlueStore::set_collection_opts(
|
|
CollectionHandle& ch,
|
|
const pool_opts_t& opts)
|
|
{
|
|
Collection *c = static_cast<Collection *>(ch.get());
|
|
dout(15) << __func__ << " " << ch->cid << " options " << opts << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
std::unique_lock l{c->lock};
|
|
c->pool_opts = opts;
|
|
|
|
string val;
|
|
c->compression_algorithm.reset();
|
|
if (c->pool_opts.get(pool_opts_t::COMPRESSION_ALGORITHM, &val)) {
|
|
auto alg = Compressor::get_comp_alg_type(val);
|
|
CompressorRef cp;
|
|
if (alg.has_value() && *alg != Compressor::COMP_ALG_NONE) {
|
|
cp = *alg < compressors.size() ? compressors[*alg] : cp;
|
|
if (!cp) {
|
|
_set_compression_alert(false, val.c_str());
|
|
derr << __func__ << " unable to load compressor plugin for " << val.c_str()
|
|
<< dendl;
|
|
} else {
|
|
ceph_assert(cp->get_type() == *alg);
|
|
c->compression_algorithm = cp->get_type();
|
|
}
|
|
}
|
|
}
|
|
c->compression_mode.reset();
|
|
if (c->pool_opts.get(pool_opts_t::COMPRESSION_MODE, &val)) {
|
|
auto cm = Compressor::get_comp_mode_type(val);
|
|
if (!cm) {
|
|
derr << __func__ << " unrecognized compression mode: " << val.c_str()
|
|
<< dendl;
|
|
} else {
|
|
c->compression_mode = cm;
|
|
}
|
|
}
|
|
int64_t ival;
|
|
c->csum_type.reset();
|
|
if (c->pool_opts.get(pool_opts_t::CSUM_TYPE, &ival)) {
|
|
if (ival >= Checksummer::CSUM_MAX) {
|
|
derr << __func__ << " unrecognized checksum type: " << ival
|
|
<< dendl;
|
|
} else {
|
|
c->csum_type = Checksummer::CSumType(ival);
|
|
}
|
|
}
|
|
c->comp_min_blob_size.reset();
|
|
if (c->pool_opts.get(pool_opts_t::COMPRESSION_MIN_BLOB_SIZE, &ival)) {
|
|
if (ival <= 0) {
|
|
derr << __func__ << " invalid min compression blob size: " << ival
|
|
<< dendl;
|
|
} else {
|
|
c->comp_min_blob_size = ival;
|
|
}
|
|
}
|
|
c->comp_max_blob_size.reset();
|
|
if (c->pool_opts.get(pool_opts_t::COMPRESSION_MAX_BLOB_SIZE, &ival)) {
|
|
if (ival <= 0) {
|
|
derr << __func__ << " invalid max compression blob size: " << ival
|
|
<< dendl;
|
|
} else {
|
|
c->comp_max_blob_size = ival;
|
|
}
|
|
}
|
|
double dval;
|
|
c->compression_req_ratio.reset();
|
|
if (c->pool_opts.get(pool_opts_t::COMPRESSION_REQUIRED_RATIO, &dval)) {
|
|
c->compression_req_ratio = dval;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::read(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
uint64_t offset,
|
|
size_t length,
|
|
bufferlist& bl,
|
|
uint32_t op_flags)
|
|
{
|
|
auto start = mono_clock::now();
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
const coll_t &cid = c->get_cid();
|
|
dout(15) << __func__ << " " << cid << " " << oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
|
|
bl.clear();
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
auto start1 = mono_clock::now();
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
log_latency("get_onode@read",
|
|
l_bluestore_read_onode_meta_lat,
|
|
mono_clock::now() - start1,
|
|
cct->_conf->bluestore_log_op_age,
|
|
"", l_bluestore_slow_read_onode_meta_count);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
if (offset == length && offset == 0)
|
|
length = o->onode.size;
|
|
|
|
r = _do_read(c, o, offset, length, bl, op_flags);
|
|
if (r == -EIO) {
|
|
logger->inc(l_bluestore_read_eio);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (r >= 0 && _debug_data_eio(oid)) {
|
|
r = -EIO;
|
|
derr << __func__ << " " << c->cid << " " << oid << " INJECT EIO" << dendl;
|
|
} else if (oid.hobj.pool > 0 && /* FIXME, see #23029 */
|
|
cct->_conf->bluestore_debug_random_read_err &&
|
|
(rand() % (int)(cct->_conf->bluestore_debug_random_read_err *
|
|
100.0)) == 0) {
|
|
dout(0) << __func__ << ": inject random EIO" << dendl;
|
|
r = -EIO;
|
|
}
|
|
dout(10) << __func__ << " " << cid << " " << oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< " = " << r << dendl;
|
|
log_latency(__func__,
|
|
l_bluestore_read_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_read_cache(
|
|
OnodeRef& o,
|
|
uint64_t offset,
|
|
size_t length,
|
|
int read_cache_policy,
|
|
ready_regions_t& ready_regions,
|
|
blobs2read_t& blobs2read)
|
|
{
|
|
// build blob-wise list to of stuff read (that isn't cached)
|
|
unsigned left = length;
|
|
uint64_t pos = offset;
|
|
auto lp = o->extent_map.seek_lextent(offset);
|
|
while (left > 0 && lp != o->extent_map.extent_map.end()) {
|
|
if (pos < lp->logical_offset) {
|
|
unsigned hole = lp->logical_offset - pos;
|
|
if (hole >= left) {
|
|
break;
|
|
}
|
|
dout(30) << __func__ << " hole 0x" << std::hex << pos << "~" << hole
|
|
<< std::dec << dendl;
|
|
pos += hole;
|
|
left -= hole;
|
|
}
|
|
BlobRef& bptr = lp->blob;
|
|
unsigned l_off = pos - lp->logical_offset;
|
|
unsigned b_off = l_off + lp->blob_offset;
|
|
unsigned b_len = std::min(left, lp->length - l_off);
|
|
|
|
ready_regions_t cache_res;
|
|
interval_set<uint32_t> cache_interval;
|
|
o->bc.read(
|
|
o->c->cache, pos, b_len, cache_res, cache_interval,
|
|
read_cache_policy);
|
|
dout(20) << __func__ << " blob " << *bptr << std::hex
|
|
<< " need 0x" << pos << "~" << b_len
|
|
<< " cache has 0x" << cache_interval
|
|
<< std::dec << dendl;
|
|
|
|
auto pc = cache_res.begin();
|
|
uint64_t chunk_size = bptr->get_blob().get_chunk_size(block_size);
|
|
while (b_len > 0) {
|
|
unsigned l;
|
|
if (pc != cache_res.end() &&
|
|
pc->first == pos) {
|
|
l = pc->second.length();
|
|
ready_regions[pos] = std::move(pc->second);
|
|
dout(30) << __func__ << " use cache 0x" << std::hex << pos << ": 0x"
|
|
<< pos << "~" << l << std::dec << dendl;
|
|
++pc;
|
|
} else {
|
|
l = b_len;
|
|
if (pc != cache_res.end()) {
|
|
ceph_assert(pc->first > pos);
|
|
l = pc->first - pos;
|
|
}
|
|
dout(30) << __func__ << " will read 0x" << std::hex << pos << ": 0x"
|
|
<< b_off << "~" << l << std::dec << dendl;
|
|
// merge regions
|
|
{
|
|
uint64_t r_off = b_off;
|
|
uint64_t r_len = l;
|
|
uint64_t front = r_off % chunk_size;
|
|
if (front) {
|
|
r_off -= front;
|
|
r_len += front;
|
|
}
|
|
unsigned tail = r_len % chunk_size;
|
|
if (tail) {
|
|
r_len += chunk_size - tail;
|
|
}
|
|
bool merged = false;
|
|
regions2read_t& r2r = blobs2read[bptr];
|
|
if (r2r.size()) {
|
|
read_req_t& pre = r2r.back();
|
|
if (r_off <= (pre.r_off + pre.r_len)) {
|
|
front += (r_off - pre.r_off);
|
|
pre.r_len += (r_off + r_len - pre.r_off - pre.r_len);
|
|
pre.regs.emplace_back(region_t(pos, b_off, l, front));
|
|
merged = true;
|
|
}
|
|
}
|
|
if (!merged) {
|
|
read_req_t req(r_off, r_len);
|
|
req.regs.emplace_back(region_t(pos, b_off, l, front));
|
|
r2r.emplace_back(std::move(req));
|
|
}
|
|
}
|
|
}
|
|
pos += l;
|
|
b_off += l;
|
|
left -= l;
|
|
b_len -= l;
|
|
}
|
|
++lp;
|
|
}
|
|
}
|
|
|
|
int BlueStore::_prepare_read_ioc(
|
|
blobs2read_t& blobs2read,
|
|
vector<bufferlist>* compressed_blob_bls,
|
|
IOContext* ioc)
|
|
{
|
|
for (auto& p : blobs2read) {
|
|
const BlobRef& bptr = p.first;
|
|
regions2read_t& r2r = p.second;
|
|
dout(20) << __func__ << " blob " << *bptr << " need "
|
|
<< r2r << dendl;
|
|
if (bptr->get_blob().is_compressed()) {
|
|
// read the whole thing
|
|
if (compressed_blob_bls->empty()) {
|
|
// ensure we avoid any reallocation on subsequent blobs
|
|
compressed_blob_bls->reserve(blobs2read.size());
|
|
}
|
|
compressed_blob_bls->push_back(bufferlist());
|
|
bufferlist& bl = compressed_blob_bls->back();
|
|
auto r = bptr->get_blob().map(
|
|
0, bptr->get_blob().get_ondisk_length(),
|
|
[&](uint64_t offset, uint64_t length) {
|
|
int r = bdev->aio_read(offset, length, &bl, ioc);
|
|
if (r < 0)
|
|
return r;
|
|
return 0;
|
|
});
|
|
if (r < 0) {
|
|
derr << __func__ << " bdev-read failed: " << cpp_strerror(r) << dendl;
|
|
if (r == -EIO) {
|
|
// propagate EIO to caller
|
|
return r;
|
|
}
|
|
ceph_assert(r == 0);
|
|
}
|
|
} else {
|
|
// read the pieces
|
|
for (auto& req : r2r) {
|
|
dout(20) << __func__ << " region 0x" << std::hex
|
|
<< req.regs.front().logical_offset
|
|
<< ": 0x" << req.regs.front().blob_xoffset
|
|
<< " reading 0x" << req.r_off
|
|
<< "~" << req.r_len << std::dec
|
|
<< dendl;
|
|
|
|
// read it
|
|
auto r = bptr->get_blob().map(
|
|
req.r_off, req.r_len,
|
|
[&](uint64_t offset, uint64_t length) {
|
|
int r = bdev->aio_read(offset, length, &req.bl, ioc);
|
|
if (r < 0)
|
|
return r;
|
|
return 0;
|
|
});
|
|
if (r < 0) {
|
|
derr << __func__ << " bdev-read failed: " << cpp_strerror(r)
|
|
<< dendl;
|
|
if (r == -EIO) {
|
|
// propagate EIO to caller
|
|
return r;
|
|
}
|
|
ceph_assert(r == 0);
|
|
}
|
|
ceph_assert(req.bl.length() == req.r_len);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_generate_read_result_bl(
|
|
OnodeRef& o,
|
|
uint64_t offset,
|
|
size_t length,
|
|
ready_regions_t& ready_regions,
|
|
vector<bufferlist>& compressed_blob_bls,
|
|
blobs2read_t& blobs2read,
|
|
bool buffered,
|
|
bool* csum_error,
|
|
bufferlist& bl)
|
|
{
|
|
// enumerate and decompress desired blobs
|
|
auto p = compressed_blob_bls.begin();
|
|
blobs2read_t::iterator b2r_it = blobs2read.begin();
|
|
while (b2r_it != blobs2read.end()) {
|
|
const BlobRef& bptr = b2r_it->first;
|
|
regions2read_t& r2r = b2r_it->second;
|
|
dout(20) << __func__ << " blob " << *bptr << " need "
|
|
<< r2r << dendl;
|
|
if (bptr->get_blob().is_compressed()) {
|
|
ceph_assert(p != compressed_blob_bls.end());
|
|
bufferlist& compressed_bl = *p++;
|
|
uint32_t offset = r2r.front().regs.front().logical_offset;
|
|
uint32_t blob_offset = r2r.front().regs.front().blob_xoffset;
|
|
uint32_t length = r2r.front().regs.front().length;
|
|
if (_verify_csum(o, &bptr->get_blob(), 0, compressed_bl, offset) < 0) {
|
|
*csum_error = true;
|
|
return -EIO;
|
|
}
|
|
bufferlist raw_bl;
|
|
auto r = _decompress(compressed_bl, &raw_bl);
|
|
if (r < 0)
|
|
return r;
|
|
if (buffered) {
|
|
bufferlist region_buffer;
|
|
region_buffer.substr_of(raw_bl, blob_offset, length);
|
|
o->bc.did_read(o->c->cache, offset, std::move(region_buffer));
|
|
}
|
|
for (auto& req : r2r) {
|
|
for (auto& r : req.regs) {
|
|
ready_regions[r.logical_offset].substr_of(
|
|
raw_bl, r.blob_xoffset, r.length);
|
|
}
|
|
}
|
|
} else {
|
|
for (auto& req : r2r) {
|
|
uint64_t offset = r2r.front().regs.front().logical_offset;
|
|
if (_verify_csum(o, &bptr->get_blob(), req.r_off, req.bl, offset) < 0) {
|
|
*csum_error = true;
|
|
return -EIO;
|
|
}
|
|
|
|
// prune and keep result
|
|
for (const auto& r : req.regs) {
|
|
if (buffered) {
|
|
bufferlist region_buffer;
|
|
region_buffer.substr_of(req.bl, r.front, r.length);
|
|
// need offset before padding
|
|
o->bc.did_read(o->c->cache, r.logical_offset, std::move(region_buffer));
|
|
}
|
|
ready_regions[r.logical_offset].substr_of(req.bl, r.front, r.length);
|
|
}
|
|
}
|
|
}
|
|
++b2r_it;
|
|
}
|
|
|
|
// generate a resulting buffer
|
|
auto pr = ready_regions.begin();
|
|
auto pr_end = ready_regions.end();
|
|
uint64_t pos = 0;
|
|
while (pos < length) {
|
|
if (pr != pr_end && pr->first == pos + offset) {
|
|
dout(30) << __func__ << " assemble 0x" << std::hex << pos
|
|
<< ": data from 0x" << pr->first << "~" << pr->second.length()
|
|
<< std::dec << dendl;
|
|
pos += pr->second.length();
|
|
bl.claim_append(pr->second);
|
|
++pr;
|
|
} else {
|
|
uint64_t l = length - pos;
|
|
if (pr != pr_end) {
|
|
ceph_assert(pr->first > pos + offset);
|
|
l = pr->first - (pos + offset);
|
|
}
|
|
dout(30) << __func__ << " assemble 0x" << std::hex << pos
|
|
<< ": zeros for 0x" << (pos + offset) << "~" << l
|
|
<< std::dec << dendl;
|
|
bl.append_zero(l);
|
|
pos += l;
|
|
}
|
|
}
|
|
ceph_assert(bl.length() == length);
|
|
ceph_assert(pos == length);
|
|
ceph_assert(pr == pr_end);
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_do_read(
|
|
Collection *c,
|
|
OnodeRef& o,
|
|
uint64_t offset,
|
|
size_t length,
|
|
bufferlist& bl,
|
|
uint32_t op_flags,
|
|
uint64_t retry_count)
|
|
{
|
|
FUNCTRACE(cct);
|
|
int r = 0;
|
|
int read_cache_policy = 0; // do not bypass clean or dirty cache
|
|
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< " size 0x" << o->onode.size << " (" << std::dec
|
|
<< o->onode.size << ")" << dendl;
|
|
bl.clear();
|
|
|
|
if (offset >= o->onode.size) {
|
|
return r;
|
|
}
|
|
|
|
// generally, don't buffer anything, unless the client explicitly requests
|
|
// it.
|
|
bool buffered = false;
|
|
if (op_flags & CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) {
|
|
dout(20) << __func__ << " will do buffered read" << dendl;
|
|
buffered = true;
|
|
} else if (cct->_conf->bluestore_default_buffered_read &&
|
|
(op_flags & (CEPH_OSD_OP_FLAG_FADVISE_DONTNEED |
|
|
CEPH_OSD_OP_FLAG_FADVISE_NOCACHE)) == 0) {
|
|
dout(20) << __func__ << " defaulting to buffered read" << dendl;
|
|
buffered = true;
|
|
}
|
|
|
|
if (offset + length > o->onode.size) {
|
|
length = o->onode.size - offset;
|
|
}
|
|
|
|
auto start = mono_clock::now();
|
|
o->extent_map.fault_range(db, offset, length);
|
|
log_latency(__func__,
|
|
l_bluestore_read_onode_meta_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age,
|
|
"", l_bluestore_slow_read_onode_meta_count);
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
// for deep-scrub, we only read dirty cache and bypass clean cache in
|
|
// order to read underlying block device in case there are silent disk errors.
|
|
if (op_flags & CEPH_OSD_OP_FLAG_BYPASS_CLEAN_CACHE) {
|
|
dout(20) << __func__ << " will bypass cache and do direct read" << dendl;
|
|
read_cache_policy = BufferSpace::BYPASS_CLEAN_CACHE;
|
|
}
|
|
|
|
// build blob-wise list to of stuff read (that isn't cached)
|
|
ready_regions_t ready_regions;
|
|
blobs2read_t blobs2read;
|
|
_read_cache(o, offset, length, read_cache_policy, ready_regions, blobs2read);
|
|
|
|
|
|
// read raw blob data.
|
|
start = mono_clock::now(); // for the sake of simplicity
|
|
// measure the whole block below.
|
|
// The error isn't that much...
|
|
vector<bufferlist> compressed_blob_bls;
|
|
IOContext ioc(cct, NULL, !cct->_conf->bluestore_fail_eio);
|
|
r = _prepare_read_ioc(blobs2read, &compressed_blob_bls, &ioc);
|
|
// we always issue aio for reading, so errors other than EIO are not allowed
|
|
if (r < 0)
|
|
return r;
|
|
|
|
int64_t num_ios = blobs2read.size();
|
|
if (ioc.has_pending_aios()) {
|
|
num_ios = ioc.get_num_ios();
|
|
bdev->aio_submit(&ioc);
|
|
dout(20) << __func__ << " waiting for aio" << dendl;
|
|
ioc.aio_wait();
|
|
r = ioc.get_return_value();
|
|
if (r < 0) {
|
|
ceph_assert(r == -EIO); // no other errors allowed
|
|
return -EIO;
|
|
}
|
|
}
|
|
log_latency_fn(__func__,
|
|
l_bluestore_read_wait_aio_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age,
|
|
[&](auto lat) { return ", num_ios = " + stringify(num_ios); },
|
|
l_bluestore_slow_read_wait_aio_count
|
|
);
|
|
|
|
bool csum_error = false;
|
|
r = _generate_read_result_bl(o, offset, length, ready_regions,
|
|
compressed_blob_bls, blobs2read,
|
|
buffered && !ioc.skip_cache(),
|
|
&csum_error, bl);
|
|
if (csum_error) {
|
|
// Handles spurious read errors caused by a kernel bug.
|
|
// We sometimes get all-zero pages as a result of the read under
|
|
// high memory pressure. Retrying the failing read succeeds in most
|
|
// cases.
|
|
// See also: http://tracker.ceph.com/issues/22464
|
|
if (retry_count >= cct->_conf->bluestore_retry_disk_reads) {
|
|
return -EIO;
|
|
}
|
|
return _do_read(c, o, offset, length, bl, op_flags, retry_count + 1);
|
|
}
|
|
r = bl.length();
|
|
if (retry_count) {
|
|
logger->inc(l_bluestore_reads_with_retries);
|
|
dout(5) << __func__ << " read at 0x" << std::hex << offset << "~" << length
|
|
<< " failed " << std::dec << retry_count << " times before succeeding" << dendl;
|
|
stringstream s;
|
|
s << " reads with retries: " << logger->get(l_bluestore_reads_with_retries);
|
|
_set_spurious_read_errors_alert(s.str());
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void inline BlueStore::_do_read_and_pad(
|
|
Collection* c,
|
|
OnodeRef& o,
|
|
uint32_t offset,
|
|
uint32_t length,
|
|
ceph::buffer::list& bl)
|
|
{
|
|
int r = _do_read(c, o, offset, length, bl, 0);
|
|
ceph_assert(r >= 0 && r <= (int)length);
|
|
size_t zlen = length - r;
|
|
if (zlen > 0) {
|
|
bl.append_zero(zlen);
|
|
logger->inc(l_bluestore_write_pad_bytes, zlen);
|
|
}
|
|
}
|
|
|
|
int BlueStore::_verify_csum(OnodeRef& o,
|
|
const bluestore_blob_t* blob, uint64_t blob_xoffset,
|
|
const bufferlist& bl,
|
|
uint64_t logical_offset)
|
|
{
|
|
int bad;
|
|
uint64_t bad_csum;
|
|
auto start = mono_clock::now();
|
|
int r = blob->verify_csum(blob_xoffset, bl, &bad, &bad_csum);
|
|
if (cct->_conf->bluestore_debug_inject_csum_err_probability > 0 &&
|
|
(rand() % 10000) < cct->_conf->bluestore_debug_inject_csum_err_probability * 10000.0) {
|
|
derr << __func__ << " injecting bluestore checksum verifcation error" << dendl;
|
|
bad = blob_xoffset;
|
|
r = -1;
|
|
bad_csum = 0xDEADBEEF;
|
|
}
|
|
if (r < 0) {
|
|
if (r == -1) {
|
|
PExtentVector pex;
|
|
blob->map(
|
|
bad,
|
|
blob->get_csum_chunk_size(),
|
|
[&](uint64_t offset, uint64_t length) {
|
|
pex.emplace_back(bluestore_pextent_t(offset, length));
|
|
return 0;
|
|
});
|
|
derr << __func__ << " bad "
|
|
<< Checksummer::get_csum_type_string(blob->csum_type)
|
|
<< "/0x" << std::hex << blob->get_csum_chunk_size()
|
|
<< " checksum at blob offset 0x" << bad
|
|
<< ", got 0x" << bad_csum << ", expected 0x"
|
|
<< blob->get_csum_item(bad / blob->get_csum_chunk_size()) << std::dec
|
|
<< ", device location " << pex
|
|
<< ", logical extent 0x" << std::hex
|
|
<< (logical_offset + bad - blob_xoffset) << "~"
|
|
<< blob->get_csum_chunk_size() << std::dec
|
|
<< ", object " << o->oid
|
|
<< dendl;
|
|
} else {
|
|
derr << __func__ << " failed with exit code: " << cpp_strerror(r) << dendl;
|
|
}
|
|
}
|
|
log_latency(__func__,
|
|
l_bluestore_csum_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
if (cct->_conf->bluestore_ignore_data_csum) {
|
|
return 0;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_decompress(bufferlist& source, bufferlist* result)
|
|
{
|
|
int r = 0;
|
|
auto start = mono_clock::now();
|
|
auto i = source.cbegin();
|
|
bluestore_compression_header_t chdr;
|
|
decode(chdr, i);
|
|
int alg = int(chdr.type);
|
|
CompressorRef cp =
|
|
alg < int(compressors.size()) ? compressors[alg] : CompressorRef();
|
|
if (!cp.get()) {
|
|
if (alg != Compressor::COMP_ALG_NONE) {
|
|
// if compressor isn't available - error, because cannot return
|
|
// decompressed data?
|
|
const char* alg_name = Compressor::get_comp_alg_name(alg);
|
|
derr << __func__ << " can't locate compressor plugin for " << alg_name
|
|
<< dendl;
|
|
_set_compression_alert(false, alg_name);
|
|
r = -EIO;
|
|
} else {
|
|
dout(0) << __func__
|
|
<< " [warn] Compressed Blob has got no alg in the header "
|
|
<< dendl;
|
|
i.copy_all(*result);
|
|
}
|
|
} else {
|
|
ceph_assert((int)cp->get_type() == alg);
|
|
r = cp->decompress(i, chdr.length, *result, chdr.compressor_message);
|
|
if (r < 0) {
|
|
derr << __func__ << " decompression failed with exit code " << r << dendl;
|
|
r = -EIO;
|
|
}
|
|
}
|
|
log_latency(__func__,
|
|
l_bluestore_decompress_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
return r;
|
|
}
|
|
|
|
// this stores fiemap into interval_set, other variations
|
|
// use it internally
|
|
int BlueStore::_fiemap(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
uint64_t offset,
|
|
size_t length,
|
|
interval_set<uint64_t>& destset)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
return -ENOENT;
|
|
}
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< " size 0x" << o->onode.size << std::dec << dendl;
|
|
|
|
boost::intrusive::set<Extent>::iterator ep, eend;
|
|
if (offset >= o->onode.size)
|
|
goto out;
|
|
|
|
if (offset + length > o->onode.size) {
|
|
length = o->onode.size - offset;
|
|
}
|
|
|
|
o->extent_map.fault_range(db, offset, length);
|
|
eend = o->extent_map.extent_map.end();
|
|
ep = o->extent_map.seek_lextent(offset);
|
|
while (length > 0) {
|
|
dout(20) << __func__ << " offset " << offset << dendl;
|
|
if (ep != eend && ep->logical_offset + ep->length <= offset) {
|
|
++ep;
|
|
continue;
|
|
}
|
|
|
|
uint64_t x_len = length;
|
|
if (ep != eend && ep->logical_offset <= offset) {
|
|
uint64_t x_off = offset - ep->logical_offset;
|
|
x_len = std::min(x_len, ep->length - x_off);
|
|
dout(30) << __func__ << " lextent 0x" << std::hex << offset << "~"
|
|
<< x_len << std::dec << " blob " << ep->blob << dendl;
|
|
destset.insert(offset, x_len);
|
|
length -= x_len;
|
|
offset += x_len;
|
|
if (x_off + x_len == ep->length)
|
|
++ep;
|
|
continue;
|
|
}
|
|
if (ep != eend &&
|
|
ep->logical_offset > offset &&
|
|
ep->logical_offset - offset < x_len) {
|
|
x_len = ep->logical_offset - offset;
|
|
}
|
|
offset += x_len;
|
|
length -= x_len;
|
|
}
|
|
}
|
|
|
|
out:
|
|
dout(20) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< " size = 0x(" << destset << ")" << std::dec << dendl;
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::fiemap(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
uint64_t offset,
|
|
size_t length,
|
|
bufferlist& bl)
|
|
{
|
|
interval_set<uint64_t> m;
|
|
int r = _fiemap(c_, oid, offset, length, m);
|
|
if (r >= 0) {
|
|
encode(m, bl);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::fiemap(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
uint64_t offset,
|
|
size_t length,
|
|
map<uint64_t, uint64_t>& destmap)
|
|
{
|
|
interval_set<uint64_t> m;
|
|
int r = _fiemap(c_, oid, offset, length, m);
|
|
if (r >= 0) {
|
|
destmap = std::move(m).detach();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::readv(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
interval_set<uint64_t>& m,
|
|
bufferlist& bl,
|
|
uint32_t op_flags)
|
|
{
|
|
auto start = mono_clock::now();
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
const coll_t &cid = c->get_cid();
|
|
dout(15) << __func__ << " " << cid << " " << oid
|
|
<< " fiemap " << m
|
|
<< dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
|
|
bl.clear();
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
auto start1 = mono_clock::now();
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
log_latency("get_onode@read",
|
|
l_bluestore_read_onode_meta_lat,
|
|
mono_clock::now() - start1,
|
|
cct->_conf->bluestore_log_op_age);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
if (m.empty()) {
|
|
r = 0;
|
|
goto out;
|
|
}
|
|
|
|
r = _do_readv(c, o, m, bl, op_flags);
|
|
if (r == -EIO) {
|
|
logger->inc(l_bluestore_read_eio);
|
|
}
|
|
}
|
|
|
|
out:
|
|
if (r >= 0 && _debug_data_eio(oid)) {
|
|
r = -EIO;
|
|
derr << __func__ << " " << c->cid << " " << oid << " INJECT EIO" << dendl;
|
|
} else if (oid.hobj.pool > 0 && /* FIXME, see #23029 */
|
|
cct->_conf->bluestore_debug_random_read_err &&
|
|
(rand() % (int)(cct->_conf->bluestore_debug_random_read_err *
|
|
100.0)) == 0) {
|
|
dout(0) << __func__ << ": inject random EIO" << dendl;
|
|
r = -EIO;
|
|
}
|
|
dout(10) << __func__ << " " << cid << " " << oid
|
|
<< " fiemap " << m << std::dec
|
|
<< " = " << r << dendl;
|
|
log_latency(__func__,
|
|
l_bluestore_read_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_do_readv(
|
|
Collection *c,
|
|
OnodeRef& o,
|
|
const interval_set<uint64_t>& m,
|
|
bufferlist& bl,
|
|
uint32_t op_flags,
|
|
uint64_t retry_count)
|
|
{
|
|
FUNCTRACE(cct);
|
|
int r = 0;
|
|
int read_cache_policy = 0; // do not bypass clean or dirty cache
|
|
|
|
dout(20) << __func__ << " fiemap " << m << std::hex
|
|
<< " size 0x" << o->onode.size << " (" << std::dec
|
|
<< o->onode.size << ")" << dendl;
|
|
|
|
// generally, don't buffer anything, unless the client explicitly requests
|
|
// it.
|
|
bool buffered = false;
|
|
if (op_flags & CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) {
|
|
dout(20) << __func__ << " will do buffered read" << dendl;
|
|
buffered = true;
|
|
} else if (cct->_conf->bluestore_default_buffered_read &&
|
|
(op_flags & (CEPH_OSD_OP_FLAG_FADVISE_DONTNEED |
|
|
CEPH_OSD_OP_FLAG_FADVISE_NOCACHE)) == 0) {
|
|
dout(20) << __func__ << " defaulting to buffered read" << dendl;
|
|
buffered = true;
|
|
}
|
|
// this method must be idempotent since we may call it several times
|
|
// before we finally read the expected result.
|
|
bl.clear();
|
|
|
|
// call fiemap first!
|
|
ceph_assert(m.range_start() <= o->onode.size);
|
|
ceph_assert(m.range_end() <= o->onode.size);
|
|
auto start = mono_clock::now();
|
|
o->extent_map.fault_range(db, m.range_start(), m.range_end() - m.range_start());
|
|
log_latency(__func__,
|
|
l_bluestore_read_onode_meta_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age,
|
|
"", l_bluestore_slow_read_onode_meta_count);
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
IOContext ioc(cct, NULL, !cct->_conf->bluestore_fail_eio);
|
|
vector<std::tuple<ready_regions_t, vector<bufferlist>, blobs2read_t>> raw_results;
|
|
raw_results.reserve(m.num_intervals());
|
|
int i = 0;
|
|
for (auto p = m.begin(); p != m.end(); p++, i++) {
|
|
raw_results.push_back({});
|
|
_read_cache(o, p.get_start(), p.get_len(), read_cache_policy,
|
|
std::get<0>(raw_results[i]), std::get<2>(raw_results[i]));
|
|
r = _prepare_read_ioc(std::get<2>(raw_results[i]), &std::get<1>(raw_results[i]), &ioc);
|
|
// we always issue aio for reading, so errors other than EIO are not allowed
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
auto num_ios = m.size();
|
|
if (ioc.has_pending_aios()) {
|
|
num_ios = ioc.get_num_ios();
|
|
bdev->aio_submit(&ioc);
|
|
dout(20) << __func__ << " waiting for aio" << dendl;
|
|
ioc.aio_wait();
|
|
r = ioc.get_return_value();
|
|
if (r < 0) {
|
|
ceph_assert(r == -EIO); // no other errors allowed
|
|
return -EIO;
|
|
}
|
|
}
|
|
log_latency_fn(__func__,
|
|
l_bluestore_read_wait_aio_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age,
|
|
[&](auto lat) { return ", num_ios = " + stringify(num_ios); },
|
|
l_bluestore_slow_read_wait_aio_count
|
|
);
|
|
|
|
ceph_assert(raw_results.size() == (size_t)m.num_intervals());
|
|
i = 0;
|
|
for (auto p = m.begin(); p != m.end(); p++, i++) {
|
|
bool csum_error = false;
|
|
bufferlist t;
|
|
r = _generate_read_result_bl(o, p.get_start(), p.get_len(),
|
|
std::get<0>(raw_results[i]),
|
|
std::get<1>(raw_results[i]),
|
|
std::get<2>(raw_results[i]),
|
|
buffered, &csum_error, t);
|
|
if (csum_error) {
|
|
// Handles spurious read errors caused by a kernel bug.
|
|
// We sometimes get all-zero pages as a result of the read under
|
|
// high memory pressure. Retrying the failing read succeeds in most
|
|
// cases.
|
|
// See also: http://tracker.ceph.com/issues/22464
|
|
if (retry_count >= cct->_conf->bluestore_retry_disk_reads) {
|
|
return -EIO;
|
|
}
|
|
return _do_readv(c, o, m, bl, op_flags, retry_count + 1);
|
|
}
|
|
bl.claim_append(t);
|
|
}
|
|
if (retry_count) {
|
|
logger->inc(l_bluestore_reads_with_retries);
|
|
dout(5) << __func__ << " read fiemap " << m
|
|
<< " failed " << retry_count << " times before succeeding"
|
|
<< dendl;
|
|
}
|
|
return bl.length();
|
|
}
|
|
|
|
int BlueStore::dump_onode(CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
const string& section_name,
|
|
Formatter *f)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(15) << __func__ << " " << c->cid << " " << oid << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
// FIXME minor: actually the next line isn't enough to
|
|
// load shared blobs. Leaving as is for now..
|
|
//
|
|
o->extent_map.fault_range(db, 0, OBJECT_MAX_SIZE);
|
|
|
|
_dump_onode<0>(cct, *o);
|
|
f->open_object_section(section_name.c_str());
|
|
o->dump(f);
|
|
f->close_section();
|
|
r = 0;
|
|
}
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << oid
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::getattr(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
const char *name,
|
|
bufferptr& value)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(15) << __func__ << " " << c->cid << " " << oid << " " << name << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
mempool::bluestore_cache_meta::string k(name);
|
|
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
if (!o->onode.attrs.count(k)) {
|
|
r = -ENODATA;
|
|
goto out;
|
|
}
|
|
value = o->onode.attrs[k];
|
|
r = 0;
|
|
}
|
|
out:
|
|
if (r == 0 && _debug_mdata_eio(oid)) {
|
|
r = -EIO;
|
|
derr << __func__ << " " << c->cid << " " << oid << " INJECT EIO" << dendl;
|
|
}
|
|
dout(10) << __func__ << " " << c->cid << " " << oid << " " << name
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::getattrs(
|
|
CollectionHandle &c_,
|
|
const ghobject_t& oid,
|
|
map<string,bufferptr,less<>>& aset)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(15) << __func__ << " " << c->cid << " " << oid << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
aset.clear();
|
|
for (auto& i : o->onode.attrs) {
|
|
aset.emplace(i.first.c_str(), i.second);
|
|
}
|
|
r = 0;
|
|
}
|
|
|
|
out:
|
|
if (r == 0 && _debug_mdata_eio(oid)) {
|
|
r = -EIO;
|
|
derr << __func__ << " " << c->cid << " " << oid << " INJECT EIO" << dendl;
|
|
}
|
|
dout(10) << __func__ << " " << c->cid << " " << oid
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::list_collections(vector<coll_t>& ls)
|
|
{
|
|
std::shared_lock l(coll_lock);
|
|
ls.reserve(coll_map.size());
|
|
for (auto p = coll_map.begin(); p != coll_map.end(); ++p)
|
|
ls.push_back(p->first);
|
|
return 0;
|
|
}
|
|
|
|
bool BlueStore::collection_exists(const coll_t& c)
|
|
{
|
|
std::shared_lock l(coll_lock);
|
|
return coll_map.count(c);
|
|
}
|
|
|
|
int BlueStore::collection_empty(CollectionHandle& ch, bool *empty)
|
|
{
|
|
dout(15) << __func__ << " " << ch->cid << dendl;
|
|
vector<ghobject_t> ls;
|
|
ghobject_t next;
|
|
int r = collection_list(ch, ghobject_t(), ghobject_t::get_max(), 1,
|
|
&ls, &next);
|
|
if (r < 0) {
|
|
derr << __func__ << " collection_list returned: " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
*empty = ls.empty();
|
|
dout(10) << __func__ << " " << ch->cid << " = " << (int)(*empty) << dendl;
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::collection_bits(CollectionHandle& ch)
|
|
{
|
|
dout(15) << __func__ << " " << ch->cid << dendl;
|
|
Collection *c = static_cast<Collection*>(ch.get());
|
|
std::shared_lock l(c->lock);
|
|
dout(10) << __func__ << " " << ch->cid << " = " << c->cnode.bits << dendl;
|
|
return c->cnode.bits;
|
|
}
|
|
|
|
int BlueStore::collection_list(
|
|
CollectionHandle &c_, const ghobject_t& start, const ghobject_t& end, int max,
|
|
vector<ghobject_t> *ls, ghobject_t *pnext)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
c->flush();
|
|
dout(15) << __func__ << " " << c->cid
|
|
<< " start " << start << " end " << end << " max " << max << dendl;
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
r = _collection_list(c, start, end, max, false, ls, pnext);
|
|
}
|
|
|
|
dout(10) << __func__ << " " << c->cid
|
|
<< " start " << start << " end " << end << " max " << max
|
|
<< " = " << r << ", ls.size() = " << ls->size()
|
|
<< ", next = " << (pnext ? *pnext : ghobject_t()) << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::collection_list_legacy(
|
|
CollectionHandle &c_, const ghobject_t& start, const ghobject_t& end, int max,
|
|
vector<ghobject_t> *ls, ghobject_t *pnext)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
c->flush();
|
|
dout(15) << __func__ << " " << c->cid
|
|
<< " start " << start << " end " << end << " max " << max << dendl;
|
|
int r;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
r = _collection_list(c, start, end, max, true, ls, pnext);
|
|
}
|
|
|
|
dout(10) << __func__ << " " << c->cid
|
|
<< " start " << start << " end " << end << " max " << max
|
|
<< " = " << r << ", ls.size() = " << ls->size()
|
|
<< ", next = " << (pnext ? *pnext : ghobject_t()) << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_collection_list(
|
|
Collection *c, const ghobject_t& start, const ghobject_t& end, int max,
|
|
bool legacy, vector<ghobject_t> *ls, ghobject_t *pnext)
|
|
{
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
|
|
ghobject_t static_next;
|
|
std::unique_ptr<CollectionListIterator> it;
|
|
ghobject_t coll_range_temp_start, coll_range_temp_end;
|
|
ghobject_t coll_range_start, coll_range_end;
|
|
std::vector<std::tuple<ghobject_t, ghobject_t>> ranges;
|
|
|
|
if (!pnext)
|
|
pnext = &static_next;
|
|
|
|
auto log_latency = make_scope_guard(
|
|
[&, start_time = mono_clock::now(), func_name = __func__] {
|
|
log_latency_fn(
|
|
func_name,
|
|
l_bluestore_clist_lat,
|
|
mono_clock::now() - start_time,
|
|
cct->_conf->bluestore_log_collection_list_age,
|
|
[&](const ceph::timespan& lat) {
|
|
ostringstream ostr;
|
|
ostr << ", lat = " << timespan_str(lat)
|
|
<< " cid =" << c->cid
|
|
<< " start " << start << " end " << end
|
|
<< " max " << max;
|
|
return ostr.str();
|
|
});
|
|
});
|
|
|
|
if (start.is_max() || start.hobj.is_max()) {
|
|
*pnext = ghobject_t::get_max();
|
|
return 0;
|
|
}
|
|
get_coll_range(c->cid, c->cnode.bits, &coll_range_temp_start,
|
|
&coll_range_temp_end, &coll_range_start, &coll_range_end, legacy);
|
|
dout(20) << __func__
|
|
<< " range " << coll_range_temp_start
|
|
<< " to " << coll_range_temp_end
|
|
<< " and " << coll_range_start
|
|
<< " to " << coll_range_end
|
|
<< " start " << start << dendl;
|
|
|
|
// if specified start is not specifically in the pg normal range, we should start with temp iter
|
|
if ((start == ghobject_t() ||
|
|
start.hobj == hobject_t() ||
|
|
start == c->cid.get_min_hobj() ||
|
|
start.hobj.is_temp())
|
|
&& coll_range_temp_start != coll_range_temp_end) {
|
|
ranges.push_back(std::tuple(std::move(coll_range_temp_start), std::move(coll_range_temp_end)));
|
|
}
|
|
// if end param is in temp section, then we do not need to proceed to the normal section
|
|
if (!end.hobj.is_temp()) {
|
|
ranges.push_back(std::tuple(std::move(coll_range_start), std::move(coll_range_end)));
|
|
}
|
|
|
|
for (const auto & [cur_range_start, cur_range_end] : ranges) {
|
|
dout(30) << __func__ << " cur_range " << cur_range_start << " to " << cur_range_end << dendl;
|
|
|
|
const ghobject_t low = start > cur_range_start ? start : cur_range_start;
|
|
const ghobject_t high = end < cur_range_end ? end : cur_range_end;
|
|
if (low >= high) {
|
|
continue;
|
|
}
|
|
|
|
std::string kv_low_key, kv_high_key;
|
|
_key_encode_prefix(low, &kv_low_key);
|
|
_key_encode_prefix(high, &kv_high_key);
|
|
kv_high_key.push_back('\xff');
|
|
dout(30) << __func__ << " kv_low_key: " << kv_low_key << " kv_high_key: " << kv_high_key << dendl;
|
|
const KeyValueDB::IteratorBounds bounds = KeyValueDB::IteratorBounds{std::move(kv_low_key), std::move(kv_high_key)};
|
|
if (legacy) {
|
|
it = std::make_unique<SimpleCollectionListIterator>(
|
|
cct, db->get_iterator(PREFIX_OBJ, 0, std::move(bounds)));
|
|
} else {
|
|
it = std::make_unique<SortedCollectionListIterator>(
|
|
db->get_iterator(PREFIX_OBJ, 0, std::move(bounds)));
|
|
}
|
|
it->lower_bound(low);
|
|
while (it->valid()) {
|
|
if (it->oid() < low) {
|
|
it->next();
|
|
continue;
|
|
}
|
|
if (it->oid() > high) {
|
|
break;
|
|
}
|
|
if (ls->size() >= (unsigned)max || it->oid() == high) {
|
|
*pnext = it->oid();
|
|
return 0;
|
|
}
|
|
dout(20) << __func__ << " oid " << it->oid() << dendl;
|
|
ls->push_back(it->oid());
|
|
it->next();
|
|
}
|
|
}
|
|
*pnext = ghobject_t::get_max();
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::omap_get(
|
|
CollectionHandle &c_, ///< [in] Collection containing oid
|
|
const ghobject_t &oid, ///< [in] Object containing omap
|
|
bufferlist *header, ///< [out] omap header
|
|
map<string, bufferlist> *out /// < [out] Key to value map
|
|
)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
return _omap_get(c, oid, header, out);
|
|
}
|
|
|
|
int BlueStore::_omap_get(
|
|
Collection *c, ///< [in] Collection containing oid
|
|
const ghobject_t &oid, ///< [in] Object containing omap
|
|
bufferlist *header, ///< [out] omap header
|
|
map<string, bufferlist> *out /// < [out] Key to value map
|
|
)
|
|
{
|
|
dout(15) << __func__ << " " << c->get_cid() << " oid " << oid << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
std::shared_lock l(c->lock);
|
|
int r = 0;
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
r = _onode_omap_get(o, header, out);
|
|
out:
|
|
dout(10) << __func__ << " " << c->get_cid() << " oid " << oid << " = " << r
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_onode_omap_get(
|
|
const OnodeRef &o, ///< [in] Object containing omap
|
|
bufferlist *header, ///< [out] omap header
|
|
map<string, bufferlist> *out /// < [out] Key to value map
|
|
)
|
|
{
|
|
int r = 0;
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
if (!o->onode.has_omap())
|
|
goto out;
|
|
o->flush();
|
|
{
|
|
const string& prefix = o->get_omap_prefix();
|
|
string head, tail;
|
|
o->get_omap_header(&head);
|
|
o->get_omap_tail(&tail);
|
|
KeyValueDB::Iterator it = db->get_iterator(prefix, 0, KeyValueDB::IteratorBounds{head, tail});
|
|
it->lower_bound(head);
|
|
while (it->valid()) {
|
|
if (it->key() == head) {
|
|
dout(30) << __func__ << " got header" << dendl;
|
|
*header = it->value();
|
|
} else if (it->key() >= tail) {
|
|
dout(30) << __func__ << " reached tail" << dendl;
|
|
break;
|
|
} else {
|
|
string user_key;
|
|
o->decode_omap_key(it->key(), &user_key);
|
|
dout(20) << __func__ << " got " << pretty_binary_string(it->key())
|
|
<< " -> " << user_key << dendl;
|
|
(*out)[user_key] = it->value();
|
|
}
|
|
it->next();
|
|
}
|
|
}
|
|
out:
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::omap_get_header(
|
|
CollectionHandle &c_, ///< [in] Collection containing oid
|
|
const ghobject_t &oid, ///< [in] Object containing omap
|
|
bufferlist *header, ///< [out] omap header
|
|
bool allow_eio ///< [in] don't assert on eio
|
|
)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(15) << __func__ << " " << c->get_cid() << " oid " << oid << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
std::shared_lock l(c->lock);
|
|
int r = 0;
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
if (!o->onode.has_omap())
|
|
goto out;
|
|
o->flush();
|
|
{
|
|
string head;
|
|
o->get_omap_header(&head);
|
|
if (db->get(o->get_omap_prefix(), head, header) >= 0) {
|
|
dout(30) << __func__ << " got header" << dendl;
|
|
} else {
|
|
dout(30) << __func__ << " no header" << dendl;
|
|
}
|
|
}
|
|
out:
|
|
dout(10) << __func__ << " " << c->get_cid() << " oid " << oid << " = " << r
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::omap_get_values(
|
|
CollectionHandle &c_, ///< [in] Collection containing oid
|
|
const ghobject_t &oid, ///< [in] Object containing omap
|
|
const set<string> &keys, ///< [in] Keys to get
|
|
map<string, bufferlist> *out ///< [out] Returned keys and values
|
|
)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(15) << __func__ << " " << c->get_cid() << " oid " << oid << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
std::shared_lock l(c->lock);
|
|
auto start1 = mono_clock::now();
|
|
int r = 0;
|
|
string final_key;
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
if (!o->onode.has_omap()) {
|
|
goto out;
|
|
}
|
|
o->flush();
|
|
{
|
|
const string& prefix = o->get_omap_prefix();
|
|
o->get_omap_key(string(), &final_key);
|
|
size_t base_key_len = final_key.size();
|
|
for (set<string>::const_iterator p = keys.begin(); p != keys.end(); ++p) {
|
|
final_key.resize(base_key_len); // keep prefix
|
|
final_key += *p;
|
|
bufferlist val;
|
|
if (db->get(prefix, final_key, &val) >= 0) {
|
|
dout(30) << __func__ << " got " << pretty_binary_string(final_key)
|
|
<< " -> " << *p << dendl;
|
|
out->insert(make_pair(*p, val));
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
c->store->log_latency(
|
|
__func__,
|
|
l_bluestore_omap_get_values_lat,
|
|
mono_clock::now() - start1,
|
|
c->store->cct->_conf->bluestore_log_omap_iterator_age);
|
|
|
|
dout(10) << __func__ << " " << c->get_cid() << " oid " << oid << " = " << r
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::omap_check_keys(
|
|
CollectionHandle &c_, ///< [in] Collection containing oid
|
|
const ghobject_t &oid, ///< [in] Object containing omap
|
|
const set<string> &keys, ///< [in] Keys to check
|
|
set<string> *out ///< [out] Subset of keys defined on oid
|
|
)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(15) << __func__ << " " << c->get_cid() << " oid " << oid << dendl;
|
|
if (!c->exists)
|
|
return -ENOENT;
|
|
std::shared_lock l(c->lock);
|
|
int r = 0;
|
|
string final_key;
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
if (!o->onode.has_omap()) {
|
|
goto out;
|
|
}
|
|
o->flush();
|
|
{
|
|
const string& prefix = o->get_omap_prefix();
|
|
o->get_omap_key(string(), &final_key);
|
|
size_t base_key_len = final_key.size();
|
|
for (set<string>::const_iterator p = keys.begin(); p != keys.end(); ++p) {
|
|
final_key.resize(base_key_len); // keep prefix
|
|
final_key += *p;
|
|
bufferlist val;
|
|
if (db->get(prefix, final_key, &val) >= 0) {
|
|
dout(30) << __func__ << " have " << pretty_binary_string(final_key)
|
|
<< " -> " << *p << dendl;
|
|
out->insert(*p);
|
|
} else {
|
|
dout(30) << __func__ << " miss " << pretty_binary_string(final_key)
|
|
<< " -> " << *p << dendl;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
dout(10) << __func__ << " " << c->get_cid() << " oid " << oid << " = " << r
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::omap_iterate(
|
|
CollectionHandle &c_, ///< [in] collection
|
|
const ghobject_t &oid, ///< [in] object
|
|
ObjectStore::omap_iter_seek_t start_from, ///< [in] where the iterator should point to at the beginning
|
|
std::function<omap_iter_ret_t(std::string_view, std::string_view)> f
|
|
)
|
|
{
|
|
Collection *c = static_cast<Collection *>(c_.get());
|
|
dout(10) << __func__ << " " << c->get_cid() << " " << oid << dendl;
|
|
if (!c->exists) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
KeyValueDB::Iterator it;
|
|
std::string tail;
|
|
std::string seek_key;
|
|
std::string_view::size_type userkey_offset_in_dbkey;
|
|
{
|
|
std::shared_lock l(c->lock);
|
|
|
|
OnodeRef o = c->get_onode(oid, false);
|
|
if (!o || !o->exists) {
|
|
dout(10) << __func__ << " " << oid << "doesn't exist" <<dendl;
|
|
return -ENOENT;
|
|
}
|
|
o->flush();
|
|
dout(10) << __func__ << " has_omap = " << (int)o->onode.has_omap() <<dendl;
|
|
if (!o->onode.has_omap()) {
|
|
return 0; // nothing to do
|
|
}
|
|
|
|
// acquire data depedencies for seek & iterate
|
|
o->get_omap_key(start_from.seek_position, &seek_key);
|
|
o->get_omap_tail(&tail);
|
|
userkey_offset_in_dbkey = o->calc_userkey_offset_in_omap_key();
|
|
|
|
// acquire the iterator
|
|
{
|
|
auto bounds = KeyValueDB::IteratorBounds();
|
|
std::string lower_bound, upper_bound;
|
|
o->get_omap_key(string(), &lower_bound);
|
|
o->get_omap_tail(&upper_bound);
|
|
bounds.lower_bound = std::move(lower_bound);
|
|
bounds.upper_bound = std::move(upper_bound);
|
|
it = db->get_iterator(o->get_omap_prefix(), 0, std::move(bounds));
|
|
}
|
|
}
|
|
|
|
// seek the iterator
|
|
{
|
|
auto start = ceph::mono_clock::now();
|
|
if (start_from.seek_type == omap_iter_seek_t::LOWER_BOUND) {
|
|
it->lower_bound(seek_key);
|
|
c->store->log_latency(
|
|
__func__,
|
|
l_bluestore_omap_lower_bound_lat,
|
|
ceph::mono_clock::now() - start,
|
|
c->store->cct->_conf->bluestore_log_omap_iterator_age);
|
|
} else {
|
|
it->upper_bound(seek_key);
|
|
c->store->log_latency(
|
|
__func__,
|
|
l_bluestore_omap_upper_bound_lat,
|
|
ceph::mono_clock::now() - start,
|
|
c->store->cct->_conf->bluestore_log_omap_iterator_age);
|
|
}
|
|
}
|
|
|
|
// iterate!
|
|
bool more = false;
|
|
ceph::timespan next_lat_acc{0};
|
|
while (it->valid()) {
|
|
const auto& db_key = it->raw_key_as_sv().second;
|
|
if (db_key >= tail) {
|
|
break;
|
|
}
|
|
std::string_view user_key = db_key.substr(userkey_offset_in_dbkey);
|
|
omap_iter_ret_t ret = f(user_key, it->value_as_sv());
|
|
if (ret == omap_iter_ret_t::STOP) {
|
|
more = true;
|
|
break;
|
|
} else if (ret == omap_iter_ret_t::NEXT) {
|
|
ceph::time_guard<ceph::mono_clock> measure_next{next_lat_acc};
|
|
it->next();
|
|
} else {
|
|
ceph_abort();
|
|
}
|
|
}
|
|
c->store->log_latency(
|
|
__func__,
|
|
l_bluestore_omap_next_lat,
|
|
next_lat_acc,
|
|
c->store->cct->_conf->bluestore_log_omap_iterator_age);
|
|
return more;
|
|
}
|
|
|
|
// -----------------
|
|
// write helpers
|
|
|
|
uint64_t BlueStore::_get_ondisk_reserved() const {
|
|
static_assert(BDEV_FIRST_LABEL_POSITION == 0);
|
|
ceph_assert(min_alloc_size);
|
|
uint64_t size = p2roundup(SUPER_RESERVED, min_alloc_size);
|
|
return size;
|
|
}
|
|
|
|
void BlueStore::_prepare_ondisk_format_super(KeyValueDB::Transaction& t)
|
|
{
|
|
dout(10) << __func__ << " ondisk_format " << ondisk_format
|
|
<< " min_compat_ondisk_format " << min_compat_ondisk_format
|
|
<< dendl;
|
|
ceph_assert(ondisk_format == latest_ondisk_format);
|
|
{
|
|
bufferlist bl;
|
|
encode(ondisk_format, bl);
|
|
t->set(PREFIX_SUPER, "ondisk_format", bl);
|
|
}
|
|
{
|
|
bufferlist bl;
|
|
encode(min_compat_ondisk_format, bl);
|
|
t->set(PREFIX_SUPER, "min_compat_ondisk_format", bl);
|
|
}
|
|
}
|
|
|
|
int BlueStore::_open_super_meta()
|
|
{
|
|
// nid
|
|
{
|
|
nid_max = 0;
|
|
bufferlist bl;
|
|
db->get(PREFIX_SUPER, "nid_max", &bl);
|
|
auto p = bl.cbegin();
|
|
try {
|
|
uint64_t v;
|
|
decode(v, p);
|
|
nid_max = v;
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " unable to read nid_max" << dendl;
|
|
return -EIO;
|
|
}
|
|
dout(1) << __func__ << " old nid_max " << nid_max << dendl;
|
|
nid_last = nid_max.load();
|
|
}
|
|
|
|
// blobid
|
|
{
|
|
blobid_max = 0;
|
|
bufferlist bl;
|
|
db->get(PREFIX_SUPER, "blobid_max", &bl);
|
|
auto p = bl.cbegin();
|
|
try {
|
|
uint64_t v;
|
|
decode(v, p);
|
|
blobid_max = v;
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " unable to read blobid_max" << dendl;
|
|
return -EIO;
|
|
}
|
|
dout(1) << __func__ << " old blobid_max " << blobid_max << dendl;
|
|
blobid_last = blobid_max.load();
|
|
}
|
|
|
|
// freelist
|
|
{
|
|
bufferlist bl;
|
|
db->get(PREFIX_SUPER, "freelist_type", &bl);
|
|
if (bl.length()) {
|
|
freelist_type = std::string(bl.c_str(), bl.length());
|
|
} else {
|
|
ceph_abort_msg("Not Support extent freelist manager");
|
|
}
|
|
dout(5) << __func__ << "::NCB::freelist_type=" << freelist_type << dendl;
|
|
}
|
|
// ondisk format
|
|
int32_t compat_ondisk_format = 0;
|
|
{
|
|
bufferlist bl;
|
|
int r = db->get(PREFIX_SUPER, "ondisk_format", &bl);
|
|
if (r < 0) {
|
|
// base case: kraken bluestore is v1 and readable by v1
|
|
dout(20) << __func__ << " missing ondisk_format; assuming kraken"
|
|
<< dendl;
|
|
ondisk_format = 1;
|
|
compat_ondisk_format = 1;
|
|
} else {
|
|
auto p = bl.cbegin();
|
|
try {
|
|
decode(ondisk_format, p);
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " unable to read ondisk_format" << dendl;
|
|
return -EIO;
|
|
}
|
|
bl.clear();
|
|
{
|
|
r = db->get(PREFIX_SUPER, "min_compat_ondisk_format", &bl);
|
|
ceph_assert(!r);
|
|
auto p = bl.cbegin();
|
|
try {
|
|
decode(compat_ondisk_format, p);
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " unable to read compat_ondisk_format" << dendl;
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
dout(1) << __func__ << " ondisk_format " << ondisk_format
|
|
<< " compat_ondisk_format " << compat_ondisk_format
|
|
<< dendl;
|
|
}
|
|
|
|
if (latest_ondisk_format < compat_ondisk_format) {
|
|
derr << __func__ << " compat_ondisk_format is "
|
|
<< compat_ondisk_format << " but we only understand version "
|
|
<< latest_ondisk_format << dendl;
|
|
return -EPERM;
|
|
}
|
|
|
|
{
|
|
if(cct->_conf->bluestore_debug_enforce_min_alloc_size == 0) {
|
|
bufferlist bl;
|
|
db->get(PREFIX_SUPER, "min_alloc_size", &bl);
|
|
auto p = bl.cbegin();
|
|
try {
|
|
uint64_t val;
|
|
decode(val, p);
|
|
min_alloc_size = val;
|
|
min_alloc_size_order = std::countr_zero(val);
|
|
min_alloc_size_mask = min_alloc_size - 1;
|
|
|
|
ceph_assert(min_alloc_size == 1u << min_alloc_size_order);
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " unable to read min_alloc_size" << dendl;
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
min_alloc_size = cct->_conf->bluestore_debug_enforce_min_alloc_size;
|
|
min_alloc_size_order = std::countr_zero(min_alloc_size);
|
|
min_alloc_size_mask = min_alloc_size - 1;
|
|
}
|
|
dout(1) << __func__ << " min_alloc_size 0x" << std::hex << min_alloc_size
|
|
<< std::dec << dendl;
|
|
logger->set(l_bluestore_alloc_unit, min_alloc_size);
|
|
}
|
|
|
|
_set_per_pool_omap();
|
|
|
|
_open_statfs();
|
|
_set_alloc_sizes();
|
|
_set_throttle_params();
|
|
|
|
_set_csum();
|
|
_set_compression();
|
|
_set_blob_size();
|
|
_update_allocator_lookup_policy();
|
|
|
|
_validate_bdev();
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_upgrade_super()
|
|
{
|
|
dout(1) << __func__ << " from " << ondisk_format << ", latest "
|
|
<< latest_ondisk_format << dendl;
|
|
if (ondisk_format < latest_ondisk_format) {
|
|
ceph_assert(ondisk_format > 0);
|
|
ceph_assert(ondisk_format < latest_ondisk_format);
|
|
|
|
KeyValueDB::Transaction t = db->get_transaction();
|
|
if (ondisk_format == 1) {
|
|
// changes:
|
|
// - super: added ondisk_format
|
|
// - super: added min_readable_ondisk_format
|
|
// - super: added min_compat_ondisk_format
|
|
// - super: added min_alloc_size
|
|
// - super: removed min_min_alloc_size
|
|
{
|
|
bufferlist bl;
|
|
db->get(PREFIX_SUPER, "min_min_alloc_size", &bl);
|
|
auto p = bl.cbegin();
|
|
try {
|
|
uint64_t val;
|
|
decode(val, p);
|
|
min_alloc_size = val;
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " failed to read min_min_alloc_size" << dendl;
|
|
return -EIO;
|
|
}
|
|
t->set(PREFIX_SUPER, "min_alloc_size", bl);
|
|
t->rmkey(PREFIX_SUPER, "min_min_alloc_size");
|
|
}
|
|
ondisk_format = 2;
|
|
}
|
|
if (ondisk_format == 2) {
|
|
// changes:
|
|
// - onode has FLAG_PERPOOL_OMAP. Note that we do not know that *all*
|
|
// oondes are using the per-pool prefix until a repair is run; at that
|
|
// point the per_pool_omap=1 key will be set.
|
|
// - super: added per_pool_omap key, which indicates that *all* objects
|
|
// are using the new prefix and key format
|
|
ondisk_format = 3;
|
|
}
|
|
if (ondisk_format == 3) {
|
|
// changes:
|
|
// - FreelistManager keeps meta within bdev label
|
|
int r = _write_out_fm_meta(0);
|
|
ceph_assert(r == 0);
|
|
ondisk_format = 4;
|
|
}
|
|
// This to be the last operation
|
|
_prepare_ondisk_format_super(t);
|
|
int r = db->submit_transaction_sync(t);
|
|
ceph_assert(r == 0);
|
|
}
|
|
// done
|
|
dout(1) << __func__ << " done" << dendl;
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_assign_nid(TransContext *txc, OnodeRef& o)
|
|
{
|
|
if (o->onode.nid) {
|
|
ceph_assert(o->exists);
|
|
return;
|
|
}
|
|
uint64_t nid = ++nid_last;
|
|
dout(20) << __func__ << " " << nid << dendl;
|
|
o->onode.nid = nid;
|
|
txc->last_nid = nid;
|
|
o->exists = true;
|
|
}
|
|
|
|
uint64_t BlueStore::_assign_blobid(TransContext *txc)
|
|
{
|
|
uint64_t bid = ++blobid_last;
|
|
dout(20) << __func__ << " " << bid << dendl;
|
|
txc->last_blobid = bid;
|
|
return bid;
|
|
}
|
|
|
|
void BlueStore::get_db_statistics(Formatter *f)
|
|
{
|
|
db->get_statistics(f);
|
|
}
|
|
|
|
BlueStore::TransContext *BlueStore::_txc_create(
|
|
Collection *c, OpSequencer *osr,
|
|
list<Context*> *on_commits,
|
|
TrackedOpRef osd_op)
|
|
{
|
|
TransContext *txc = new TransContext(cct, c, osr, on_commits);
|
|
txc->t = db->get_transaction();
|
|
|
|
#ifdef WITH_BLKIN
|
|
if (osd_op && osd_op->pg_trace) {
|
|
txc->trace.init("TransContext", &trace_endpoint,
|
|
&osd_op->pg_trace);
|
|
txc->trace.event("txc create");
|
|
//txc->trace.keyval("txc seq", txc->seq);
|
|
txc->trace.keyval("txc", txc);
|
|
}
|
|
#endif
|
|
|
|
osr->queue_new(txc);
|
|
dout(20) << __func__ << " osr " << osr << " = " << txc
|
|
// << " seq " << txc->seq
|
|
<< dendl;
|
|
return txc;
|
|
}
|
|
|
|
void BlueStore::_txc_calc_cost(TransContext *txc)
|
|
{
|
|
// one "io" for the kv commit
|
|
auto ios = 1 + txc->ioc.get_num_ios();
|
|
auto cost = throttle_cost_per_io.load();
|
|
txc->cost = ios * cost + txc->bytes;
|
|
txc->ios = ios;
|
|
dout(10) << __func__ << " " << txc << " cost " << txc->cost << " ("
|
|
<< ios << " ios * " << cost << " + " << txc->bytes
|
|
<< " bytes)" << dendl;
|
|
}
|
|
|
|
void BlueStore::_txc_update_store_statfs(TransContext *txc)
|
|
{
|
|
if (txc->statfs_delta.is_empty())
|
|
return;
|
|
|
|
logger->inc(l_bluestore_allocated, txc->statfs_delta.allocated());
|
|
logger->inc(l_bluestore_stored, txc->statfs_delta.stored());
|
|
logger->inc(l_bluestore_compressed, txc->statfs_delta.compressed());
|
|
logger->inc(l_bluestore_compressed_allocated, txc->statfs_delta.compressed_allocated());
|
|
logger->inc(l_bluestore_compressed_original, txc->statfs_delta.compressed_original());
|
|
|
|
if (per_pool_stat_collection) {
|
|
if (!is_statfs_recoverable()) {
|
|
bufferlist bl;
|
|
txc->statfs_delta.encode(bl);
|
|
string key;
|
|
get_pool_stat_key(txc->osd_pool_id, &key);
|
|
txc->t->merge(PREFIX_STAT, key, bl);
|
|
}
|
|
|
|
std::lock_guard l(vstatfs_lock);
|
|
auto& stats = osd_pools[txc->osd_pool_id];
|
|
stats += txc->statfs_delta;
|
|
|
|
vstatfs += txc->statfs_delta; //non-persistent in this mode
|
|
|
|
} else {
|
|
if (!is_statfs_recoverable()) {
|
|
bufferlist bl;
|
|
txc->statfs_delta.encode(bl);
|
|
txc->t->merge(PREFIX_STAT, BLUESTORE_GLOBAL_STATFS_KEY, bl);
|
|
}
|
|
|
|
std::lock_guard l(vstatfs_lock);
|
|
vstatfs += txc->statfs_delta;
|
|
}
|
|
txc->statfs_delta.reset();
|
|
}
|
|
|
|
void BlueStore::_txc_state_proc(TransContext *txc)
|
|
{
|
|
while (true) {
|
|
dout(10) << __func__ << " txc " << txc
|
|
<< " " << txc->get_state_name() << dendl;
|
|
switch (txc->get_state()) {
|
|
case TransContext::STATE_PREPARE:
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_prepare_lat);
|
|
if (txc->ioc.has_pending_aios()) {
|
|
txc->set_state(TransContext::STATE_AIO_WAIT);
|
|
#ifdef WITH_BLKIN
|
|
if (txc->trace) {
|
|
txc->trace.keyval("pending aios", txc->ioc.num_pending.load());
|
|
}
|
|
#endif
|
|
txc->had_ios = true;
|
|
_txc_aio_submit(txc);
|
|
return;
|
|
}
|
|
// ** fall-thru **
|
|
|
|
case TransContext::STATE_AIO_WAIT:
|
|
{
|
|
mono_clock::duration lat = throttle.log_state_latency(
|
|
*txc, logger, l_bluestore_state_aio_wait_lat);
|
|
if (ceph::to_seconds<double>(lat) >= cct->_conf->bluestore_log_op_age) {
|
|
logger->inc(l_bluestore_slow_aio_wait_count);
|
|
dout(0) << __func__ << " slow aio_wait, txc = " << txc
|
|
<< ", latency = " << lat
|
|
<< dendl;
|
|
}
|
|
}
|
|
|
|
_txc_finish_io(txc); // may trigger blocked txc's too
|
|
return;
|
|
|
|
case TransContext::STATE_IO_DONE:
|
|
ceph_assert(ceph_mutex_is_locked(txc->osr->qlock)); // see _txc_finish_io
|
|
if (txc->had_ios) {
|
|
++txc->osr->txc_with_unstable_io;
|
|
}
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_io_done_lat);
|
|
txc->set_state(TransContext::STATE_KV_QUEUED);
|
|
if (cct->_conf->bluestore_sync_submit_transaction) {
|
|
if (txc->last_nid >= nid_max ||
|
|
txc->last_blobid >= blobid_max) {
|
|
dout(20) << __func__
|
|
<< " last_{nid,blobid} exceeds max, submit via kv thread"
|
|
<< dendl;
|
|
} else if (txc->osr->kv_committing_serially) {
|
|
dout(20) << __func__ << " prior txc submitted via kv thread, us too"
|
|
<< dendl;
|
|
// note: this is starvation-prone. once we have a txc in a busy
|
|
// sequencer that is committing serially it is possible to keep
|
|
// submitting new transactions fast enough that we get stuck doing
|
|
// so. the alternative is to block here... fixme?
|
|
} else if (txc->osr->txc_with_unstable_io) {
|
|
dout(20) << __func__ << " prior txc(s) with unstable ios "
|
|
<< txc->osr->txc_with_unstable_io.load() << dendl;
|
|
} else if (cct->_conf->bluestore_debug_randomize_serial_transaction &&
|
|
rand() % cct->_conf->bluestore_debug_randomize_serial_transaction
|
|
== 0) {
|
|
dout(20) << __func__ << " DEBUG randomly forcing submit via kv thread"
|
|
<< dendl;
|
|
} else {
|
|
_txc_apply_kv(txc, true);
|
|
}
|
|
}
|
|
{
|
|
std::lock_guard l(kv_lock);
|
|
kv_queue.push_back(txc);
|
|
if (!kv_sync_in_progress) {
|
|
kv_sync_in_progress = true;
|
|
kv_cond.notify_one();
|
|
}
|
|
if (txc->get_state() != TransContext::STATE_KV_SUBMITTED) {
|
|
kv_queue_unsubmitted.push_back(txc);
|
|
++txc->osr->kv_committing_serially;
|
|
}
|
|
if (txc->had_ios)
|
|
kv_ios++;
|
|
kv_throttle_costs += txc->cost;
|
|
++kv_throttle_txcs;
|
|
}
|
|
return;
|
|
case TransContext::STATE_KV_SUBMITTED:
|
|
_txc_committed_kv(txc);
|
|
// ** fall-thru **
|
|
|
|
case TransContext::STATE_KV_DONE:
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_kv_done_lat);
|
|
if (txc->deferred_txn) {
|
|
txc->set_state(TransContext::STATE_DEFERRED_QUEUED);
|
|
_deferred_queue(txc);
|
|
return;
|
|
}
|
|
txc->set_state(TransContext::STATE_FINISHING);
|
|
break;
|
|
|
|
case TransContext::STATE_DEFERRED_CLEANUP:
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_deferred_cleanup_lat);
|
|
txc->set_state(TransContext::STATE_FINISHING);
|
|
// ** fall-thru **
|
|
|
|
case TransContext::STATE_FINISHING:
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_finishing_lat);
|
|
_txc_finish(txc);
|
|
return;
|
|
|
|
default:
|
|
derr << __func__ << " unexpected txc " << txc
|
|
<< " state " << txc->get_state_name() << dendl;
|
|
ceph_abort_msg("unexpected txc state");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_txc_finish_io(TransContext *txc)
|
|
{
|
|
dout(20) << __func__ << " " << txc << dendl;
|
|
|
|
/*
|
|
* we need to preserve the order of kv transactions,
|
|
* even though aio will complete in any order.
|
|
*/
|
|
|
|
OpSequencer *osr = txc->osr.get();
|
|
std::lock_guard l(osr->qlock);
|
|
txc->set_state(TransContext::STATE_IO_DONE);
|
|
txc->ioc.release_running_aios();
|
|
OpSequencer::q_list_t::iterator p = osr->q.iterator_to(*txc);
|
|
while (p != osr->q.begin()) {
|
|
--p;
|
|
if (p->get_state() < TransContext::STATE_IO_DONE) {
|
|
dout(20) << __func__ << " " << txc << " blocked by " << &*p << " "
|
|
<< p->get_state_name() << dendl;
|
|
return;
|
|
}
|
|
if (p->get_state() > TransContext::STATE_IO_DONE) {
|
|
++p;
|
|
break;
|
|
}
|
|
}
|
|
do {
|
|
_txc_state_proc(&*p++);
|
|
} while (p != osr->q.end() &&
|
|
p->get_state() == TransContext::STATE_IO_DONE);
|
|
|
|
if (osr->kv_submitted_waiters) {
|
|
osr->qcond.notify_all();
|
|
}
|
|
}
|
|
|
|
void BlueStore::_txc_write_nodes(TransContext *txc, KeyValueDB::Transaction t)
|
|
{
|
|
dout(20) << __func__ << " txc " << txc
|
|
<< " onodes " << txc->onodes
|
|
<< " shared_blobs " << txc->shared_blobs
|
|
<< dendl;
|
|
|
|
// finalize onodes
|
|
for (auto o : txc->onodes) {
|
|
_record_onode(o, t);
|
|
int16_t spanning_change =
|
|
o->extent_map.spanning_blob_map.size() - o->prev_spanning_cnt;
|
|
if (spanning_change != 0) {
|
|
o->prev_spanning_cnt = o->extent_map.spanning_blob_map.size();
|
|
logger->inc(l_bluestore_spanning_blobs, spanning_change);
|
|
}
|
|
o->flushing_count++;
|
|
}
|
|
|
|
// objects we modified but didn't affect the onode
|
|
auto p = txc->modified_objects.begin();
|
|
while (p != txc->modified_objects.end()) {
|
|
if (txc->onodes.count(*p) == 0) {
|
|
(*p)->flushing_count++;
|
|
++p;
|
|
} else {
|
|
// remove dups with onodes list to avoid problems in _txc_finish
|
|
p = txc->modified_objects.erase(p);
|
|
}
|
|
}
|
|
|
|
// finalize shared_blobs
|
|
for (auto sb : txc->shared_blobs) {
|
|
string key;
|
|
auto sbid = sb->get_sbid();
|
|
get_shared_blob_key(sbid, &key);
|
|
if (sb->persistent->empty()) {
|
|
dout(20) << __func__ << " shared_blob 0x"
|
|
<< std::hex << sbid << std::dec
|
|
<< " is empty" << dendl;
|
|
t->rmkey(PREFIX_SHARED_BLOB, key);
|
|
} else {
|
|
bufferlist bl;
|
|
encode(*(sb->persistent), bl);
|
|
dout(20) << __func__ << " shared_blob 0x"
|
|
<< std::hex << sbid << std::dec
|
|
<< " is " << bl.length() << " " << *sb << dendl;
|
|
t->set(PREFIX_SHARED_BLOB, key, bl);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::BSPerfTracker::update_from_perfcounters(
|
|
PerfCounters &logger)
|
|
{
|
|
os_commit_latency_ns.consume_next(
|
|
logger.get_tavg_ns(
|
|
l_bluestore_commit_lat));
|
|
os_apply_latency_ns.consume_next(
|
|
logger.get_tavg_ns(
|
|
l_bluestore_commit_lat));
|
|
}
|
|
|
|
void BlueStore::_txc_finalize_kv(TransContext *txc, KeyValueDB::Transaction t)
|
|
{
|
|
dout(20) << __func__ << " txc " << txc << std::hex
|
|
<< " allocated 0x" << txc->allocated
|
|
<< " released 0x" << txc->released
|
|
<< std::dec << dendl;
|
|
|
|
if (!fm->is_null_manager())
|
|
{
|
|
// We have to handle the case where we allocate *and* deallocate the
|
|
// same region in this transaction. The freelist doesn't like that.
|
|
// (Actually, the only thing that cares is the BitmapFreelistManager
|
|
// debug check. But that's important.)
|
|
interval_set<uint64_t> tmp_allocated, tmp_released;
|
|
interval_set<uint64_t> *pallocated = &txc->allocated;
|
|
interval_set<uint64_t> *preleased = &txc->released;
|
|
if (!txc->allocated.empty() && !txc->released.empty()) {
|
|
interval_set<uint64_t> overlap;
|
|
overlap.intersection_of(txc->allocated, txc->released);
|
|
if (!overlap.empty()) {
|
|
tmp_allocated = txc->allocated;
|
|
tmp_allocated.subtract(overlap);
|
|
tmp_released = txc->released;
|
|
tmp_released.subtract(overlap);
|
|
dout(20) << __func__ << " overlap 0x" << std::hex << overlap
|
|
<< ", new allocated 0x" << tmp_allocated
|
|
<< " released 0x" << tmp_released << std::dec
|
|
<< dendl;
|
|
pallocated = &tmp_allocated;
|
|
preleased = &tmp_released;
|
|
}
|
|
}
|
|
|
|
// update freelist with non-overlap sets
|
|
for (interval_set<uint64_t>::iterator p = pallocated->begin();
|
|
p != pallocated->end();
|
|
++p) {
|
|
fm->allocate(p.get_start(), p.get_len(), t);
|
|
}
|
|
for (interval_set<uint64_t>::iterator p = preleased->begin();
|
|
p != preleased->end();
|
|
++p) {
|
|
dout(20) << __func__ << " release 0x" << std::hex << p.get_start()
|
|
<< "~" << p.get_len() << std::dec << dendl;
|
|
fm->release(p.get_start(), p.get_len(), t);
|
|
}
|
|
}
|
|
|
|
_txc_update_store_statfs(txc);
|
|
}
|
|
|
|
void BlueStore::_txc_apply_kv(TransContext *txc, bool sync_submit_transaction)
|
|
{
|
|
ceph_assert(txc->get_state() == TransContext::STATE_KV_QUEUED);
|
|
{
|
|
#if defined(WITH_LTTNG)
|
|
auto start = mono_clock::now();
|
|
#endif
|
|
|
|
#ifdef WITH_BLKIN
|
|
if (txc->trace) {
|
|
txc->trace.event("db async submit");
|
|
}
|
|
#endif
|
|
|
|
int r = cct->_conf->bluestore_debug_omit_kv_commit ? 0 : db->submit_transaction(txc->t);
|
|
ceph_assert(r == 0);
|
|
txc->set_state(TransContext::STATE_KV_SUBMITTED);
|
|
if (txc->osr->kv_submitted_waiters) {
|
|
std::lock_guard l(txc->osr->qlock);
|
|
txc->osr->qcond.notify_all();
|
|
}
|
|
|
|
#if defined(WITH_LTTNG)
|
|
if (txc->tracing) {
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_kv_submit_latency,
|
|
txc->osr->get_sequencer_id(),
|
|
(uint64_t)txc,
|
|
sync_submit_transaction,
|
|
ceph::to_seconds<double>(mono_clock::now() - start));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
for (auto ls : { &txc->onodes, &txc->modified_objects }) {
|
|
for (auto& o : *ls) {
|
|
dout(20) << __func__ << " onode " << o << " had " << o->flushing_count
|
|
<< dendl;
|
|
if (--o->flushing_count == 0 && o->waiting_count.load()) {
|
|
std::lock_guard l(o->flush_lock);
|
|
o->flush_cond.notify_all();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_txc_committed_kv(TransContext *txc)
|
|
{
|
|
dout(20) << __func__ << " txc " << txc << dendl;
|
|
throttle.complete_kv(*txc);
|
|
{
|
|
std::lock_guard l(txc->osr->qlock);
|
|
txc->set_state(TransContext::STATE_KV_DONE);
|
|
if (txc->ch->commit_queue) {
|
|
txc->ch->commit_queue->queue(txc->oncommits);
|
|
} else {
|
|
finisher.queue(txc->oncommits);
|
|
}
|
|
}
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_kv_committing_lat);
|
|
log_latency_fn(
|
|
__func__,
|
|
l_bluestore_commit_lat,
|
|
mono_clock::now() - txc->start,
|
|
cct->_conf->bluestore_log_op_age,
|
|
[&](auto lat) {
|
|
return ", txc = " + stringify(txc) +
|
|
", txc bytes = " + stringify(txc->bytes) +
|
|
", txc ios = " + stringify(txc->ios) +
|
|
", txc cost = " + stringify(txc->cost) +
|
|
", txc onodes = " + stringify(txc->onodes.size()) +
|
|
", DB updates = " + stringify(txc->t->get_count()) +
|
|
", DB bytes = " + stringify(txc->t->get_size_bytes()) +
|
|
", cost max = " + stringify(throttle.bytes_observed_max) +
|
|
" on " + stringify(throttle.bytes_max_ts) +
|
|
", txc max = " + stringify(throttle.transactions_observed_max) +
|
|
" on " + stringify(throttle.transactions_max_ts)
|
|
;
|
|
},
|
|
l_bluestore_slow_committed_kv_count
|
|
);
|
|
}
|
|
|
|
void BlueStore::_txc_finish(TransContext *txc)
|
|
{
|
|
dout(20) << __func__ << " " << txc << " onodes " << txc->onodes << dendl;
|
|
ceph_assert(txc->get_state() == TransContext::STATE_FINISHING);
|
|
|
|
txc->finish_writing();
|
|
|
|
while (!txc->removed_collections.empty()) {
|
|
_queue_reap_collection(txc->removed_collections.front());
|
|
txc->removed_collections.pop_front();
|
|
}
|
|
|
|
OpSequencerRef osr = txc->osr;
|
|
bool empty = false;
|
|
bool submit_deferred = false;
|
|
OpSequencer::q_list_t releasing_txc;
|
|
{
|
|
std::lock_guard l(osr->qlock);
|
|
txc->set_state(TransContext::STATE_DONE);
|
|
bool notify = false;
|
|
while (!osr->q.empty()) {
|
|
TransContext *txc = &osr->q.front();
|
|
dout(20) << __func__ << " txc " << txc << " " << txc->get_state_name()
|
|
<< dendl;
|
|
if (txc->get_state() != TransContext::STATE_DONE) {
|
|
if (txc->get_state() == TransContext::STATE_PREPARE &&
|
|
deferred_aggressive) {
|
|
// for _osr_drain_preceding()
|
|
notify = true;
|
|
}
|
|
if (txc->get_state() == TransContext::STATE_DEFERRED_QUEUED &&
|
|
osr->q.size() > g_conf()->bluestore_max_deferred_txc) {
|
|
submit_deferred = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
osr->q.pop_front();
|
|
releasing_txc.push_back(*txc);
|
|
}
|
|
|
|
if (osr->q.empty()) {
|
|
dout(20) << __func__ << " osr " << osr << " q now empty" << dendl;
|
|
empty = true;
|
|
}
|
|
|
|
// only drain()/drain_preceding() need wakeup,
|
|
// other cases use kv_submitted_waiters
|
|
if (notify || empty) {
|
|
osr->qcond.notify_all();
|
|
}
|
|
}
|
|
|
|
while (!releasing_txc.empty()) {
|
|
// release to allocator only after all preceding txc's have also
|
|
// finished any deferred writes that potentially land in these
|
|
// blocks
|
|
auto txc = &releasing_txc.front();
|
|
_txc_release_alloc(txc);
|
|
releasing_txc.pop_front();
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_done_lat);
|
|
throttle.complete(*txc);
|
|
delete txc;
|
|
}
|
|
|
|
if (submit_deferred) {
|
|
// we're pinning memory; flush! we could be more fine-grained here but
|
|
// i'm not sure it's worth the bother.
|
|
deferred_try_submit();
|
|
}
|
|
|
|
if (empty && osr->zombie) {
|
|
std::lock_guard l(zombie_osr_lock);
|
|
if (zombie_osr_set.erase(osr->cid)) {
|
|
dout(10) << __func__ << " reaping empty zombie osr " << osr << dendl;
|
|
} else {
|
|
dout(10) << __func__ << " empty zombie osr " << osr << " already reaped"
|
|
<< dendl;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_txc_release_alloc(TransContext *txc)
|
|
{
|
|
bool discard_queued = false;
|
|
// it's expected we're called with lazy_release_lock already taken!
|
|
if (unlikely(cct->_conf->bluestore_debug_no_reuse_blocks ||
|
|
txc->released.size() == 0 ||
|
|
!alloc)) {
|
|
goto out;
|
|
}
|
|
discard_queued = bdev->try_discard(txc->released);
|
|
// if async discard succeeded, will do alloc->release when discard callback
|
|
// else we should release here
|
|
if (!discard_queued) {
|
|
dout(10) << __func__ << "(sync) " << txc << " " << std::hex
|
|
<< txc->released << std::dec << dendl;
|
|
alloc->release(txc->released);
|
|
}
|
|
|
|
out:
|
|
txc->allocated.clear();
|
|
txc->released.clear();
|
|
}
|
|
|
|
void BlueStore::_osr_attach(Collection *c)
|
|
{
|
|
// note: caller has coll_lock
|
|
auto q = coll_map.find(c->cid);
|
|
if (q != coll_map.end()) {
|
|
c->osr = q->second->osr;
|
|
ldout(cct, 10) << __func__ << " " << c->cid
|
|
<< " reusing osr " << c->osr << " from existing coll "
|
|
<< q->second << dendl;
|
|
} else {
|
|
std::lock_guard l(zombie_osr_lock);
|
|
auto p = zombie_osr_set.find(c->cid);
|
|
if (p == zombie_osr_set.end()) {
|
|
c->osr = ceph::make_ref<OpSequencer>(this, next_sequencer_id++, c->cid);
|
|
ldout(cct, 10) << __func__ << " " << c->cid
|
|
<< " fresh osr " << c->osr << dendl;
|
|
} else {
|
|
c->osr = p->second;
|
|
zombie_osr_set.erase(p);
|
|
ldout(cct, 10) << __func__ << " " << c->cid
|
|
<< " resurrecting zombie osr " << c->osr << dendl;
|
|
c->osr->zombie = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_osr_register_zombie(OpSequencer *osr)
|
|
{
|
|
std::lock_guard l(zombie_osr_lock);
|
|
dout(10) << __func__ << " " << osr << " " << osr->cid << dendl;
|
|
osr->zombie = true;
|
|
auto i = zombie_osr_set.emplace(osr->cid, osr);
|
|
// this is either a new insertion or the same osr is already there
|
|
ceph_assert(i.second || i.first->second == osr);
|
|
}
|
|
|
|
void BlueStore::_osr_drain_preceding(TransContext *txc)
|
|
{
|
|
OpSequencer *osr = txc->osr.get();
|
|
dout(10) << __func__ << " " << txc << " osr " << osr << dendl;
|
|
++deferred_aggressive; // FIXME: maybe osr-local aggressive flag?
|
|
{
|
|
// submit anything pending
|
|
osr->deferred_lock.lock();
|
|
if (osr->deferred_pending && !osr->deferred_running) {
|
|
_deferred_submit_unlock(osr);
|
|
} else {
|
|
osr->deferred_lock.unlock();
|
|
}
|
|
}
|
|
{
|
|
// wake up any previously finished deferred events
|
|
std::lock_guard l(kv_lock);
|
|
if (!kv_sync_in_progress) {
|
|
kv_sync_in_progress = true;
|
|
kv_cond.notify_one();
|
|
}
|
|
}
|
|
osr->drain_preceding(txc);
|
|
--deferred_aggressive;
|
|
dout(10) << __func__ << " " << osr << " done" << dendl;
|
|
}
|
|
|
|
void BlueStore::_osr_drain(OpSequencer *osr)
|
|
{
|
|
dout(10) << __func__ << " " << osr << dendl;
|
|
++deferred_aggressive; // FIXME: maybe osr-local aggressive flag?
|
|
{
|
|
// submit anything pending
|
|
osr->deferred_lock.lock();
|
|
if (osr->deferred_pending && !osr->deferred_running) {
|
|
_deferred_submit_unlock(osr);
|
|
} else {
|
|
osr->deferred_lock.unlock();
|
|
}
|
|
}
|
|
{
|
|
// wake up any previously finished deferred events
|
|
std::lock_guard l(kv_lock);
|
|
if (!kv_sync_in_progress) {
|
|
kv_sync_in_progress = true;
|
|
kv_cond.notify_one();
|
|
}
|
|
}
|
|
osr->drain();
|
|
--deferred_aggressive;
|
|
dout(10) << __func__ << " " << osr << " done" << dendl;
|
|
}
|
|
|
|
void BlueStore::_osr_drain_all()
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
|
|
set<OpSequencerRef> s;
|
|
vector<OpSequencerRef> zombies;
|
|
{
|
|
std::shared_lock l(coll_lock);
|
|
for (auto& i : coll_map) {
|
|
s.insert(i.second->osr);
|
|
}
|
|
}
|
|
{
|
|
std::lock_guard l(zombie_osr_lock);
|
|
for (auto& i : zombie_osr_set) {
|
|
s.insert(i.second);
|
|
zombies.push_back(i.second);
|
|
}
|
|
}
|
|
dout(20) << __func__ << " osr_set " << s << dendl;
|
|
|
|
++deferred_aggressive;
|
|
{
|
|
// submit anything pending
|
|
deferred_try_submit();
|
|
}
|
|
{
|
|
// wake up any previously finished deferred events
|
|
std::lock_guard l(kv_lock);
|
|
kv_cond.notify_one();
|
|
}
|
|
{
|
|
std::lock_guard l(kv_finalize_lock);
|
|
kv_finalize_cond.notify_one();
|
|
}
|
|
for (auto osr : s) {
|
|
dout(20) << __func__ << " drain " << osr << dendl;
|
|
osr->drain();
|
|
}
|
|
--deferred_aggressive;
|
|
|
|
{
|
|
std::lock_guard l(zombie_osr_lock);
|
|
for (auto& osr : zombies) {
|
|
if (zombie_osr_set.erase(osr->cid)) {
|
|
dout(10) << __func__ << " reaping empty zombie osr " << osr << dendl;
|
|
ceph_assert(osr->q.empty());
|
|
} else if (osr->zombie) {
|
|
dout(10) << __func__ << " empty zombie osr " << osr
|
|
<< " already reaped" << dendl;
|
|
ceph_assert(osr->q.empty());
|
|
} else {
|
|
dout(10) << __func__ << " empty zombie osr " << osr
|
|
<< " resurrected" << dendl;
|
|
}
|
|
}
|
|
}
|
|
|
|
dout(10) << __func__ << " done" << dendl;
|
|
}
|
|
|
|
|
|
void BlueStore::_kv_start()
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
|
|
finisher.start();
|
|
kv_sync_thread.create("bstore_kv_sync");
|
|
kv_finalize_thread.create("bstore_kv_final");
|
|
}
|
|
|
|
void BlueStore::_kv_stop()
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
{
|
|
std::unique_lock l{kv_lock};
|
|
while (!kv_sync_started) {
|
|
kv_cond.wait(l);
|
|
}
|
|
kv_stop = true;
|
|
kv_cond.notify_all();
|
|
}
|
|
{
|
|
std::unique_lock l{kv_finalize_lock};
|
|
while (!kv_finalize_started) {
|
|
kv_finalize_cond.wait(l);
|
|
}
|
|
kv_finalize_stop = true;
|
|
kv_finalize_cond.notify_all();
|
|
}
|
|
kv_sync_thread.join();
|
|
kv_finalize_thread.join();
|
|
ceph_assert(removed_collections.empty());
|
|
{
|
|
std::lock_guard l(kv_lock);
|
|
kv_stop = false;
|
|
}
|
|
{
|
|
std::lock_guard l(kv_finalize_lock);
|
|
kv_finalize_stop = false;
|
|
}
|
|
dout(10) << __func__ << " stopping finishers" << dendl;
|
|
finisher.wait_for_empty();
|
|
finisher.stop();
|
|
dout(10) << __func__ << " stopped" << dendl;
|
|
}
|
|
|
|
void BlueStore::_kv_sync_thread()
|
|
{
|
|
dout(10) << __func__ << " start" << dendl;
|
|
deque<DeferredBatch*> deferred_stable_queue; ///< deferred ios done + stable
|
|
std::unique_lock l{kv_lock};
|
|
ceph_assert(!kv_sync_started);
|
|
kv_sync_started = true;
|
|
kv_cond.notify_all();
|
|
|
|
auto t0 = mono_clock::now();
|
|
timespan twait = ceph::make_timespan(0);
|
|
size_t kv_submitted = 0;
|
|
|
|
while (true) {
|
|
auto period = cct->_conf->bluestore_kv_sync_util_logging_s;
|
|
auto observation_period =
|
|
ceph::make_timespan(period);
|
|
auto elapsed = mono_clock::now() - t0;
|
|
if (period && elapsed >= observation_period) {
|
|
dout(5) << __func__ << " utilization: idle "
|
|
<< twait << " of " << elapsed
|
|
<< ", submitted: " << kv_submitted
|
|
<<dendl;
|
|
t0 = mono_clock::now();
|
|
twait = ceph::make_timespan(0);
|
|
kv_submitted = 0;
|
|
}
|
|
ceph_assert(kv_committing.empty());
|
|
if (kv_queue.empty() &&
|
|
((deferred_done_queue.empty() && deferred_stable_queue.empty()) ||
|
|
!deferred_aggressive)) {
|
|
if (kv_stop)
|
|
break;
|
|
dout(20) << __func__ << " sleep" << dendl;
|
|
auto t = mono_clock::now();
|
|
kv_sync_in_progress = false;
|
|
kv_cond.wait(l);
|
|
twait += mono_clock::now() - t;
|
|
|
|
dout(20) << __func__ << " wake" << dendl;
|
|
} else {
|
|
deque<TransContext*> kv_submitting;
|
|
deque<DeferredBatch*> deferred_done, deferred_stable;
|
|
uint64_t aios = 0, costs = 0, txcs = 0;
|
|
|
|
dout(20) << __func__ << " committing " << kv_queue.size()
|
|
<< " submitting " << kv_queue_unsubmitted.size()
|
|
<< " deferred done " << deferred_done_queue.size()
|
|
<< " stable " << deferred_stable_queue.size()
|
|
<< dendl;
|
|
kv_committing.swap(kv_queue);
|
|
kv_submitting.swap(kv_queue_unsubmitted);
|
|
deferred_done.swap(deferred_done_queue);
|
|
deferred_stable.swap(deferred_stable_queue);
|
|
aios = kv_ios;
|
|
costs = kv_throttle_costs;
|
|
txcs = kv_throttle_txcs;
|
|
kv_ios = 0;
|
|
kv_throttle_costs = 0;
|
|
kv_throttle_txcs = 0;
|
|
l.unlock();
|
|
|
|
dout(30) << __func__ << " committing " << kv_committing << dendl;
|
|
dout(30) << __func__ << " submitting " << kv_submitting << dendl;
|
|
dout(30) << __func__ << " deferred_done " << deferred_done << dendl;
|
|
dout(30) << __func__ << " deferred_stable " << deferred_stable << dendl;
|
|
|
|
auto start = mono_clock::now();
|
|
|
|
bool force_flush = false;
|
|
// if bluefs is sharing the same device as data (only), then we
|
|
// can rely on the bluefs commit to flush the device and make
|
|
// deferred aios stable. that means that if we do have done deferred
|
|
// txcs AND we are not on a single device, we need to force a flush.
|
|
if (bluefs && bluefs_layout.single_shared_device()) {
|
|
if (aios) {
|
|
force_flush = true;
|
|
} else if (kv_committing.empty() && deferred_stable.empty()) {
|
|
force_flush = true; // there's nothing else to commit!
|
|
} else if (deferred_aggressive) {
|
|
force_flush = true;
|
|
}
|
|
} else {
|
|
if (aios || !deferred_done.empty()) {
|
|
force_flush = true;
|
|
} else {
|
|
dout(20) << __func__ << " skipping flush (no aios, no deferred_done)" << dendl;
|
|
}
|
|
}
|
|
|
|
if (force_flush) {
|
|
dout(20) << __func__ << " num_aios=" << aios
|
|
<< " force_flush=" << (int)force_flush
|
|
<< ", flushing, deferred done->stable" << dendl;
|
|
// flush/barrier on block device
|
|
bdev->flush();
|
|
|
|
// if we flush then deferred done are now deferred stable
|
|
if (deferred_stable.empty()) {
|
|
deferred_stable.swap(deferred_done);
|
|
} else {
|
|
deferred_stable.insert(deferred_stable.end(), deferred_done.begin(),
|
|
deferred_done.end());
|
|
deferred_done.clear();
|
|
}
|
|
}
|
|
auto after_flush = mono_clock::now();
|
|
|
|
// we will use one final transaction to force a sync
|
|
KeyValueDB::Transaction synct = db->get_transaction();
|
|
|
|
// increase {nid,blobid}_max? note that this covers both the
|
|
// case where we are approaching the max and the case we passed
|
|
// it. in either case, we increase the max in the earlier txn
|
|
// we submit.
|
|
uint64_t new_nid_max = 0, new_blobid_max = 0;
|
|
if (nid_last + cct->_conf->bluestore_nid_prealloc/2 > nid_max) {
|
|
KeyValueDB::Transaction t =
|
|
kv_submitting.empty() ? synct : kv_submitting.front()->t;
|
|
new_nid_max = nid_last + cct->_conf->bluestore_nid_prealloc;
|
|
bufferlist bl;
|
|
encode(new_nid_max, bl);
|
|
t->set(PREFIX_SUPER, "nid_max", bl);
|
|
dout(10) << __func__ << " new_nid_max " << new_nid_max << dendl;
|
|
}
|
|
if (blobid_last + cct->_conf->bluestore_blobid_prealloc/2 > blobid_max) {
|
|
KeyValueDB::Transaction t =
|
|
kv_submitting.empty() ? synct : kv_submitting.front()->t;
|
|
new_blobid_max = blobid_last + cct->_conf->bluestore_blobid_prealloc;
|
|
bufferlist bl;
|
|
encode(new_blobid_max, bl);
|
|
t->set(PREFIX_SUPER, "blobid_max", bl);
|
|
dout(10) << __func__ << " new_blobid_max " << new_blobid_max << dendl;
|
|
}
|
|
|
|
for (auto txc : kv_committing) {
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_kv_queued_lat);
|
|
if (txc->get_state() == TransContext::STATE_KV_QUEUED) {
|
|
++kv_submitted;
|
|
_txc_apply_kv(txc, false);
|
|
--txc->osr->kv_committing_serially;
|
|
} else {
|
|
ceph_assert(txc->get_state() == TransContext::STATE_KV_SUBMITTED);
|
|
}
|
|
if (txc->had_ios) {
|
|
--txc->osr->txc_with_unstable_io;
|
|
}
|
|
}
|
|
|
|
// release throttle *before* we commit. this allows new ops
|
|
// to be prepared and enter pipeline while we are waiting on
|
|
// the kv commit sync/flush. then hopefully on the next
|
|
// iteration there will already be ops awake. otherwise, we
|
|
// end up going to sleep, and then wake up when the very first
|
|
// transaction is ready for commit.
|
|
throttle.release_kv_throttle(costs, txcs);
|
|
|
|
// cleanup sync deferred keys
|
|
for (auto b : deferred_stable) {
|
|
for (auto& txc : b->txcs) {
|
|
bluestore_deferred_transaction_t& wt = *txc.deferred_txn;
|
|
ceph_assert(wt.released.empty()); // only kraken did this
|
|
string key;
|
|
get_deferred_key(wt.seq, &key);
|
|
synct->rm_single_key(PREFIX_DEFERRED, key);
|
|
}
|
|
}
|
|
|
|
#if defined(WITH_LTTNG)
|
|
auto sync_start = mono_clock::now();
|
|
#endif
|
|
// submit synct synchronously (block and wait for it to commit)
|
|
int r = db_was_opened_read_only || cct->_conf->bluestore_debug_omit_kv_commit ?
|
|
0 : db->submit_transaction_sync(synct);
|
|
ceph_assert(r == 0);
|
|
|
|
#ifdef WITH_BLKIN
|
|
for (auto txc : kv_committing) {
|
|
if (txc->trace) {
|
|
txc->trace.event("db sync submit");
|
|
txc->trace.keyval("kv_committing size", kv_committing.size());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int committing_size = kv_committing.size();
|
|
int deferred_size = deferred_stable.size();
|
|
|
|
#if defined(WITH_LTTNG)
|
|
double sync_latency = ceph::to_seconds<double>(mono_clock::now() - sync_start);
|
|
for (auto txc: kv_committing) {
|
|
if (txc->tracing) {
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_kv_sync_latency,
|
|
txc->osr->get_sequencer_id(),
|
|
(uint64_t)txc,
|
|
kv_committing.size(),
|
|
deferred_done.size(),
|
|
deferred_stable.size(),
|
|
sync_latency);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
{
|
|
std::unique_lock m{kv_finalize_lock};
|
|
if (kv_committing_to_finalize.empty()) {
|
|
kv_committing_to_finalize.swap(kv_committing);
|
|
} else {
|
|
kv_committing_to_finalize.insert(
|
|
kv_committing_to_finalize.end(),
|
|
kv_committing.begin(),
|
|
kv_committing.end());
|
|
kv_committing.clear();
|
|
}
|
|
if (deferred_stable_to_finalize.empty()) {
|
|
deferred_stable_to_finalize.swap(deferred_stable);
|
|
} else {
|
|
deferred_stable_to_finalize.insert(
|
|
deferred_stable_to_finalize.end(),
|
|
deferred_stable.begin(),
|
|
deferred_stable.end());
|
|
deferred_stable.clear();
|
|
}
|
|
if (!kv_finalize_in_progress) {
|
|
kv_finalize_in_progress = true;
|
|
kv_finalize_cond.notify_one();
|
|
}
|
|
}
|
|
|
|
if (new_nid_max) {
|
|
nid_max = new_nid_max;
|
|
dout(10) << __func__ << " nid_max now " << nid_max << dendl;
|
|
}
|
|
if (new_blobid_max) {
|
|
blobid_max = new_blobid_max;
|
|
dout(10) << __func__ << " blobid_max now " << blobid_max << dendl;
|
|
}
|
|
|
|
{
|
|
auto finish = mono_clock::now();
|
|
ceph::timespan dur_flush = after_flush - start;
|
|
ceph::timespan dur_kv = finish - after_flush;
|
|
ceph::timespan dur = finish - start;
|
|
dout(20) << __func__ << " committed " << committing_size
|
|
<< " cleaned " << deferred_size
|
|
<< " in " << dur
|
|
<< " (" << dur_flush << " flush + " << dur_kv << " kv commit)"
|
|
<< dendl;
|
|
log_latency("kv_flush",
|
|
l_bluestore_kv_flush_lat,
|
|
dur_flush,
|
|
cct->_conf->bluestore_log_op_age);
|
|
log_latency("kv_commit",
|
|
l_bluestore_kv_commit_lat,
|
|
dur_kv,
|
|
cct->_conf->bluestore_log_op_age);
|
|
log_latency("kv_sync",
|
|
l_bluestore_kv_sync_lat,
|
|
dur,
|
|
cct->_conf->bluestore_log_op_age);
|
|
}
|
|
|
|
l.lock();
|
|
// previously deferred "done" are now "stable" by virtue of this
|
|
// commit cycle.
|
|
deferred_stable_queue.swap(deferred_done);
|
|
}
|
|
}
|
|
dout(10) << __func__ << " finish" << dendl;
|
|
kv_sync_started = false;
|
|
}
|
|
|
|
void BlueStore::_kv_finalize_thread()
|
|
{
|
|
deque<TransContext*> kv_committed;
|
|
deque<DeferredBatch*> deferred_stable;
|
|
dout(10) << __func__ << " start" << dendl;
|
|
std::unique_lock l(kv_finalize_lock);
|
|
ceph_assert(!kv_finalize_started);
|
|
kv_finalize_started = true;
|
|
kv_finalize_cond.notify_all();
|
|
while (true) {
|
|
ceph_assert(kv_committed.empty());
|
|
ceph_assert(deferred_stable.empty());
|
|
if (kv_committing_to_finalize.empty() &&
|
|
deferred_stable_to_finalize.empty()) {
|
|
if (kv_finalize_stop)
|
|
break;
|
|
dout(20) << __func__ << " sleep" << dendl;
|
|
kv_finalize_in_progress = false;
|
|
kv_finalize_cond.wait(l);
|
|
dout(20) << __func__ << " wake" << dendl;
|
|
} else {
|
|
kv_committed.swap(kv_committing_to_finalize);
|
|
deferred_stable.swap(deferred_stable_to_finalize);
|
|
l.unlock();
|
|
dout(20) << __func__ << " kv_committed " << kv_committed << dendl;
|
|
dout(20) << __func__ << " deferred_stable " << deferred_stable << dendl;
|
|
|
|
auto start = mono_clock::now();
|
|
|
|
while (!kv_committed.empty()) {
|
|
TransContext *txc = kv_committed.front();
|
|
ceph_assert(txc->get_state() == TransContext::STATE_KV_SUBMITTED);
|
|
_txc_state_proc(txc);
|
|
kv_committed.pop_front();
|
|
}
|
|
|
|
for (auto b : deferred_stable) {
|
|
auto p = b->txcs.begin();
|
|
while (p != b->txcs.end()) {
|
|
TransContext *txc = &*p;
|
|
p = b->txcs.erase(p); // unlink here because
|
|
_txc_state_proc(txc); // this may destroy txc
|
|
}
|
|
delete b;
|
|
}
|
|
deferred_stable.clear();
|
|
|
|
if (!deferred_aggressive) {
|
|
if (deferred_queue_size >= deferred_batch_ops.load() ||
|
|
throttle.should_submit_deferred()) {
|
|
deferred_try_submit();
|
|
}
|
|
}
|
|
|
|
// this is as good a place as any ...
|
|
_reap_collections();
|
|
log_latency("kv_final",
|
|
l_bluestore_kv_final_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
|
|
l.lock();
|
|
}
|
|
}
|
|
dout(10) << __func__ << " finish" << dendl;
|
|
kv_finalize_started = false;
|
|
}
|
|
|
|
|
|
bluestore_deferred_op_t *BlueStore::_get_deferred_op(
|
|
TransContext *txc, uint64_t len)
|
|
{
|
|
if (!txc->deferred_txn) {
|
|
txc->deferred_txn = new bluestore_deferred_transaction_t;
|
|
}
|
|
txc->deferred_txn->ops.push_back(bluestore_deferred_op_t());
|
|
logger->inc(l_bluestore_issued_deferred_writes);
|
|
logger->inc(l_bluestore_issued_deferred_write_bytes, len);
|
|
return &txc->deferred_txn->ops.back();
|
|
}
|
|
|
|
void BlueStore::_deferred_queue(TransContext *txc)
|
|
{
|
|
dout(20) << __func__ << " txc " << txc << " osr " << txc->osr << dendl;
|
|
|
|
DeferredBatch *tmp;
|
|
txc->osr->deferred_lock.lock();
|
|
{
|
|
if (!txc->osr->deferred_pending) {
|
|
tmp = new DeferredBatch(cct, txc->osr.get());
|
|
} else {
|
|
tmp = txc->osr->deferred_pending;
|
|
}
|
|
}
|
|
|
|
tmp->txcs.push_back(*txc);
|
|
bluestore_deferred_transaction_t& wt = *txc->deferred_txn;
|
|
for (auto opi = wt.ops.begin(); opi != wt.ops.end(); ++opi) {
|
|
const auto& op = *opi;
|
|
ceph_assert(op.op == bluestore_deferred_op_t::OP_WRITE);
|
|
bufferlist::const_iterator p = op.data.begin();
|
|
for (auto e : op.extents) {
|
|
tmp->prepare_write(cct, wt.seq, e.offset, e.length, p);
|
|
}
|
|
}
|
|
|
|
{
|
|
++deferred_queue_size;
|
|
txc->osr->deferred_pending = tmp;
|
|
// condition "tmp->txcs.size() == 1" mean deferred_pending was originally empty.
|
|
// So we should add osr into deferred_queue.
|
|
if (!txc->osr->deferred_running && (tmp->txcs.size() == 1)) {
|
|
deferred_lock.lock();
|
|
deferred_queue.push_back(*txc->osr);
|
|
deferred_lock.unlock();
|
|
}
|
|
|
|
if (deferred_aggressive &&
|
|
!txc->osr->deferred_running) {
|
|
_deferred_submit_unlock(txc->osr.get());
|
|
} else {
|
|
txc->osr->deferred_lock.unlock();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::deferred_try_submit()
|
|
{
|
|
dout(20) << __func__ << " " << deferred_queue.size() << " osrs, "
|
|
<< deferred_queue_size << " txcs" << dendl;
|
|
vector<OpSequencerRef> osrs;
|
|
|
|
{
|
|
std::lock_guard l(deferred_lock);
|
|
osrs.reserve(deferred_queue.size());
|
|
for (auto& osr : deferred_queue) {
|
|
osrs.push_back(&osr);
|
|
}
|
|
}
|
|
|
|
for (auto& osr : osrs) {
|
|
osr->deferred_lock.lock();
|
|
if (osr->deferred_pending) {
|
|
if (!osr->deferred_running) {
|
|
_deferred_submit_unlock(osr.get());
|
|
} else {
|
|
osr->deferred_lock.unlock();
|
|
dout(20) << __func__ << " osr " << osr << " already has running"
|
|
<< dendl;
|
|
}
|
|
} else {
|
|
osr->deferred_lock.unlock();
|
|
dout(20) << __func__ << " osr " << osr << " has no pending" << dendl;
|
|
}
|
|
}
|
|
|
|
{
|
|
std::lock_guard l(deferred_lock);
|
|
deferred_last_submitted = ceph_clock_now();
|
|
}
|
|
}
|
|
|
|
void BlueStore::_deferred_submit_unlock(OpSequencer *osr)
|
|
{
|
|
dout(10) << __func__ << " osr " << osr
|
|
<< " " << osr->deferred_pending->iomap.size() << " ios pending "
|
|
<< dendl;
|
|
ceph_assert(osr->deferred_pending);
|
|
ceph_assert(!osr->deferred_running);
|
|
|
|
auto b = osr->deferred_pending;
|
|
deferred_queue_size -= b->seq_bytes.size();
|
|
ceph_assert(deferred_queue_size >= 0);
|
|
|
|
osr->deferred_running = osr->deferred_pending;
|
|
osr->deferred_pending = nullptr;
|
|
|
|
osr->deferred_lock.unlock();
|
|
|
|
for (auto& txc : b->txcs) {
|
|
throttle.log_state_latency(txc, logger, l_bluestore_state_deferred_queued_lat);
|
|
}
|
|
uint64_t start = 0, pos = 0;
|
|
bufferlist bl;
|
|
auto i = b->iomap.begin();
|
|
while (true) {
|
|
if (i == b->iomap.end() || i->first != pos) {
|
|
if (bl.length()) {
|
|
dout(20) << __func__ << " write 0x" << std::hex
|
|
<< start << "~" << bl.length()
|
|
<< " crc " << bl.crc32c(-1) << std::dec << dendl;
|
|
if (!g_conf()->bluestore_debug_omit_block_device_write) {
|
|
logger->inc(l_bluestore_submitted_deferred_writes);
|
|
logger->inc(l_bluestore_submitted_deferred_write_bytes, bl.length());
|
|
int r = bdev->aio_write(start, bl, &b->ioc, false);
|
|
ceph_assert(r == 0);
|
|
}
|
|
}
|
|
if (i == b->iomap.end()) {
|
|
break;
|
|
}
|
|
start = 0;
|
|
pos = i->first;
|
|
bl.clear();
|
|
}
|
|
dout(20) << __func__ << " seq " << i->second.seq << " 0x"
|
|
<< std::hex << pos << "~" << i->second.bl.length() << std::dec
|
|
<< dendl;
|
|
if (!bl.length()) {
|
|
start = pos;
|
|
}
|
|
pos += i->second.bl.length();
|
|
bl.claim_append(i->second.bl);
|
|
++i;
|
|
}
|
|
|
|
bdev->aio_submit(&b->ioc);
|
|
}
|
|
|
|
struct C_DeferredTrySubmit : public Context {
|
|
BlueStore *store;
|
|
C_DeferredTrySubmit(BlueStore *s) : store(s) {}
|
|
void finish(int r) {
|
|
store->deferred_try_submit();
|
|
}
|
|
};
|
|
|
|
void BlueStore::_deferred_aio_finish(OpSequencer *osr)
|
|
{
|
|
dout(10) << __func__ << " osr " << osr << dendl;
|
|
ceph_assert(osr->deferred_running);
|
|
DeferredBatch *b = osr->deferred_running;
|
|
|
|
{
|
|
osr->deferred_lock.lock();
|
|
ceph_assert(osr->deferred_running == b);
|
|
osr->deferred_running = nullptr;
|
|
if (!osr->deferred_pending) {
|
|
dout(20) << __func__ << " dequeueing" << dendl;
|
|
{
|
|
deferred_lock.lock();
|
|
auto q = deferred_queue.iterator_to(*osr);
|
|
deferred_queue.erase(q);
|
|
deferred_lock.unlock();
|
|
}
|
|
osr->deferred_lock.unlock();
|
|
} else {
|
|
osr->deferred_lock.unlock();
|
|
if (deferred_aggressive) {
|
|
dout(20) << __func__ << " queuing async deferred_try_submit" << dendl;
|
|
finisher.queue(new C_DeferredTrySubmit(this));
|
|
} else {
|
|
dout(20) << __func__ << " leaving queued, more pending" << dendl;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
uint64_t costs = 0;
|
|
{
|
|
for (auto& i : b->txcs) {
|
|
TransContext *txc = &i;
|
|
throttle.log_state_latency(*txc, logger, l_bluestore_state_deferred_aio_wait_lat);
|
|
txc->set_state(TransContext::STATE_DEFERRED_CLEANUP);
|
|
costs += txc->cost;
|
|
}
|
|
}
|
|
throttle.release_deferred_throttle(costs);
|
|
}
|
|
|
|
{
|
|
std::lock_guard l(kv_lock);
|
|
deferred_done_queue.emplace_back(b);
|
|
|
|
// in the normal case, do not bother waking up the kv thread; it will
|
|
// catch us on the next commit anyway.
|
|
if (deferred_aggressive && !kv_sync_in_progress) {
|
|
kv_sync_in_progress = true;
|
|
kv_cond.notify_one();
|
|
}
|
|
}
|
|
}
|
|
|
|
int BlueStore::_deferred_replay()
|
|
{
|
|
dout(10) << __func__ << " start" << dendl;
|
|
int count = 0;
|
|
int r = 0;
|
|
interval_set<uint64_t> bluefs_extents;
|
|
if (bluefs) {
|
|
bluefs->foreach_block_extents(
|
|
bluefs_layout.shared_bdev,
|
|
[&] (uint64_t start, uint32_t len) {
|
|
bluefs_extents.insert(start, len);
|
|
}
|
|
);
|
|
}
|
|
CollectionRef ch = _get_collection(coll_t::meta());
|
|
bool fake_ch = false;
|
|
if (!ch) {
|
|
// hmm, replaying initial mkfs?
|
|
ch = static_cast<Collection*>(create_new_collection(coll_t::meta()).get());
|
|
fake_ch = true;
|
|
}
|
|
OpSequencer *osr = static_cast<OpSequencer*>(ch->osr.get());
|
|
KeyValueDB::Iterator it = db->get_iterator(PREFIX_DEFERRED);
|
|
for (it->lower_bound(string()); it->valid(); it->next(), ++count) {
|
|
dout(20) << __func__ << " replay " << pretty_binary_string(it->key())
|
|
<< dendl;
|
|
bluestore_deferred_transaction_t *deferred_txn =
|
|
new bluestore_deferred_transaction_t;
|
|
bufferlist bl = it->value();
|
|
auto p = bl.cbegin();
|
|
try {
|
|
decode(*deferred_txn, p);
|
|
} catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " failed to decode deferred txn "
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
delete deferred_txn;
|
|
r = -EIO;
|
|
goto out;
|
|
}
|
|
bool has_some = _eliminate_outdated_deferred(deferred_txn, bluefs_extents);
|
|
if (has_some) {
|
|
TransContext *txc = _txc_create(ch.get(), osr, nullptr);
|
|
txc->deferred_txn = deferred_txn;
|
|
txc->set_state(TransContext::STATE_KV_DONE);
|
|
_txc_state_proc(txc);
|
|
} else {
|
|
delete deferred_txn;
|
|
}
|
|
}
|
|
out:
|
|
dout(20) << __func__ << " draining osr" << dendl;
|
|
_osr_register_zombie(osr);
|
|
_osr_drain_all();
|
|
if (fake_ch) {
|
|
new_coll_map.clear();
|
|
}
|
|
dout(10) << __func__ << " completed " << count << " events" << dendl;
|
|
return r;
|
|
}
|
|
|
|
bool BlueStore::_eliminate_outdated_deferred(bluestore_deferred_transaction_t* deferred_txn,
|
|
interval_set<uint64_t>& bluefs_extents)
|
|
{
|
|
bool has_some = false;
|
|
dout(30) << __func__ << " bluefs_extents: " << std::hex << bluefs_extents << std::dec << dendl;
|
|
auto it = deferred_txn->ops.begin();
|
|
while (it != deferred_txn->ops.end()) {
|
|
// We process a pair of _data_/_extents_ (here: it->data/it->extents)
|
|
// by eliminating _extents_ that belong to bluefs, removing relevant parts of _data_
|
|
// example:
|
|
// +------------+---------------+---------------+---------------+
|
|
// | data | aaaaaaaabbbbb | bbbbcccccdddd | ddddeeeeeefff |
|
|
// | extent | 40000 - 44000 | 50000 - 58000 | 58000 - 60000 |
|
|
// | in bluefs? | no | yes | no |
|
|
// +------------+---------------+---------------+---------------+
|
|
// result:
|
|
// +------------+---------------+---------------+
|
|
// | data | aaaaaaaabbbbb | ddddeeeeeefff |
|
|
// | extent | 40000 - 44000 | 58000 - 60000 |
|
|
// +------------+---------------+---------------+
|
|
PExtentVector new_extents;
|
|
ceph::buffer::list new_data;
|
|
uint32_t data_offset = 0; // this tracks location of extent 'e' inside it->data
|
|
dout(30) << __func__ << " input extents: " << it->extents << dendl;
|
|
for (auto& e: it->extents) {
|
|
interval_set<uint64_t> region;
|
|
region.insert(e.offset, e.length);
|
|
|
|
auto mi = bluefs_extents.lower_bound(e.offset);
|
|
if (mi != bluefs_extents.begin()) {
|
|
--mi;
|
|
if (mi.get_end() <= e.offset) {
|
|
++mi;
|
|
}
|
|
}
|
|
while (mi != bluefs_extents.end() && mi.get_start() < e.offset + e.length) {
|
|
// The interval_set does not like (asserts) when we erase interval that does not exist.
|
|
// Hence we do we implement (region-mi) by ((region+mi)-mi).
|
|
region.union_insert(mi.get_start(), mi.get_len());
|
|
region.erase(mi.get_start(), mi.get_len());
|
|
++mi;
|
|
}
|
|
// 'region' is now a subset of e, without parts used by bluefs
|
|
// we trim coresponding parts from it->data (actally constructing new_data / new_extents)
|
|
for (auto ki = region.begin(); ki != region.end(); ki++) {
|
|
ceph::buffer::list chunk;
|
|
// A chunk from it->data; data_offset is a an offset where 'e' was located;
|
|
// 'ki.get_start() - e.offset' is an offset of ki inside 'e'.
|
|
chunk.substr_of(it->data, data_offset + (ki.get_start() - e.offset), ki.get_len());
|
|
new_data.claim_append(chunk);
|
|
new_extents.emplace_back(bluestore_pextent_t(ki.get_start(), ki.get_len()));
|
|
}
|
|
data_offset += e.length;
|
|
}
|
|
dout(30) << __func__ << " output extents: " << new_extents << dendl;
|
|
if (it->data.length() != new_data.length()) {
|
|
dout(10) << __func__ << " trimmed deferred extents: " << it->extents << "->" << new_extents << dendl;
|
|
}
|
|
if (new_extents.size() == 0) {
|
|
it = deferred_txn->ops.erase(it);
|
|
} else {
|
|
has_some = true;
|
|
std::swap(it->extents, new_extents);
|
|
std::swap(it->data, new_data);
|
|
++it;
|
|
}
|
|
}
|
|
return has_some;
|
|
}
|
|
|
|
// ---------------------------
|
|
// transactions
|
|
|
|
int BlueStore::queue_transactions(
|
|
CollectionHandle& ch,
|
|
vector<Transaction>& tls,
|
|
TrackedOpRef op,
|
|
ThreadPool::TPHandle *handle)
|
|
{
|
|
FUNCTRACE(cct);
|
|
list<Context *> on_applied, on_commit, on_applied_sync;
|
|
ObjectStore::Transaction::collect_contexts(
|
|
tls, &on_applied, &on_commit, &on_applied_sync);
|
|
|
|
auto start = mono_clock::now();
|
|
|
|
Collection *c = static_cast<Collection*>(ch.get());
|
|
OpSequencer *osr = c->osr.get();
|
|
dout(10) << __func__ << " ch " << c << " " << c->cid << dendl;
|
|
|
|
// prepare
|
|
TransContext *txc = _txc_create(static_cast<Collection*>(ch.get()), osr,
|
|
&on_commit, op);
|
|
|
|
for (vector<Transaction>::iterator p = tls.begin(); p != tls.end(); ++p) {
|
|
txc->bytes += (*p).get_num_bytes();
|
|
_txc_add_transaction(txc, &(*p));
|
|
}
|
|
_txc_calc_cost(txc);
|
|
|
|
_txc_write_nodes(txc, txc->t);
|
|
|
|
// journal deferred items
|
|
if (txc->deferred_txn) {
|
|
txc->deferred_txn->seq = ++deferred_seq;
|
|
bufferlist bl;
|
|
encode(*txc->deferred_txn, bl);
|
|
string key;
|
|
get_deferred_key(txc->deferred_txn->seq, &key);
|
|
txc->t->set(PREFIX_DEFERRED, key, bl);
|
|
}
|
|
|
|
_txc_finalize_kv(txc, txc->t);
|
|
|
|
#ifdef WITH_BLKIN
|
|
if (txc->trace) {
|
|
txc->trace.event("txc encode finished");
|
|
}
|
|
#endif
|
|
|
|
if (handle)
|
|
handle->suspend_tp_timeout();
|
|
|
|
auto tstart = mono_clock::now();
|
|
|
|
if (!throttle.try_start_transaction(
|
|
*db,
|
|
*txc,
|
|
tstart)) {
|
|
// ensure we do not block here because of deferred writes
|
|
dout(10) << __func__ << " failed get throttle_deferred_bytes, aggressive"
|
|
<< dendl;
|
|
++deferred_aggressive;
|
|
deferred_try_submit();
|
|
{
|
|
// wake up any previously finished deferred events
|
|
std::lock_guard l(kv_lock);
|
|
if (!kv_sync_in_progress) {
|
|
kv_sync_in_progress = true;
|
|
kv_cond.notify_one();
|
|
}
|
|
}
|
|
throttle.finish_start_transaction(*db, *txc, tstart);
|
|
--deferred_aggressive;
|
|
}
|
|
auto tend = mono_clock::now();
|
|
|
|
if (handle)
|
|
handle->reset_tp_timeout();
|
|
|
|
logger->inc(l_bluestore_txc);
|
|
|
|
// execute (start)
|
|
_txc_state_proc(txc);
|
|
|
|
// we're immediately readable (unlike FileStore)
|
|
for (auto c : on_applied_sync) {
|
|
c->complete(0);
|
|
}
|
|
if (!on_applied.empty()) {
|
|
if (c->commit_queue) {
|
|
c->commit_queue->queue(on_applied);
|
|
} else {
|
|
finisher.queue(on_applied);
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_BLKIN
|
|
if (txc->trace) {
|
|
txc->trace.event("txc applied");
|
|
}
|
|
#endif
|
|
|
|
log_latency("submit_transact",
|
|
l_bluestore_submit_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
log_latency("throttle_transact",
|
|
l_bluestore_throttle_lat,
|
|
tend - tstart,
|
|
cct->_conf->bluestore_log_op_age);
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_txc_aio_submit(TransContext *txc)
|
|
{
|
|
dout(10) << __func__ << " txc " << txc << dendl;
|
|
bdev->aio_submit(&txc->ioc);
|
|
}
|
|
|
|
void BlueStore::_txc_add_transaction(TransContext *txc, Transaction *t)
|
|
{
|
|
Transaction::iterator i = t->begin();
|
|
|
|
_dump_transaction<30>(cct, t);
|
|
|
|
vector<CollectionRef> cvec(i.colls.size());
|
|
unsigned j = 0;
|
|
for (vector<coll_t>::iterator p = i.colls.begin(); p != i.colls.end();
|
|
++p, ++j) {
|
|
cvec[j] = _get_collection(*p);
|
|
}
|
|
|
|
vector<OnodeRef> ovec(i.objects.size());
|
|
|
|
for (int pos = 0; i.have_op(); ++pos) {
|
|
Transaction::Op *op = i.decode_op();
|
|
int r = 0;
|
|
|
|
// no coll or obj
|
|
if (op->op == Transaction::OP_NOP)
|
|
continue;
|
|
|
|
|
|
// collection operations
|
|
CollectionRef &c = cvec[op->cid];
|
|
|
|
// initialize osd_pool_id and do a smoke test that all collections belong
|
|
// to the same pool
|
|
spg_t pgid;
|
|
if (!!c ? c->cid.is_pg(&pgid) : false) {
|
|
ceph_assert(txc->osd_pool_id == META_POOL_ID ||
|
|
txc->osd_pool_id == pgid.pool());
|
|
txc->osd_pool_id = pgid.pool();
|
|
}
|
|
|
|
switch (op->op) {
|
|
case Transaction::OP_RMCOLL:
|
|
{
|
|
const coll_t &cid = i.get_cid(op->cid);
|
|
r = _remove_collection(txc, cid, &c);
|
|
if (!r)
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_MKCOLL:
|
|
{
|
|
ceph_assert(!c);
|
|
const coll_t &cid = i.get_cid(op->cid);
|
|
r = _create_collection(txc, cid, op->split_bits, &c);
|
|
if (!r)
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_SPLIT_COLLECTION:
|
|
ceph_abort_msg("deprecated");
|
|
break;
|
|
|
|
case Transaction::OP_SPLIT_COLLECTION2:
|
|
{
|
|
uint32_t bits = op->split_bits;
|
|
uint32_t rem = op->split_rem;
|
|
r = _split_collection(txc, c, cvec[op->dest_cid], bits, rem);
|
|
if (!r)
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_MERGE_COLLECTION:
|
|
{
|
|
uint32_t bits = op->split_bits;
|
|
r = _merge_collection(txc, &c, cvec[op->dest_cid], bits);
|
|
if (!r)
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_COLL_HINT:
|
|
{
|
|
uint32_t type = op->hint;
|
|
bufferlist hint;
|
|
i.decode_bl(hint);
|
|
auto hiter = hint.cbegin();
|
|
if (type == Transaction::COLL_HINT_EXPECTED_NUM_OBJECTS) {
|
|
uint32_t pg_num;
|
|
uint64_t num_objs;
|
|
decode(pg_num, hiter);
|
|
decode(num_objs, hiter);
|
|
dout(10) << __func__ << " collection hint objects is a no-op, "
|
|
<< " pg_num " << pg_num << " num_objects " << num_objs
|
|
<< dendl;
|
|
} else {
|
|
// Ignore the hint
|
|
dout(10) << __func__ << " unknown collection hint " << type << dendl;
|
|
}
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_COLL_SETATTR:
|
|
r = -EOPNOTSUPP;
|
|
break;
|
|
|
|
case Transaction::OP_COLL_RMATTR:
|
|
r = -EOPNOTSUPP;
|
|
break;
|
|
|
|
case Transaction::OP_COLL_RENAME:
|
|
ceph_abort_msg("not implemented");
|
|
break;
|
|
}
|
|
if (r < 0) {
|
|
derr << __func__ << " error " << cpp_strerror(r)
|
|
<< " not handled on operation " << op->op
|
|
<< " (op " << pos << ", counting from 0)" << dendl;
|
|
_dump_transaction<0>(cct, t);
|
|
if (!g_conf().get_val<bool>("objectstore_debug_throw_on_failed_txc")) {
|
|
ceph_abort_msg("unexpected error");
|
|
} else {
|
|
txc->osr->undo_queue(txc);
|
|
delete txc;
|
|
throw r;
|
|
}
|
|
}
|
|
|
|
// these operations implicity create the object
|
|
bool create = false;
|
|
if (op->op == Transaction::OP_TOUCH ||
|
|
op->op == Transaction::OP_CREATE ||
|
|
op->op == Transaction::OP_WRITE ||
|
|
op->op == Transaction::OP_ZERO) {
|
|
create = true;
|
|
}
|
|
|
|
// object operations
|
|
std::unique_lock l(c->lock);
|
|
OnodeRef &o = ovec[op->oid];
|
|
if (!o) {
|
|
ghobject_t oid = i.get_oid(op->oid);
|
|
o = c->get_onode(oid, create, op->op == Transaction::OP_CREATE);
|
|
}
|
|
if (!create && (!o || !o->exists)) {
|
|
dout(10) << __func__ << " op " << op->op << " got ENOENT on "
|
|
<< i.get_oid(op->oid) << dendl;
|
|
r = -ENOENT;
|
|
goto endop;
|
|
}
|
|
|
|
switch (op->op) {
|
|
case Transaction::OP_CREATE:
|
|
case Transaction::OP_TOUCH:
|
|
r = _touch(txc, c, o);
|
|
break;
|
|
|
|
case Transaction::OP_WRITE:
|
|
{
|
|
uint64_t off = op->off;
|
|
uint64_t len = op->len;
|
|
uint32_t fadvise_flags = i.get_fadvise_flags();
|
|
bufferlist bl;
|
|
i.decode_bl(bl);
|
|
r = _write(txc, c, o, off, len, bl, fadvise_flags);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_ZERO:
|
|
{
|
|
uint64_t off = op->off;
|
|
uint64_t len = op->len;
|
|
r = _zero(txc, c, o, off, len);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_TRIMCACHE:
|
|
{
|
|
// deprecated, no-op
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_TRUNCATE:
|
|
{
|
|
uint64_t off = op->off;
|
|
r = _truncate(txc, c, o, off);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_REMOVE:
|
|
{
|
|
r = _remove(txc, c, o);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_SETATTR:
|
|
{
|
|
string name = i.decode_string();
|
|
bufferlist bl;
|
|
i.decode_bl(bl);
|
|
r = _setattr(txc, c, o, name, bl);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_SETATTRS:
|
|
{
|
|
map<string, bufferptr> aset;
|
|
i.decode_attrset(aset);
|
|
r = _setattrs(txc, c, o, aset);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_RMATTR:
|
|
{
|
|
string name = i.decode_string();
|
|
r = _rmattr(txc, c, o, name);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_RMATTRS:
|
|
{
|
|
r = _rmattrs(txc, c, o);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_CLONE:
|
|
{
|
|
OnodeRef& no = ovec[op->dest_oid];
|
|
if (!no) {
|
|
const ghobject_t& noid = i.get_oid(op->dest_oid);
|
|
no = c->get_onode(noid, true);
|
|
}
|
|
r = _clone(txc, c, o, no);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_CLONERANGE:
|
|
ceph_abort_msg("deprecated");
|
|
break;
|
|
|
|
case Transaction::OP_CLONERANGE2:
|
|
{
|
|
OnodeRef& no = ovec[op->dest_oid];
|
|
if (!no) {
|
|
const ghobject_t& noid = i.get_oid(op->dest_oid);
|
|
no = c->get_onode(noid, true);
|
|
}
|
|
uint64_t srcoff = op->off;
|
|
uint64_t len = op->len;
|
|
uint64_t dstoff = op->dest_off;
|
|
r = _clone_range(txc, c, o, no, srcoff, len, dstoff);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_COLL_ADD:
|
|
ceph_abort_msg("not implemented");
|
|
break;
|
|
|
|
case Transaction::OP_COLL_REMOVE:
|
|
ceph_abort_msg("not implemented");
|
|
break;
|
|
|
|
case Transaction::OP_COLL_MOVE:
|
|
ceph_abort_msg("deprecated");
|
|
break;
|
|
|
|
case Transaction::OP_COLL_MOVE_RENAME:
|
|
case Transaction::OP_TRY_RENAME:
|
|
{
|
|
ceph_assert(op->cid == op->dest_cid);
|
|
const ghobject_t& noid = i.get_oid(op->dest_oid);
|
|
OnodeRef& no = ovec[op->dest_oid];
|
|
if (!no) {
|
|
no = c->get_onode(noid, false);
|
|
}
|
|
r = _rename(txc, c, o, no, noid);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_OMAP_CLEAR:
|
|
{
|
|
r = _omap_clear(txc, c, o);
|
|
}
|
|
break;
|
|
case Transaction::OP_OMAP_SETKEYS:
|
|
{
|
|
bufferlist aset_bl;
|
|
i.decode_attrset_bl(&aset_bl);
|
|
r = _omap_setkeys(txc, c, o, aset_bl);
|
|
}
|
|
break;
|
|
case Transaction::OP_OMAP_RMKEYS:
|
|
{
|
|
bufferlist keys_bl;
|
|
i.decode_keyset_bl(&keys_bl);
|
|
r = _omap_rmkeys(txc, c, o, keys_bl);
|
|
}
|
|
break;
|
|
case Transaction::OP_OMAP_RMKEYRANGE:
|
|
{
|
|
string first, last;
|
|
first = i.decode_string();
|
|
last = i.decode_string();
|
|
r = _omap_rmkey_range(txc, c, o, first, last);
|
|
}
|
|
break;
|
|
case Transaction::OP_OMAP_SETHEADER:
|
|
{
|
|
bufferlist bl;
|
|
i.decode_bl(bl);
|
|
r = _omap_setheader(txc, c, o, bl);
|
|
}
|
|
break;
|
|
|
|
case Transaction::OP_SETALLOCHINT:
|
|
{
|
|
r = _set_alloc_hint(txc, c, o,
|
|
op->expected_object_size,
|
|
op->expected_write_size,
|
|
op->hint);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
derr << __func__ << " bad op " << op->op << dendl;
|
|
ceph_abort();
|
|
}
|
|
|
|
endop:
|
|
if (r < 0) {
|
|
bool ok = false;
|
|
|
|
if (r == -ENOENT && !(op->op == Transaction::OP_CLONERANGE ||
|
|
op->op == Transaction::OP_CLONE ||
|
|
op->op == Transaction::OP_CLONERANGE2 ||
|
|
op->op == Transaction::OP_COLL_ADD ||
|
|
op->op == Transaction::OP_SETATTR ||
|
|
op->op == Transaction::OP_SETATTRS ||
|
|
op->op == Transaction::OP_RMATTR ||
|
|
op->op == Transaction::OP_OMAP_SETKEYS ||
|
|
op->op == Transaction::OP_OMAP_RMKEYS ||
|
|
op->op == Transaction::OP_OMAP_RMKEYRANGE ||
|
|
op->op == Transaction::OP_OMAP_SETHEADER))
|
|
// -ENOENT is usually okay
|
|
ok = true;
|
|
if (r == -ENODATA)
|
|
ok = true;
|
|
|
|
if (!ok) {
|
|
const char *msg = "unexpected error code";
|
|
|
|
if (r == -ENOENT && (op->op == Transaction::OP_CLONERANGE ||
|
|
op->op == Transaction::OP_CLONE ||
|
|
op->op == Transaction::OP_CLONERANGE2))
|
|
msg = "ENOENT on clone suggests osd bug";
|
|
|
|
if (r == -ENOSPC)
|
|
// For now, if we hit _any_ ENOSPC, crash, before we do any damage
|
|
// by partially applying transactions.
|
|
msg = "ENOSPC from bluestore, misconfigured cluster";
|
|
|
|
if (r == -ENOTEMPTY) {
|
|
msg = "ENOTEMPTY suggests garbage data in osd data dir";
|
|
}
|
|
|
|
derr << __func__ << " error " << cpp_strerror(r)
|
|
<< " not handled on operation " << op->op
|
|
<< " (op " << pos << ", counting from 0)"
|
|
<< dendl;
|
|
derr << msg << dendl;
|
|
_dump_transaction<0>(cct, t);
|
|
if (!g_conf().get_val<bool>("objectstore_debug_throw_on_failed_txc")) {
|
|
ceph_abort_msg("unexpected error");
|
|
} else {
|
|
txc->osr->undo_queue(txc);
|
|
delete txc;
|
|
throw r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// -----------------
|
|
// write operations
|
|
|
|
int BlueStore::_touch(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
int r = 0;
|
|
_assign_nid(txc, o);
|
|
txc->write_onode(o);
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_pad_zeros(
|
|
bufferlist *bl, uint64_t *offset,
|
|
uint64_t chunk_size)
|
|
{
|
|
auto length = bl->length();
|
|
dout(30) << __func__ << " 0x" << std::hex << *offset << "~" << length
|
|
<< " chunk_size 0x" << chunk_size << std::dec << dendl;
|
|
dout(40) << "before:\n";
|
|
bl->hexdump(*_dout);
|
|
*_dout << dendl;
|
|
// front
|
|
size_t front_pad = *offset % chunk_size;
|
|
size_t back_pad = 0;
|
|
size_t pad_count = 0;
|
|
if (front_pad) {
|
|
size_t front_copy = std::min<uint64_t>(chunk_size - front_pad, length);
|
|
bufferptr z = ceph::buffer::create_small_page_aligned(chunk_size);
|
|
z.zero(0, front_pad, false);
|
|
pad_count += front_pad;
|
|
bl->begin().copy(front_copy, z.c_str() + front_pad);
|
|
if (front_copy + front_pad < chunk_size) {
|
|
back_pad = chunk_size - (length + front_pad);
|
|
z.zero(front_pad + length, back_pad, false);
|
|
pad_count += back_pad;
|
|
}
|
|
bufferlist old, t;
|
|
old.swap(*bl);
|
|
t.substr_of(old, front_copy, length - front_copy);
|
|
bl->append(z);
|
|
bl->claim_append(t);
|
|
*offset -= front_pad;
|
|
length += pad_count;
|
|
}
|
|
|
|
// back
|
|
uint64_t end = *offset + length;
|
|
unsigned back_copy = end % chunk_size;
|
|
if (back_copy) {
|
|
ceph_assert(back_pad == 0);
|
|
back_pad = chunk_size - back_copy;
|
|
ceph_assert(back_copy <= length);
|
|
bufferptr tail(chunk_size);
|
|
bl->begin(length - back_copy).copy(back_copy, tail.c_str());
|
|
tail.zero(back_copy, back_pad, false);
|
|
bufferlist old;
|
|
old.swap(*bl);
|
|
bl->substr_of(old, 0, length - back_copy);
|
|
bl->append(tail);
|
|
length += back_pad;
|
|
pad_count += back_pad;
|
|
}
|
|
dout(20) << __func__ << " pad 0x" << std::hex << front_pad << " + 0x"
|
|
<< back_pad << " on front/back, now 0x" << *offset << "~"
|
|
<< length << std::dec << dendl;
|
|
dout(40) << "after:\n";
|
|
bl->hexdump(*_dout);
|
|
*_dout << dendl;
|
|
if (pad_count)
|
|
logger->inc(l_bluestore_write_pad_bytes, pad_count);
|
|
ceph_assert(bl->length() == length);
|
|
}
|
|
|
|
void BlueStore::_do_write_small(
|
|
TransContext *txc,
|
|
CollectionRef &c,
|
|
OnodeRef& o,
|
|
uint64_t offset, uint64_t length,
|
|
bufferlist::iterator& blp,
|
|
WriteContext *wctx)
|
|
{
|
|
dout(10) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
ceph_assert(length < min_alloc_size);
|
|
|
|
uint64_t end_offs = offset + length;
|
|
|
|
logger->inc(l_bluestore_write_small);
|
|
logger->inc(l_bluestore_write_small_bytes, length);
|
|
|
|
bufferlist bl;
|
|
blp.copy(length, bl);
|
|
|
|
auto max_bsize = std::max(wctx->target_blob_size, min_alloc_size);
|
|
auto min_off = offset >= max_bsize ? offset - max_bsize : 0;
|
|
uint32_t alloc_len = min_alloc_size;
|
|
auto offset0 = p2align<uint64_t>(offset, alloc_len);
|
|
|
|
bool any_change;
|
|
|
|
// search suitable extent in both forward and reverse direction in
|
|
// [offset - target_max_blob_size, offset + target_max_blob_size] range
|
|
// then check if blob can be reused via can_reuse_blob func or apply
|
|
// direct/deferred write (the latter for extents including or higher
|
|
// than 'offset' only).
|
|
o->extent_map.fault_range(db, min_off, offset + max_bsize - min_off);
|
|
|
|
// Look for an existing mutable blob we can use.
|
|
auto begin = o->extent_map.extent_map.begin();
|
|
auto end = o->extent_map.extent_map.end();
|
|
auto ep = o->extent_map.seek_lextent(offset);
|
|
if (ep != begin) {
|
|
--ep;
|
|
if (ep->blob_end() <= offset) {
|
|
++ep;
|
|
}
|
|
}
|
|
auto prev_ep = end;
|
|
if (ep != begin) {
|
|
prev_ep = ep;
|
|
--prev_ep;
|
|
}
|
|
|
|
boost::container::flat_set<const bluestore_blob_t*> inspected_blobs;
|
|
// We don't want to have more blobs than min alloc units fit
|
|
// into 2 max blobs
|
|
size_t blob_threshold = max_blob_size / min_alloc_size * 2 + 1;
|
|
bool above_blob_threshold = false;
|
|
|
|
inspected_blobs.reserve(blob_threshold);
|
|
|
|
uint64_t max_off = 0;
|
|
auto start_ep = ep;
|
|
auto end_ep = ep; // exclusively
|
|
do {
|
|
any_change = false;
|
|
|
|
if (ep != end && ep->logical_offset < offset + max_bsize) {
|
|
BlobRef b = ep->blob;
|
|
if (!above_blob_threshold) {
|
|
inspected_blobs.insert(&b->get_blob());
|
|
above_blob_threshold = inspected_blobs.size() >= blob_threshold;
|
|
}
|
|
max_off = ep->logical_end();
|
|
auto bstart = ep->blob_start();
|
|
|
|
dout(20) << __func__ << " considering " << *b
|
|
<< " bstart 0x" << std::hex << bstart << std::dec << dendl;
|
|
if (bstart >= end_offs) {
|
|
dout(20) << __func__ << " ignoring distant " << *b << dendl;
|
|
} else if (!b->get_blob().is_mutable()) {
|
|
dout(20) << __func__ << " ignoring immutable " << *b << dendl;
|
|
} else if (ep->logical_offset % min_alloc_size !=
|
|
ep->blob_offset % min_alloc_size) {
|
|
dout(20) << __func__ << " ignoring offset-skewed " << *b << dendl;
|
|
} else {
|
|
uint64_t chunk_size = b->get_blob().get_chunk_size(block_size);
|
|
// can we pad our head/tail out with zeros?
|
|
uint64_t head_pad, tail_pad;
|
|
head_pad = p2phase(offset, chunk_size);
|
|
tail_pad = p2nphase(end_offs, chunk_size);
|
|
if (head_pad || tail_pad) {
|
|
o->extent_map.fault_range(db, offset - head_pad,
|
|
end_offs - offset + head_pad + tail_pad);
|
|
}
|
|
if (head_pad &&
|
|
o->extent_map.has_any_lextents(offset - head_pad, head_pad)) {
|
|
head_pad = 0;
|
|
}
|
|
if (tail_pad && o->extent_map.has_any_lextents(end_offs, tail_pad)) {
|
|
tail_pad = 0;
|
|
}
|
|
|
|
uint64_t b_off = offset - head_pad - bstart;
|
|
uint64_t b_len = length + head_pad + tail_pad;
|
|
|
|
// direct write into unused blocks of an existing mutable blob?
|
|
if ((b_off % chunk_size == 0 && b_len % chunk_size == 0) &&
|
|
b->get_blob().get_ondisk_length() >= b_off + b_len &&
|
|
b->get_blob().is_unused(b_off, b_len) &&
|
|
b->get_blob().is_allocated(b_off, b_len)) {
|
|
_buffer_cache_write(txc, o, offset, bl,
|
|
wctx->buffered ? 0 : Buffer::FLAG_NOCACHE);
|
|
_apply_padding(head_pad, tail_pad, bl);
|
|
|
|
dout(20) << __func__ << " write to unused 0x" << std::hex << b_off
|
|
<< "~" << b_len << " pad 0x" << head_pad << " + 0x"
|
|
<< tail_pad << std::dec << " of mutable " << *b << dendl;
|
|
|
|
if (!g_conf()->bluestore_debug_omit_block_device_write) {
|
|
if (b_len < prefer_deferred_size) {
|
|
dout(20) << __func__ << " deferring small 0x" << std::hex
|
|
<< b_len << std::dec << " unused write via deferred" << dendl;
|
|
bluestore_deferred_op_t *op = _get_deferred_op(txc, bl.length());
|
|
op->op = bluestore_deferred_op_t::OP_WRITE;
|
|
b->get_blob().map(
|
|
b_off, b_len,
|
|
[&](uint64_t offset, uint64_t length) {
|
|
op->extents.emplace_back(bluestore_pextent_t(offset, length));
|
|
return 0;
|
|
});
|
|
op->data = bl;
|
|
} else {
|
|
b->get_blob().map_bl(
|
|
b_off, bl,
|
|
[&](uint64_t offset, bufferlist& t) {
|
|
bdev->aio_write(offset, t,
|
|
&txc->ioc, wctx->buffered);
|
|
});
|
|
}
|
|
}
|
|
b->dirty_blob().calc_csum(b_off, bl);
|
|
dout(20) << __func__ << " lex old " << *ep << dendl;
|
|
Extent *le = o->extent_map.set_lextent(c, offset, b_off + head_pad, length,
|
|
b,
|
|
&wctx->old_extents);
|
|
b->dirty_blob().mark_used(le->blob_offset, le->length);
|
|
|
|
txc->statfs_delta.stored() += le->length;
|
|
dout(20) << __func__ << " lex " << *le << dendl;
|
|
logger->inc(l_bluestore_write_small_unused);
|
|
return;
|
|
}
|
|
// read some data to fill out the chunk?
|
|
uint64_t head_read = p2phase(b_off, chunk_size);
|
|
uint64_t tail_read = p2nphase(b_off + b_len, chunk_size);
|
|
if ((head_read || tail_read) &&
|
|
(b->get_blob().get_ondisk_length() >= b_off + b_len + tail_read) &&
|
|
head_read + tail_read < min_alloc_size) {
|
|
b_off -= head_read;
|
|
b_len += head_read + tail_read;
|
|
|
|
} else {
|
|
head_read = tail_read = 0;
|
|
}
|
|
|
|
// chunk-aligned deferred overwrite?
|
|
if (b->get_blob().get_ondisk_length() >= b_off + b_len &&
|
|
b_off % chunk_size == 0 &&
|
|
b_len % chunk_size == 0 &&
|
|
b->get_blob().is_allocated(b_off, b_len)) {
|
|
|
|
_apply_padding(head_pad, tail_pad, bl);
|
|
|
|
dout(20) << __func__ << " reading head 0x" << std::hex << head_read
|
|
<< " and tail 0x" << tail_read << std::dec << dendl;
|
|
if (head_read) {
|
|
bufferlist head_bl;
|
|
int r = _do_read(c.get(), o, offset - head_pad - head_read, head_read,
|
|
head_bl, 0);
|
|
ceph_assert(r >= 0 && r <= (int)head_read);
|
|
size_t zlen = head_read - r;
|
|
if (zlen) {
|
|
head_bl.append_zero(zlen);
|
|
logger->inc(l_bluestore_write_pad_bytes, zlen);
|
|
}
|
|
head_bl.claim_append(bl);
|
|
bl.swap(head_bl);
|
|
logger->inc(l_bluestore_write_penalty_read_ops);
|
|
}
|
|
if (tail_read) {
|
|
bufferlist tail_bl;
|
|
int r = _do_read(c.get(), o, offset + length + tail_pad, tail_read,
|
|
tail_bl, 0);
|
|
ceph_assert(r >= 0 && r <= (int)tail_read);
|
|
size_t zlen = tail_read - r;
|
|
if (zlen) {
|
|
tail_bl.append_zero(zlen);
|
|
logger->inc(l_bluestore_write_pad_bytes, zlen);
|
|
}
|
|
bl.claim_append(tail_bl);
|
|
logger->inc(l_bluestore_write_penalty_read_ops);
|
|
}
|
|
logger->inc(l_bluestore_write_small_pre_read);
|
|
|
|
_buffer_cache_write(txc, o, offset - head_read - head_pad, bl,
|
|
wctx->buffered ? 0 : Buffer::FLAG_NOCACHE);
|
|
|
|
b->dirty_blob().calc_csum(b_off, bl);
|
|
|
|
if (!g_conf()->bluestore_debug_omit_block_device_write) {
|
|
bluestore_deferred_op_t *op = _get_deferred_op(txc, bl.length());
|
|
op->op = bluestore_deferred_op_t::OP_WRITE;
|
|
int r = b->get_blob().map(
|
|
b_off, b_len,
|
|
[&](uint64_t offset, uint64_t length) {
|
|
op->extents.emplace_back(bluestore_pextent_t(offset, length));
|
|
return 0;
|
|
});
|
|
ceph_assert(r == 0);
|
|
op->data = std::move(bl);
|
|
dout(20) << __func__ << " deferred write 0x" << std::hex << b_off
|
|
<< "~" << b_len << std::dec << " of mutable " << *b << " at "
|
|
<< op->extents << dendl;
|
|
}
|
|
|
|
Extent *le = o->extent_map.set_lextent(c, offset, offset - bstart, length,
|
|
b, &wctx->old_extents);
|
|
b->dirty_blob().mark_used(le->blob_offset, le->length);
|
|
txc->statfs_delta.stored() += le->length;
|
|
dout(20) << __func__ << " lex " << *le << dendl;
|
|
return;
|
|
}
|
|
// try to reuse blob if we can
|
|
if (b->can_reuse_blob(min_alloc_size,
|
|
max_bsize,
|
|
offset0 - bstart,
|
|
&alloc_len)) {
|
|
ceph_assert(alloc_len == min_alloc_size); // expecting data always
|
|
// fit into reused blob
|
|
// Need to check for pending writes desiring to
|
|
// reuse the same pextent. The rationale is that during GC two chunks
|
|
// from garbage blobs(compressed?) can share logical space within the same
|
|
// AU. That's in turn might be caused by unaligned len in clone_range2.
|
|
// Hence the second write will fail in an attempt to reuse blob at
|
|
// do_alloc_write().
|
|
if (!wctx->has_conflict(b,
|
|
offset0,
|
|
offset0 + alloc_len,
|
|
min_alloc_size)) {
|
|
|
|
// we can't reuse pad_head/pad_tail since they might be truncated
|
|
// due to existent extents
|
|
uint64_t b_off = offset - bstart;
|
|
uint64_t b_off0 = b_off;
|
|
o->extent_map.punch_hole(c, offset, length, &wctx->old_extents);
|
|
|
|
// Zero detection -- small block
|
|
if (!cct->_conf->bluestore_zero_block_detection || !bl.is_zero()) {
|
|
_pad_zeros(&bl, &b_off0, chunk_size);
|
|
|
|
dout(20) << __func__ << " reuse blob " << *b << std::hex
|
|
<< " (0x" << b_off0 << "~" << bl.length() << ")"
|
|
<< " (0x" << b_off << "~" << length << ")"
|
|
<< std::dec << dendl;
|
|
|
|
wctx->write(offset, b, alloc_len, b_off0, bl, b_off, length,
|
|
false, false);
|
|
logger->inc(l_bluestore_write_small_unused);
|
|
} else { // if (bl.is_zero())
|
|
dout(20) << __func__ << " skip small zero block " << std::hex
|
|
<< " (0x" << b_off0 << "~" << bl.length() << ")"
|
|
<< " (0x" << b_off << "~" << length << ")"
|
|
<< std::dec << dendl;
|
|
logger->inc(l_bluestore_write_small_skipped);
|
|
logger->inc(l_bluestore_write_small_skipped_bytes, length);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
++ep;
|
|
end_ep = ep;
|
|
any_change = true;
|
|
} // if (ep != end && ep->logical_offset < offset + max_bsize)
|
|
|
|
// check extent for reuse in reverse order
|
|
if (prev_ep != end && prev_ep->logical_offset >= min_off) {
|
|
BlobRef b = prev_ep->blob;
|
|
if (!above_blob_threshold) {
|
|
inspected_blobs.insert(&b->get_blob());
|
|
above_blob_threshold = inspected_blobs.size() >= blob_threshold;
|
|
}
|
|
start_ep = prev_ep;
|
|
auto bstart = prev_ep->blob_start();
|
|
dout(20) << __func__ << " considering " << *b
|
|
<< " bstart 0x" << std::hex << bstart << std::dec << dendl;
|
|
if (b->can_reuse_blob(min_alloc_size,
|
|
max_bsize,
|
|
offset0 - bstart,
|
|
&alloc_len)) {
|
|
ceph_assert(alloc_len == min_alloc_size); // expecting data always
|
|
// fit into reused blob
|
|
// Need to check for pending writes desiring to
|
|
// reuse the same pextent. The rationale is that during GC two chunks
|
|
// from garbage blobs(compressed?) can share logical space within the same
|
|
// AU. That's in turn might be caused by unaligned len in clone_range2.
|
|
// Hence the second write will fail in an attempt to reuse blob at
|
|
// do_alloc_write().
|
|
if (!wctx->has_conflict(b,
|
|
offset0,
|
|
offset0 + alloc_len,
|
|
min_alloc_size)) {
|
|
|
|
uint64_t b_off = offset - bstart;
|
|
uint64_t b_off0 = b_off;
|
|
o->extent_map.punch_hole(c, offset, length, &wctx->old_extents);
|
|
|
|
// Zero detection -- small block
|
|
if (!cct->_conf->bluestore_zero_block_detection || !bl.is_zero()) {
|
|
uint64_t chunk_size = b->get_blob().get_chunk_size(block_size);
|
|
_pad_zeros(&bl, &b_off0, chunk_size);
|
|
|
|
dout(20) << __func__ << " reuse blob " << *b << std::hex
|
|
<< " (0x" << b_off0 << "~" << bl.length() << ")"
|
|
<< " (0x" << b_off << "~" << length << ")"
|
|
<< std::dec << dendl;
|
|
|
|
wctx->write(offset, b, alloc_len, b_off0, bl, b_off, length,
|
|
false, false);
|
|
logger->inc(l_bluestore_write_small_unused);
|
|
} else { // if (bl.is_zero())
|
|
dout(20) << __func__ << " skip small zero block " << std::hex
|
|
<< " (0x" << b_off0 << "~" << bl.length() << ")"
|
|
<< " (0x" << b_off << "~" << length << ")"
|
|
<< std::dec << dendl;
|
|
logger->inc(l_bluestore_write_small_skipped);
|
|
logger->inc(l_bluestore_write_small_skipped_bytes, length);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
if (prev_ep != begin) {
|
|
--prev_ep;
|
|
any_change = true;
|
|
} else {
|
|
prev_ep = end; // to avoid useless first extent re-check
|
|
}
|
|
} // if (prev_ep != end && prev_ep->logical_offset >= min_off)
|
|
} while (any_change);
|
|
|
|
if (above_blob_threshold) {
|
|
dout(10) << __func__ << " request GC, blobs >= " << inspected_blobs.size()
|
|
<< " " << std::hex << min_off << "~" << max_off << std::dec
|
|
<< dendl;
|
|
ceph_assert(start_ep != end_ep);
|
|
for (auto ep = start_ep; ep != end_ep; ++ep) {
|
|
dout(20) << __func__ << " inserting for GC "
|
|
<< std::hex << ep->logical_offset << "~" << ep->length
|
|
<< std::dec << dendl;
|
|
|
|
wctx->extents_to_gc.union_insert(ep->logical_offset, ep->length);
|
|
}
|
|
// insert newly written extent to GC
|
|
wctx->extents_to_gc.union_insert(offset, length);
|
|
dout(20) << __func__ << " inserting (last) for GC "
|
|
<< std::hex << offset << "~" << length
|
|
<< std::dec << dendl;
|
|
}
|
|
uint64_t b_off = p2phase<uint64_t>(offset, alloc_len);
|
|
uint64_t b_off0 = b_off;
|
|
o->extent_map.punch_hole(c, offset, length, &wctx->old_extents);
|
|
|
|
// Zero detection -- small block
|
|
if (!cct->_conf->bluestore_zero_block_detection || !bl.is_zero()) {
|
|
// new blob.
|
|
BlobRef b = c->new_blob();
|
|
_pad_zeros(&bl, &b_off0, block_size);
|
|
wctx->write(offset, b, alloc_len, b_off0, bl, b_off, length,
|
|
min_alloc_size != block_size, // use 'unused' bitmap when alloc granularity
|
|
// doesn't match disk one only
|
|
true);
|
|
} else { // if (bl.is_zero())
|
|
dout(20) << __func__ << " skip small zero block " << std::hex
|
|
<< " (0x" << b_off0 << "~" << bl.length() << ")"
|
|
<< " (0x" << b_off << "~" << length << ")"
|
|
<< std::dec << dendl;
|
|
logger->inc(l_bluestore_write_small_skipped);
|
|
logger->inc(l_bluestore_write_small_skipped_bytes, length);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool BlueStore::BigDeferredWriteContext::can_defer(
|
|
BlueStore::extent_map_t::iterator ep,
|
|
uint64_t prefer_deferred_size,
|
|
uint64_t block_size,
|
|
uint64_t offset,
|
|
uint64_t l)
|
|
{
|
|
bool res = false;
|
|
auto& blob = ep->blob->get_blob();
|
|
if (offset >= ep->blob_start() &&
|
|
blob.is_mutable()) {
|
|
off = offset;
|
|
b_off = offset - ep->blob_start();
|
|
uint64_t chunk_size = blob.get_chunk_size(block_size);
|
|
uint64_t ondisk = blob.get_ondisk_length();
|
|
used = std::min(l, ondisk - b_off);
|
|
|
|
// will read some data to fill out the chunk?
|
|
head_read = p2phase<uint64_t>(b_off, chunk_size);
|
|
tail_read = p2nphase<uint64_t>(b_off + used, chunk_size);
|
|
b_off -= head_read;
|
|
|
|
ceph_assert(b_off % chunk_size == 0);
|
|
ceph_assert(blob_aligned_len() % chunk_size == 0);
|
|
|
|
res = blob_aligned_len() < prefer_deferred_size &&
|
|
blob_aligned_len() <= ondisk &&
|
|
blob.is_allocated(b_off, blob_aligned_len());
|
|
if (res) {
|
|
blob_ref = ep->blob;
|
|
blob_start = ep->blob_start();
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
bool BlueStore::BigDeferredWriteContext::apply_defer()
|
|
{
|
|
int r = blob_ref->get_blob().map(
|
|
b_off, blob_aligned_len(),
|
|
[&](const bluestore_pextent_t& pext,
|
|
uint64_t offset,
|
|
uint64_t length) {
|
|
// apply deferred if overwrite breaks blob continuity only.
|
|
// if it totally overlaps some pextent - fallback to regular write
|
|
if (pext.offset < offset ||
|
|
pext.end() > offset + length) {
|
|
res_extents.emplace_back(bluestore_pextent_t(offset, length));
|
|
return 0;
|
|
}
|
|
return -1;
|
|
});
|
|
return r >= 0;
|
|
}
|
|
|
|
void BlueStore::_do_write_big_apply_deferred(
|
|
TransContext* txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
BlueStore::BigDeferredWriteContext& dctx,
|
|
bufferlist::iterator& blp,
|
|
WriteContext* wctx)
|
|
{
|
|
bufferlist bl;
|
|
dout(20) << __func__ << " reading head 0x" << std::hex << dctx.head_read
|
|
<< " and tail 0x" << dctx.tail_read << std::dec << dendl;
|
|
if (dctx.head_read) {
|
|
int r = _do_read(c.get(), o,
|
|
dctx.off - dctx.head_read,
|
|
dctx.head_read,
|
|
bl,
|
|
0);
|
|
ceph_assert(r >= 0 && r <= (int)dctx.head_read);
|
|
size_t zlen = dctx.head_read - r;
|
|
if (zlen) {
|
|
bl.append_zero(zlen);
|
|
logger->inc(l_bluestore_write_pad_bytes, zlen);
|
|
}
|
|
logger->inc(l_bluestore_write_penalty_read_ops);
|
|
}
|
|
blp.copy(dctx.used, bl);
|
|
|
|
if (dctx.tail_read) {
|
|
bufferlist tail_bl;
|
|
int r = _do_read(c.get(), o,
|
|
dctx.off + dctx.used, dctx.tail_read,
|
|
tail_bl, 0);
|
|
ceph_assert(r >= 0 && r <= (int)dctx.tail_read);
|
|
size_t zlen = dctx.tail_read - r;
|
|
if (zlen) {
|
|
tail_bl.append_zero(zlen);
|
|
logger->inc(l_bluestore_write_pad_bytes, zlen);
|
|
}
|
|
bl.claim_append(tail_bl);
|
|
logger->inc(l_bluestore_write_penalty_read_ops);
|
|
}
|
|
auto& b0 = dctx.blob_ref;
|
|
_buffer_cache_write(txc, o, dctx.off - dctx.head_read, bl,
|
|
wctx->buffered ? 0 : Buffer::FLAG_NOCACHE);
|
|
|
|
b0->dirty_blob().calc_csum(dctx.b_off, bl);
|
|
|
|
Extent* le = o->extent_map.set_lextent(c, dctx.off,
|
|
dctx.off - dctx.blob_start, dctx.used, b0, &wctx->old_extents);
|
|
|
|
// in fact this is a no-op for big writes but left here to maintain
|
|
// uniformity and avoid missing after some refactor.
|
|
b0->dirty_blob().mark_used(le->blob_offset, le->length);
|
|
txc->statfs_delta.stored() += le->length;
|
|
|
|
if (!g_conf()->bluestore_debug_omit_block_device_write) {
|
|
bluestore_deferred_op_t* op = _get_deferred_op(txc, bl.length());
|
|
op->op = bluestore_deferred_op_t::OP_WRITE;
|
|
op->extents.swap(dctx.res_extents);
|
|
op->data = std::move(bl);
|
|
}
|
|
}
|
|
|
|
void BlueStore::_do_write_big(
|
|
TransContext *txc,
|
|
CollectionRef &c,
|
|
OnodeRef& o,
|
|
uint64_t offset, uint64_t length,
|
|
bufferlist::iterator& blp,
|
|
WriteContext *wctx)
|
|
{
|
|
dout(10) << __func__ << " 0x" << std::hex << offset << "~" << length
|
|
<< " target_blob_size 0x" << wctx->target_blob_size << std::dec
|
|
<< " compress " << (int)wctx->compress
|
|
<< dendl;
|
|
logger->inc(l_bluestore_write_big);
|
|
logger->inc(l_bluestore_write_big_bytes, length);
|
|
auto max_bsize = std::max(wctx->target_blob_size, min_alloc_size);
|
|
uint64_t prefer_deferred_size_snapshot = prefer_deferred_size.load();
|
|
while (length > 0) {
|
|
bool new_blob = false;
|
|
BlobRef b;
|
|
uint32_t b_off = 0;
|
|
uint32_t l = 0;
|
|
|
|
//attempting to reuse existing blob
|
|
if (!wctx->compress) {
|
|
// enforce target blob alignment with max_bsize
|
|
l = max_bsize - p2phase(offset, max_bsize);
|
|
l = std::min(uint64_t(l), length);
|
|
|
|
auto end = o->extent_map.extent_map.end();
|
|
|
|
dout(20) << __func__ << " may be defer: 0x" << std::hex
|
|
<< offset << "~" << l
|
|
<< std::dec << dendl;
|
|
|
|
if (prefer_deferred_size_snapshot &&
|
|
l <= prefer_deferred_size_snapshot * 2) {
|
|
// Single write that spans two adjusted existing blobs can result
|
|
// in up to two deferred blocks of 'prefer_deferred_size'
|
|
// So we're trying to minimize the amount of resulting blobs
|
|
// and preserve 2 blobs rather than inserting one more in between
|
|
// E.g. write 0x10000~20000 over existing blobs
|
|
// (0x0~20000 and 0x20000~20000) is better (from subsequent reading
|
|
// performance point of view) to result in two deferred writes to
|
|
// existing blobs than having 3 blobs: 0x0~10000, 0x10000~20000, 0x30000~10000
|
|
|
|
// look for an existing mutable blob we can write into
|
|
auto ep = o->extent_map.seek_lextent(offset);
|
|
auto ep_next = end;
|
|
BigDeferredWriteContext head_info, tail_info;
|
|
|
|
bool will_defer = ep != end ?
|
|
head_info.can_defer(ep,
|
|
prefer_deferred_size_snapshot,
|
|
block_size,
|
|
offset,
|
|
l) :
|
|
false;
|
|
auto offset_next = offset + head_info.used;
|
|
auto remaining = l - head_info.used;
|
|
if (will_defer && remaining) {
|
|
will_defer = false;
|
|
if (remaining <= prefer_deferred_size_snapshot) {
|
|
ep_next = o->extent_map.seek_lextent(offset_next);
|
|
// check if we can defer remaining totally
|
|
will_defer = ep_next == end ?
|
|
false :
|
|
tail_info.can_defer(ep_next,
|
|
prefer_deferred_size_snapshot,
|
|
block_size,
|
|
offset_next,
|
|
remaining);
|
|
will_defer = will_defer && remaining == tail_info.used;
|
|
}
|
|
}
|
|
if (will_defer) {
|
|
dout(20) << __func__ << " " << *(head_info.blob_ref)
|
|
<< " deferring big " << std::hex
|
|
<< " (0x" << head_info.b_off << "~" << head_info.blob_aligned_len() << ")"
|
|
<< std::dec << " write via deferred"
|
|
<< dendl;
|
|
if (remaining) {
|
|
dout(20) << __func__ << " " << *(tail_info.blob_ref)
|
|
<< " deferring big " << std::hex
|
|
<< " (0x" << tail_info.b_off << "~" << tail_info.blob_aligned_len() << ")"
|
|
<< std::dec << " write via deferred"
|
|
<< dendl;
|
|
}
|
|
|
|
will_defer = head_info.apply_defer();
|
|
if (!will_defer) {
|
|
dout(20) << __func__
|
|
<< " deferring big fell back, head isn't continuous"
|
|
<< dendl;
|
|
} else if (remaining) {
|
|
will_defer = tail_info.apply_defer();
|
|
if (!will_defer) {
|
|
dout(20) << __func__
|
|
<< " deferring big fell back, tail isn't continuous"
|
|
<< dendl;
|
|
}
|
|
}
|
|
}
|
|
if (will_defer) {
|
|
_do_write_big_apply_deferred(txc, c, o, head_info, blp, wctx);
|
|
if (remaining) {
|
|
_do_write_big_apply_deferred(txc, c, o, tail_info,
|
|
blp, wctx);
|
|
}
|
|
dout(20) << __func__ << " defer big: 0x" << std::hex
|
|
<< offset << "~" << l
|
|
<< std::dec << dendl;
|
|
offset += l;
|
|
length -= l;
|
|
logger->inc(l_bluestore_write_big_blobs, remaining ? 2 : 1);
|
|
logger->inc(l_bluestore_write_big_deferred, remaining ? 2 : 1);
|
|
continue;
|
|
}
|
|
}
|
|
dout(20) << __func__ << " lookup for blocks to reuse..." << dendl;
|
|
|
|
o->extent_map.punch_hole(c, offset, l, &wctx->old_extents);
|
|
|
|
// seek again as punch_hole could invalidate ep
|
|
auto ep = o->extent_map.seek_lextent(offset);
|
|
auto begin = o->extent_map.extent_map.begin();
|
|
auto prev_ep = end;
|
|
if (ep != begin) {
|
|
prev_ep = ep;
|
|
--prev_ep;
|
|
}
|
|
|
|
auto min_off = offset >= max_bsize ? offset - max_bsize : 0;
|
|
// search suitable extent in both forward and reverse direction in
|
|
// [offset - target_max_blob_size, offset + target_max_blob_size] range
|
|
// then check if blob can be reused via can_reuse_blob func.
|
|
bool any_change;
|
|
do {
|
|
any_change = false;
|
|
if (ep != end && ep->logical_offset < offset + max_bsize) {
|
|
dout(20) << __func__ << " considering " << *ep
|
|
<< " bstart 0x" << std::hex << ep->blob_start() << std::dec << dendl;
|
|
|
|
if (offset >= ep->blob_start() &&
|
|
ep->blob->can_reuse_blob(min_alloc_size, max_bsize,
|
|
offset - ep->blob_start(),
|
|
&l)) {
|
|
b = ep->blob;
|
|
b_off = offset - ep->blob_start();
|
|
prev_ep = end; // to avoid check below
|
|
dout(20) << __func__ << " reuse blob " << *b << std::hex
|
|
<< " (0x" << b_off << "~" << l << ")" << std::dec << dendl;
|
|
} else {
|
|
++ep;
|
|
any_change = true;
|
|
}
|
|
}
|
|
|
|
if (prev_ep != end && prev_ep->logical_offset >= min_off) {
|
|
dout(20) << __func__ << " considering rev " << *prev_ep
|
|
<< " bstart 0x" << std::hex << prev_ep->blob_start() << std::dec << dendl;
|
|
if (prev_ep->blob->can_reuse_blob(min_alloc_size, max_bsize,
|
|
offset - prev_ep->blob_start(),
|
|
&l)) {
|
|
b = prev_ep->blob;
|
|
b_off = offset - prev_ep->blob_start();
|
|
dout(20) << __func__ << " reuse blob " << *b << std::hex
|
|
<< " (0x" << b_off << "~" << l << ")" << std::dec << dendl;
|
|
} else if (prev_ep != begin) {
|
|
--prev_ep;
|
|
any_change = true;
|
|
} else {
|
|
prev_ep = end; // to avoid useless first extent re-check
|
|
}
|
|
}
|
|
} while (b == nullptr && any_change);
|
|
} else {
|
|
// trying to utilize as longer chunk as permitted in case of compression.
|
|
l = std::min(max_bsize, length);
|
|
o->extent_map.punch_hole(c, offset, l, &wctx->old_extents);
|
|
} // if (!wctx->compress)
|
|
|
|
if (b == nullptr) {
|
|
b = c->new_blob();
|
|
b_off = 0;
|
|
new_blob = true;
|
|
}
|
|
bufferlist t;
|
|
blp.copy(l, t);
|
|
|
|
// Zero detection -- big block
|
|
if (!cct->_conf->bluestore_zero_block_detection || !t.is_zero()) {
|
|
wctx->write(offset, b, l, b_off, t, b_off, l, false, new_blob);
|
|
|
|
dout(20) << __func__ << " schedule write big: 0x"
|
|
<< std::hex << offset << "~" << l << std::dec
|
|
<< (new_blob ? " new " : " reuse ")
|
|
<< *b << dendl;
|
|
|
|
logger->inc(l_bluestore_write_big_blobs);
|
|
} else { // if (!t.is_zero())
|
|
dout(20) << __func__ << " skip big zero block " << std::hex
|
|
<< " (0x" << b_off << "~" << t.length() << ")"
|
|
<< " (0x" << b_off << "~" << l << ")"
|
|
<< std::dec << dendl;
|
|
logger->inc(l_bluestore_write_big_skipped_blobs);
|
|
logger->inc(l_bluestore_write_big_skipped_bytes, l);
|
|
}
|
|
|
|
offset += l;
|
|
length -= l;
|
|
}
|
|
}
|
|
|
|
int BlueStore::_do_alloc_write(
|
|
TransContext *txc,
|
|
CollectionRef coll,
|
|
OnodeRef& o,
|
|
WriteContext *wctx)
|
|
{
|
|
dout(20) << __func__ << " txc " << txc
|
|
<< " " << wctx->writes.size() << " blobs"
|
|
<< dendl;
|
|
if (wctx->writes.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// checksum
|
|
int64_t csum = wctx->csum_type;
|
|
|
|
// compress (as needed) and calc needed space
|
|
uint64_t need = 0;
|
|
uint64_t data_size = 0;
|
|
// 'need' is amount of space that must be provided by allocator.
|
|
// 'data_size' is a size of data that will be transferred to disk.
|
|
// Note that data_size is always <= need. This comes from:
|
|
// - write to blob was unaligned, and there is free space
|
|
// - data has been compressed
|
|
//
|
|
// We make one decision and apply it to all blobs.
|
|
// All blobs will be deferred or none will.
|
|
// We assume that allocator does its best to provide contiguous space,
|
|
// and the condition is : (data_size < deferred).
|
|
|
|
auto max_bsize = std::max(wctx->target_blob_size, min_alloc_size);
|
|
for (auto& wi : wctx->writes) {
|
|
if (wctx->compressor && wi.blob_length > min_alloc_size) {
|
|
auto start = mono_clock::now();
|
|
|
|
// compress
|
|
ceph_assert(wi.b_off == 0);
|
|
ceph_assert(wi.blob_length == wi.bl.length());
|
|
|
|
// FIXME: memory alignment here is bad
|
|
bufferlist t;
|
|
std::optional<int32_t> compressor_message;
|
|
int r = wctx->compressor->compress(wi.bl, t, compressor_message);
|
|
uint64_t want_len_raw = wi.blob_length * wctx->crr;
|
|
uint64_t want_len = p2roundup(want_len_raw, min_alloc_size);
|
|
bool rejected = false;
|
|
uint64_t compressed_len = t.length();
|
|
// do an approximate (fast) estimation for resulting blob size
|
|
// that doesn't take header overhead into account
|
|
uint64_t result_len = p2roundup(compressed_len, min_alloc_size);
|
|
if (r == 0 && result_len <= want_len && result_len < wi.blob_length) {
|
|
bluestore_compression_header_t chdr;
|
|
chdr.type = wctx->compressor->get_type();
|
|
chdr.length = t.length();
|
|
chdr.compressor_message = compressor_message;
|
|
encode(chdr, wi.compressed_bl);
|
|
wi.compressed_bl.claim_append(t);
|
|
|
|
compressed_len = wi.compressed_bl.length();
|
|
result_len = p2roundup(compressed_len, min_alloc_size);
|
|
if (result_len <= want_len && result_len < wi.blob_length) {
|
|
// Cool. We compressed at least as much as we were hoping to.
|
|
// pad out to min_alloc_size
|
|
wi.compressed_bl.append_zero(result_len - compressed_len);
|
|
wi.compressed_len = compressed_len;
|
|
wi.compressed = true;
|
|
logger->inc(l_bluestore_write_pad_bytes, result_len - compressed_len);
|
|
dout(20) << __func__ << std::hex << " compressed 0x" << wi.blob_length
|
|
<< " -> 0x" << compressed_len << " => 0x" << result_len
|
|
<< " with " << wctx->compressor->get_type()
|
|
<< std::dec << dendl;
|
|
txc->statfs_delta.compressed() += compressed_len;
|
|
txc->statfs_delta.compressed_original() += wi.blob_length;
|
|
txc->statfs_delta.compressed_allocated() += result_len;
|
|
logger->inc(l_bluestore_compress_success_count);
|
|
need += result_len;
|
|
data_size += result_len;
|
|
} else {
|
|
rejected = true;
|
|
}
|
|
} else if (r != 0) {
|
|
dout(5) << __func__ << std::hex << " 0x" << wi.blob_length
|
|
<< " bytes compressed using " << wctx->compressor->get_type_name()
|
|
<< std::dec
|
|
<< " failed with errcode = " << r
|
|
<< ", leaving uncompressed"
|
|
<< dendl;
|
|
logger->inc(l_bluestore_compress_rejected_count);
|
|
need += wi.blob_length;
|
|
data_size += wi.bl.length();
|
|
} else {
|
|
rejected = true;
|
|
}
|
|
|
|
if (rejected) {
|
|
dout(20) << __func__ << std::hex << " 0x" << wi.blob_length
|
|
<< " compressed to 0x" << compressed_len << " -> 0x" << result_len
|
|
<< " with " << wctx->compressor->get_type()
|
|
<< ", which is more than required 0x" << want_len_raw
|
|
<< " -> 0x" << want_len
|
|
<< ", leaving uncompressed"
|
|
<< std::dec << dendl;
|
|
logger->inc(l_bluestore_compress_rejected_count);
|
|
need += wi.blob_length;
|
|
data_size += wi.bl.length();
|
|
}
|
|
log_latency("compress@_do_alloc_write",
|
|
l_bluestore_compress_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age );
|
|
} else {
|
|
need += wi.blob_length;
|
|
data_size += wi.bl.length();
|
|
}
|
|
}
|
|
PExtentVector prealloc;
|
|
prealloc.reserve(2 * wctx->writes.size());
|
|
int64_t prealloc_left = 0;
|
|
auto start = mono_clock::now();
|
|
prealloc_left = alloc->allocate(
|
|
need, min_alloc_size, need,
|
|
use_last_allocator_lookup_position ? -1 : 0,
|
|
&prealloc);
|
|
log_latency("allocator@_do_alloc_write",
|
|
l_bluestore_allocator_lat,
|
|
mono_clock::now() - start,
|
|
cct->_conf->bluestore_log_op_age);
|
|
if (prealloc_left < 0 || prealloc_left < (int64_t)need) {
|
|
derr << __func__ << " failed to allocate 0x" << std::hex << need
|
|
<< " allocated 0x " << (prealloc_left < 0 ? 0 : prealloc_left)
|
|
<< " min_alloc_size 0x" << min_alloc_size
|
|
<< " available 0x " << alloc->get_free()
|
|
<< std::dec << dendl;
|
|
if (prealloc.size()) {
|
|
alloc->release(prealloc);
|
|
}
|
|
return -ENOSPC;
|
|
}
|
|
_collect_allocation_stats(need, min_alloc_size, prealloc);
|
|
|
|
dout(20) << __func__ << std::hex << " need=0x" << need << " data=0x" << data_size
|
|
<< " prealloc " << prealloc << dendl;
|
|
auto prealloc_pos = prealloc.begin();
|
|
ceph_assert(prealloc_pos != prealloc.end());
|
|
|
|
for (auto& wi : wctx->writes) {
|
|
bluestore_blob_t& dblob = wi.b->dirty_blob();
|
|
uint64_t b_off = wi.b_off;
|
|
bufferlist *l = &wi.bl;
|
|
uint64_t final_length = wi.blob_length;
|
|
uint64_t csum_length = wi.blob_length;
|
|
if (wi.compressed) {
|
|
final_length = wi.compressed_bl.length();
|
|
csum_length = final_length;
|
|
unsigned csum_order = std::countr_zero(csum_length);
|
|
l = &wi.compressed_bl;
|
|
dblob.set_compressed(wi.blob_length, wi.compressed_len);
|
|
if (csum != Checksummer::CSUM_NONE) {
|
|
dout(20) << __func__
|
|
<< " initialize csum setting for compressed blob " << *wi.b
|
|
<< " csum_type " << Checksummer::get_csum_type_string(csum)
|
|
<< " csum_order " << csum_order
|
|
<< " csum_length 0x" << std::hex << csum_length
|
|
<< " blob_length 0x" << wi.blob_length
|
|
<< " compressed_length 0x" << wi.compressed_len << std::dec
|
|
<< dendl;
|
|
dblob.init_csum(csum, csum_order, csum_length);
|
|
}
|
|
} else if (wi.new_blob) {
|
|
unsigned csum_order;
|
|
// initialize newly created blob only
|
|
ceph_assert(dblob.is_mutable());
|
|
if (l->length() != wi.blob_length) {
|
|
// hrm, maybe we could do better here, but let's not bother.
|
|
dout(20) << __func__ << " forcing csum_order to block_size_order "
|
|
<< block_size_order << dendl;
|
|
csum_order = block_size_order;
|
|
} else {
|
|
csum_order = std::min<unsigned>(wctx->csum_order, std::countr_zero(l->length()));
|
|
}
|
|
// try to align blob with max_blob_size to improve
|
|
// its reuse ratio, e.g. in case of reverse write
|
|
uint32_t suggested_boff =
|
|
(wi.logical_offset - (wi.b_off0 - wi.b_off)) % max_bsize;
|
|
if ((suggested_boff % (1 << csum_order)) == 0 &&
|
|
suggested_boff + final_length <= max_bsize &&
|
|
suggested_boff > b_off) {
|
|
dout(20) << __func__ << " forcing blob_offset to 0x"
|
|
<< std::hex << suggested_boff << std::dec << dendl;
|
|
ceph_assert(suggested_boff >= b_off);
|
|
csum_length += suggested_boff - b_off;
|
|
b_off = suggested_boff;
|
|
}
|
|
if (csum != Checksummer::CSUM_NONE) {
|
|
dout(20) << __func__
|
|
<< " initialize csum setting for new blob " << *wi.b
|
|
<< " csum_type " << Checksummer::get_csum_type_string(csum)
|
|
<< " csum_order " << csum_order
|
|
<< " csum_length 0x" << std::hex << csum_length << std::dec
|
|
<< dendl;
|
|
dblob.init_csum(csum, csum_order, csum_length);
|
|
}
|
|
}
|
|
|
|
PExtentVector extents;
|
|
int64_t left = final_length;
|
|
auto prefer_deferred_size_snapshot = prefer_deferred_size.load();
|
|
while (left > 0) {
|
|
ceph_assert(prealloc_left > 0);
|
|
if (prealloc_pos->length <= left) {
|
|
prealloc_left -= prealloc_pos->length;
|
|
left -= prealloc_pos->length;
|
|
txc->statfs_delta.allocated() += prealloc_pos->length;
|
|
extents.push_back(*prealloc_pos);
|
|
++prealloc_pos;
|
|
} else {
|
|
extents.emplace_back(prealloc_pos->offset, left);
|
|
prealloc_pos->offset += left;
|
|
prealloc_pos->length -= left;
|
|
prealloc_left -= left;
|
|
txc->statfs_delta.allocated() += left;
|
|
left = 0;
|
|
break;
|
|
}
|
|
}
|
|
for (auto& p : extents) {
|
|
txc->allocated.insert(p.offset, p.length);
|
|
}
|
|
dblob.allocated(p2align(b_off, min_alloc_size), final_length, extents);
|
|
|
|
dout(20) << __func__ << " blob " << *wi.b << dendl;
|
|
if (dblob.has_csum()) {
|
|
dblob.calc_csum(b_off, *l);
|
|
}
|
|
|
|
if (wi.mark_unused) {
|
|
ceph_assert(!dblob.is_compressed());
|
|
auto b_end = b_off + wi.bl.length();
|
|
if (b_off) {
|
|
dblob.add_unused(0, b_off);
|
|
}
|
|
uint64_t llen = dblob.get_logical_length();
|
|
if (b_end < llen) {
|
|
dblob.add_unused(b_end, llen - b_end);
|
|
}
|
|
}
|
|
|
|
Extent *le = o->extent_map.set_lextent(coll, wi.logical_offset,
|
|
b_off + (wi.b_off0 - wi.b_off),
|
|
wi.length0,
|
|
wi.b,
|
|
nullptr);
|
|
wi.b->dirty_blob().mark_used(le->blob_offset, le->length);
|
|
txc->statfs_delta.stored() += le->length;
|
|
dout(20) << __func__ << " lex " << *le << dendl;
|
|
bufferlist without_pad;
|
|
without_pad.substr_of(wi.bl, wi.b_off0-wi.b_off, wi.length0);
|
|
_buffer_cache_write(txc, o, wi.logical_offset, std::move(without_pad),
|
|
wctx->buffered ? 0 : Buffer::FLAG_NOCACHE);
|
|
|
|
// queue io
|
|
if (!g_conf()->bluestore_debug_omit_block_device_write) {
|
|
if (data_size < prefer_deferred_size_snapshot) {
|
|
dout(20) << __func__ << " deferring 0x" << std::hex
|
|
<< l->length() << " write via deferred, pds=0x"
|
|
<< prefer_deferred_size_snapshot
|
|
<< std::dec<< dendl;
|
|
bluestore_deferred_op_t *op = _get_deferred_op(txc, l->length());
|
|
op->op = bluestore_deferred_op_t::OP_WRITE;
|
|
int r = wi.b->get_blob().map(
|
|
b_off, l->length(),
|
|
[&](uint64_t offset, uint64_t length) {
|
|
op->extents.emplace_back(bluestore_pextent_t(offset, length));
|
|
return 0;
|
|
});
|
|
ceph_assert(r == 0);
|
|
op->data = *l;
|
|
} else {
|
|
wi.b->get_blob().map_bl(
|
|
b_off, *l,
|
|
[&](uint64_t offset, bufferlist& t) {
|
|
bdev->aio_write(offset, t, &txc->ioc, false);
|
|
});
|
|
logger->inc(l_bluestore_write_new);
|
|
}
|
|
}
|
|
}
|
|
ceph_assert(prealloc_pos == prealloc.end());
|
|
ceph_assert(prealloc_left == 0);
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_wctx_finish(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
WriteContext *wctx,
|
|
set<SharedBlob*> *maybe_unshared_blobs)
|
|
{
|
|
auto oep = wctx->old_extents.begin();
|
|
while (oep != wctx->old_extents.end()) {
|
|
auto &lo = *oep;
|
|
oep = wctx->old_extents.erase(oep);
|
|
dout(20) << __func__ << " lex_old " << lo.e << dendl;
|
|
BlobRef b = lo.e.blob;
|
|
const bluestore_blob_t& blob = b->get_blob();
|
|
if (blob.is_compressed()) {
|
|
if (lo.blob_empty) {
|
|
txc->statfs_delta.compressed() -= blob.get_compressed_payload_length();
|
|
}
|
|
txc->statfs_delta.compressed_original() -= lo.e.length;
|
|
}
|
|
auto& r = lo.r;
|
|
txc->statfs_delta.stored() -= lo.e.length;
|
|
if (!r.empty()) {
|
|
dout(20) << __func__ << " blob " << *b << " release " << r << dendl;
|
|
if (blob.is_shared()) {
|
|
PExtentVector final;
|
|
c->load_shared_blob(b->get_shared_blob());
|
|
bool unshare = false;
|
|
bool* unshare_ptr =
|
|
!maybe_unshared_blobs || b->is_referenced() ? nullptr : &unshare;
|
|
for (auto e : r) {
|
|
b->get_shared_blob()->put_ref(
|
|
e.offset, e.length, &final,
|
|
unshare_ptr);
|
|
}
|
|
if (unshare) {
|
|
ceph_assert(maybe_unshared_blobs);
|
|
maybe_unshared_blobs->insert(b->get_shared_blob().get());
|
|
}
|
|
dout(20) << __func__ << " shared_blob release " << final
|
|
<< " from " << *b->get_shared_blob() << dendl;
|
|
txc->write_shared_blob(b->get_shared_blob());
|
|
r.clear();
|
|
r.swap(final);
|
|
}
|
|
}
|
|
|
|
b->maybe_prune_tail();
|
|
for (auto e : r) {
|
|
dout(20) << __func__ << " release " << e << dendl;
|
|
txc->released.insert(e.offset, e.length);
|
|
txc->statfs_delta.allocated() -= e.length;
|
|
if (blob.is_compressed()) {
|
|
txc->statfs_delta.compressed_allocated() -= e.length;
|
|
}
|
|
}
|
|
|
|
if (b->is_spanning() && !b->is_referenced() && lo.blob_empty) {
|
|
dout(20) << __func__ << " spanning_blob_map removing empty " << *b
|
|
<< dendl;
|
|
o->extent_map.spanning_blob_map.erase(b->id);
|
|
}
|
|
delete &lo;
|
|
}
|
|
}
|
|
|
|
void BlueStore::_do_write_data(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset,
|
|
uint64_t length,
|
|
bufferlist& bl,
|
|
WriteContext *wctx)
|
|
{
|
|
uint64_t end = offset + length;
|
|
bufferlist::iterator p = bl.begin();
|
|
|
|
if (offset / min_alloc_size == (end - 1) / min_alloc_size &&
|
|
(length != min_alloc_size)) {
|
|
// we fall within the same block
|
|
_do_write_small(txc, c, o, offset, length, p, wctx);
|
|
} else {
|
|
uint64_t head_offset, head_length;
|
|
uint64_t middle_offset, middle_length;
|
|
uint64_t tail_offset, tail_length;
|
|
|
|
head_offset = offset;
|
|
head_length = p2nphase(offset, min_alloc_size);
|
|
|
|
tail_offset = p2align(end, min_alloc_size);
|
|
tail_length = p2phase(end, min_alloc_size);
|
|
|
|
middle_offset = head_offset + head_length;
|
|
middle_length = length - head_length - tail_length;
|
|
|
|
if (head_length) {
|
|
_do_write_small(txc, c, o, head_offset, head_length, p, wctx);
|
|
}
|
|
uint32_t segment_size = o->onode.segment_size;
|
|
if (segment_size) {
|
|
// split data to chunks
|
|
uint64_t write_offset = middle_offset;
|
|
while (write_offset < middle_offset + middle_length) {
|
|
uint64_t segment_end = std::min(
|
|
p2roundup<uint64_t>(write_offset + 1, segment_size),
|
|
middle_offset + middle_length);
|
|
_do_write_big(txc, c, o, write_offset, segment_end - write_offset, p, wctx);
|
|
write_offset = segment_end;
|
|
}
|
|
} else {
|
|
_do_write_big(txc, c, o, middle_offset, middle_length, p, wctx);
|
|
}
|
|
|
|
if (tail_length) {
|
|
_do_write_small(txc, c, o, tail_offset, tail_length, p, wctx);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::_choose_write_options(
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint32_t fadvise_flags,
|
|
WriteContext *wctx)
|
|
{
|
|
if (fadvise_flags & CEPH_OSD_OP_FLAG_FADVISE_WILLNEED) {
|
|
dout(20) << __func__ << " will do buffered write" << dendl;
|
|
wctx->buffered = true;
|
|
} else if (cct->_conf->bluestore_default_buffered_write &&
|
|
(fadvise_flags & (CEPH_OSD_OP_FLAG_FADVISE_DONTNEED |
|
|
CEPH_OSD_OP_FLAG_FADVISE_NOCACHE)) == 0) {
|
|
dout(20) << __func__ << " defaulting to buffered write" << dendl;
|
|
wctx->buffered = true;
|
|
}
|
|
|
|
// apply basic csum block size
|
|
wctx->csum_order = block_size_order;
|
|
|
|
// checksum
|
|
wctx->csum_type= c->csum_type.has_value() ?
|
|
*(c->csum_type):
|
|
csum_type.load();
|
|
|
|
// compression parameters
|
|
unsigned alloc_hints = o->onode.alloc_hint_flags;
|
|
|
|
auto cm = c->compression_mode.has_value() ?
|
|
*(c->compression_mode) :
|
|
comp_mode.load();
|
|
|
|
wctx->compress = (cm != Compressor::COMP_NONE) &&
|
|
((cm == Compressor::COMP_FORCE) ||
|
|
(cm == Compressor::COMP_AGGRESSIVE &&
|
|
(alloc_hints & CEPH_OSD_ALLOC_HINT_FLAG_INCOMPRESSIBLE) == 0) ||
|
|
(cm == Compressor::COMP_PASSIVE &&
|
|
(alloc_hints & CEPH_OSD_ALLOC_HINT_FLAG_COMPRESSIBLE)));
|
|
|
|
if ((alloc_hints & CEPH_OSD_ALLOC_HINT_FLAG_SEQUENTIAL_READ) &&
|
|
(alloc_hints & CEPH_OSD_ALLOC_HINT_FLAG_RANDOM_READ) == 0 &&
|
|
(alloc_hints & (CEPH_OSD_ALLOC_HINT_FLAG_IMMUTABLE |
|
|
CEPH_OSD_ALLOC_HINT_FLAG_APPEND_ONLY)) &&
|
|
(alloc_hints & CEPH_OSD_ALLOC_HINT_FLAG_RANDOM_WRITE) == 0) {
|
|
|
|
dout(20) << __func__ << " will prefer large blob and csum sizes" << dendl;
|
|
|
|
if (o->onode.expected_write_size) {
|
|
wctx->csum_order = std::max(min_alloc_size_order,
|
|
(uint8_t)std::countr_zero(o->onode.expected_write_size));
|
|
} else {
|
|
wctx->csum_order = min_alloc_size_order;
|
|
}
|
|
|
|
if (wctx->compress) {
|
|
wctx->target_blob_size = c->comp_max_blob_size.has_value() ?
|
|
*(c->comp_max_blob_size):
|
|
comp_max_blob_size.load();
|
|
}
|
|
} else {
|
|
if (wctx->compress) {
|
|
wctx->target_blob_size = c->comp_min_blob_size.has_value() ?
|
|
*(c->comp_min_blob_size):
|
|
comp_min_blob_size.load();
|
|
}
|
|
}
|
|
|
|
uint64_t max_bsize = max_blob_size.load();
|
|
if (wctx->target_blob_size == 0 || wctx->target_blob_size > max_bsize) {
|
|
wctx->target_blob_size = max_bsize;
|
|
}
|
|
|
|
// set the min blob size floor at 2x the min_alloc_size, or else we
|
|
// won't be able to allocate a smaller extent for the compressed
|
|
// data.
|
|
if (wctx->compress &&
|
|
wctx->target_blob_size < min_alloc_size * 2) {
|
|
wctx->target_blob_size = min_alloc_size * 2;
|
|
}
|
|
if (wctx->compress) {
|
|
wctx->compressor = c->compression_algorithm.has_value() ?
|
|
compressors[*(c->compression_algorithm)]:
|
|
compressors[def_compressor_alg];
|
|
wctx->crr = c->compression_req_ratio.has_value() ?
|
|
*(c->compression_req_ratio) :
|
|
cct->_conf->bluestore_compression_required_ratio;
|
|
}
|
|
|
|
dout(20) << __func__ << " prefer csum_order " << wctx->csum_order
|
|
<< " target_blob_size 0x" << std::hex << wctx->target_blob_size
|
|
<< " compress=" << (int)wctx->compress
|
|
<< " buffered=" << (int)wctx->buffered
|
|
<< std::dec << dendl;
|
|
}
|
|
|
|
int BlueStore::_do_gc(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
const WriteContext& wctx,
|
|
uint64_t *dirty_start,
|
|
uint64_t *dirty_end)
|
|
{
|
|
|
|
bool dirty_range_updated = false;
|
|
WriteContext wctx_gc;
|
|
wctx_gc.fork(wctx); // make a clone for garbage collection
|
|
|
|
auto & extents_to_collect = wctx.extents_to_gc;
|
|
for (auto it = extents_to_collect.begin();
|
|
it != extents_to_collect.end();
|
|
++it) {
|
|
bufferlist bl;
|
|
auto offset = (*it).first;
|
|
auto length = (*it).second;
|
|
dout(20) << __func__ << " processing " << std::hex
|
|
<< offset << "~" << length << std::dec
|
|
<< dendl;
|
|
int r = _do_read(c.get(), o, offset, length, bl, 0);
|
|
ceph_assert(r == (int)length);
|
|
|
|
_do_write_data(txc, c, o, offset, length, bl, &wctx_gc);
|
|
logger->inc(l_bluestore_gc_merged, length);
|
|
|
|
if (*dirty_start > offset) {
|
|
*dirty_start = offset;
|
|
dirty_range_updated = true;
|
|
}
|
|
|
|
if (*dirty_end < offset + length) {
|
|
*dirty_end = offset + length;
|
|
dirty_range_updated = true;
|
|
}
|
|
}
|
|
if (dirty_range_updated) {
|
|
o->extent_map.fault_range(db, *dirty_start, *dirty_end);
|
|
}
|
|
|
|
dout(30) << __func__ << " alloc write" << dendl;
|
|
int r = _do_alloc_write(txc, c, o, &wctx_gc);
|
|
if (r < 0) {
|
|
derr << __func__ << " _do_alloc_write failed with " << cpp_strerror(r)
|
|
<< dendl;
|
|
return r;
|
|
}
|
|
|
|
_wctx_finish(txc, c, o, &wctx_gc);
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_do_write(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset,
|
|
uint64_t length,
|
|
bufferlist& bl,
|
|
uint32_t fadvise_flags)
|
|
{
|
|
int r = 0;
|
|
|
|
dout(20) << __func__
|
|
<< " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length
|
|
<< " - have 0x" << o->onode.size
|
|
<< " (" << std::dec << o->onode.size << ")"
|
|
<< " bytes" << std::hex
|
|
<< " fadvise_flags 0x" << fadvise_flags
|
|
<< " alloc_hint 0x" << o->onode.alloc_hint_flags
|
|
<< " expected_object_size " << o->onode.expected_object_size
|
|
<< " expected_write_size " << o->onode.expected_write_size
|
|
<< std::dec
|
|
<< dendl;
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
if (length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint64_t end = offset + length;
|
|
|
|
GarbageCollector gc(c->store->cct);
|
|
int64_t benefit = 0;
|
|
auto dirty_start = offset;
|
|
auto dirty_end = end;
|
|
|
|
WriteContext wctx;
|
|
_choose_write_options(c, o, fadvise_flags, &wctx);
|
|
o->extent_map.fault_range(db, offset, length);
|
|
_do_write_data(txc, c, o, offset, length, bl, &wctx);
|
|
r = _do_alloc_write(txc, c, o, &wctx);
|
|
if (r < 0) {
|
|
derr << __func__ << " _do_alloc_write failed with " << cpp_strerror(r)
|
|
<< dendl;
|
|
goto out;
|
|
}
|
|
|
|
if (wctx.extents_to_gc.empty() ||
|
|
wctx.extents_to_gc.range_start() > offset ||
|
|
wctx.extents_to_gc.range_end() < offset + length) {
|
|
benefit = gc.estimate(offset,
|
|
length,
|
|
o->extent_map,
|
|
wctx.old_extents,
|
|
min_alloc_size);
|
|
}
|
|
|
|
// NB: _wctx_finish() will empty old_extents
|
|
// so we must do gc estimation before that
|
|
_wctx_finish(txc, c, o, &wctx);
|
|
if (end > o->onode.size) {
|
|
dout(20) << __func__ << " extending size to 0x" << std::hex << end
|
|
<< std::dec << dendl;
|
|
o->onode.size = end;
|
|
}
|
|
|
|
if (benefit >= g_conf()->bluestore_gc_enable_total_threshold) {
|
|
wctx.extents_to_gc.union_of(gc.get_extents_to_collect());
|
|
dout(20) << __func__
|
|
<< " perform garbage collection for compressed extents, "
|
|
<< "expected benefit = " << benefit << " AUs" << dendl;
|
|
}
|
|
if (!wctx.extents_to_gc.empty()) {
|
|
dout(20) << __func__ << " perform garbage collection" << dendl;
|
|
|
|
r = _do_gc(txc, c, o,
|
|
wctx,
|
|
&dirty_start, &dirty_end);
|
|
if (r < 0) {
|
|
derr << __func__ << " _do_gc failed with " << cpp_strerror(r)
|
|
<< dendl;
|
|
goto out;
|
|
}
|
|
dout(20)<<__func__<<" gc range is " << std::hex << dirty_start
|
|
<< "~" << dirty_end - dirty_start << std::dec << dendl;
|
|
}
|
|
o->extent_map.compress_extent_map(dirty_start, dirty_end - dirty_start);
|
|
o->extent_map.dirty_range(dirty_start, dirty_end - dirty_start);
|
|
|
|
r = 0;
|
|
|
|
out:
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_do_write_v2(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset,
|
|
uint64_t length,
|
|
bufferlist& bl,
|
|
uint32_t fadvise_flags)
|
|
{
|
|
int r = 0;
|
|
|
|
dout(20) << __func__
|
|
<< " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length
|
|
<< " - have 0x" << o->onode.size
|
|
<< " (" << std::dec << o->onode.size << ")"
|
|
<< " bytes" << std::hex
|
|
<< " fadvise_flags 0x" << fadvise_flags
|
|
<< " alloc_hint 0x" << o->onode.alloc_hint_flags
|
|
<< " expected_object_size " << o->onode.expected_object_size
|
|
<< " expected_write_size " << o->onode.expected_write_size
|
|
<< std::dec
|
|
<< dendl;
|
|
_dump_onode<30>(cct, *o);
|
|
if (length == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (bl.length() != length) {
|
|
bl.splice(length, bl.length() - length);
|
|
}
|
|
|
|
WriteContext wctx;
|
|
_choose_write_options(c, o, fadvise_flags, &wctx);
|
|
if (wctx.compressor) {
|
|
uint32_t end = offset + length;
|
|
uint32_t segment_size = o->onode.segment_size;
|
|
if (segment_size) {
|
|
// split data into segments
|
|
// first and last segments will do lookaround scan
|
|
uint32_t write_offset = offset;
|
|
while (write_offset != end) {
|
|
uint32_t this_segment_begin = p2align(write_offset, segment_size);
|
|
uint32_t this_segment_end = this_segment_begin + segment_size;
|
|
uint32_t write_length = std::min(this_segment_end, end) - write_offset;
|
|
bufferlist chunk;
|
|
chunk.substr_of(bl, 0, write_length);
|
|
bl.splice(0, write_length);
|
|
_do_write_v2_compressed(txc, c, o, wctx, write_offset, write_length, chunk,
|
|
this_segment_begin, this_segment_end);
|
|
write_offset += write_length;
|
|
};
|
|
} else {
|
|
const uint32_t scan_range = 0x20000; //128kB
|
|
uint32_t scan_left = offset < scan_range ? 0: offset - scan_range;
|
|
uint32_t scan_right = end + scan_range;
|
|
_do_write_v2_compressed(txc, c, o, wctx, offset, length, bl,
|
|
scan_left, scan_right);
|
|
}
|
|
} else {
|
|
// normal uncompressed path
|
|
BlueStore::Writer wr(this, txc, &wctx, o);
|
|
uint64_t start = p2align(offset, min_alloc_size);
|
|
uint64_t end = p2roundup(offset + length, min_alloc_size);
|
|
wr.left_affected_range = start;
|
|
wr.right_affected_range = end;
|
|
std::tie(wr.left_shard_bound, wr.right_shard_bound) =
|
|
o->extent_map.fault_range_ex(db, start, end - start);
|
|
wr.do_write(offset, bl);
|
|
o->extent_map.dirty_range(wr.left_affected_range, wr.right_affected_range - wr.left_affected_range);
|
|
o->extent_map.maybe_reshard(wr.left_affected_range, wr.right_affected_range);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_do_write_v2_compressed(
|
|
TransContext *txc,
|
|
CollectionRef &c,
|
|
OnodeRef& o,
|
|
WriteContext& wctx,
|
|
uint32_t offset, uint32_t length,
|
|
ceph::buffer::list& input_bl,
|
|
uint32_t scan_left, uint32_t scan_right)
|
|
{
|
|
o->extent_map.fault_range(db, scan_left, scan_right - scan_left);
|
|
if (!c->estimator) c->estimator.reset(create_estimator());
|
|
Estimator* estimator = c->estimator.get();
|
|
estimator->set_wctx(&wctx);
|
|
Scanner scanner(this);
|
|
scanner.write_lookaround(o.get(), offset, length, scan_left, scan_right, estimator);
|
|
std::vector<Estimator::region_t> regions;
|
|
estimator->get_regions(regions);
|
|
dout(15) << __func__ << " " << std::hex << offset << "~" << length << " -> ";
|
|
for (const auto& i : regions) {
|
|
*_dout << i.offset << "~" << i.length << " ";
|
|
}
|
|
*_dout << std::dec << dendl;
|
|
for (const auto& i : regions) {
|
|
ceph::buffer::list data_bl;
|
|
if (i.offset <= offset && offset < i.offset + i.length) {
|
|
// the starting point is withing the region, so the end must too
|
|
ceph_assert(offset + length <= i.offset + i.length);
|
|
if (i.offset < offset) {
|
|
_do_read_and_pad(c.get(), o, i.offset, offset - i.offset, data_bl);
|
|
}
|
|
data_bl.claim_append(input_bl);
|
|
if (offset + length < i.offset + i.length) {
|
|
ceph::buffer::list right_bl;
|
|
_do_read_and_pad(c.get(), o, offset + length,
|
|
i.offset + i.length - (offset + length), right_bl);
|
|
data_bl.claim_append(right_bl);
|
|
}
|
|
} else {
|
|
// the starting point is not within region, so the end is not allowed either
|
|
ceph_assert(offset + length < i.offset || offset + length >= i.offset + i.length);
|
|
_do_read_and_pad(c.get(), o, i.offset, i.length, data_bl);
|
|
}
|
|
ceph_assert(data_bl.length() == i.length);
|
|
Writer::blob_vec bd;
|
|
int32_t disk_for_compressed;
|
|
int32_t disk_for_raw;
|
|
uint32_t au_size = min_alloc_size;
|
|
disk_for_compressed = estimator->split_and_compress(data_bl, bd);
|
|
disk_for_raw = p2roundup(i.offset + i.length, au_size) - p2align(i.offset, au_size);
|
|
BlueStore::Writer wr(this, txc, &wctx, o);
|
|
if (disk_for_compressed < disk_for_raw) {
|
|
wr.do_write_with_blobs(i.offset, i.offset + i.length, i.offset + i.length, bd);
|
|
} else {
|
|
wr.do_write(i.offset, data_bl);
|
|
}
|
|
}
|
|
estimator->finish();
|
|
uint32_t changes_start = regions.front().offset;
|
|
uint32_t changes_end = regions.back().offset + regions.back().length;
|
|
o->extent_map.compress_extent_map(changes_start, changes_end - changes_start);
|
|
o->extent_map.dirty_range(changes_start, changes_end - changes_start);
|
|
o->extent_map.maybe_reshard(changes_start, changes_end);
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_write(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset, size_t length,
|
|
bufferlist& bl,
|
|
uint32_t fadvise_flags)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< dendl;
|
|
auto start = mono_clock::now();
|
|
int r = 0;
|
|
if (offset + length >= OBJECT_MAX_SIZE) {
|
|
r = -E2BIG;
|
|
} else {
|
|
_assign_nid(txc, o);
|
|
if (use_write_v2) {
|
|
r = _do_write_v2(txc, c, o, offset, length, bl, fadvise_flags);
|
|
} else {
|
|
r = _do_write(txc, c, o, offset, length, bl, fadvise_flags);
|
|
}
|
|
txc->write_onode(o);
|
|
}
|
|
auto finish = mono_clock::now();
|
|
logger->tinc(l_bluestore_write_lat, finish - start);
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_zero(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset, size_t length)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< dendl;
|
|
int r = 0;
|
|
if (offset + length >= OBJECT_MAX_SIZE) {
|
|
r = -E2BIG;
|
|
} else {
|
|
_assign_nid(txc, o);
|
|
r = _do_zero(txc, c, o, offset, length);
|
|
}
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_do_zero(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset, size_t length)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< dendl;
|
|
int r = 0;
|
|
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
WriteContext wctx;
|
|
o->extent_map.fault_range(db, offset, length);
|
|
o->extent_map.punch_hole(c, offset, length, &wctx.old_extents);
|
|
o->bc.discard(o->c->cache, offset, length);
|
|
o->extent_map.dirty_range(offset, length);
|
|
_wctx_finish(txc, c, o, &wctx);
|
|
|
|
if (length > 0 && offset + length > o->onode.size) {
|
|
o->onode.size = offset + length;
|
|
dout(20) << __func__ << " extending size to " << offset + length
|
|
<< dendl;
|
|
}
|
|
txc->write_onode(o);
|
|
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << "~" << length << std::dec
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_do_truncate(
|
|
TransContext *txc, CollectionRef& c, OnodeRef& o, uint64_t offset,
|
|
set<SharedBlob*> *maybe_unshared_blobs)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << std::dec << dendl;
|
|
|
|
_dump_onode<30>(cct, *o);
|
|
|
|
if (offset == o->onode.size)
|
|
return;
|
|
|
|
WriteContext wctx;
|
|
if (offset < o->onode.size) {
|
|
uint64_t length = o->onode.size - offset;
|
|
o->bc.discard(o->c->cache, offset, length);
|
|
o->extent_map.fault_range(db, offset, length);
|
|
o->extent_map.punch_hole(c, offset, length, &wctx.old_extents);
|
|
o->extent_map.dirty_range(offset, length);
|
|
|
|
_wctx_finish(txc, c, o, &wctx, maybe_unshared_blobs);
|
|
|
|
// if we have shards past EOF, ask for a reshard
|
|
if (!o->onode.extent_map_shards.empty() &&
|
|
o->onode.extent_map_shards.back().offset >= offset) {
|
|
dout(10) << __func__ << " request reshard past EOF" << dendl;
|
|
if (offset) {
|
|
o->extent_map.request_reshard(offset - 1, offset + length);
|
|
} else {
|
|
o->extent_map.request_reshard(0, length);
|
|
}
|
|
}
|
|
}
|
|
|
|
o->onode.size = offset;
|
|
|
|
txc->write_onode(o);
|
|
}
|
|
|
|
int BlueStore::_truncate(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t offset)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << std::dec
|
|
<< dendl;
|
|
|
|
auto start_time = mono_clock::now();
|
|
int r = 0;
|
|
if (offset >= OBJECT_MAX_SIZE) {
|
|
r = -E2BIG;
|
|
} else {
|
|
_do_truncate(txc, c, o, offset);
|
|
}
|
|
log_latency_fn(
|
|
__func__,
|
|
l_bluestore_truncate_lat,
|
|
mono_clock::now() - start_time,
|
|
cct->_conf->bluestore_log_op_age,
|
|
[&](const ceph::timespan& lat) {
|
|
ostringstream ostr;
|
|
ostr << ", lat = " << timespan_str(lat)
|
|
<< " cid =" << c->cid
|
|
<< " oid =" << o->oid;
|
|
return ostr.str();
|
|
}
|
|
);
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " 0x" << std::hex << offset << std::dec
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_do_remove(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o)
|
|
{
|
|
set<SharedBlob*> maybe_unshared_blobs;
|
|
bool is_gen = !o->oid.is_no_gen();
|
|
_do_truncate(txc, c, o, 0, is_gen ? &maybe_unshared_blobs : nullptr);
|
|
if (o->onode.has_omap()) {
|
|
o->flush();
|
|
_do_omap_clear(txc, o);
|
|
}
|
|
o->exists = false;
|
|
string key;
|
|
for (auto &s : o->extent_map.shards) {
|
|
dout(20) << __func__ << " removing shard 0x" << std::hex
|
|
<< s.shard_info->offset << std::dec << dendl;
|
|
generate_extent_shard_key_and_apply(o->key, s.shard_info->offset, &key,
|
|
[&](const string& final_key) {
|
|
txc->t->rmkey(PREFIX_OBJ, final_key);
|
|
}
|
|
);
|
|
}
|
|
txc->t->rmkey(PREFIX_OBJ, o->key.c_str(), o->key.size());
|
|
txc->note_removed_object(o);
|
|
o->extent_map.clear();
|
|
o->onode = bluestore_onode_t();
|
|
_debug_obj_on_delete(o->oid);
|
|
|
|
if (!is_gen || maybe_unshared_blobs.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// see if we can unshare blobs still referenced by the head
|
|
dout(10) << __func__ << " gen and maybe_unshared_blobs "
|
|
<< maybe_unshared_blobs << dendl;
|
|
ghobject_t nogen = o->oid;
|
|
nogen.generation = ghobject_t::NO_GEN;
|
|
OnodeRef h = c->get_onode(nogen, false);
|
|
|
|
if (!h || !h->exists) {
|
|
return 0;
|
|
}
|
|
// Set maybe_unshared_blobs contains those shared blobs that have all nref=1.
|
|
// Is .head object is using all those segments?
|
|
// If it is using all, then no one else can use the shared blob,
|
|
// and we can fallback to regular non-shared blob.
|
|
|
|
// Note. We only process loaded shared blobs.
|
|
// This is very smart optimization - there is no way that we can unshare blob
|
|
// that is not yet loaded! We must have had inspected it to even check nrefs.
|
|
dout(20) << __func__ << " checking for unshareable blobs on " << h
|
|
<< " " << h->oid << dendl;
|
|
map<SharedBlob*,bluestore_extent_ref_map_t> expect;
|
|
for (auto& e : h->extent_map.extent_map) {
|
|
const bluestore_blob_t& b = e.blob->get_blob();
|
|
SharedBlob *sb = e.blob->get_shared_blob().get();
|
|
if (b.is_shared() &&
|
|
sb->loaded &&
|
|
maybe_unshared_blobs.count(sb)) {
|
|
if (b.is_compressed()) {
|
|
expect[sb].get(0, b.get_ondisk_length());
|
|
} else {
|
|
// todo: it seems to be an overkill to go through map()
|
|
b.map(e.blob_offset, e.length, [&](uint64_t off, uint64_t len) {
|
|
expect[sb].get(off, len);
|
|
return 0;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// expect has now refs set exactly as .head is using it
|
|
vector<SharedBlob*> unshared_blobs;
|
|
unshared_blobs.reserve(maybe_unshared_blobs.size());
|
|
for (auto& p : expect) {
|
|
dout(20) << " ? " << *p.first << " vs " << p.second << dendl;
|
|
if (p.first->persistent->ref_map == p.second) {
|
|
// yup, .head is only one that is using the shared blob now
|
|
SharedBlob *sb = p.first;
|
|
dout(20) << __func__ << " unsharing " << *sb << dendl;
|
|
unshared_blobs.push_back(sb);
|
|
txc->unshare_blob(sb);
|
|
uint64_t sbid = c->make_blob_unshared(sb);
|
|
string key;
|
|
get_shared_blob_key(sbid, &key);
|
|
txc->t->rmkey(PREFIX_SHARED_BLOB, key);
|
|
}
|
|
}
|
|
|
|
if (unshared_blobs.empty()) {
|
|
return 0;
|
|
}
|
|
|
|
// And now a run through .head extents to clear up freshly unshared blobs.
|
|
for (auto& e : h->extent_map.extent_map) {
|
|
const bluestore_blob_t& b = e.blob->get_blob();
|
|
SharedBlob *sb = e.blob->get_shared_blob().get();
|
|
if (b.is_shared() &&
|
|
std::find(unshared_blobs.begin(), unshared_blobs.end(),
|
|
sb) != unshared_blobs.end()) {
|
|
dout(20) << __func__ << " unsharing " << e << dendl;
|
|
bluestore_blob_t& blob = e.blob->dirty_blob();
|
|
blob.clear_flag(bluestore_blob_t::FLAG_SHARED);
|
|
e.blob->get_dirty_shared_blob() = nullptr;
|
|
h->extent_map.dirty_range(e.logical_offset, e.length);
|
|
}
|
|
}
|
|
txc->write_onode(h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_remove(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " onode " << o.get()
|
|
<< " txc "<< txc << dendl;
|
|
auto start_time = mono_clock::now();
|
|
int r = _do_remove(txc, c, o);
|
|
|
|
log_latency_fn(
|
|
__func__,
|
|
l_bluestore_remove_lat,
|
|
mono_clock::now() - start_time,
|
|
cct->_conf->bluestore_log_op_age,
|
|
[&](const ceph::timespan& lat) {
|
|
ostringstream ostr;
|
|
ostr << ", lat = " << timespan_str(lat)
|
|
<< " cid =" << c->cid
|
|
<< " oid =" << o->oid;
|
|
return ostr.str();
|
|
}
|
|
);
|
|
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
|
|
|
|
int BlueStore::_setattr(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
const string& name,
|
|
bufferlist& val)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " " << name << " (" << val.length() << " bytes)"
|
|
<< dendl;
|
|
int r = 0;
|
|
auto& b = o->onode.attrs[name.c_str()];
|
|
if (val.length() == 0) {
|
|
b = bufferptr("", 0);
|
|
} else {
|
|
if (!val.is_contiguous() || val.front().is_partial()) {
|
|
val.rebuild();
|
|
}
|
|
b = val.front();
|
|
}
|
|
b.reassign_to_mempool(mempool::mempool_bluestore_cache_meta);
|
|
|
|
txc->write_onode(o);
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " " << name << " (" << val.length() << " bytes)"
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_setattrs(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
const map<string,bufferptr>& aset)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " " << aset.size() << " keys"
|
|
<< dendl;
|
|
int r = 0;
|
|
for (map<string,bufferptr>::const_iterator p = aset.begin();
|
|
p != aset.end(); ++p) {
|
|
if (p->second.is_partial()) {
|
|
auto& b = o->onode.attrs[p->first.c_str()] =
|
|
bufferptr(p->second.c_str(), p->second.length());
|
|
b.reassign_to_mempool(mempool::mempool_bluestore_cache_meta);
|
|
} else {
|
|
auto& b = o->onode.attrs[p->first.c_str()] = p->second;
|
|
b.reassign_to_mempool(mempool::mempool_bluestore_cache_meta);
|
|
}
|
|
}
|
|
txc->write_onode(o);
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " " << aset.size() << " keys"
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
|
|
int BlueStore::_rmattr(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
const string& name)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " " << name << dendl;
|
|
int r = 0;
|
|
auto it = o->onode.attrs.find(name.c_str());
|
|
if (it == o->onode.attrs.end())
|
|
goto out;
|
|
|
|
o->onode.attrs.erase(it);
|
|
txc->write_onode(o);
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " " << name << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_rmattrs(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
int r = 0;
|
|
|
|
if (o->onode.attrs.empty())
|
|
goto out;
|
|
|
|
o->onode.attrs.clear();
|
|
txc->write_onode(o);
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_do_omap_clear(TransContext *txc, OnodeRef& o)
|
|
{
|
|
const string& omap_prefix = o->get_omap_prefix();
|
|
string prefix, tail;
|
|
o->get_omap_header(&prefix);
|
|
o->get_omap_tail(&tail);
|
|
txc->t->rm_range_keys(omap_prefix, prefix, tail);
|
|
txc->t->rmkey(omap_prefix, tail);
|
|
o->onode.clear_omap_flag();
|
|
dout(20) << __func__ << " remove range start: "
|
|
<< pretty_binary_string(prefix) << " end: "
|
|
<< pretty_binary_string(tail) << dendl;
|
|
}
|
|
|
|
int BlueStore::_omap_clear(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
auto t0 = mono_clock::now();
|
|
|
|
int r = 0;
|
|
if (o->onode.has_omap()) {
|
|
o->flush();
|
|
_do_omap_clear(txc, o);
|
|
txc->write_onode(o);
|
|
}
|
|
logger->tinc(l_bluestore_omap_clear_lat, mono_clock::now() - t0);
|
|
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_omap_setkeys(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
bufferlist &bl)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
int r;
|
|
auto p = bl.cbegin();
|
|
__u32 num;
|
|
if (!o->onode.has_omap()) {
|
|
if (o->oid.is_pgmeta()) {
|
|
o->onode.set_omap_flags_pgmeta();
|
|
} else {
|
|
o->onode.set_omap_flags(per_pool_omap == OMAP_BULK);
|
|
}
|
|
txc->write_onode(o);
|
|
|
|
const string& prefix = o->get_omap_prefix();
|
|
string key_tail;
|
|
bufferlist tail;
|
|
o->get_omap_tail(&key_tail);
|
|
txc->t->set(prefix, key_tail, tail);
|
|
} else {
|
|
txc->note_modified_object(o);
|
|
}
|
|
const string& prefix = o->get_omap_prefix();
|
|
string final_key;
|
|
o->get_omap_key(string(), &final_key);
|
|
size_t base_key_len = final_key.size();
|
|
decode(num, p);
|
|
auto num0 = num;
|
|
uint64_t total_bytes = 0;
|
|
while (num--) {
|
|
string key;
|
|
bufferlist value;
|
|
decode(key, p);
|
|
decode(value, p);
|
|
final_key.resize(base_key_len); // keep prefix
|
|
final_key += key;
|
|
dout(20) << __func__ << " " << pretty_binary_string(final_key)
|
|
<< " <- " << key << dendl;
|
|
txc->t->set(prefix, final_key, value);
|
|
total_bytes += value.length();
|
|
}
|
|
logger->inc(l_bluestore_omap_setkeys_count);
|
|
logger->inc(l_bluestore_omap_setkeys_records, num0);
|
|
logger->inc(l_bluestore_omap_setkeys_bytes, total_bytes);
|
|
r = 0;
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_omap_setheader(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
bufferlist& bl)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
int r;
|
|
string key;
|
|
if (!o->onode.has_omap()) {
|
|
if (o->oid.is_pgmeta()) {
|
|
o->onode.set_omap_flags_pgmeta();
|
|
} else {
|
|
o->onode.set_omap_flags(per_pool_omap == OMAP_BULK);
|
|
}
|
|
txc->write_onode(o);
|
|
|
|
const string& prefix = o->get_omap_prefix();
|
|
string key_tail;
|
|
bufferlist tail;
|
|
o->get_omap_tail(&key_tail);
|
|
txc->t->set(prefix, key_tail, tail);
|
|
} else {
|
|
txc->note_modified_object(o);
|
|
}
|
|
const string& prefix = o->get_omap_prefix();
|
|
o->get_omap_header(&key);
|
|
txc->t->set(prefix, key, bl);
|
|
logger->inc(l_bluestore_omap_setheader_count);
|
|
logger->inc(l_bluestore_omap_setheader_bytes, bl.length());
|
|
r = 0;
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_omap_rmkeys(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
bufferlist& bl)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
int r = 0;
|
|
auto p = bl.cbegin();
|
|
__u32 num;
|
|
string final_key;
|
|
if (!o->onode.has_omap()) {
|
|
goto out;
|
|
}
|
|
{
|
|
const string& prefix = o->get_omap_prefix();
|
|
o->get_omap_key(string(), &final_key);
|
|
size_t base_key_len = final_key.size();
|
|
decode(num, p);
|
|
logger->inc(l_bluestore_omap_rmkeys_count, num);
|
|
while (num--) {
|
|
string key;
|
|
decode(key, p);
|
|
final_key.resize(base_key_len); // keep prefix
|
|
final_key += key;
|
|
dout(20) << __func__ << " rm " << pretty_binary_string(final_key)
|
|
<< " <- " << key << dendl;
|
|
txc->t->rmkey(prefix, final_key);
|
|
}
|
|
}
|
|
txc->note_modified_object(o);
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_omap_rmkey_range(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
const string& first, const string& last)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid << dendl;
|
|
string key_first, key_last;
|
|
int r = 0;
|
|
if (!o->onode.has_omap()) {
|
|
goto out;
|
|
}
|
|
{
|
|
const string& prefix = o->get_omap_prefix();
|
|
o->flush();
|
|
o->get_omap_key(first, &key_first);
|
|
o->get_omap_key(last, &key_last);
|
|
logger->inc(l_bluestore_omap_rmkey_ranges_count);
|
|
txc->t->rm_range_keys(prefix, key_first, key_last);
|
|
dout(20) << __func__ << " remove range start: "
|
|
<< pretty_binary_string(key_first) << " end: "
|
|
<< pretty_binary_string(key_last) << dendl;
|
|
}
|
|
txc->note_modified_object(o);
|
|
|
|
out:
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_set_alloc_hint(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& o,
|
|
uint64_t expected_object_size,
|
|
uint64_t expected_write_size,
|
|
uint32_t flags)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " object_size " << expected_object_size
|
|
<< " write_size " << expected_write_size
|
|
<< " flags " << ceph_osd_alloc_hint_flag_string(flags)
|
|
<< dendl;
|
|
int r = 0;
|
|
o->onode.expected_object_size = expected_object_size;
|
|
o->onode.expected_write_size = expected_write_size;
|
|
o->onode.alloc_hint_flags = flags;
|
|
txc->write_onode(o);
|
|
dout(10) << __func__ << " " << c->cid << " " << o->oid
|
|
<< " object_size " << expected_object_size
|
|
<< " write_size " << expected_write_size
|
|
<< " flags " << ceph_osd_alloc_hint_flag_string(flags)
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_clone(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& oldo,
|
|
OnodeRef& newo)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << oldo->oid << " -> "
|
|
<< newo->oid << dendl;
|
|
int r = 0;
|
|
if (oldo->oid.hobj.get_hash() != newo->oid.hobj.get_hash()) {
|
|
derr << __func__ << " mismatched hash on " << oldo->oid
|
|
<< " and " << newo->oid << dendl;
|
|
return -EINVAL;
|
|
}
|
|
|
|
_assign_nid(txc, newo);
|
|
|
|
// clone data
|
|
oldo->flush();
|
|
_do_truncate(txc, c, newo, 0);
|
|
if (cct->_conf->bluestore_clone_cow) {
|
|
_do_clone_range(txc, c, oldo, newo, 0, oldo->onode.size, 0);
|
|
} else {
|
|
bufferlist bl;
|
|
r = _do_read(c.get(), oldo, 0, oldo->onode.size, bl, 0);
|
|
if (r < 0)
|
|
goto out;
|
|
r = _do_write(txc, c, newo, 0, oldo->onode.size, bl, 0);
|
|
if (r < 0)
|
|
goto out;
|
|
}
|
|
|
|
// clone attrs
|
|
newo->onode.attrs = oldo->onode.attrs;
|
|
|
|
// clone omap
|
|
if (newo->onode.has_omap()) {
|
|
dout(20) << __func__ << " clearing old omap data" << dendl;
|
|
newo->flush();
|
|
_do_omap_clear(txc, newo);
|
|
}
|
|
if (oldo->onode.has_omap()) {
|
|
dout(20) << __func__ << " copying omap data" << dendl;
|
|
if (newo->oid.is_pgmeta()) {
|
|
newo->onode.set_omap_flags_pgmeta();
|
|
} else {
|
|
newo->onode.set_omap_flags(per_pool_omap == OMAP_BULK);
|
|
}
|
|
// check if prefix for omap key is exactly the same size for both objects
|
|
// otherwise rewrite_omap_key will corrupt data
|
|
ceph_assert(oldo->onode.flags == newo->onode.flags);
|
|
const string& prefix = newo->get_omap_prefix();
|
|
string head, tail;
|
|
oldo->get_omap_header(&head);
|
|
oldo->get_omap_tail(&tail);
|
|
KeyValueDB::Iterator it = db->get_iterator(prefix, 0, KeyValueDB::IteratorBounds{head, tail});
|
|
it->lower_bound(head);
|
|
while (it->valid()) {
|
|
if (it->key() >= tail) {
|
|
dout(30) << __func__ << " reached tail" << dendl;
|
|
break;
|
|
} else {
|
|
dout(30) << __func__ << " got header/data "
|
|
<< pretty_binary_string(it->key()) << dendl;
|
|
string key;
|
|
newo->rewrite_omap_key(it->key(), &key);
|
|
txc->t->set(prefix, key, it->value());
|
|
}
|
|
it->next();
|
|
}
|
|
string new_tail;
|
|
bufferlist new_tail_value;
|
|
newo->get_omap_tail(&new_tail);
|
|
txc->t->set(prefix, new_tail, new_tail_value);
|
|
}
|
|
|
|
txc->write_onode(newo);
|
|
r = 0;
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << oldo->oid << " -> "
|
|
<< newo->oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_do_clone_range(
|
|
TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& oldo,
|
|
OnodeRef& newo,
|
|
uint64_t srcoff,
|
|
uint64_t length,
|
|
uint64_t dstoff)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << oldo->oid << " -> "
|
|
<< newo->oid
|
|
<< " 0x" << std::hex << srcoff << "~" << length << " -> "
|
|
<< " 0x" << dstoff << "~" << length << std::dec << dendl;
|
|
oldo->extent_map.fault_range(db, srcoff, length);
|
|
newo->extent_map.fault_range(db, dstoff, length);
|
|
// it is possible the onode had previous buffers written
|
|
newo->bc.discard(c->cache, dstoff, length);
|
|
_dump_onode<30>(cct, *oldo);
|
|
_dump_onode<30>(cct, *newo);
|
|
|
|
if (elastic_shared_blobs) {
|
|
oldo->extent_map.dup_esb(this, txc, c, oldo, newo, srcoff, length, dstoff);
|
|
} else {
|
|
oldo->extent_map.dup(this, txc, c, oldo, newo, srcoff, length, dstoff);
|
|
}
|
|
|
|
_dump_onode<30>(cct, *oldo);
|
|
_dump_onode<30>(cct, *newo);
|
|
return 0;
|
|
}
|
|
|
|
int BlueStore::_clone_range(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& oldo,
|
|
OnodeRef& newo,
|
|
uint64_t srcoff, uint64_t length, uint64_t dstoff)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << oldo->oid << " -> "
|
|
<< newo->oid << " from 0x" << std::hex << srcoff << "~" << length
|
|
<< " to offset 0x" << dstoff << std::dec << dendl;
|
|
int r = 0;
|
|
|
|
if (srcoff + length >= OBJECT_MAX_SIZE ||
|
|
dstoff + length >= OBJECT_MAX_SIZE) {
|
|
r = -E2BIG;
|
|
goto out;
|
|
}
|
|
if (srcoff + length > oldo->onode.size) {
|
|
r = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
_assign_nid(txc, newo);
|
|
|
|
if (length > 0) {
|
|
if (cct->_conf->bluestore_clone_cow) {
|
|
_do_zero(txc, c, newo, dstoff, length);
|
|
_do_clone_range(txc, c, oldo, newo, srcoff, length, dstoff);
|
|
} else {
|
|
bufferlist bl;
|
|
r = _do_read(c.get(), oldo, srcoff, length, bl, 0);
|
|
if (r < 0)
|
|
goto out;
|
|
r = _do_write(txc, c, newo, dstoff, bl.length(), bl, 0);
|
|
if (r < 0)
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
txc->write_onode(newo);
|
|
r = 0;
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << oldo->oid << " -> "
|
|
<< newo->oid << " from 0x" << std::hex << srcoff << "~" << length
|
|
<< " to offset 0x" << dstoff << std::dec
|
|
<< " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_rename(TransContext *txc,
|
|
CollectionRef& c,
|
|
OnodeRef& oldo,
|
|
OnodeRef& newo,
|
|
const ghobject_t& new_oid)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " " << oldo->oid << " -> "
|
|
<< new_oid << dendl;
|
|
int r;
|
|
ghobject_t old_oid = oldo->oid;
|
|
mempool::bluestore_cache_meta::string new_okey;
|
|
|
|
if (newo) {
|
|
if (newo->exists) {
|
|
r = -EEXIST;
|
|
goto out;
|
|
}
|
|
ceph_assert(txc->onodes.count(newo) == 0);
|
|
}
|
|
|
|
txc->t->rmkey(PREFIX_OBJ, oldo->key.c_str(), oldo->key.size());
|
|
|
|
// rewrite shards
|
|
{
|
|
oldo->extent_map.fault_range(db, 0, oldo->onode.size);
|
|
get_object_key(cct, new_oid, &new_okey);
|
|
string key;
|
|
for (auto &s : oldo->extent_map.shards) {
|
|
generate_extent_shard_key_and_apply(oldo->key, s.shard_info->offset, &key,
|
|
[&](const string& final_key) {
|
|
txc->t->rmkey(PREFIX_OBJ, final_key);
|
|
}
|
|
);
|
|
s.dirty = true;
|
|
}
|
|
}
|
|
|
|
newo = oldo;
|
|
txc->write_onode(newo);
|
|
|
|
// this adjusts oldo->{oid,key}, and reset oldo to a fresh empty
|
|
// Onode in the old slot
|
|
c->onode_space.rename(oldo, old_oid, new_oid, new_okey);
|
|
r = 0;
|
|
|
|
// hold a ref to new Onode in old name position, to ensure we don't drop
|
|
// it from the cache before this txc commits (or else someone may come along
|
|
// and read newo's metadata via the old name).
|
|
txc->note_modified_object(oldo);
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << c->cid << " " << old_oid << " -> "
|
|
<< new_oid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
// collections
|
|
|
|
int BlueStore::_create_collection(
|
|
TransContext *txc,
|
|
const coll_t &cid,
|
|
unsigned bits,
|
|
CollectionRef *c)
|
|
{
|
|
dout(15) << __func__ << " " << cid << " bits " << bits << dendl;
|
|
int r;
|
|
bufferlist bl;
|
|
|
|
{
|
|
std::unique_lock l(coll_lock);
|
|
if (*c) {
|
|
r = -EEXIST;
|
|
goto out;
|
|
}
|
|
auto p = new_coll_map.find(cid);
|
|
ceph_assert(p != new_coll_map.end());
|
|
*c = p->second;
|
|
(*c)->cnode.bits = bits;
|
|
coll_map[cid] = *c;
|
|
new_coll_map.erase(p);
|
|
}
|
|
encode((*c)->cnode, bl);
|
|
txc->t->set(PREFIX_COLL, stringify(cid), bl);
|
|
r = 0;
|
|
|
|
out:
|
|
dout(10) << __func__ << " " << cid << " bits " << bits << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_remove_collection(TransContext *txc, const coll_t &cid,
|
|
CollectionRef *c)
|
|
{
|
|
dout(15) << __func__ << " " << cid << dendl;
|
|
int r;
|
|
|
|
(*c)->flush_all_but_last();
|
|
{
|
|
std::unique_lock l(coll_lock);
|
|
if (!*c) {
|
|
r = -ENOENT;
|
|
goto out;
|
|
}
|
|
size_t nonexistent_count = 0;
|
|
ceph_assert((*c)->exists);
|
|
if ((*c)->onode_space.map_any([&](Onode* o) {
|
|
if (o->exists) {
|
|
dout(1) << __func__ << " " << o->oid << " " << o
|
|
<< " exists in onode_map" << dendl;
|
|
return true;
|
|
}
|
|
++nonexistent_count;
|
|
return false;
|
|
})) {
|
|
r = -ENOTEMPTY;
|
|
goto out;
|
|
}
|
|
vector<ghobject_t> ls;
|
|
ghobject_t next;
|
|
// Enumerate onodes in db, up to nonexistent_count + 1
|
|
// then check if all of them are marked as non-existent.
|
|
// Bypass the check if (next != ghobject_t::get_max())
|
|
r = _collection_list(c->get(), ghobject_t(), ghobject_t::get_max(),
|
|
nonexistent_count + 1, false, &ls, &next);
|
|
if (r >= 0) {
|
|
// If true mean collecton has more objects than nonexistent_count,
|
|
// so bypass check.
|
|
bool exists = (!next.is_max());
|
|
for (auto it = ls.begin(); !exists && it < ls.end(); ++it) {
|
|
dout(10) << __func__ << " oid " << *it << dendl;
|
|
auto onode = (*c)->onode_space.lookup(*it);
|
|
exists = !onode || onode->exists;
|
|
if (exists) {
|
|
dout(1) << __func__ << " " << *it
|
|
<< " exists in db, "
|
|
<< (!onode ? "not present in ram" : "present in ram")
|
|
<< dendl;
|
|
}
|
|
}
|
|
if (!exists) {
|
|
_do_remove_collection(txc, c);
|
|
r = 0;
|
|
} else {
|
|
dout(10) << __func__ << " " << cid
|
|
<< " is non-empty" << dendl;
|
|
r = -ENOTEMPTY;
|
|
}
|
|
}
|
|
}
|
|
out:
|
|
dout(10) << __func__ << " " << cid << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
void BlueStore::_do_remove_collection(TransContext *txc,
|
|
CollectionRef *c)
|
|
{
|
|
coll_map.erase((*c)->cid);
|
|
txc->removed_collections.push_back(*c);
|
|
(*c)->exists = false;
|
|
_osr_register_zombie((*c)->osr.get());
|
|
txc->t->rmkey(PREFIX_COLL, stringify((*c)->cid));
|
|
c->reset();
|
|
}
|
|
|
|
int BlueStore::_split_collection(TransContext *txc,
|
|
CollectionRef& c,
|
|
CollectionRef& d,
|
|
unsigned bits, int rem)
|
|
{
|
|
dout(15) << __func__ << " " << c->cid << " to " << d->cid << " "
|
|
<< " bits " << bits << dendl;
|
|
std::unique_lock l(c->lock);
|
|
std::unique_lock l2(d->lock);
|
|
int r;
|
|
|
|
// flush all previous deferred writes on this sequencer. this is a bit
|
|
// heavyweight, but we need to make sure all deferred writes complete
|
|
// before we split as the new collection's sequencer may need to order
|
|
// this after those writes, and we don't bother with the complexity of
|
|
// moving those TransContexts over to the new osr.
|
|
_osr_drain_preceding(txc);
|
|
|
|
// move any cached items (onodes and referenced shared blobs) that will
|
|
// belong to the child collection post-split. leave everything else behind.
|
|
// this may include things that don't strictly belong to the now-smaller
|
|
// parent split, but the OSD will always send us a split for every new
|
|
// child.
|
|
|
|
spg_t pgid, dest_pgid;
|
|
bool is_pg = c->cid.is_pg(&pgid);
|
|
ceph_assert(is_pg);
|
|
is_pg = d->cid.is_pg(&dest_pgid);
|
|
ceph_assert(is_pg);
|
|
|
|
// the destination should initially be empty.
|
|
ceph_assert(d->onode_space.empty());
|
|
ceph_assert(d->shared_blob_set.empty());
|
|
ceph_assert(d->cnode.bits == bits);
|
|
|
|
c->split_cache(d.get());
|
|
|
|
// adjust bits. note that this will be redundant for all but the first
|
|
// split call for this parent (first child).
|
|
c->cnode.bits = bits;
|
|
ceph_assert(d->cnode.bits == bits);
|
|
r = 0;
|
|
|
|
bufferlist bl;
|
|
encode(c->cnode, bl);
|
|
txc->t->set(PREFIX_COLL, stringify(c->cid), bl);
|
|
|
|
dout(10) << __func__ << " " << c->cid << " to " << d->cid << " "
|
|
<< " bits " << bits << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
int BlueStore::_merge_collection(
|
|
TransContext *txc,
|
|
CollectionRef *c,
|
|
CollectionRef& d,
|
|
unsigned bits)
|
|
{
|
|
dout(15) << __func__ << " " << (*c)->cid << " to " << d->cid
|
|
<< " bits " << bits << dendl;
|
|
std::unique_lock l((*c)->lock);
|
|
std::unique_lock l2(d->lock);
|
|
int r;
|
|
|
|
coll_t cid = (*c)->cid;
|
|
|
|
// flush all previous deferred writes on the source collection to ensure
|
|
// that all deferred writes complete before we merge as the target collection's
|
|
// sequencer may need to order new ops after those writes.
|
|
|
|
_osr_drain((*c)->osr.get());
|
|
|
|
// move any cached items (onodes and referenced shared blobs) that will
|
|
// belong to the child collection post-split. leave everything else behind.
|
|
// this may include things that don't strictly belong to the now-smaller
|
|
// parent split, but the OSD will always send us a split for every new
|
|
// child.
|
|
|
|
spg_t pgid, dest_pgid;
|
|
bool is_pg = cid.is_pg(&pgid);
|
|
ceph_assert(is_pg);
|
|
is_pg = d->cid.is_pg(&dest_pgid);
|
|
ceph_assert(is_pg);
|
|
|
|
// adjust bits. note that this will be redundant for all but the first
|
|
// merge call for the parent/target.
|
|
d->cnode.bits = bits;
|
|
|
|
// behavior depends on target (d) bits, so this after that is updated.
|
|
(*c)->split_cache(d.get());
|
|
|
|
// remove source collection
|
|
{
|
|
std::unique_lock l3(coll_lock);
|
|
_do_remove_collection(txc, c);
|
|
}
|
|
|
|
r = 0;
|
|
|
|
bufferlist bl;
|
|
encode(d->cnode, bl);
|
|
txc->t->set(PREFIX_COLL, stringify(d->cid), bl);
|
|
|
|
dout(10) << __func__ << " " << cid << " to " << d->cid << " "
|
|
<< " bits " << bits << " = " << r << dendl;
|
|
return r;
|
|
}
|
|
|
|
size_t BlueStore::_trim_slow_op_event_queue(mono_clock::time_point cur_time) {
|
|
ceph_assert(ceph_mutex_is_locked(qlock));
|
|
auto warn_duration = std::chrono::seconds(cct->_conf->bluestore_slow_ops_warn_lifetime);
|
|
while (!slow_op_event_queue.empty() &&
|
|
((slow_op_event_queue.front() < cur_time - warn_duration) ||
|
|
(slow_op_event_queue.size() > cct->_conf->bluestore_slow_ops_warn_threshold))) {
|
|
slow_op_event_queue.pop();
|
|
}
|
|
return slow_op_event_queue.size();
|
|
}
|
|
|
|
void BlueStore::_add_slow_op_event() {
|
|
if (!cct->_conf->bluestore_slow_ops_warn_threshold) {
|
|
return;
|
|
}
|
|
std::lock_guard lock(qlock);
|
|
auto cur_time = mono_clock::now();
|
|
slow_op_event_queue.push(cur_time);
|
|
_trim_slow_op_event_queue(cur_time);
|
|
}
|
|
|
|
void BlueStore::log_latency(
|
|
const char* name,
|
|
int idx,
|
|
const ceph::timespan& l,
|
|
double lat_threshold,
|
|
const char* info,
|
|
int idx2)
|
|
{
|
|
logger->tinc(idx, l);
|
|
if (lat_threshold > 0.0 &&
|
|
l >= make_timespan(lat_threshold)) {
|
|
dout(0) << __func__ << " slow operation observed for " << name
|
|
<< ", latency = " << l
|
|
<< info
|
|
<< dendl;
|
|
_add_slow_op_event();
|
|
if (idx2 > l_bluestore_first && idx2 < l_bluestore_last) {
|
|
logger->inc(idx2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::log_latency_fn(
|
|
const char* name,
|
|
int idx,
|
|
const ceph::timespan& l,
|
|
double lat_threshold,
|
|
std::function<string (const ceph::timespan& lat)> fn,
|
|
int idx2)
|
|
{
|
|
logger->tinc(idx, l);
|
|
if (lat_threshold > 0.0 &&
|
|
l >= make_timespan(lat_threshold)) {
|
|
dout(0) << __func__ << " slow operation observed for " << name
|
|
<< ", latency = " << l
|
|
<< fn(l)
|
|
<< dendl;
|
|
_add_slow_op_event();
|
|
if (idx2 > l_bluestore_first && idx2 < l_bluestore_last) {
|
|
logger->inc(idx2);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(WITH_LTTNG)
|
|
void BlueStore::BlueStoreThrottle::emit_initial_tracepoint(
|
|
KeyValueDB &db,
|
|
TransContext &txc,
|
|
mono_clock::time_point start_throttle_acquire)
|
|
{
|
|
pending_kv_ios += txc.ios;
|
|
if (txc.deferred_txn) {
|
|
pending_deferred_ios += txc.ios;
|
|
}
|
|
|
|
uint64_t started = 0;
|
|
uint64_t completed = 0;
|
|
if (should_trace(&started, &completed)) {
|
|
txc.tracing = true;
|
|
uint64_t rocksdb_base_level,
|
|
rocksdb_estimate_pending_compaction_bytes,
|
|
rocksdb_cur_size_all_mem_tables,
|
|
rocksdb_compaction_pending,
|
|
rocksdb_mem_table_flush_pending,
|
|
rocksdb_num_running_compactions,
|
|
rocksdb_num_running_flushes,
|
|
rocksdb_actual_delayed_write_rate;
|
|
db.get_property(
|
|
"rocksdb.base-level",
|
|
&rocksdb_base_level);
|
|
db.get_property(
|
|
"rocksdb.estimate-pending-compaction-bytes",
|
|
&rocksdb_estimate_pending_compaction_bytes);
|
|
db.get_property(
|
|
"rocksdb.cur-size-all-mem-tables",
|
|
&rocksdb_cur_size_all_mem_tables);
|
|
db.get_property(
|
|
"rocksdb.compaction-pending",
|
|
&rocksdb_compaction_pending);
|
|
db.get_property(
|
|
"rocksdb.mem-table-flush-pending",
|
|
&rocksdb_mem_table_flush_pending);
|
|
db.get_property(
|
|
"rocksdb.num-running-compactions",
|
|
&rocksdb_num_running_compactions);
|
|
db.get_property(
|
|
"rocksdb.num-running-flushes",
|
|
&rocksdb_num_running_flushes);
|
|
db.get_property(
|
|
"rocksdb.actual-delayed-write-rate",
|
|
&rocksdb_actual_delayed_write_rate);
|
|
|
|
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_initial_state,
|
|
txc.osr->get_sequencer_id(),
|
|
(uint64_t)&txc,
|
|
throttle_bytes.get_current(),
|
|
throttle_deferred_bytes.get_current(),
|
|
pending_kv_ios,
|
|
pending_deferred_ios,
|
|
started,
|
|
completed,
|
|
ceph::to_seconds<double>(mono_clock::now() - start_throttle_acquire));
|
|
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_initial_state_rocksdb,
|
|
txc.osr->get_sequencer_id(),
|
|
(uint64_t)&txc,
|
|
rocksdb_base_level,
|
|
rocksdb_estimate_pending_compaction_bytes,
|
|
rocksdb_cur_size_all_mem_tables,
|
|
rocksdb_compaction_pending,
|
|
rocksdb_mem_table_flush_pending,
|
|
rocksdb_num_running_compactions,
|
|
rocksdb_num_running_flushes,
|
|
rocksdb_actual_delayed_write_rate);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
mono_clock::duration BlueStore::BlueStoreThrottle::log_state_latency(
|
|
TransContext &txc, PerfCounters *logger, int state)
|
|
{
|
|
mono_clock::time_point now = mono_clock::now();
|
|
mono_clock::duration lat = now - txc.last_stamp;
|
|
logger->tinc(state, lat);
|
|
#if defined(WITH_LTTNG)
|
|
if (txc.tracing &&
|
|
state >= l_bluestore_state_prepare_lat &&
|
|
state <= l_bluestore_state_done_lat) {
|
|
OID_ELAPSED("", lat.to_nsec() / 1000.0, txc.get_state_latency_name(state));
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_state_duration,
|
|
txc.osr->get_sequencer_id(),
|
|
(uint64_t)&txc,
|
|
state,
|
|
ceph::to_seconds<double>(lat));
|
|
}
|
|
#endif
|
|
txc.last_stamp = now;
|
|
return lat;
|
|
}
|
|
|
|
bool BlueStore::BlueStoreThrottle::try_start_transaction(
|
|
KeyValueDB &db,
|
|
TransContext &txc,
|
|
mono_clock::time_point start_throttle_acquire)
|
|
{
|
|
{
|
|
std::lock_guard l(lock);
|
|
auto cost0 = throttle_bytes.get_current();
|
|
if (std::cmp_greater(cost0 + txc.cost, bytes_observed_max)) {
|
|
bytes_observed_max = cost0 + txc.cost;
|
|
bytes_max_ts = ceph_clock_now();
|
|
}
|
|
auto txcs = ++transactions;
|
|
if (txcs > transactions_observed_max) {
|
|
transactions_observed_max = txcs;
|
|
transactions_max_ts = ceph_clock_now();
|
|
}
|
|
}
|
|
|
|
throttle_bytes.get(txc.cost);
|
|
|
|
if (!txc.deferred_txn || throttle_deferred_bytes.get_or_fail(txc.cost)) {
|
|
emit_initial_tracepoint(db, txc, start_throttle_acquire);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void BlueStore::BlueStoreThrottle::finish_start_transaction(
|
|
KeyValueDB &db,
|
|
TransContext &txc,
|
|
mono_clock::time_point start_throttle_acquire)
|
|
{
|
|
ceph_assert(txc.deferred_txn);
|
|
throttle_deferred_bytes.get(txc.cost);
|
|
emit_initial_tracepoint(db, txc, start_throttle_acquire);
|
|
}
|
|
|
|
#if defined(WITH_LTTNG)
|
|
void BlueStore::BlueStoreThrottle::complete_kv(TransContext &txc)
|
|
{
|
|
pending_kv_ios -= 1;
|
|
ios_completed_since_last_traced++;
|
|
if (txc.tracing) {
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_commit_latency,
|
|
txc.osr->get_sequencer_id(),
|
|
(uint64_t)&txc,
|
|
ceph::to_seconds<double>(mono_clock::now() - txc.start));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(WITH_LTTNG)
|
|
void BlueStore::BlueStoreThrottle::complete(TransContext &txc)
|
|
{
|
|
if (txc.deferred_txn) {
|
|
pending_deferred_ios -= 1;
|
|
}
|
|
if (txc.tracing) {
|
|
mono_clock::time_point now = mono_clock::now();
|
|
mono_clock::duration lat = now - txc.start;
|
|
tracepoint(
|
|
bluestore,
|
|
transaction_total_duration,
|
|
txc.osr->get_sequencer_id(),
|
|
(uint64_t)&txc,
|
|
ceph::to_seconds<double>(lat));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
const string prefix_onode = "o";
|
|
const string prefix_onode_shard = "x";
|
|
const string prefix_other = "Z";
|
|
//Itrerates through the db and collects the stats
|
|
void BlueStore::generate_db_histogram(Formatter *f)
|
|
{
|
|
//globals
|
|
uint64_t num_onodes = 0;
|
|
uint64_t num_shards = 0;
|
|
uint64_t num_super = 0;
|
|
uint64_t num_coll = 0;
|
|
uint64_t num_omap = 0;
|
|
uint64_t num_pgmeta_omap = 0;
|
|
uint64_t num_deferred = 0;
|
|
uint64_t num_alloc = 0;
|
|
uint64_t num_stat = 0;
|
|
uint64_t num_others = 0;
|
|
uint64_t num_shared_shards = 0;
|
|
size_t max_key_size =0, max_value_size = 0;
|
|
uint64_t total_key_size = 0, total_value_size = 0;
|
|
size_t key_size = 0, value_size = 0;
|
|
KeyValueHistogram hist;
|
|
|
|
auto start = coarse_mono_clock::now();
|
|
|
|
KeyValueDB::WholeSpaceIterator iter = db->get_wholespace_iterator();
|
|
iter->seek_to_first();
|
|
while (iter->valid()) {
|
|
dout(30) << __func__ << " Key: " << iter->key() << dendl;
|
|
key_size = iter->key_size();
|
|
value_size = iter->value_size();
|
|
hist.value_hist[hist.get_value_slab(value_size)]++;
|
|
max_key_size = std::max(max_key_size, key_size);
|
|
max_value_size = std::max(max_value_size, value_size);
|
|
total_key_size += key_size;
|
|
total_value_size += value_size;
|
|
|
|
pair<string,string> key(iter->raw_key());
|
|
|
|
if (key.first == PREFIX_SUPER) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_SUPER, key_size, value_size);
|
|
num_super++;
|
|
} else if (key.first == PREFIX_STAT) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_STAT, key_size, value_size);
|
|
num_stat++;
|
|
} else if (key.first == PREFIX_COLL) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_COLL, key_size, value_size);
|
|
num_coll++;
|
|
} else if (key.first == PREFIX_OBJ) {
|
|
if (key.second.back() == ONODE_KEY_SUFFIX) {
|
|
hist.update_hist_entry(hist.key_hist, prefix_onode, key_size, value_size);
|
|
num_onodes++;
|
|
} else {
|
|
hist.update_hist_entry(hist.key_hist, prefix_onode_shard, key_size, value_size);
|
|
num_shards++;
|
|
}
|
|
} else if (key.first == PREFIX_OMAP) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_OMAP, key_size, value_size);
|
|
num_omap++;
|
|
} else if (key.first == PREFIX_PERPOOL_OMAP) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_PERPOOL_OMAP, key_size, value_size);
|
|
num_omap++;
|
|
} else if (key.first == PREFIX_PERPG_OMAP) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_PERPG_OMAP, key_size, value_size);
|
|
num_omap++;
|
|
} else if (key.first == PREFIX_PGMETA_OMAP) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_PGMETA_OMAP, key_size, value_size);
|
|
num_pgmeta_omap++;
|
|
} else if (key.first == PREFIX_DEFERRED) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_DEFERRED, key_size, value_size);
|
|
num_deferred++;
|
|
} else if (key.first == PREFIX_ALLOC || key.first == PREFIX_ALLOC_BITMAP) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_ALLOC, key_size, value_size);
|
|
num_alloc++;
|
|
} else if (key.first == PREFIX_SHARED_BLOB) {
|
|
hist.update_hist_entry(hist.key_hist, PREFIX_SHARED_BLOB, key_size, value_size);
|
|
num_shared_shards++;
|
|
} else {
|
|
hist.update_hist_entry(hist.key_hist, prefix_other, key_size, value_size);
|
|
num_others++;
|
|
}
|
|
iter->next();
|
|
}
|
|
|
|
ceph::timespan duration = coarse_mono_clock::now() - start;
|
|
f->open_object_section("rocksdb_key_value_stats");
|
|
f->dump_unsigned("num_onodes", num_onodes);
|
|
f->dump_unsigned("num_shards", num_shards);
|
|
f->dump_unsigned("num_super", num_super);
|
|
f->dump_unsigned("num_coll", num_coll);
|
|
f->dump_unsigned("num_omap", num_omap);
|
|
f->dump_unsigned("num_pgmeta_omap", num_pgmeta_omap);
|
|
f->dump_unsigned("num_deferred", num_deferred);
|
|
f->dump_unsigned("num_alloc", num_alloc);
|
|
f->dump_unsigned("num_stat", num_stat);
|
|
f->dump_unsigned("num_shared_shards", num_shared_shards);
|
|
f->dump_unsigned("num_others", num_others);
|
|
f->dump_unsigned("max_key_size", max_key_size);
|
|
f->dump_unsigned("max_value_size", max_value_size);
|
|
f->dump_unsigned("total_key_size", total_key_size);
|
|
f->dump_unsigned("total_value_size", total_value_size);
|
|
f->close_section();
|
|
|
|
hist.dump(f);
|
|
|
|
dout(20) << __func__ << " finished in " << duration << " seconds" << dendl;
|
|
|
|
}
|
|
|
|
void BlueStore::_shutdown_cache()
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
for (auto i : buffer_cache_shards) {
|
|
i->flush();
|
|
ceph_assert(i->empty());
|
|
}
|
|
for (auto& p : coll_map) {
|
|
// Clear deferred write buffers before clearing up Onodes
|
|
std::unique_lock l(p.second->lock);
|
|
|
|
p.second->onode_space.clear();
|
|
if (!p.second->shared_blob_set.empty()) {
|
|
derr << __func__ << " stray shared blobs on " << p.first << dendl;
|
|
p.second->shared_blob_set.dump<0>(cct);
|
|
}
|
|
ceph_assert(p.second->onode_space.empty());
|
|
ceph_assert(p.second->shared_blob_set.empty());
|
|
ceph_assert(p.second->get_nref() == 1);
|
|
}
|
|
coll_map.clear();
|
|
for (auto i : onode_cache_shards) {
|
|
ceph_assert(i->empty());
|
|
}
|
|
ceph_assert(Buffer::total == 0);
|
|
}
|
|
|
|
// For external caller.
|
|
// We use a best-effort policy instead, e.g.,
|
|
// we don't care if there are still some pinned onodes/data in the cache
|
|
// after this command is completed.
|
|
int BlueStore::flush_cache(ostream *os)
|
|
{
|
|
dout(10) << __func__ << dendl;
|
|
for (auto i : onode_cache_shards) {
|
|
i->flush();
|
|
}
|
|
for (auto i : buffer_cache_shards) {
|
|
i->flush();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void BlueStore::_apply_padding(uint64_t head_pad,
|
|
uint64_t tail_pad,
|
|
bufferlist& padded)
|
|
{
|
|
if (head_pad) {
|
|
padded.prepend_zero(head_pad);
|
|
}
|
|
if (tail_pad) {
|
|
padded.append_zero(tail_pad);
|
|
}
|
|
if (head_pad || tail_pad) {
|
|
dout(20) << __func__ << " can pad head 0x" << std::hex << head_pad
|
|
<< " tail 0x" << tail_pad << std::dec << dendl;
|
|
logger->inc(l_bluestore_write_pad_bytes, head_pad + tail_pad);
|
|
}
|
|
}
|
|
|
|
void BlueStore::_record_onode(OnodeRef& o, KeyValueDB::Transaction &txn)
|
|
{
|
|
// finalize extent_map shards
|
|
o->extent_map.update(txn, false);
|
|
if (o->extent_map.needs_reshard()) {
|
|
o->extent_map.reshard(db, txn, o->onode.segment_size);
|
|
o->extent_map.update(txn, true);
|
|
if (o->extent_map.needs_reshard()) {
|
|
dout(20) << __func__ << " warning: still wants reshard, check options?"
|
|
<< dendl;
|
|
o->extent_map.clear_needs_reshard();
|
|
}
|
|
logger->inc(l_bluestore_onode_reshard);
|
|
}
|
|
|
|
// bound encode
|
|
size_t bound = 0;
|
|
uint64_t flag = segment_size != 0 ? 0 : bluestore_onode_t::FLAG_DEBUG_FORCE_V2;
|
|
denc(o->onode, bound, flag);
|
|
o->extent_map.bound_encode_spanning_blobs(bound);
|
|
if (o->onode.extent_map_shards.empty()) {
|
|
denc(o->extent_map.inline_bl, bound);
|
|
}
|
|
|
|
// encode
|
|
bufferlist bl;
|
|
unsigned onode_part, blob_part, extent_part;
|
|
{
|
|
auto p = bl.get_contiguous_appender(bound, true);
|
|
denc(o->onode, p, flag);
|
|
onode_part = p.get_logical_offset();
|
|
o->extent_map.encode_spanning_blobs(p);
|
|
blob_part = p.get_logical_offset() - onode_part;
|
|
if (o->onode.extent_map_shards.empty()) {
|
|
denc(o->extent_map.inline_bl, p);
|
|
}
|
|
extent_part = p.get_logical_offset() - onode_part - blob_part;
|
|
}
|
|
|
|
dout(20) << __func__ << " onode " << o->oid << " is " << bl.length()
|
|
<< " (" << onode_part << " bytes onode + "
|
|
<< blob_part << " bytes spanning blobs + "
|
|
<< extent_part << " bytes inline extents)"
|
|
<< dendl;
|
|
|
|
|
|
txn->set(PREFIX_OBJ, o->key.c_str(), o->key.size(), bl);
|
|
}
|
|
|
|
void BlueStore::_log_alerts(osd_alert_list_t& alerts)
|
|
{
|
|
std::lock_guard l(qlock);
|
|
size_t used = bluefs && bluefs_layout.shared_bdev == BlueFS::BDEV_SLOW ?
|
|
bluefs->get_used(BlueFS::BDEV_SLOW) : 0;
|
|
if (used > 0) {
|
|
auto db_used = bluefs->get_used(BlueFS::BDEV_DB);
|
|
auto db_total = bluefs->get_block_device_size(BlueFS::BDEV_DB);
|
|
ostringstream ss;
|
|
ss << "spilled over " << byte_u_t(used)
|
|
<< " metadata from 'db' device (" << byte_u_t(db_used)
|
|
<< " used of " << byte_u_t(db_total) << ") to slow device";
|
|
spillover_alert = ss.str();
|
|
} else if (!spillover_alert.empty()){
|
|
spillover_alert.clear();
|
|
}
|
|
if (cct->_conf->bluestore_slow_ops_warn_threshold) {
|
|
size_t qsize = _trim_slow_op_event_queue(mono_clock::now());
|
|
if (qsize >= cct->_conf->bluestore_slow_ops_warn_threshold) {
|
|
ostringstream ss;
|
|
ss << "observed slow operation indications in BlueStore";
|
|
alerts.emplace("BLUESTORE_SLOW_OP_ALERT", ss.str());
|
|
}
|
|
}
|
|
bdev->collect_alerts(alerts, "BLOCK");
|
|
bluefs->collect_alerts(alerts);
|
|
if (!spurious_read_errors_alert.empty() &&
|
|
cct->_conf->bluestore_warn_on_spurious_read_errors) {
|
|
alerts.emplace(
|
|
"BLUESTORE_SPURIOUS_READ_ERRORS",
|
|
spurious_read_errors_alert);
|
|
}
|
|
if (!disk_size_mismatch_alert.empty()) {
|
|
alerts.emplace(
|
|
"BLUESTORE_DISK_SIZE_MISMATCH",
|
|
disk_size_mismatch_alert);
|
|
}
|
|
if (!legacy_statfs_alert.empty()) {
|
|
alerts.emplace(
|
|
"BLUESTORE_LEGACY_STATFS",
|
|
legacy_statfs_alert);
|
|
}
|
|
if (!spillover_alert.empty() &&
|
|
cct->_conf->bluestore_warn_on_bluefs_spillover) {
|
|
alerts.emplace(
|
|
"BLUEFS_SPILLOVER",
|
|
spillover_alert);
|
|
}
|
|
if (!no_per_pg_omap_alert.empty()) {
|
|
alerts.emplace(
|
|
"BLUESTORE_NO_PER_PG_OMAP",
|
|
no_per_pg_omap_alert);
|
|
}
|
|
if (!no_per_pool_omap_alert.empty()) {
|
|
alerts.emplace(
|
|
"BLUESTORE_NO_PER_POOL_OMAP",
|
|
no_per_pool_omap_alert);
|
|
}
|
|
string s0(failed_cmode);
|
|
|
|
if (!failed_compressors.empty()) {
|
|
if (!s0.empty()) {
|
|
s0 += ", ";
|
|
}
|
|
s0 += "unable to load:";
|
|
bool first = true;
|
|
for (auto& s : failed_compressors) {
|
|
if (first) {
|
|
first = false;
|
|
} else {
|
|
s0 += ", ";
|
|
}
|
|
s0 += s;
|
|
}
|
|
alerts.emplace(
|
|
"BLUESTORE_NO_COMPRESSION",
|
|
s0);
|
|
}
|
|
if (logger->get(l_bluestore_fragmentation) >
|
|
cct->_conf.get_val<double>("bluestore_warn_on_free_fragmentation") * 1e6) {
|
|
alerts.emplace("BLUESTORE_FREE_FRAGMENTATION",
|
|
fmt::format("{0:.6f}", logger->get(l_bluestore_fragmentation) * 1e-6));
|
|
}
|
|
}
|
|
|
|
void BlueStore::_collect_allocation_stats(uint64_t need, uint32_t alloc_size,
|
|
const PExtentVector& extents)
|
|
{
|
|
if (alloc_size != min_alloc_size) {
|
|
alloc_stats_count++;
|
|
alloc_stats_fragments += extents.size();
|
|
alloc_stats_size += need;
|
|
}
|
|
|
|
for (auto& e : extents) {
|
|
logger->hinc(l_bluestore_allocate_hist, e.length, need);
|
|
}
|
|
}
|
|
|
|
void BlueStore::_record_allocation_stats()
|
|
{
|
|
// don't care about data consistency,
|
|
// fields can be partially modified while making the tuple
|
|
auto t0 = std::make_tuple(
|
|
alloc_stats_count.exchange(0),
|
|
alloc_stats_fragments.exchange(0),
|
|
alloc_stats_size.exchange(0));
|
|
|
|
dout(0) << " allocation stats probe "
|
|
<< probe_count << ":"
|
|
<< " cnt: " << std::get<0>(t0)
|
|
<< " frags: " << std::get<1>(t0)
|
|
<< " size: " << std::get<2>(t0)
|
|
<< dendl;
|
|
|
|
|
|
//
|
|
// Keep the history for probes from the power-of-two sequence:
|
|
// -1, -2, -4, -8, -16
|
|
//
|
|
size_t base = 1;
|
|
for (auto& t : alloc_stats_history) {
|
|
dout(0) << " probe -"
|
|
<< base + (probe_count % base) << ": "
|
|
<< std::get<0>(t)
|
|
<< ", " << std::get<1>(t)
|
|
<< ", " << std::get<2>(t)
|
|
<< dendl;
|
|
base <<= 1;
|
|
}
|
|
dout(0) << "------------" << dendl;
|
|
|
|
++ probe_count;
|
|
|
|
for (ssize_t i = alloc_stats_history.size() - 1 ; i > 0 ; --i) {
|
|
if ((probe_count % (1 << i)) == 0) {
|
|
alloc_stats_history[i] = alloc_stats_history[i - 1];
|
|
}
|
|
}
|
|
alloc_stats_history[0].swap(t0);
|
|
}
|
|
|
|
// ===========================================
|
|
// BlueStoreRepairer
|
|
|
|
size_t BlueStoreRepairer::StoreSpaceTracker::filter_out(
|
|
const interval_set<uint64_t>& extents)
|
|
{
|
|
ceph_assert(granularity); // initialized
|
|
// can't call for the second time
|
|
ceph_assert(!was_filtered_out);
|
|
ceph_assert(collections_bfs.size() == objects_bfs.size());
|
|
|
|
uint64_t prev_pos = 0;
|
|
uint64_t npos = collections_bfs.size();
|
|
|
|
bloom_vector collections_reduced;
|
|
bloom_vector objects_reduced;
|
|
|
|
for (auto e : extents) {
|
|
if (e.second == 0) {
|
|
continue;
|
|
}
|
|
uint64_t pos = max(e.first / granularity, prev_pos);
|
|
uint64_t end_pos = 1 + (e.first + e.second - 1) / granularity;
|
|
while (pos != npos && pos < end_pos) {
|
|
ceph_assert( collections_bfs[pos].element_count() ==
|
|
objects_bfs[pos].element_count());
|
|
if (collections_bfs[pos].element_count()) {
|
|
collections_reduced.push_back(std::move(collections_bfs[pos]));
|
|
objects_reduced.push_back(std::move(objects_bfs[pos]));
|
|
}
|
|
++pos;
|
|
}
|
|
prev_pos = end_pos;
|
|
}
|
|
collections_reduced.swap(collections_bfs);
|
|
objects_reduced.swap(objects_bfs);
|
|
was_filtered_out = true;
|
|
return collections_bfs.size();
|
|
}
|
|
|
|
bool BlueStoreRepairer::remove_key(KeyValueDB *db,
|
|
const string& prefix,
|
|
const string& key)
|
|
{
|
|
std::lock_guard l(lock);
|
|
if (!remove_key_txn) {
|
|
remove_key_txn = db->get_transaction();
|
|
}
|
|
++to_repair_cnt;
|
|
remove_key_txn->rmkey(prefix, key);
|
|
|
|
return true;
|
|
}
|
|
|
|
void BlueStoreRepairer::fix_per_pool_omap(KeyValueDB *db, int val)
|
|
{
|
|
std::lock_guard l(lock); // possibly redundant
|
|
ceph_assert(fix_per_pool_omap_txn == nullptr);
|
|
fix_per_pool_omap_txn = db->get_transaction();
|
|
++to_repair_cnt;
|
|
bufferlist bl;
|
|
bl.append(stringify(val));
|
|
fix_per_pool_omap_txn->set(PREFIX_SUPER, "per_pool_omap", bl);
|
|
}
|
|
|
|
bool BlueStoreRepairer::fix_shared_blob(
|
|
KeyValueDB::Transaction txn,
|
|
uint64_t sbid,
|
|
bluestore_extent_ref_map_t* ref_map,
|
|
size_t repaired)
|
|
{
|
|
string key;
|
|
get_shared_blob_key(sbid, &key);
|
|
if (ref_map) {
|
|
bluestore_shared_blob_t persistent(sbid, std::move(*ref_map));
|
|
bufferlist bl;
|
|
encode(persistent, bl);
|
|
txn->set(PREFIX_SHARED_BLOB, key, bl);
|
|
} else {
|
|
txn->rmkey(PREFIX_SHARED_BLOB, key);
|
|
}
|
|
to_repair_cnt += repaired;
|
|
return true;
|
|
}
|
|
|
|
bool BlueStoreRepairer::fix_statfs(KeyValueDB *db,
|
|
const string& key,
|
|
const store_statfs_t& new_statfs)
|
|
{
|
|
std::lock_guard l(lock);
|
|
if (!fix_statfs_txn) {
|
|
fix_statfs_txn = db->get_transaction();
|
|
}
|
|
BlueStore::volatile_statfs vstatfs;
|
|
vstatfs = new_statfs;
|
|
bufferlist bl;
|
|
vstatfs.encode(bl);
|
|
++to_repair_cnt;
|
|
fix_statfs_txn->set(PREFIX_STAT, key, bl);
|
|
return true;
|
|
}
|
|
|
|
bool BlueStoreRepairer::fix_leaked(KeyValueDB *db,
|
|
FreelistManager* fm,
|
|
uint64_t offset, uint64_t len)
|
|
{
|
|
std::lock_guard l(lock);
|
|
ceph_assert(!fm->is_null_manager());
|
|
|
|
if (!fix_fm_leaked_txn) {
|
|
fix_fm_leaked_txn = db->get_transaction();
|
|
}
|
|
++to_repair_cnt;
|
|
fm->release(offset, len, fix_fm_leaked_txn);
|
|
return true;
|
|
}
|
|
bool BlueStoreRepairer::fix_false_free(KeyValueDB *db,
|
|
FreelistManager* fm,
|
|
uint64_t offset, uint64_t len)
|
|
{
|
|
std::lock_guard l(lock);
|
|
ceph_assert(!fm->is_null_manager());
|
|
|
|
if (!fix_fm_false_free_txn) {
|
|
fix_fm_false_free_txn = db->get_transaction();
|
|
}
|
|
++to_repair_cnt;
|
|
fm->allocate(offset, len, fix_fm_false_free_txn);
|
|
return true;
|
|
}
|
|
|
|
bool BlueStoreRepairer::fix_spanning_blobs(
|
|
KeyValueDB* db,
|
|
std::function<void(KeyValueDB::Transaction)> f)
|
|
{
|
|
std::lock_guard l(lock);
|
|
if (!fix_onode_txn) {
|
|
fix_onode_txn = db->get_transaction();
|
|
}
|
|
f(fix_onode_txn);
|
|
++to_repair_cnt;
|
|
return true;
|
|
}
|
|
|
|
bool BlueStoreRepairer::preprocess_misreference(KeyValueDB *db)
|
|
{
|
|
//NB: not for use in multithreading mode!!!
|
|
if (misreferenced_extents.size()) {
|
|
size_t n = space_usage_tracker.filter_out(misreferenced_extents);
|
|
ceph_assert(n > 0);
|
|
if (!fix_misreferences_txn) {
|
|
fix_misreferences_txn = db->get_transaction();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
unsigned BlueStoreRepairer::apply(KeyValueDB* db)
|
|
{
|
|
//NB: not for use in multithreading mode!!!
|
|
if (fix_per_pool_omap_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_per_pool_omap_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_per_pool_omap_txn = nullptr;
|
|
}
|
|
if (fix_fm_leaked_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_fm_leaked_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_fm_leaked_txn = nullptr;
|
|
}
|
|
if (fix_fm_false_free_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_fm_false_free_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_fm_false_free_txn = nullptr;
|
|
}
|
|
if (remove_key_txn) {
|
|
auto ok = db->submit_transaction_sync(remove_key_txn) == 0;
|
|
ceph_assert(ok);
|
|
remove_key_txn = nullptr;
|
|
}
|
|
if (fix_misreferences_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_misreferences_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_misreferences_txn = nullptr;
|
|
}
|
|
if (fix_onode_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_onode_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_onode_txn = nullptr;
|
|
}
|
|
if (fix_shared_blob_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_shared_blob_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_shared_blob_txn = nullptr;
|
|
}
|
|
if (fix_statfs_txn) {
|
|
auto ok = db->submit_transaction_sync(fix_statfs_txn) == 0;
|
|
ceph_assert(ok);
|
|
fix_statfs_txn = nullptr;
|
|
}
|
|
if (need_compact) {
|
|
db->compact();
|
|
need_compact = false;
|
|
}
|
|
unsigned repaired = to_repair_cnt;
|
|
to_repair_cnt = 0;
|
|
return repaired;
|
|
}
|
|
|
|
// =======================================================
|
|
// RocksDBBlueFSVolumeSelector
|
|
|
|
uint8_t RocksDBBlueFSVolumeSelector::select_prefer_bdev(void* h) {
|
|
ceph_assert(h != nullptr);
|
|
uint64_t hint = reinterpret_cast<uint64_t>(h);
|
|
uint8_t res;
|
|
switch (hint) {
|
|
case LEVEL_SLOW:
|
|
res = BlueFS::BDEV_SLOW;
|
|
if (db_avail4slow > 0) {
|
|
// considering statically available db space vs.
|
|
// - observed maximums on DB dev for DB/WAL/UNSORTED data
|
|
// - observed maximum spillovers
|
|
uint64_t max_db_use = 0; // max db usage we potentially observed
|
|
max_db_use += per_level_per_dev_max.at(BlueFS::BDEV_DB, LEVEL_LOG - LEVEL_FIRST);
|
|
max_db_use += per_level_per_dev_max.at(BlueFS::BDEV_DB, LEVEL_WAL - LEVEL_FIRST);
|
|
max_db_use += per_level_per_dev_max.at(BlueFS::BDEV_DB, LEVEL_DB - LEVEL_FIRST);
|
|
// this could go to db hence using it in the estimation
|
|
max_db_use += per_level_per_dev_max.at(BlueFS::BDEV_SLOW, LEVEL_DB - LEVEL_FIRST);
|
|
|
|
auto db_total = l_totals[LEVEL_DB - LEVEL_FIRST];
|
|
uint64_t avail = min(
|
|
db_avail4slow,
|
|
max_db_use < db_total ? db_total - max_db_use : 0);
|
|
|
|
// considering current DB dev usage for SLOW data
|
|
if (avail > per_level_per_dev_usage.at(BlueFS::BDEV_DB, LEVEL_SLOW - LEVEL_FIRST)) {
|
|
res = BlueFS::BDEV_DB;
|
|
}
|
|
}
|
|
break;
|
|
case LEVEL_LOG:
|
|
case LEVEL_WAL:
|
|
res = BlueFS::BDEV_WAL;
|
|
break;
|
|
case LEVEL_DB:
|
|
default:
|
|
res = BlueFS::BDEV_DB;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void RocksDBBlueFSVolumeSelector::get_paths(const std::string& base, paths& res) const
|
|
{
|
|
auto db_size = l_totals[LEVEL_DB - LEVEL_FIRST];
|
|
res.emplace_back(base, db_size);
|
|
auto slow_size = l_totals[LEVEL_SLOW - LEVEL_FIRST];
|
|
if (slow_size == 0) {
|
|
slow_size = db_size;
|
|
}
|
|
res.emplace_back(base + ".slow", slow_size);
|
|
}
|
|
|
|
void* RocksDBBlueFSVolumeSelector::get_hint_by_dir(std::string_view dirname) const {
|
|
uint8_t res = LEVEL_DB;
|
|
if (dirname.length() > 5) {
|
|
// the "db.slow" and "db.wal" directory names are hard-coded at
|
|
// match up with bluestore. the slow device is always the second
|
|
// one (when a dedicated block.db device is present and used at
|
|
// bdev 0). the wal device is always last.
|
|
if (boost::algorithm::ends_with(dirname, ".slow")) {
|
|
res = LEVEL_SLOW;
|
|
}
|
|
else if (boost::algorithm::ends_with(dirname, ".wal")) {
|
|
res = LEVEL_WAL;
|
|
}
|
|
}
|
|
return reinterpret_cast<void*>(res);
|
|
}
|
|
|
|
void RocksDBBlueFSVolumeSelector::dump(ostream& sout) {
|
|
auto max_x = per_level_per_dev_usage.get_max_x();
|
|
auto max_y = per_level_per_dev_usage.get_max_y();
|
|
|
|
sout << "RocksDBBlueFSVolumeSelector " << std::endl;
|
|
sout << ">>Settings<<"
|
|
<< " extra=" << byte_u_t(db_avail4slow)
|
|
<< ", extra level=" << extra_level
|
|
<< ", l0_size=" << byte_u_t(level0_size)
|
|
<< ", l_base=" << byte_u_t(level_base)
|
|
<< ", l_multi=" << byte_u_t(level_multiplier)
|
|
<< std::endl;
|
|
constexpr std::array<const char*, 8> names{ {
|
|
"LEV/DEV",
|
|
"WAL",
|
|
"DB",
|
|
"SLOW",
|
|
"*",
|
|
"*",
|
|
"REAL",
|
|
"FILES",
|
|
} };
|
|
const size_t width = 12;
|
|
for (size_t i = 0; i < names.size(); ++i) {
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
sout << names[i];
|
|
}
|
|
sout << std::endl;
|
|
for (size_t l = 0; l < max_y; l++) {
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
switch (l + LEVEL_FIRST) {
|
|
case LEVEL_LOG:
|
|
sout << "log"; break;
|
|
case LEVEL_WAL:
|
|
sout << "db.wal"; break;
|
|
case LEVEL_DB:
|
|
sout << "db"; break;
|
|
case LEVEL_SLOW:
|
|
sout << "db.slow"; break;
|
|
case LEVEL_MAX:
|
|
sout << "TOTAL"; break;
|
|
}
|
|
for (size_t d = 0; d < max_x; d++) {
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
sout << stringify(byte_u_t(per_level_per_dev_usage.at(d, l)));
|
|
}
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
sout << stringify(per_level_files[l]) << std::endl;
|
|
}
|
|
ceph_assert(max_x == per_level_per_dev_max.get_max_x());
|
|
ceph_assert(max_y == per_level_per_dev_max.get_max_y());
|
|
sout << "MAXIMUMS:" << std::endl;
|
|
for (size_t l = 0; l < max_y; l++) {
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
switch (l + LEVEL_FIRST) {
|
|
case LEVEL_LOG:
|
|
sout << "log"; break;
|
|
case LEVEL_WAL:
|
|
sout << "db.wal"; break;
|
|
case LEVEL_DB:
|
|
sout << "db"; break;
|
|
case LEVEL_SLOW:
|
|
sout << "db.slow"; break;
|
|
case LEVEL_MAX:
|
|
sout << "TOTAL"; break;
|
|
}
|
|
for (size_t d = 0; d < max_x - 1; d++) {
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
sout << stringify(byte_u_t(per_level_per_dev_max.at(d, l)));
|
|
}
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
sout << stringify(byte_u_t(per_level_per_dev_max.at(max_x - 1, l)));
|
|
sout << std::endl;
|
|
}
|
|
string sizes[] = {
|
|
">> SIZE <<",
|
|
stringify(byte_u_t(l_totals[LEVEL_WAL - LEVEL_FIRST])),
|
|
stringify(byte_u_t(l_totals[LEVEL_DB - LEVEL_FIRST])),
|
|
stringify(byte_u_t(l_totals[LEVEL_SLOW - LEVEL_FIRST])),
|
|
};
|
|
for (size_t i = 0; i < (sizeof(sizes) / sizeof(sizes[0])); i++) {
|
|
sout.setf(std::ios::left, std::ios::adjustfield);
|
|
sout.width(width);
|
|
sout << sizes[i];
|
|
}
|
|
sout << std::endl;
|
|
}
|
|
|
|
BlueFSVolumeSelector* RocksDBBlueFSVolumeSelector::clone_empty() const {
|
|
RocksDBBlueFSVolumeSelector* ns =
|
|
new RocksDBBlueFSVolumeSelector(0, 0, 0, 0, 0, 0, false);
|
|
return ns;
|
|
}
|
|
|
|
bool RocksDBBlueFSVolumeSelector::compare(BlueFSVolumeSelector* other) {
|
|
RocksDBBlueFSVolumeSelector* o = dynamic_cast<RocksDBBlueFSVolumeSelector*>(other);
|
|
ceph_assert(o);
|
|
bool equal = true;
|
|
for (size_t x = 0; x < BlueFS::MAX_BDEV + 1; x++) {
|
|
for (size_t y = 0; y <LEVEL_MAX - LEVEL_FIRST + 1; y++) {
|
|
equal &= (per_level_per_dev_usage.at(x, y) == o->per_level_per_dev_usage.at(x, y));
|
|
}
|
|
}
|
|
for (size_t t = 0; t < LEVEL_MAX - LEVEL_FIRST + 1; t++) {
|
|
equal &= (per_level_files[t] == o->per_level_files[t]);
|
|
}
|
|
return equal;
|
|
}
|
|
|
|
// =======================================================
|
|
|
|
//================================================================================================================
|
|
// BlueStore is committing all allocation information (alloc/release) into RocksDB before the client Write is performed.
|
|
// This cause a delay in write path and add significant load to the CPU/Memory/Disk.
|
|
// The reason for the RocksDB updates is that it allows Ceph to survive any failure without losing the allocation state.
|
|
//
|
|
// We changed the code skiping RocksDB updates on allocation time and instead performing a full desatge of the allocator object
|
|
// with all the OSD allocation state in a single step during umount().
|
|
// This change leads to a 25% increase in IOPS and reduced latency in small random-write workload, but exposes the system
|
|
// to losing allocation info in failure cases where we don't call umount.
|
|
// We add code to perform a full allocation-map rebuild from information stored inside the ONode which is used in failure cases.
|
|
// When we perform a graceful shutdown there is no need for recovery and we simply read the allocation-map from a flat file
|
|
// where we store the allocation-map during umount().
|
|
//================================================================================================================
|
|
|
|
#undef dout_prefix
|
|
#define dout_prefix *_dout << "bluestore::NCB::" << __func__ << "::"
|
|
|
|
static const std::string allocator_dir = "ALLOCATOR_NCB_DIR";
|
|
static const std::string allocator_file = "ALLOCATOR_NCB_FILE";
|
|
static uint32_t s_format_version = 0x01; // support future changes to allocator-map file
|
|
static uint32_t s_serial = 0x01;
|
|
|
|
#if 1
|
|
#define CEPHTOH_32 le32toh
|
|
#define CEPHTOH_64 le64toh
|
|
#define HTOCEPH_32 htole32
|
|
#define HTOCEPH_64 htole64
|
|
#else
|
|
// help debug the encode/decode by forcing alien format
|
|
#define CEPHTOH_32 be32toh
|
|
#define CEPHTOH_64 be64toh
|
|
#define HTOCEPH_32 htobe32
|
|
#define HTOCEPH_64 htobe64
|
|
#endif
|
|
|
|
// 48 Bytes header for on-disk alloator image
|
|
const uint64_t ALLOCATOR_IMAGE_VALID_SIGNATURE = 0x1FACE0FF;
|
|
struct allocator_image_header {
|
|
uint32_t format_version; // 0x00
|
|
uint32_t valid_signature; // 0x04
|
|
utime_t timestamp; // 0x08
|
|
uint32_t serial; // 0x10
|
|
uint32_t pad[0x7]; // 0x14
|
|
|
|
allocator_image_header() {
|
|
memset((char*)this, 0, sizeof(allocator_image_header));
|
|
}
|
|
|
|
// create header in CEPH format
|
|
allocator_image_header(utime_t timestamp, uint32_t format_version, uint32_t serial) {
|
|
this->format_version = format_version;
|
|
this->timestamp = timestamp;
|
|
this->valid_signature = ALLOCATOR_IMAGE_VALID_SIGNATURE;
|
|
this->serial = serial;
|
|
memset(this->pad, 0, sizeof(this->pad));
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream& out, const allocator_image_header& header) {
|
|
out << "format_version = " << header.format_version << std::endl;
|
|
out << "valid_signature = " << header.valid_signature << "/" << ALLOCATOR_IMAGE_VALID_SIGNATURE << std::endl;
|
|
out << "timestamp = " << header.timestamp << std::endl;
|
|
out << "serial = " << header.serial << std::endl;
|
|
for (unsigned i = 0; i < sizeof(header.pad)/sizeof(uint32_t); i++) {
|
|
if (header.pad[i]) {
|
|
out << "header.pad[" << i << "] = " << header.pad[i] << std::endl;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
DENC(allocator_image_header, v, p) {
|
|
denc(v.format_version, p);
|
|
denc(v.valid_signature, p);
|
|
denc(v.timestamp.tv.tv_sec, p);
|
|
denc(v.timestamp.tv.tv_nsec, p);
|
|
denc(v.serial, p);
|
|
for (auto& pad: v.pad) {
|
|
denc(pad, p);
|
|
}
|
|
}
|
|
|
|
|
|
int verify(CephContext* cct, const std::string &path) {
|
|
if (valid_signature == ALLOCATOR_IMAGE_VALID_SIGNATURE) {
|
|
for (unsigned i = 0; i < (sizeof(pad) / sizeof(uint32_t)); i++) {
|
|
if (this->pad[i]) {
|
|
derr << "Illegal Header - pad[" << i << "]="<< pad[i] << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
else {
|
|
derr << "Illegal Header - signature="<< valid_signature << "(" << ALLOCATOR_IMAGE_VALID_SIGNATURE << ")" << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
};
|
|
WRITE_CLASS_DENC(allocator_image_header)
|
|
|
|
// 56 Bytes trailer for on-disk alloator image
|
|
struct allocator_image_trailer {
|
|
extent_t null_extent; // 0x00
|
|
|
|
uint32_t format_version; // 0x10
|
|
uint32_t valid_signature; // 0x14
|
|
|
|
utime_t timestamp; // 0x18
|
|
|
|
uint32_t serial; // 0x20
|
|
uint32_t pad; // 0x24
|
|
uint64_t entries_count; // 0x28
|
|
uint64_t allocation_size; // 0x30
|
|
|
|
// trailer is created in CEPH format
|
|
allocator_image_trailer(utime_t timestamp, uint32_t format_version, uint32_t serial, uint64_t entries_count, uint64_t allocation_size) {
|
|
memset((char*)&(this->null_extent), 0, sizeof(this->null_extent));
|
|
this->format_version = format_version;
|
|
this->valid_signature = ALLOCATOR_IMAGE_VALID_SIGNATURE;
|
|
this->timestamp = timestamp;
|
|
this->serial = serial;
|
|
this->pad = 0;
|
|
this->entries_count = entries_count;
|
|
this->allocation_size = allocation_size;
|
|
}
|
|
|
|
allocator_image_trailer() {
|
|
memset((char*)this, 0, sizeof(allocator_image_trailer));
|
|
}
|
|
|
|
friend std::ostream& operator<<(std::ostream& out, const allocator_image_trailer& trailer) {
|
|
if (trailer.null_extent.offset || trailer.null_extent.length) {
|
|
out << "trailer.null_extent.offset = " << trailer.null_extent.offset << std::endl;
|
|
out << "trailer.null_extent.length = " << trailer.null_extent.length << std::endl;
|
|
}
|
|
out << "format_version = " << trailer.format_version << std::endl;
|
|
out << "valid_signature = " << trailer.valid_signature << "/" << ALLOCATOR_IMAGE_VALID_SIGNATURE << std::endl;
|
|
out << "timestamp = " << trailer.timestamp << std::endl;
|
|
out << "serial = " << trailer.serial << std::endl;
|
|
if (trailer.pad) {
|
|
out << "trailer.pad= " << trailer.pad << std::endl;
|
|
}
|
|
out << "entries_count = " << trailer.entries_count << std::endl;
|
|
out << "allocation_size = " << trailer.allocation_size << std::endl;
|
|
return out;
|
|
}
|
|
|
|
int verify(CephContext* cct, const std::string &path, const allocator_image_header *p_header, uint64_t entries_count, uint64_t allocation_size) {
|
|
if (valid_signature == ALLOCATOR_IMAGE_VALID_SIGNATURE) {
|
|
|
|
// trailer must starts with null extents (both fields set to zero) [no need to convert formats for zero)
|
|
if (null_extent.offset || null_extent.length) {
|
|
derr << "illegal trailer - null_extent = [" << null_extent.offset << "," << null_extent.length << "]"<< dendl;
|
|
return -1;
|
|
}
|
|
|
|
if (serial != p_header->serial) {
|
|
derr << "Illegal trailer: header->serial(" << p_header->serial << ") != trailer->serial(" << serial << ")" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
if (format_version != p_header->format_version) {
|
|
derr << "Illegal trailer: header->format_version(" << p_header->format_version
|
|
<< ") != trailer->format_version(" << format_version << ")" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
if (timestamp != p_header->timestamp) {
|
|
derr << "Illegal trailer: header->timestamp(" << p_header->timestamp
|
|
<< ") != trailer->timestamp(" << timestamp << ")" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
if (this->entries_count != entries_count) {
|
|
derr << "Illegal trailer: entries_count(" << entries_count << ") != trailer->entries_count("
|
|
<< this->entries_count << ")" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
if (this->allocation_size != allocation_size) {
|
|
derr << "Illegal trailer: allocation_size(" << allocation_size << ") != trailer->allocation_size("
|
|
<< this->allocation_size << ")" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
if (pad) {
|
|
derr << "Illegal Trailer - pad="<< pad << dendl;
|
|
return -1;
|
|
}
|
|
|
|
// if arrived here -> trailer is valid !!
|
|
return 0;
|
|
} else {
|
|
derr << "Illegal Trailer - signature="<< valid_signature << "(" << ALLOCATOR_IMAGE_VALID_SIGNATURE << ")" << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
DENC(allocator_image_trailer, v, p) {
|
|
denc(v.null_extent.offset, p);
|
|
denc(v.null_extent.length, p);
|
|
denc(v.format_version, p);
|
|
denc(v.valid_signature, p);
|
|
denc(v.timestamp.tv.tv_sec, p);
|
|
denc(v.timestamp.tv.tv_nsec, p);
|
|
denc(v.serial, p);
|
|
denc(v.pad, p);
|
|
denc(v.entries_count, p);
|
|
denc(v.allocation_size, p);
|
|
}
|
|
};
|
|
WRITE_CLASS_DENC(allocator_image_trailer)
|
|
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
// invalidate old allocation file if exists so will go directly to recovery after failure
|
|
// we can safely ignore non-existing file
|
|
int BlueStore::invalidate_allocation_file_on_bluefs()
|
|
{
|
|
// mark that allocation-file was invalidated and we should destage a new copy whne closing db
|
|
need_to_destage_allocation_file = true;
|
|
dout(10) << __func__ << " need_to_destage_allocation_file was set" << dendl;
|
|
|
|
BlueFS::FileWriter *p_handle = nullptr;
|
|
if (!bluefs->dir_exists(allocator_dir)) {
|
|
dout(5) << "allocator_dir(" << allocator_dir << ") doesn't exist" << dendl;
|
|
// nothing to do -> return
|
|
return 0;
|
|
}
|
|
|
|
int ret = bluefs->stat(allocator_dir, allocator_file, nullptr, nullptr);
|
|
if (ret != 0) {
|
|
dout(5) << __func__ << " allocator_file(" << allocator_file << ") doesn't exist" << dendl;
|
|
// nothing to do -> return
|
|
return 0;
|
|
}
|
|
|
|
|
|
ret = bluefs->open_for_write(allocator_dir, allocator_file, &p_handle, true);
|
|
if (ret != 0) {
|
|
derr << __func__ << "::NCB:: Failed open_for_write with error-code "
|
|
<< ret << dendl;
|
|
return -1;
|
|
}
|
|
|
|
dout(5) << "invalidate using bluefs->truncate(p_handle, 0)" << dendl;
|
|
ret = bluefs->truncate(p_handle, 0);
|
|
if (ret != 0) {
|
|
derr << __func__ << "::NCB:: Failed truncaste with error-code "
|
|
<< ret << dendl;
|
|
bluefs->close_writer(p_handle);
|
|
return -1;
|
|
}
|
|
|
|
bluefs->fsync(p_handle);
|
|
bluefs->close_writer(p_handle);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
int BlueStore::copy_allocator(Allocator* src_alloc, Allocator* dest_alloc, uint64_t* p_num_entries)
|
|
{
|
|
*p_num_entries = 0;
|
|
auto count_entries = [&](uint64_t extent_offset, uint64_t extent_length) {
|
|
(*p_num_entries)++;
|
|
};
|
|
src_alloc->foreach(count_entries);
|
|
|
|
dout(5) << "count num_entries=" << *p_num_entries << dendl;
|
|
|
|
// add 16K extra entries in case new allocation happened
|
|
(*p_num_entries) += 16*1024;
|
|
unique_ptr<extent_t[]> arr;
|
|
try {
|
|
arr = make_unique<extent_t[]>(*p_num_entries);
|
|
} catch (std::bad_alloc&) {
|
|
derr << "****Failed dynamic allocation, num_entries=" << *p_num_entries << dendl;
|
|
return -1;
|
|
}
|
|
|
|
uint64_t idx = 0;
|
|
auto copy_entries = [&](uint64_t extent_offset, uint64_t extent_length) {
|
|
if (extent_length > 0) {
|
|
if (idx < *p_num_entries) {
|
|
arr[idx] = {extent_offset, extent_length};
|
|
}
|
|
idx++;
|
|
}
|
|
else {
|
|
derr << "zero length extent!!! offset=" << extent_offset << ", index=" << idx << dendl;
|
|
}
|
|
};
|
|
src_alloc->foreach(copy_entries);
|
|
|
|
dout(5) << "copy num_entries=" << idx << dendl;
|
|
if (idx > *p_num_entries) {
|
|
derr << "****spillover, num_entries=" << *p_num_entries << ", spillover=" << (idx - *p_num_entries) << dendl;
|
|
ceph_assert(idx <= *p_num_entries);
|
|
}
|
|
|
|
*p_num_entries = idx;
|
|
|
|
for (idx = 0; idx < *p_num_entries; idx++) {
|
|
const extent_t *p_extent = &arr[idx];
|
|
dest_alloc->init_add_free(p_extent->offset, p_extent->length);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
static uint32_t flush_extent_buffer_with_crc(BlueFS::FileWriter *p_handle, const char* buffer, const char *p_curr, uint32_t crc)
|
|
{
|
|
std::ptrdiff_t length = p_curr - buffer;
|
|
p_handle->append(buffer, length);
|
|
|
|
crc = ceph_crc32c(crc, (const uint8_t*)buffer, length);
|
|
uint32_t encoded_crc = HTOCEPH_32(crc);
|
|
p_handle->append((byte*)&encoded_crc, sizeof(encoded_crc));
|
|
|
|
return crc;
|
|
}
|
|
|
|
const unsigned MAX_EXTENTS_IN_BUFFER = 4 * 1024; // 4K extents = 64KB of data
|
|
// write the allocator to a flat bluefs file - 4K extents at a time
|
|
//-----------------------------------------------------------------------------------
|
|
int BlueStore::store_allocator(Allocator* src_allocator)
|
|
{
|
|
// when storing allocations to file we must be sure there is no background compactions
|
|
// the easiest way to achieve it is to make sure db is closed
|
|
ceph_assert(db == nullptr);
|
|
utime_t start_time = ceph_clock_now();
|
|
int ret = 0;
|
|
|
|
// create dir if doesn't exist already
|
|
if (!bluefs->dir_exists(allocator_dir) ) {
|
|
ret = bluefs->mkdir(allocator_dir);
|
|
if (ret != 0) {
|
|
derr << "Failed mkdir with error-code " << ret << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
bluefs->compact_log();
|
|
// reuse previous file-allocation if exists
|
|
ret = bluefs->stat(allocator_dir, allocator_file, nullptr, nullptr);
|
|
bool overwrite_file = (ret == 0);
|
|
BlueFS::FileWriter *p_handle = nullptr;
|
|
ret = bluefs->open_for_write(allocator_dir, allocator_file, &p_handle, overwrite_file);
|
|
if (ret != 0) {
|
|
derr << __func__ << "Failed open_for_write with error-code " << ret << dendl;
|
|
return -1;
|
|
}
|
|
|
|
uint64_t file_size = p_handle->file->fnode.size;
|
|
uint64_t allocated = p_handle->file->fnode.get_allocated();
|
|
dout(10) << "file_size=" << file_size << ", allocated=" << allocated << dendl;
|
|
|
|
bluefs->sync_metadata(false);
|
|
unique_ptr<Allocator> allocator(clone_allocator_without_bluefs(src_allocator));
|
|
if (!allocator) {
|
|
bluefs->close_writer(p_handle);
|
|
return -1;
|
|
}
|
|
// remove allocations that are used by bdev label copies
|
|
if (bdev_label_multi == true) {
|
|
_main_bdev_label_remove(allocator.get());
|
|
}
|
|
|
|
// store all extents (except for the bluefs extents we removed) in a single flat file
|
|
utime_t timestamp = ceph_clock_now();
|
|
uint32_t crc = -1;
|
|
{
|
|
allocator_image_header header(timestamp, s_format_version, s_serial);
|
|
bufferlist header_bl;
|
|
encode(header, header_bl);
|
|
crc = header_bl.crc32c(crc);
|
|
encode(crc, header_bl);
|
|
p_handle->append(header_bl);
|
|
}
|
|
|
|
crc = -1; // reset crc
|
|
extent_t buffer[MAX_EXTENTS_IN_BUFFER]; // 64KB
|
|
extent_t *p_curr = buffer;
|
|
const extent_t *p_end = buffer + MAX_EXTENTS_IN_BUFFER;
|
|
uint64_t extent_count = 0;
|
|
uint64_t allocation_size = 0;
|
|
auto iterated_allocation = [&](uint64_t extent_offset, uint64_t extent_length) {
|
|
if (extent_length == 0) {
|
|
derr << __func__ << "" << extent_count << "::[" << extent_offset << "," << extent_length << "]" << dendl;
|
|
ret = -1;
|
|
return;
|
|
}
|
|
p_curr->offset = HTOCEPH_64(extent_offset);
|
|
p_curr->length = HTOCEPH_64(extent_length);
|
|
extent_count++;
|
|
allocation_size += extent_length;
|
|
p_curr++;
|
|
|
|
if (p_curr == p_end) {
|
|
crc = flush_extent_buffer_with_crc(p_handle, (const char*)buffer, (const char*)p_curr, crc);
|
|
p_curr = buffer; // recycle the buffer
|
|
}
|
|
};
|
|
allocator->foreach(iterated_allocation);
|
|
// if got null extent -> fail the operation
|
|
if (ret != 0) {
|
|
derr << "Illegal extent, fail store operation" << dendl;
|
|
derr << "invalidate using bluefs->truncate(p_handle, 0)" << dendl;
|
|
bluefs->truncate(p_handle, 0);
|
|
bluefs->close_writer(p_handle);
|
|
return -1;
|
|
}
|
|
|
|
// if we got any leftovers -> add crc and append to file
|
|
if (p_curr > buffer) {
|
|
crc = flush_extent_buffer_with_crc(p_handle, (const char*)buffer, (const char*)p_curr, crc);
|
|
}
|
|
|
|
{
|
|
allocator_image_trailer trailer(timestamp, s_format_version, s_serial, extent_count, allocation_size);
|
|
bufferlist trailer_bl;
|
|
encode(trailer, trailer_bl);
|
|
uint32_t crc = -1;
|
|
crc = trailer_bl.crc32c(crc);
|
|
encode(crc, trailer_bl);
|
|
p_handle->append(trailer_bl);
|
|
}
|
|
|
|
bluefs->fsync(p_handle);
|
|
bluefs->truncate(p_handle, p_handle->pos);
|
|
bluefs->fsync(p_handle);
|
|
|
|
utime_t duration = ceph_clock_now() - start_time;
|
|
dout(5) <<"WRITE-extent_count=" << extent_count << ", allocation_size=" << allocation_size << ", serial=" << s_serial << dendl;
|
|
dout(5) <<"p_handle->pos=" << p_handle->pos << " WRITE-duration=" << duration << " seconds" << dendl;
|
|
|
|
bluefs->close_writer(p_handle);
|
|
need_to_destage_allocation_file = false;
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
Allocator* BlueStore::create_bitmap_allocator(uint64_t bdev_size) {
|
|
// create allocator
|
|
uint64_t alloc_size = min_alloc_size;
|
|
Allocator* alloc = Allocator::create(cct, "bitmap", bdev_size, alloc_size,
|
|
"recovery");
|
|
if (alloc) {
|
|
return alloc;
|
|
} else {
|
|
derr << "Failed Allocator Creation" << dendl;
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
size_t calc_allocator_image_header_size()
|
|
{
|
|
utime_t timestamp = ceph_clock_now();
|
|
allocator_image_header header(timestamp, s_format_version, s_serial);
|
|
bufferlist header_bl;
|
|
encode(header, header_bl);
|
|
uint32_t crc = -1;
|
|
crc = header_bl.crc32c(crc);
|
|
encode(crc, header_bl);
|
|
|
|
return header_bl.length();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
int calc_allocator_image_trailer_size()
|
|
{
|
|
utime_t timestamp = ceph_clock_now();
|
|
uint64_t extent_count = -1;
|
|
uint64_t allocation_size = -1;
|
|
uint32_t crc = -1;
|
|
bufferlist trailer_bl;
|
|
allocator_image_trailer trailer(timestamp, s_format_version, s_serial, extent_count, allocation_size);
|
|
|
|
encode(trailer, trailer_bl);
|
|
crc = trailer_bl.crc32c(crc);
|
|
encode(crc, trailer_bl);
|
|
return trailer_bl.length();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
int BlueStore::__restore_allocator(Allocator* allocator, uint64_t *num, uint64_t *bytes)
|
|
{
|
|
if (cct->_conf->bluestore_debug_inject_allocation_from_file_failure > 0) {
|
|
boost::mt11213b rng(time(NULL));
|
|
boost::uniform_real<> ur(0, 1);
|
|
if (ur(rng) < cct->_conf->bluestore_debug_inject_allocation_from_file_failure) {
|
|
derr << __func__ << " failure injected." << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
utime_t start_time = ceph_clock_now();
|
|
BlueFS::FileReader *p_temp_handle = nullptr;
|
|
int ret = bluefs->open_for_read(allocator_dir, allocator_file, &p_temp_handle, false);
|
|
if (ret != 0) {
|
|
dout(1) << "Failed open_for_read with error-code " << ret << dendl;
|
|
return -1;
|
|
}
|
|
unique_ptr<BlueFS::FileReader> p_handle(p_temp_handle);
|
|
uint64_t read_alloc_size = 0;
|
|
uint64_t file_size = p_handle->file->fnode.size;
|
|
dout(5) << "file_size=" << file_size << ",sizeof(extent_t)=" << sizeof(extent_t) << dendl;
|
|
|
|
// make sure we were able to store a valid copy
|
|
if (file_size == 0) {
|
|
dout(1) << "No Valid allocation info on disk (empty file)" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
// first read the header
|
|
size_t offset = 0;
|
|
allocator_image_header header;
|
|
int header_size = calc_allocator_image_header_size();
|
|
{
|
|
bufferlist header_bl,temp_bl;
|
|
int read_bytes = bluefs->read(p_handle.get(), offset, header_size, &temp_bl, nullptr);
|
|
if (read_bytes != header_size) {
|
|
derr << "Failed bluefs->read() for header::read_bytes=" << read_bytes << ", req_bytes=" << header_size << dendl;
|
|
return -1;
|
|
}
|
|
|
|
offset += read_bytes;
|
|
|
|
header_bl.claim_append(temp_bl);
|
|
auto p = header_bl.cbegin();
|
|
decode(header, p);
|
|
if (header.verify(cct, path) != 0 ) {
|
|
derr << "header = \n" << header << dendl;
|
|
return -1;
|
|
}
|
|
|
|
uint32_t crc_calc = -1, crc;
|
|
crc_calc = header_bl.cbegin().crc32c(p.get_off(), crc_calc); //crc from begin to current pos
|
|
decode(crc, p);
|
|
if (crc != crc_calc) {
|
|
derr << "crc mismatch!!! crc=" << crc << ", crc_calc=" << crc_calc << dendl;
|
|
derr << "header = \n" << header << dendl;
|
|
return -1;
|
|
}
|
|
|
|
// increment version for next store
|
|
s_serial = header.serial + 1;
|
|
}
|
|
|
|
// then read the payload (extents list) using a recycled buffer
|
|
extent_t buffer[MAX_EXTENTS_IN_BUFFER]; // 64KB
|
|
uint32_t crc = -1;
|
|
int trailer_size = calc_allocator_image_trailer_size();
|
|
uint64_t extent_count = 0;
|
|
uint64_t extents_bytes_left = file_size - (header_size + trailer_size + sizeof(crc));
|
|
while (extents_bytes_left) {
|
|
int req_bytes = std::min(extents_bytes_left, static_cast<uint64_t>(sizeof(buffer)));
|
|
int read_bytes = bluefs->read(p_handle.get(), offset, req_bytes, nullptr, (char*)buffer);
|
|
if (read_bytes != req_bytes) {
|
|
derr << "Failed bluefs->read()::read_bytes=" << read_bytes << ", req_bytes=" << req_bytes << dendl;
|
|
return -1;
|
|
}
|
|
|
|
offset += read_bytes;
|
|
extents_bytes_left -= read_bytes;
|
|
|
|
const unsigned num_extent_in_buffer = read_bytes/sizeof(extent_t);
|
|
const extent_t *p_end = buffer + num_extent_in_buffer;
|
|
for (const extent_t *p_ext = buffer; p_ext < p_end; p_ext++) {
|
|
uint64_t offset = CEPHTOH_64(p_ext->offset);
|
|
uint64_t length = CEPHTOH_64(p_ext->length);
|
|
read_alloc_size += length;
|
|
|
|
if (length > 0) {
|
|
allocator->init_add_free(offset, length);
|
|
extent_count ++;
|
|
} else {
|
|
derr << "extent with zero length at idx=" << extent_count << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
uint32_t calc_crc = ceph_crc32c(crc, (const uint8_t*)buffer, read_bytes);
|
|
read_bytes = bluefs->read(p_handle.get(), offset, sizeof(crc), nullptr, (char*)&crc);
|
|
if (read_bytes == sizeof(crc) ) {
|
|
crc = CEPHTOH_32(crc);
|
|
if (crc != calc_crc) {
|
|
derr << "data crc mismatch!!! crc=" << crc << ", calc_crc=" << calc_crc << dendl;
|
|
derr << "extents_bytes_left=" << extents_bytes_left << ", offset=" << offset << ", extent_count=" << extent_count << dendl;
|
|
return -1;
|
|
}
|
|
|
|
offset += read_bytes;
|
|
if (extents_bytes_left) {
|
|
extents_bytes_left -= read_bytes;
|
|
}
|
|
} else {
|
|
derr << "Failed bluefs->read() for crc::read_bytes=" << read_bytes << ", req_bytes=" << sizeof(crc) << dendl;
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
// finally, read the trailer and verify it is in good shape and that we got all the extents
|
|
{
|
|
bufferlist trailer_bl,temp_bl;
|
|
int read_bytes = bluefs->read(p_handle.get(), offset, trailer_size, &temp_bl, nullptr);
|
|
if (read_bytes != trailer_size) {
|
|
derr << "Failed bluefs->read() for trailer::read_bytes=" << read_bytes << ", req_bytes=" << trailer_size << dendl;
|
|
return -1;
|
|
}
|
|
offset += read_bytes;
|
|
|
|
trailer_bl.claim_append(temp_bl);
|
|
uint32_t crc_calc = -1;
|
|
uint32_t crc;
|
|
allocator_image_trailer trailer;
|
|
auto p = trailer_bl.cbegin();
|
|
decode(trailer, p);
|
|
if (trailer.verify(cct, path, &header, extent_count, read_alloc_size) != 0 ) {
|
|
derr << "trailer=\n" << trailer << dendl;
|
|
return -1;
|
|
}
|
|
|
|
crc_calc = trailer_bl.cbegin().crc32c(p.get_off(), crc_calc); //crc from begin to current pos
|
|
decode(crc, p);
|
|
if (crc != crc_calc) {
|
|
derr << "trailer crc mismatch!::crc=" << crc << ", crc_calc=" << crc_calc << dendl;
|
|
derr << "trailer=\n" << trailer << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
utime_t duration = ceph_clock_now() - start_time;
|
|
dout(5) << "READ--extent_count=" << extent_count << ", read_alloc_size= "
|
|
<< read_alloc_size << ", file_size=" << file_size << dendl;
|
|
dout(5) << "READ duration=" << duration << " seconds, s_serial=" << header.serial << dendl;
|
|
*num = extent_count;
|
|
*bytes = read_alloc_size;
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
int BlueStore::restore_allocator(Allocator* dest_allocator, uint64_t *num, uint64_t *bytes)
|
|
{
|
|
utime_t start = ceph_clock_now();
|
|
auto temp_allocator = unique_ptr<Allocator>(create_bitmap_allocator(bdev->get_size()));
|
|
int ret = __restore_allocator(temp_allocator.get(), num, bytes);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
uint64_t num_entries = 0;
|
|
dout(5) << " calling copy_allocator(bitmap_allocator -> shared_alloc.a)" << dendl;
|
|
copy_allocator(temp_allocator.get(), dest_allocator, &num_entries);
|
|
utime_t duration = ceph_clock_now() - start;
|
|
dout(5) << "restored in " << duration << " seconds, num_entries=" << num_entries << dendl;
|
|
return ret;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
void BlueStore::set_allocation_in_simple_bmap(SimpleBitmap* sbmap, uint64_t offset, uint64_t length)
|
|
{
|
|
dout(30) << __func__ << " 0x" << std::hex
|
|
<< offset << "~" << length
|
|
<< " " << min_alloc_size_mask
|
|
<< dendl;
|
|
ceph_assert((offset & min_alloc_size_mask) == 0);
|
|
ceph_assert((length & min_alloc_size_mask) == 0);
|
|
sbmap->set(offset >> min_alloc_size_order, length >> min_alloc_size_order);
|
|
}
|
|
|
|
void BlueStore::ExtentDecoderPartial::_consume_new_blob(bool spanning,
|
|
uint64_t extent_no,
|
|
uint64_t sbid,
|
|
BlobRef b)
|
|
{
|
|
[[maybe_unused]] auto cct = store.cct;
|
|
ceph_assert(per_pool_statfs);
|
|
ceph_assert(oid != ghobject_t());
|
|
|
|
auto &blob = b->get_blob();
|
|
if(spanning) {
|
|
dout(20) << __func__ << " " << spanning << " " << b->id << dendl;
|
|
ceph_assert(b->id >= 0);
|
|
spanning_blobs[b->id] = b;
|
|
++stats.spanning_blob_count;
|
|
} else {
|
|
dout(20) << __func__ << " " << spanning << " " << extent_no << dendl;
|
|
blobs[extent_no] = b;
|
|
}
|
|
bool compressed = blob.is_compressed();
|
|
if (!blob.is_shared()) {
|
|
for (auto& pe : blob.get_extents()) {
|
|
if (pe.offset == bluestore_pextent_t::INVALID_OFFSET) {
|
|
++stats.skipped_illegal_extent;
|
|
continue;
|
|
}
|
|
store.set_allocation_in_simple_bmap(&sbmap, pe.offset, pe.length);
|
|
|
|
per_pool_statfs->allocated() += pe.length;
|
|
if (compressed) {
|
|
per_pool_statfs->compressed_allocated() += pe.length;
|
|
}
|
|
}
|
|
if (compressed) {
|
|
per_pool_statfs->compressed() +=
|
|
blob.get_compressed_payload_length();
|
|
++stats.compressed_blob_count;
|
|
}
|
|
} else {
|
|
auto it = sb_info.find(sbid);
|
|
if (it != sb_info.end()) {
|
|
auto &sbi = *it;
|
|
auto pool_id = oid.hobj.get_logical_pool();
|
|
if (sbi.pool_id == sb_info_t::INVALID_POOL_ID) {
|
|
sbi.pool_id = pool_id;
|
|
size_t alloc_delta = sbi.allocated_chunks << min_alloc_size_order;
|
|
per_pool_statfs->allocated() += alloc_delta;
|
|
if (compressed) {
|
|
per_pool_statfs->compressed_allocated() += alloc_delta;
|
|
++stats.compressed_blob_count;
|
|
}
|
|
}
|
|
} else {
|
|
derr << __func__ << " shared blob not found:" << sbid << dendl;
|
|
}
|
|
if (compressed) {
|
|
per_pool_statfs->compressed() +=
|
|
blob.get_compressed_payload_length();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentDecoderPartial::consume_blobid(Extent* le,
|
|
bool spanning,
|
|
uint64_t blobid)
|
|
{
|
|
[[maybe_unused]] auto cct = store.cct;
|
|
dout(20) << __func__ << " " << spanning << " " << blobid << dendl;
|
|
auto &map = spanning ? spanning_blobs : blobs;
|
|
auto it = map.find(blobid);
|
|
ceph_assert(it != map.end());
|
|
per_pool_statfs->stored() += le->length;
|
|
if (it->second->get_blob().is_compressed()) {
|
|
per_pool_statfs->compressed_original() += le->length;
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentDecoderPartial::consume_blob(Extent* le,
|
|
uint64_t extent_no,
|
|
uint64_t sbid,
|
|
BlobRef b)
|
|
{
|
|
_consume_new_blob(false, extent_no, sbid, b);
|
|
per_pool_statfs->stored() += le->length;
|
|
if (b->get_blob().is_compressed()) {
|
|
per_pool_statfs->compressed_original() += le->length;
|
|
}
|
|
}
|
|
|
|
void BlueStore::ExtentDecoderPartial::consume_spanning_blob(uint64_t sbid,
|
|
BlobRef b)
|
|
{
|
|
_consume_new_blob(true, 0/*doesn't matter*/, sbid, b);
|
|
}
|
|
|
|
void BlueStore::ExtentDecoderPartial::reset(const ghobject_t _oid,
|
|
volatile_statfs* _per_pool_statfs)
|
|
{
|
|
oid = _oid;
|
|
per_pool_statfs = _per_pool_statfs;
|
|
blob_map_t empty;
|
|
blob_map_t empty2;
|
|
std::swap(blobs, empty);
|
|
std::swap(spanning_blobs, empty2);
|
|
}
|
|
|
|
int BlueStore::read_allocation_from_onodes(SimpleBitmap *sbmap, read_alloc_stats_t& stats)
|
|
{
|
|
sb_info_space_efficient_map_t sb_info;
|
|
// iterate over all shared blobs
|
|
auto it = db->get_iterator(PREFIX_SHARED_BLOB, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (!it) {
|
|
derr << "failed getting shared blob's iterator" << dendl;
|
|
return -ENOENT;
|
|
}
|
|
if (it) {
|
|
for (it->lower_bound(string()); it->valid(); it->next()) {
|
|
const auto& key = it->key();
|
|
dout(20) << __func__ << " decode sb " << pretty_binary_string(key) << dendl;
|
|
uint64_t sbid = 0;
|
|
if (get_key_shared_blob(key, &sbid) != 0) {
|
|
derr << __func__ << " bad shared blob key '" << pretty_binary_string(key)
|
|
<< "'" << dendl;
|
|
}
|
|
bluestore_shared_blob_t shared_blob(sbid);
|
|
bufferlist bl = it->value();
|
|
auto blp = bl.cbegin();
|
|
try {
|
|
decode(shared_blob, blp);
|
|
}
|
|
catch (ceph::buffer::error& e) {
|
|
derr << __func__ << " failed to decode Shared Blob"
|
|
<< pretty_binary_string(key) << dendl;
|
|
continue;
|
|
}
|
|
dout(20) << __func__ << " " << shared_blob << dendl;
|
|
uint64_t allocated = 0;
|
|
for (auto& r : shared_blob.ref_map.ref_map) {
|
|
ceph_assert(r.first != bluestore_pextent_t::INVALID_OFFSET);
|
|
set_allocation_in_simple_bmap(sbmap, r.first, r.second.length);
|
|
allocated += r.second.length;
|
|
}
|
|
auto &sbi = sb_info.add_or_adopt(sbid);
|
|
ceph_assert(p2phase(allocated, min_alloc_size) == 0);
|
|
sbi.allocated_chunks += (allocated >> min_alloc_size_order);
|
|
++stats.shared_blob_count;
|
|
}
|
|
}
|
|
|
|
it = db->get_iterator(PREFIX_OBJ, KeyValueDB::ITERATOR_NOCACHE);
|
|
if (!it) {
|
|
derr << "failed getting onode's iterator" << dendl;
|
|
return -ENOENT;
|
|
}
|
|
|
|
uint64_t kv_count = 0;
|
|
uint64_t count_interval = 1'000'000;
|
|
ExtentDecoderPartial edecoder(*this,
|
|
stats,
|
|
*sbmap,
|
|
sb_info,
|
|
min_alloc_size_order);
|
|
|
|
// iterate over all ONodes stored in RocksDB
|
|
for (it->lower_bound(string()); it->valid(); it->next(), kv_count++) {
|
|
// trace an even after every million processed objects (typically every 5-10 seconds)
|
|
if (kv_count && (kv_count % count_interval == 0) ) {
|
|
dout(5) << __func__ << " processed objects count = " << kv_count << dendl;
|
|
}
|
|
|
|
auto key = it->key();
|
|
auto okey = key;
|
|
dout(20) << __func__ << " decode onode " << pretty_binary_string(key) << dendl;
|
|
ghobject_t oid;
|
|
if (!is_extent_shard_key(it->key())) {
|
|
int r = get_key_object(okey, &oid);
|
|
if (r != 0) {
|
|
derr << __func__ << " failed to decode onode key = "
|
|
<< pretty_binary_string(okey) << dendl;
|
|
return -EIO;
|
|
}
|
|
edecoder.reset(oid,
|
|
&stats.actual_pool_vstatfs[oid.hobj.get_logical_pool()]);
|
|
Onode dummy_on(cct);
|
|
Onode::decode_raw(&dummy_on,
|
|
it->value(),
|
|
edecoder,
|
|
segment_size != 0);
|
|
++stats.onode_count;
|
|
} else {
|
|
uint32_t offset;
|
|
int r = get_key_extent_shard(key, &okey, &offset);
|
|
if (r != 0) {
|
|
derr << __func__ << " failed to decode onode extent key = "
|
|
<< pretty_binary_string(key) << dendl;
|
|
return -EIO;
|
|
}
|
|
r = get_key_object(okey, &oid);
|
|
if (r != 0) {
|
|
derr << __func__
|
|
<< " failed to decode onode key= " << pretty_binary_string(okey)
|
|
<< " from extent key= " << pretty_binary_string(key)
|
|
<< dendl;
|
|
return -EIO;
|
|
}
|
|
ceph_assert(oid == edecoder.get_oid());
|
|
edecoder.decode_some(it->value(), nullptr);
|
|
++stats.shard_count;
|
|
}
|
|
}
|
|
|
|
std::lock_guard l(vstatfs_lock);
|
|
store_statfs_t s;
|
|
osd_pools.clear();
|
|
for (auto& p : stats.actual_pool_vstatfs) {
|
|
if (per_pool_stat_collection) {
|
|
osd_pools[p.first] = p.second;
|
|
}
|
|
stats.actual_store_vstatfs += p.second;
|
|
p.second.publish(&s);
|
|
dout(5) << __func__ << " recovered pool "
|
|
<< std::hex
|
|
<< p.first << "->" << s
|
|
<< std::dec
|
|
<< " per-pool:" << per_pool_stat_collection
|
|
<< dendl;
|
|
}
|
|
vstatfs = stats.actual_store_vstatfs;
|
|
vstatfs.publish(&s);
|
|
dout(5) << __func__ << " recovered " << s
|
|
<< dendl;
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
int BlueStore::reconstruct_allocations(SimpleBitmap *sbmap, read_alloc_stats_t &stats)
|
|
{
|
|
// first set space used by superblock
|
|
auto super_length = std::max<uint64_t>(min_alloc_size, SUPER_RESERVED);
|
|
set_allocation_in_simple_bmap(sbmap, 0, super_length);
|
|
stats.extent_count++;
|
|
|
|
// then set all space taken by Objects
|
|
int ret = read_allocation_from_onodes(sbmap, stats);
|
|
if (ret < 0) {
|
|
derr << "failed read_allocation_from_onodes()" << dendl;
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------------
|
|
static void copy_simple_bitmap_to_allocator(SimpleBitmap* sbmap, Allocator* dest_alloc, uint64_t alloc_size)
|
|
{
|
|
int alloc_size_shift = std::countr_zero(alloc_size);
|
|
uint64_t offset = 0;
|
|
extent_t ext = sbmap->get_next_clr_extent(offset);
|
|
while (ext.length != 0) {
|
|
dest_alloc->init_add_free(ext.offset << alloc_size_shift, ext.length << alloc_size_shift);
|
|
offset = ext.offset + ext.length;
|
|
ext = sbmap->get_next_clr_extent(offset);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
int BlueStore::read_allocation_from_drive_on_startup()
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = _open_collections();
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
auto shutdown_cache = make_scope_guard([&] {
|
|
_shutdown_cache();
|
|
});
|
|
|
|
utime_t start = ceph_clock_now();
|
|
read_alloc_stats_t stats = {};
|
|
SimpleBitmap sbmap(cct, (bdev->get_size()/ min_alloc_size));
|
|
ret = reconstruct_allocations(&sbmap, stats);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
copy_simple_bitmap_to_allocator(&sbmap, alloc, min_alloc_size);
|
|
|
|
utime_t duration = ceph_clock_now() - start;
|
|
dout(1) << "::Allocation Recovery was completed in " << duration << " seconds, extent_count=" << stats.extent_count << dendl;
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only used for debugging purposes - we build a secondary allocator from the Onodes and compare it to the existing one
|
|
// Not meant to be run by customers
|
|
#ifdef CEPH_BLUESTORE_TOOL_RESTORE_ALLOCATION
|
|
|
|
//---------------------------------------------------------
|
|
int cmpfunc (const void * a, const void * b)
|
|
{
|
|
if ( ((extent_t*)a)->offset > ((extent_t*)b)->offset ) {
|
|
return 1;
|
|
}
|
|
else if( ((extent_t*)a)->offset < ((extent_t*)b)->offset ) {
|
|
return -1;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// compare the allocator built from Onodes with the system allocator (CF-B)
|
|
//---------------------------------------------------------
|
|
int BlueStore::compare_allocators(Allocator* alloc1, Allocator* alloc2, uint64_t req_extent_count, uint64_t memory_target)
|
|
{
|
|
uint64_t allocation_size = std::min((req_extent_count) * sizeof(extent_t), memory_target / 3);
|
|
uint64_t extent_count = allocation_size/sizeof(extent_t);
|
|
dout(5) << "req_extent_count=" << req_extent_count << ", granted extent_count="<< extent_count << dendl;
|
|
|
|
unique_ptr<extent_t[]> arr1;
|
|
unique_ptr<extent_t[]> arr2;
|
|
try {
|
|
arr1 = make_unique<extent_t[]>(extent_count);
|
|
arr2 = make_unique<extent_t[]>(extent_count);
|
|
} catch (std::bad_alloc&) {
|
|
derr << "****Failed dynamic allocation, extent_count=" << extent_count << dendl;
|
|
return -1;
|
|
}
|
|
|
|
// copy the extents from the allocators into simple array and then compare them
|
|
uint64_t size1 = 0, size2 = 0;
|
|
uint64_t idx1 = 0, idx2 = 0;
|
|
auto iterated_mapper1 = [&](uint64_t offset, uint64_t length) {
|
|
size1 += length;
|
|
if (idx1 < extent_count) {
|
|
arr1[idx1++] = {offset, length};
|
|
}
|
|
else if (idx1 == extent_count) {
|
|
derr << "(2)compare_allocators:: spillover" << dendl;
|
|
idx1 ++;
|
|
}
|
|
|
|
};
|
|
|
|
auto iterated_mapper2 = [&](uint64_t offset, uint64_t length) {
|
|
size2 += length;
|
|
if (idx2 < extent_count) {
|
|
arr2[idx2++] = {offset, length};
|
|
}
|
|
else if (idx2 == extent_count) {
|
|
derr << "(2)compare_allocators:: spillover" << dendl;
|
|
idx2 ++;
|
|
}
|
|
};
|
|
|
|
alloc1->foreach(iterated_mapper1);
|
|
alloc2->foreach(iterated_mapper2);
|
|
|
|
qsort(arr1.get(), std::min(idx1, extent_count), sizeof(extent_t), cmpfunc);
|
|
qsort(arr2.get(), std::min(idx2, extent_count), sizeof(extent_t), cmpfunc);
|
|
|
|
if (idx1 == idx2) {
|
|
idx1 = idx2 = std::min(idx1, extent_count);
|
|
if (memcmp(arr1.get(), arr2.get(), sizeof(extent_t) * idx2) == 0) {
|
|
return 0;
|
|
}
|
|
derr << "Failed memcmp(arr1, arr2, sizeof(extent_t)*idx2)" << dendl;
|
|
for (uint64_t i = 0; i < idx1; i++) {
|
|
if (memcmp(arr1.get()+i, arr2.get()+i, sizeof(extent_t)) != 0) {
|
|
derr << "!!!![" << i << "] arr1::<" << arr1[i].offset << "," << arr1[i].length << ">" << dendl;
|
|
derr << "!!!![" << i << "] arr2::<" << arr2[i].offset << "," << arr2[i].length << ">" << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
} else {
|
|
derr << "mismatch:: idx1=" << idx1 << " idx2=" << idx2 << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
int BlueStore::add_existing_bluefs_allocation(Allocator* allocator, read_alloc_stats_t &stats)
|
|
{
|
|
// then add space used by bluefs to store rocksdb
|
|
unsigned extent_count = 0;
|
|
if (bluefs) {
|
|
bluefs->foreach_block_extents(
|
|
bluefs_layout.shared_bdev,
|
|
[&](uint64_t start, uint32_t len) {
|
|
allocator->init_rm_free(start, len);
|
|
stats.extent_count++;
|
|
}
|
|
);
|
|
}
|
|
|
|
dout(5) << "bluefs extent_count=" << extent_count << dendl;
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
int BlueStore::read_allocation_from_drive_for_bluestore_tool()
|
|
{
|
|
dout(5) << __func__ << dendl;
|
|
int ret = 0;
|
|
uint64_t memory_target = cct->_conf.get_val<Option::size_t>("osd_memory_target");
|
|
ret = _open_db_and_around(true, false);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = _open_collections();
|
|
if (ret < 0) {
|
|
_close_db_and_around();
|
|
return ret;
|
|
}
|
|
|
|
utime_t duration;
|
|
read_alloc_stats_t stats = {};
|
|
utime_t start = ceph_clock_now();
|
|
|
|
auto shutdown_cache = make_scope_guard([&] {
|
|
dout(1) << "Allocation Recovery was completed in " << duration
|
|
<< " seconds; insert_count=" << stats.insert_count
|
|
<< "; extent_count=" << stats.extent_count << dendl;
|
|
_shutdown_cache();
|
|
_close_db_and_around();
|
|
});
|
|
|
|
{
|
|
auto allocator = unique_ptr<Allocator>(create_bitmap_allocator(bdev->get_size()));
|
|
//reconstruct allocations into a temp simple-bitmap and copy into allocator
|
|
{
|
|
SimpleBitmap sbmap(cct, (bdev->get_size()/ min_alloc_size));
|
|
ret = reconstruct_allocations(&sbmap, stats);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
copy_simple_bitmap_to_allocator(&sbmap, allocator.get(), min_alloc_size);
|
|
}
|
|
|
|
// add allocation space used by the bluefs itself
|
|
ret = add_existing_bluefs_allocation(allocator.get(), stats);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
if (bdev_label_multi) {
|
|
uint64_t lsize = std::max(BDEV_LABEL_BLOCK_SIZE, min_alloc_size);
|
|
for (uint64_t p : bdev_label_valid_locations) {
|
|
if (p != BDEV_FIRST_LABEL_POSITION) {
|
|
allocator->init_rm_free(p, lsize);
|
|
}
|
|
}
|
|
}
|
|
|
|
duration = ceph_clock_now() - start;
|
|
stats.insert_count = 0;
|
|
auto count_entries = [&](uint64_t extent_offset, uint64_t extent_length) {
|
|
stats.insert_count++;
|
|
};
|
|
allocator->foreach(count_entries);
|
|
ret = compare_allocators(allocator.get(), alloc, stats.insert_count, memory_target);
|
|
if (ret == 0) {
|
|
dout(5) << "Allocator drive - file integrity check OK" << dendl;
|
|
} else {
|
|
derr << "FAILURE. Allocator from file and allocator from metadata differ::ret=" << ret << dendl;
|
|
}
|
|
}
|
|
|
|
dout(1) << stats << dendl;
|
|
return ret;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
Allocator* BlueStore::clone_allocator_without_bluefs(Allocator *src_allocator)
|
|
{
|
|
uint64_t bdev_size = bdev->get_size();
|
|
Allocator* allocator = create_bitmap_allocator(bdev_size);
|
|
if (allocator) {
|
|
dout(5) << "bitmap-allocator=" << allocator << dendl;
|
|
} else {
|
|
derr << "****failed create_bitmap_allocator()" << dendl;
|
|
return nullptr;
|
|
}
|
|
|
|
uint64_t num_entries = 0;
|
|
copy_allocator(src_allocator, allocator, &num_entries);
|
|
|
|
// BlueFS stores its internal allocation outside RocksDB (FM) so we should not destage them to the allcoator-file
|
|
// we are going to hide bluefs allocation during allocator-destage as they are stored elsewhere
|
|
{
|
|
bluefs->foreach_block_extents(
|
|
bluefs_layout.shared_bdev,
|
|
[&] (uint64_t start, uint32_t len) {
|
|
allocator->init_add_free(start, len);
|
|
}
|
|
);
|
|
}
|
|
|
|
return allocator;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
static void clear_allocation_objects_from_rocksdb(KeyValueDB *db, CephContext *cct, const std::string &path)
|
|
{
|
|
dout(5) << "t->rmkeys_by_prefix(PREFIX_ALLOC_BITMAP)" << dendl;
|
|
KeyValueDB::Transaction t = db->get_transaction();
|
|
t->rmkeys_by_prefix(PREFIX_ALLOC_BITMAP);
|
|
db->submit_transaction_sync(t);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
void BlueStore::copy_allocator_content_to_fm(Allocator *allocator, FreelistManager *real_fm)
|
|
{
|
|
unsigned max_txn = 1024;
|
|
dout(5) << "max_transaction_submit=" << max_txn << dendl;
|
|
uint64_t size = 0, idx = 0;
|
|
KeyValueDB::Transaction txn = db->get_transaction();
|
|
auto iterated_insert = [&](uint64_t offset, uint64_t length) {
|
|
size += length;
|
|
real_fm->release(offset, length, txn);
|
|
if ((++idx % max_txn) == 0) {
|
|
db->submit_transaction_sync(txn);
|
|
txn = db->get_transaction();
|
|
}
|
|
};
|
|
allocator->foreach(iterated_insert);
|
|
if (idx % max_txn != 0) {
|
|
db->submit_transaction_sync(txn);
|
|
}
|
|
dout(5) << "size=" << size << ", num extents=" << idx << dendl;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
Allocator* BlueStore::initialize_allocator_from_freelist(FreelistManager *real_fm)
|
|
{
|
|
dout(5) << "real_fm->enumerate_next" << dendl;
|
|
Allocator* allocator2 = create_bitmap_allocator(bdev->get_size());
|
|
if (allocator2) {
|
|
dout(5) << "bitmap-allocator=" << allocator2 << dendl;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
|
|
uint64_t size2 = 0, idx2 = 0;
|
|
real_fm->enumerate_reset();
|
|
uint64_t offset, length;
|
|
while (real_fm->enumerate_next(db, &offset, &length)) {
|
|
allocator2->init_add_free(offset, length);
|
|
++idx2;
|
|
size2 += length;
|
|
}
|
|
real_fm->enumerate_reset();
|
|
|
|
dout(5) << "size2=" << size2 << ", num2=" << idx2 << dendl;
|
|
return allocator2;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// close the active fm and open it in a new mode like makefs()
|
|
// but make sure to mark the full device space as allocated
|
|
// later we will mark all exetents from the allocator as free
|
|
int BlueStore::reset_fm_for_restore()
|
|
{
|
|
dout(5) << "<<==>> fm->clear_null_manager()" << dendl;
|
|
fm->shutdown();
|
|
delete fm;
|
|
fm = nullptr;
|
|
freelist_type = "bitmap";
|
|
KeyValueDB::Transaction t = db->get_transaction();
|
|
// call _open_fm() with fm_restore set to TRUE
|
|
// this will mark the full device space as allocated (and not just the reserved space)
|
|
_open_fm(t, true, true, true);
|
|
if (fm == nullptr) {
|
|
derr << "Failed _open_fm()" << dendl;
|
|
return -1;
|
|
}
|
|
db->submit_transaction_sync(t);
|
|
ceph_assert(!fm->is_null_manager());
|
|
dout(5) << "fm was reactivated in full mode" << dendl;
|
|
return 0;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------
|
|
// create a temp allocator filled with allocation state from the fm
|
|
// and compare it to the base allocator passed in
|
|
int BlueStore::verify_rocksdb_allocations(Allocator *allocator)
|
|
{
|
|
dout(5) << "verify that alloc content is identical to FM" << dendl;
|
|
// initialize from freelist
|
|
Allocator* temp_allocator = initialize_allocator_from_freelist(fm);
|
|
if (temp_allocator == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
uint64_t insert_count = 0;
|
|
auto count_entries = [&](uint64_t extent_offset, uint64_t extent_length) {
|
|
insert_count++;
|
|
};
|
|
temp_allocator->foreach(count_entries);
|
|
uint64_t memory_target = cct->_conf.get_val<Option::size_t>("osd_memory_target");
|
|
int ret = compare_allocators(allocator, temp_allocator, insert_count, memory_target);
|
|
|
|
delete temp_allocator;
|
|
|
|
if (ret == 0) {
|
|
dout(5) << "SUCCESS!!! compare(allocator, temp_allocator)" << dendl;
|
|
return 0;
|
|
} else {
|
|
derr << "**** FAILURE compare(allocator, temp_allocator)::ret=" << ret << dendl;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
int BlueStore::db_cleanup(int ret)
|
|
{
|
|
_shutdown_cache();
|
|
_close_db_and_around();
|
|
return ret;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// convert back the system from null-allocator to using rocksdb to store allocation
|
|
int BlueStore::push_allocation_to_rocksdb()
|
|
{
|
|
if (cct->_conf->bluestore_allocation_from_file) {
|
|
derr << "cct->_conf->bluestore_allocation_from_file must be cleared first" << dendl;
|
|
derr << "please change default to false in ceph.conf file>" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
dout(5) << "calling open_db_and_around() in read/write mode" << dendl;
|
|
int ret = _open_db_and_around(false);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
|
|
if (!fm->is_null_manager()) {
|
|
derr << "This is not a NULL-MANAGER -> nothing to do..." << dendl;
|
|
return db_cleanup(0);
|
|
}
|
|
|
|
// start by creating a clone copy of the shared-allocator
|
|
unique_ptr<Allocator> allocator(clone_allocator_without_bluefs(alloc));
|
|
if (!allocator) {
|
|
return db_cleanup(-1);
|
|
}
|
|
|
|
// remove all objects of PREFIX_ALLOC_BITMAP from RocksDB to guarantee a clean start
|
|
clear_allocation_objects_from_rocksdb(db, cct, path);
|
|
|
|
// then open fm in new mode with the full devie marked as alloctaed
|
|
if (reset_fm_for_restore() != 0) {
|
|
return db_cleanup(-1);
|
|
}
|
|
|
|
// push the free-space from the allocator (shared-alloc without bfs) to rocksdb
|
|
copy_allocator_content_to_fm(allocator.get(), fm);
|
|
|
|
// compare the allocator info with the info stored in the fm/rocksdb
|
|
if (verify_rocksdb_allocations(allocator.get()) == 0) {
|
|
// all is good -> we can commit to rocksdb allocator
|
|
commit_to_real_manager();
|
|
} else {
|
|
return db_cleanup(-1);
|
|
}
|
|
|
|
// can't be too paranoid :-)
|
|
dout(5) << "Running full scale verification..." << dendl;
|
|
// close db/fm/allocator and start fresh
|
|
db_cleanup(0);
|
|
dout(5) << "calling open_db_and_around() in read-only mode" << dendl;
|
|
ret = _open_db_and_around(true);
|
|
if (ret < 0) {
|
|
return db_cleanup(ret);
|
|
}
|
|
ceph_assert(!fm->is_null_manager());
|
|
ceph_assert(verify_rocksdb_allocations(allocator.get()) == 0);
|
|
|
|
return db_cleanup(ret);
|
|
}
|
|
|
|
#endif // CEPH_BLUESTORE_TOOL_RESTORE_ALLOCATION
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
int BlueStore::commit_freelist_type()
|
|
{
|
|
// When freelist_type to "bitmap" we will store allocation in RocksDB
|
|
// When allocation-info is stored in a single file we set freelist_type to "null"
|
|
// This will direct the startup code to read allocation from file and not RocksDB
|
|
KeyValueDB::Transaction t = db->get_transaction();
|
|
if (t == nullptr) {
|
|
derr << "db->get_transaction() failed!!!" << dendl;
|
|
return -1;
|
|
}
|
|
|
|
bufferlist bl;
|
|
bl.append(freelist_type);
|
|
t->set(PREFIX_SUPER, "freelist_type", bl);
|
|
|
|
int ret = db->submit_transaction_sync(t);
|
|
if (ret != 0) {
|
|
derr << "Failed db->submit_transaction_sync(t)" << dendl;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
int BlueStore::commit_to_null_manager()
|
|
{
|
|
dout(5) << __func__ << " Set FreelistManager to NULL FM..." << dendl;
|
|
fm->set_null_manager();
|
|
freelist_type = "null";
|
|
#if 1
|
|
return commit_freelist_type();
|
|
#else
|
|
// should check how long this step take on a big configuration as deletes are expensive
|
|
if (commit_freelist_type() == 0) {
|
|
// remove all objects of PREFIX_ALLOC_BITMAP from RocksDB to guarantee a clean start
|
|
clear_allocation_objects_from_rocksdb(db, cct, path);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------------------------
|
|
int BlueStore::commit_to_real_manager()
|
|
{
|
|
dout(5) << "Set FreelistManager to Real FM..." << dendl;
|
|
ceph_assert(!fm->is_null_manager());
|
|
freelist_type = "bitmap";
|
|
int ret = commit_freelist_type();
|
|
if (ret == 0) {
|
|
//remove the allocation_file
|
|
invalidate_allocation_file_on_bluefs();
|
|
ret = bluefs->unlink(allocator_dir, allocator_file);
|
|
bluefs->sync_metadata(false);
|
|
if (ret == 0) {
|
|
dout(5) << "Remove Allocation File successfully" << dendl;
|
|
}
|
|
else {
|
|
derr << "Remove Allocation File ret_code=" << ret << dendl;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
//================================================================================================================
|
|
//================================================================================================================
|