/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2019 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
//------------------------------------------------------------------------------
// @author Georgios Bitzes
// @brief Asynchronous metadata retrieval from QDB, with caching support.
//------------------------------------------------------------------------------
#include
#include "MetadataFetcher.hh"
#include "MetadataProviderShard.hh"
#include "namespace/ns_quarkdb/FileMD.hh"
#include "namespace/ns_quarkdb/ContainerMD.hh"
#include "namespace/MDException.hh"
#include "common/Assert.hh"
#include
using std::placeholders::_1;
EOSNSNAMESPACE_BEGIN
//------------------------------------------------------------------------------
// Constructor
//------------------------------------------------------------------------------
MetadataProviderShard::MetadataProviderShard(qclient::QClient* qcl,
IContainerMDSvc* contsvc, IFileMDSvc* filesvc, folly::Executor* exec)
: mContSvc(contsvc), mFileSvc(filesvc), mContainerCache(312500),
mFileCache(2500000)
{
mExecutor = exec;
mQcl = qcl;
}
//------------------------------------------------------------------------------
// Retrieve ContainerMD by ID.
//------------------------------------------------------------------------------
folly::Future
MetadataProviderShard::retrieveContainerMD(ContainerIdentifier id)
{
// Quick check without lock on the long-lived cache. LRU is locked internally,
// so this is thread-safe.
//
// If we get no hit, we have to check again under lock.
IContainerMDPtr result = mContainerCache.get(id);
if (result) {
// Handle special case where we're dealing with a tombstone.
if (result->isDeleted()) {
return folly::makeFuture
(make_mdexception(ENOENT, "Container #" << id.getUnderlyingUInt64()
<< " does not exist (found deletion tombstone)"));
}
return folly::makeFuture(std::move(result));
}
std::unique_lock lock(mMutex);
// A ContainerMD can be in three states: Not in cache, inside in-flight cache,
// and cached. Is it inside in-flight cache?
auto it = mInFlightContainers.find(id);
if (it != mInFlightContainers.end()) {
// Cache hit: A container with such ID has been staged already. Once a
// response arrives, all futures tied to that container will be activated
// automatically, with the same IContainerMDPtr.
return it->second.getFuture();
}
// Nope.. is it inside the long-lived cache?
result = mContainerCache.get(id);
if (result) {
lock.unlock();
// Handle special case where we're dealing with a tombstone.
if (result->isDeleted()) {
return folly::makeFuture
(make_mdexception(ENOENT, "Container #" << id.getUnderlyingUInt64()
<< " does not exist (found deletion tombstone)"));
}
return folly::makeFuture(std::move(result));
}
// Nope, need to fetch, and insert into the in-flight staging area. Merge
// three asynchronous operations into one.
folly::Future protoFut =
MetadataFetcher::getContainerFromId(*mQcl, id);
folly::Future fileMapFut =
MetadataFetcher::getFileMap(*mQcl, id);
folly::Future containerMapFut =
MetadataFetcher::getContainerMap(*mQcl, id);
folly::Future fut =
folly::collect(protoFut, fileMapFut, containerMapFut)
.via(mExecutor)
.thenValue(std::bind(&MetadataProviderShard::processIncomingContainerMD, this,
id, _1))
.thenError([this, id](const folly::exception_wrapper & e) {
// If the operation failed, clear the in-flight cache.
std::lock_guard lock(mMutex);
mInFlightContainers.erase(id);
return folly::makeFuture(e);
});
mInFlightContainers[id] = folly::FutureSplitter(std::move(
fut));
return mInFlightContainers[id].getFuture();
}
//------------------------------------------------------------------------------
// Retrieve FileMD by ID.
//------------------------------------------------------------------------------
folly::Future
MetadataProviderShard::retrieveFileMD(FileIdentifier id)
{
// Quick check without lock on the long-lived cache. LRU is locked internally,
// so this is thread-safe.
//
// If we get no hit, we have to check again under lock.
// Nope.. is it inside the long-lived cache?
IFileMDPtr result = mFileCache.get(id);
if (result) {
// Handle special case where we're dealing with a tombstone.
if (result->isDeleted()) {
return folly::makeFuture
(make_mdexception(ENOENT, "File #" << id.getUnderlyingUInt64()
<< " does not exist (found deletion tombstone)"));
}
return folly::makeFuture(std::move(result));
}
std::unique_lock lock(mMutex);
// Are we asking for fid=0? Illegal, short-circuit without even contacting
// QDB. Indicates possible bug elsewhere in the MGM.
if (id == FileIdentifier(0)) {
eos_static_warning("Attempted to retrieve fid=0!");
return folly::makeFuture
(make_mdexception(ENOENT, "File #" << id.getUnderlyingUInt64()
<< " does not exist (fid=0 is illegal)"));
}
// A FileMD can be in three states: Not in cache, inside in-flight cache,
// and cached. Is it inside in-flight cache?
auto it = mInFlightFiles.find(id);
if (it != mInFlightFiles.end()) {
// Cache hit: A container with such ID has been staged already. Once a
// response arrives, all futures tied to that container will be activated
// automatically, with the same IContainerMDPtr.
return it->second.getFuture();
}
// Nope.. is it inside the long-lived cache?
result = mFileCache.get(id);
if (result) {
lock.unlock();
// Handle special case where we're dealing with a tombstone.
if (result->isDeleted()) {
return folly::makeFuture
(make_mdexception(ENOENT, "File #" << id.getUnderlyingUInt64()
<< " does not exist (found deletion tombstone)"));
}
return folly::makeFuture(std::move(result));
}
// Nope, need to fetch, and insert into the in-flight staging area.
folly::Future fut = MetadataFetcher::getFileFromId(*mQcl, id)
.via(mExecutor)
.thenValue(std::bind(&MetadataProviderShard::processIncomingFileMdProto, this,
id, _1))
.thenError([this, id](const folly::exception_wrapper & e) {
// If the operation failed, clear the in-flight cache.
std::lock_guard lock(mMutex);
mInFlightFiles.erase(id);
return folly::makeFuture(e);
});
mInFlightFiles[id] = folly::FutureSplitter(std::move(fut));
return mInFlightFiles[id].getFuture();
}
//------------------------------------------------------------------------------
// Drop cached FileID - return true if found
//------------------------------------------------------------------------------
bool
MetadataProviderShard::dropCachedFileID(FileIdentifier id)
{
std::unique_lock lock(mMutex);
return mFileCache.remove(id);
}
//------------------------------------------------------------------------------
// Drop cached ContainerID - return true if found
//------------------------------------------------------------------------------
bool
MetadataProviderShard::dropCachedContainerID(ContainerIdentifier id)
{
std::unique_lock lock(mMutex);
return mContainerCache.remove(id);
}
//----------------------------------------------------------------------------
// Check if a FileMD exists with the given id
//----------------------------------------------------------------------------
folly::Future
MetadataProviderShard::hasFileMD(FileIdentifier id)
{
return MetadataFetcher::doesFileMdExist(*mQcl, id);
}
//------------------------------------------------------------------------------
// Insert newly created item into the cache.
//------------------------------------------------------------------------------
void
MetadataProviderShard::insertFileMD(FileIdentifier id, IFileMDPtr item)
{
std::lock_guard lock(mMutex);
mFileCache.put(id, item);
}
//------------------------------------------------------------------------------
// Insert newly created item into the cache.
//------------------------------------------------------------------------------
void
MetadataProviderShard::insertContainerMD(ContainerIdentifier id,
IContainerMDPtr item)
{
std::lock_guard lock(mMutex);
mContainerCache.put(id, item);
}
//------------------------------------------------------------------------------
// Change file cache size.
//------------------------------------------------------------------------------
void MetadataProviderShard::setFileMDCacheNum(uint64_t max_num)
{
std::lock_guard lock(mMutex);
mFileCache.set_max_num(max_num);
}
//------------------------------------------------------------------------------
// Change container cache size.
//------------------------------------------------------------------------------
void MetadataProviderShard::setContainerMDCacheNum(uint64_t max_num)
{
std::lock_guard lock(mMutex);
mContainerCache.set_max_num(max_num);
}
//------------------------------------------------------------------------------
// Turn a (ContainerMDProto, FileMap, ContainerMap) triplet into a
// ContainerMDPtr, and insert into the cache.
//------------------------------------------------------------------------------
IContainerMDPtr
MetadataProviderShard::processIncomingContainerMD(ContainerIdentifier id,
std::tuple <
eos::ns::ContainerMdProto,
IContainerMD::FileMap,
IContainerMD::ContainerMap
> tup)
{
std::lock_guard lock(mMutex);
// Unpack tuple. (sigh)
eos::ns::ContainerMdProto& proto = std::get<0>(tup);
IContainerMD::FileMap& fileMap = std::get<1>(tup);
IContainerMD::ContainerMap& containerMap = std::get<2>(tup);
// Things look sane?
eos_assert(proto.id() == id.getUnderlyingUInt64());
// Yep, construct ContainerMD object..
QuarkContainerMD* containerMD = new QuarkContainerMD(0, mFileSvc, mContSvc);
containerMD->initialize(std::move(proto), std::move(fileMap),
std::move(containerMap));
// Drop inFlightContainers future..
auto it = mInFlightContainers.find(id);
eos_assert(it != mInFlightContainers.end());
mInFlightContainers.erase(it);
// Insert into the cache ...
IContainerMDPtr item { containerMD };
mContainerCache.put(id, item);
return item;
}
//------------------------------------------------------------------------------
// Turn an incoming FileMDProto into FileMD, removing from the inFlight
// staging area, and inserting into the cache.
//------------------------------------------------------------------------------
IFileMDPtr
MetadataProviderShard::processIncomingFileMdProto(FileIdentifier id,
eos::ns::FileMdProto proto)
{
std::lock_guard lock(mMutex);
// Things look sane?
eos_assert(proto.id() == id.getUnderlyingUInt64());
// Yep, construct FileMD object..
QuarkFileMD* fileMD = new QuarkFileMD(0, mFileSvc);
fileMD->initialize(std::move(proto));
// Drop inFlightFiles future..
auto it = mInFlightFiles.find(id);
eos_assert(it != mInFlightFiles.end());
mInFlightFiles.erase(it);
// Insert into the cache ...
IFileMDPtr item { fileMD };
mFileCache.put(id, item);
return item;
}
//------------------------------------------------------------------------------
// Get file cache statistics
//------------------------------------------------------------------------------
CacheStatistics MetadataProviderShard::getFileMDCacheStats()
{
CacheStatistics stats;
stats.enabled = true;
stats.occupancy = mFileCache.size();
stats.maxNum = mFileCache.get_max_num();
std::lock_guard lock(mMutex);
stats.inFlight = mInFlightFiles.size();
return stats;
}
//------------------------------------------------------------------------------
// Get container cache statistics
//------------------------------------------------------------------------------
CacheStatistics MetadataProviderShard::getContainerMDCacheStats()
{
CacheStatistics stats;
stats.enabled = true;
stats.occupancy = mContainerCache.size();
stats.maxNum = mContainerCache.get_max_num();
std::lock_guard lock(mMutex);
stats.inFlight = mInFlightContainers.size();
return stats;
}
EOSNSNAMESPACE_END