mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-20 08:40:50 +00:00
Merge pull request #16364 from ClickHouse/fix_uuid_mapping_exists
Minor improvements in DatabaseCatalog
This commit is contained in:
commit
8a6be1602b
@ -54,6 +54,7 @@
|
||||
M(LocalThread, "Number of threads in local thread pools. Should be similar to GlobalThreadActive.") \
|
||||
M(LocalThreadActive, "Number of threads in local thread pools running a task.") \
|
||||
M(DistributedFilesToInsert, "Number of pending files to process for asynchronous insertion into Distributed tables. Number of files for every shard is summed.") \
|
||||
M(TablesToDropQueueSize, "Number of dropped tables, that are waiting for background data removal.") \
|
||||
|
||||
namespace CurrentMetrics
|
||||
{
|
||||
|
@ -261,21 +261,29 @@ void DatabaseAtomic::commitCreateTable(const ASTCreateQuery & query, const Stora
|
||||
{
|
||||
DetachedTables not_in_use;
|
||||
auto table_data_path = getTableDataPath(query);
|
||||
bool locked_uuid = false;
|
||||
try
|
||||
{
|
||||
std::unique_lock lock{mutex};
|
||||
if (query.database != database_name)
|
||||
throw Exception(ErrorCodes::UNKNOWN_DATABASE, "Database was renamed to `{}`, cannot create table in `{}`",
|
||||
database_name, query.database);
|
||||
/// Do some checks before renaming file from .tmp to .sql
|
||||
not_in_use = cleanupDetachedTables();
|
||||
assertDetachedTableNotInUse(query.uuid);
|
||||
renameNoReplace(table_metadata_tmp_path, table_metadata_path);
|
||||
/// We will get en exception if some table with the same UUID exists (even if it's detached table or table from another database)
|
||||
DatabaseCatalog::instance().addUUIDMapping(query.uuid);
|
||||
locked_uuid = true;
|
||||
/// It throws if `table_metadata_path` already exists (it's possible if table was detached)
|
||||
renameNoReplace(table_metadata_tmp_path, table_metadata_path); /// Commit point (a sort of)
|
||||
attachTableUnlocked(query.table, table, lock); /// Should never throw
|
||||
table_name_to_path.emplace(query.table, table_data_path);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Poco::File(table_metadata_tmp_path).remove();
|
||||
if (locked_uuid)
|
||||
DatabaseCatalog::instance().removeUUIDMappingFinally(query.uuid);
|
||||
throw;
|
||||
}
|
||||
tryCreateSymlink(query.table, table_data_path);
|
||||
|
@ -53,6 +53,9 @@ void DatabaseMemory::dropTable(
|
||||
}
|
||||
table->is_dropped = true;
|
||||
create_queries.erase(table_name);
|
||||
UUID table_uuid = table->getStorageID().uuid;
|
||||
if (table_uuid != UUIDHelpers::Nil)
|
||||
DatabaseCatalog::instance().removeUUIDMappingFinally(table_uuid);
|
||||
}
|
||||
|
||||
ASTPtr DatabaseMemory::getCreateDatabaseQuery() const
|
||||
|
@ -223,6 +223,10 @@ void DatabaseWithDictionaries::removeDictionary(const Context &, const String &
|
||||
attachDictionary(dictionary_name, attach_info);
|
||||
throw;
|
||||
}
|
||||
|
||||
UUID dict_uuid = attach_info.create_query->as<ASTCreateQuery>()->uuid;
|
||||
if (dict_uuid != UUIDHelpers::Nil)
|
||||
DatabaseCatalog::instance().removeUUIDMappingFinally(dict_uuid);
|
||||
}
|
||||
|
||||
DatabaseDictionariesIteratorPtr DatabaseWithDictionaries::getDictionariesIterator(const FilterByNameFunction & filter_by_dictionary_name)
|
||||
|
@ -13,9 +13,16 @@
|
||||
#include <IO/ReadHelpers.h>
|
||||
#include <Poco/DirectoryIterator.h>
|
||||
#include <Common/renameat2.h>
|
||||
#include <Common/CurrentMetrics.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
namespace CurrentMetrics
|
||||
{
|
||||
extern const Metric TablesToDropQueueSize;
|
||||
}
|
||||
|
||||
namespace DB
|
||||
{
|
||||
|
||||
@ -155,7 +162,17 @@ void DatabaseCatalog::shutdownImpl()
|
||||
tables_marked_dropped.clear();
|
||||
|
||||
std::lock_guard lock(databases_mutex);
|
||||
assert(std::find_if_not(uuid_map.begin(), uuid_map.end(), [](const auto & elem) { return elem.map.empty(); }) == uuid_map.end());
|
||||
assert(std::find_if(uuid_map.begin(), uuid_map.end(), [](const auto & elem)
|
||||
{
|
||||
/// Ensure that all UUID mappings are emtpy (i.e. all mappings contain nullptr instead of a pointer to storage)
|
||||
const auto & not_empty_mapping = [] (const auto & mapping)
|
||||
{
|
||||
auto & table = mapping.second.second;
|
||||
return table;
|
||||
};
|
||||
auto it = std::find_if(elem.map.begin(), elem.map.end(), not_empty_mapping);
|
||||
return it != elem.map.end();
|
||||
}) == uuid_map.end());
|
||||
databases.clear();
|
||||
db_uuid_map.clear();
|
||||
view_dependencies.clear();
|
||||
@ -411,36 +428,76 @@ DatabasePtr DatabaseCatalog::getSystemDatabase() const
|
||||
return getDatabase(SYSTEM_DATABASE);
|
||||
}
|
||||
|
||||
void DatabaseCatalog::addUUIDMapping(const UUID & uuid, DatabasePtr database, StoragePtr table)
|
||||
void DatabaseCatalog::addUUIDMapping(const UUID & uuid)
|
||||
{
|
||||
addUUIDMapping(uuid, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void DatabaseCatalog::addUUIDMapping(const UUID & uuid, const DatabasePtr & database, const StoragePtr & table)
|
||||
{
|
||||
assert(uuid != UUIDHelpers::Nil && getFirstLevelIdx(uuid) < uuid_map.size());
|
||||
assert((database && table) || (!database && !table));
|
||||
UUIDToStorageMapPart & map_part = uuid_map[getFirstLevelIdx(uuid)];
|
||||
std::lock_guard lock{map_part.mutex};
|
||||
auto [_, inserted] = map_part.map.try_emplace(uuid, std::move(database), std::move(table));
|
||||
auto [it, inserted] = map_part.map.try_emplace(uuid, database, table);
|
||||
if (inserted)
|
||||
return;
|
||||
|
||||
auto & prev_database = it->second.first;
|
||||
auto & prev_table = it->second.second;
|
||||
assert((prev_database && prev_table) || (!prev_database && !prev_table));
|
||||
|
||||
if (!prev_table && table)
|
||||
{
|
||||
/// It's empty mapping, it was created to "lock" UUID and prevent collision. Just update it.
|
||||
prev_database = database;
|
||||
prev_table = table;
|
||||
return;
|
||||
}
|
||||
|
||||
/// We are trying to replace existing mapping (prev_table != nullptr), it's logical error
|
||||
if (table)
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Mapping for table with UUID={} already exists", toString(uuid));
|
||||
/// Normally this should never happen, but it's possible when the same UUIDs are explicitly specified in different CREATE queries,
|
||||
/// so it's not LOGICAL_ERROR
|
||||
if (!inserted)
|
||||
throw Exception("Mapping for table with UUID=" + toString(uuid) + " already exists", ErrorCodes::TABLE_ALREADY_EXISTS);
|
||||
throw Exception(ErrorCodes::TABLE_ALREADY_EXISTS, "Mapping for table with UUID={} already exists. It happened due to UUID collision, "
|
||||
"most likely because some not random UUIDs were manually specified in CREATE queries.", toString(uuid));
|
||||
}
|
||||
|
||||
void DatabaseCatalog::removeUUIDMapping(const UUID & uuid)
|
||||
{
|
||||
assert(uuid != UUIDHelpers::Nil && getFirstLevelIdx(uuid) < uuid_map.size());
|
||||
UUIDToStorageMapPart & map_part = uuid_map[getFirstLevelIdx(uuid)];
|
||||
std::lock_guard lock{map_part.mutex};
|
||||
auto it = map_part.map.find(uuid);
|
||||
if (it == map_part.map.end())
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Mapping for table with UUID={} doesn't exist", toString(uuid));
|
||||
it->second = {};
|
||||
}
|
||||
|
||||
void DatabaseCatalog::removeUUIDMappingFinally(const UUID & uuid)
|
||||
{
|
||||
assert(uuid != UUIDHelpers::Nil && getFirstLevelIdx(uuid) < uuid_map.size());
|
||||
UUIDToStorageMapPart & map_part = uuid_map[getFirstLevelIdx(uuid)];
|
||||
std::lock_guard lock{map_part.mutex};
|
||||
if (!map_part.map.erase(uuid))
|
||||
throw Exception("Mapping for table with UUID=" + toString(uuid) + " doesn't exist", ErrorCodes::LOGICAL_ERROR);
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Mapping for table with UUID={} doesn't exist", toString(uuid));
|
||||
}
|
||||
|
||||
void DatabaseCatalog::updateUUIDMapping(const UUID & uuid, DatabasePtr database, StoragePtr table)
|
||||
{
|
||||
assert(uuid != UUIDHelpers::Nil && getFirstLevelIdx(uuid) < uuid_map.size());
|
||||
assert(database && table);
|
||||
UUIDToStorageMapPart & map_part = uuid_map[getFirstLevelIdx(uuid)];
|
||||
std::lock_guard lock{map_part.mutex};
|
||||
auto it = map_part.map.find(uuid);
|
||||
if (it == map_part.map.end())
|
||||
throw Exception("Mapping for table with UUID=" + toString(uuid) + " doesn't exist", ErrorCodes::LOGICAL_ERROR);
|
||||
it->second = std::make_pair(std::move(database), std::move(table));
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Mapping for table with UUID={} doesn't exist", toString(uuid));
|
||||
auto & prev_database = it->second.first;
|
||||
auto & prev_table = it->second.second;
|
||||
assert(prev_database && prev_table);
|
||||
prev_database = std::move(database);
|
||||
prev_table = std::move(table);
|
||||
}
|
||||
|
||||
std::unique_ptr<DatabaseCatalog> DatabaseCatalog::database_catalog;
|
||||
@ -631,6 +688,8 @@ void DatabaseCatalog::loadMarkedAsDroppedTables()
|
||||
dropped_metadata.emplace(std::move(full_path), std::move(dropped_id));
|
||||
}
|
||||
|
||||
LOG_INFO(log, "Found {} partially dropped tables. Will load them and retry removal.", dropped_metadata.size());
|
||||
|
||||
ThreadPool pool;
|
||||
for (const auto & elem : dropped_metadata)
|
||||
{
|
||||
@ -695,6 +754,7 @@ void DatabaseCatalog::enqueueDroppedTableCleanup(StorageID table_id, StoragePtr
|
||||
LOG_WARNING(log, "Cannot parse metadata of partially dropped table {} from {}. Will remove metadata file and data directory. Garbage may be left in /store directory and ZooKeeper.", table_id.getNameForLogs(), dropped_metadata_path);
|
||||
}
|
||||
|
||||
addUUIDMapping(table_id.uuid);
|
||||
drop_time = Poco::File(dropped_metadata_path).getLastModified().epochTime();
|
||||
}
|
||||
|
||||
@ -704,6 +764,8 @@ void DatabaseCatalog::enqueueDroppedTableCleanup(StorageID table_id, StoragePtr
|
||||
else
|
||||
tables_marked_dropped.push_back({table_id, table, dropped_metadata_path, drop_time});
|
||||
tables_marked_dropped_ids.insert(table_id.uuid);
|
||||
CurrentMetrics::add(CurrentMetrics::TablesToDropQueueSize, 1);
|
||||
|
||||
/// If list of dropped tables was empty, start a drop task
|
||||
if (drop_task && tables_marked_dropped.size() == 1)
|
||||
(*drop_task)->schedule();
|
||||
@ -732,6 +794,10 @@ void DatabaseCatalog::dropTableDataTask()
|
||||
LOG_INFO(log, "Will try drop {}", table.table_id.getNameForLogs());
|
||||
tables_marked_dropped.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_TRACE(log, "Not found any suitable tables to drop, still have {} tables in drop queue", tables_marked_dropped.size());
|
||||
}
|
||||
need_reschedule = !tables_marked_dropped.empty();
|
||||
}
|
||||
catch (...)
|
||||
@ -770,7 +836,7 @@ void DatabaseCatalog::dropTableDataTask()
|
||||
(*drop_task)->scheduleAfter(reschedule_time_ms);
|
||||
}
|
||||
|
||||
void DatabaseCatalog::dropTableFinally(const TableMarkedAsDropped & table) const
|
||||
void DatabaseCatalog::dropTableFinally(const TableMarkedAsDropped & table)
|
||||
{
|
||||
if (table.table)
|
||||
{
|
||||
@ -789,6 +855,9 @@ void DatabaseCatalog::dropTableFinally(const TableMarkedAsDropped & table) const
|
||||
|
||||
LOG_INFO(log, "Removing metadata {} of dropped table {}", table.metadata_path, table.table_id.getNameForLogs());
|
||||
Poco::File(table.metadata_path).remove();
|
||||
|
||||
removeUUIDMappingFinally(table.table_id.uuid);
|
||||
CurrentMetrics::sub(CurrentMetrics::TablesToDropQueueSize, 1);
|
||||
}
|
||||
|
||||
String DatabaseCatalog::getPathForUUID(const UUID & uuid)
|
||||
@ -826,6 +895,8 @@ void DatabaseCatalog::waitTableFinallyDropped(const UUID & uuid)
|
||||
{
|
||||
if (uuid == UUIDHelpers::Nil)
|
||||
return;
|
||||
|
||||
LOG_DEBUG(log, "Waiting for table {} to be finally dropped", toString(uuid));
|
||||
std::unique_lock lock{tables_marked_dropped_mutex};
|
||||
wait_table_finally_dropped.wait(lock, [&]()
|
||||
{
|
||||
|
@ -165,12 +165,21 @@ public:
|
||||
void updateDependency(const StorageID & old_from, const StorageID & old_where,const StorageID & new_from, const StorageID & new_where);
|
||||
|
||||
/// If table has UUID, addUUIDMapping(...) must be called when table attached to some database
|
||||
/// and removeUUIDMapping(...) must be called when it detached.
|
||||
/// removeUUIDMapping(...) must be called when it detached,
|
||||
/// and removeUUIDMappingFinally(...) must be called when table is dropped and its data removed from disk.
|
||||
/// Such tables can be accessed by persistent UUID instead of database and table name.
|
||||
void addUUIDMapping(const UUID & uuid, DatabasePtr database, StoragePtr table);
|
||||
void addUUIDMapping(const UUID & uuid, const DatabasePtr & database, const StoragePtr & table);
|
||||
void removeUUIDMapping(const UUID & uuid);
|
||||
void removeUUIDMappingFinally(const UUID & uuid);
|
||||
/// For moving table between databases
|
||||
void updateUUIDMapping(const UUID & uuid, DatabasePtr database, StoragePtr table);
|
||||
/// This method adds empty mapping (with database and storage equal to nullptr).
|
||||
/// It's required to "lock" some UUIDs and protect us from collision.
|
||||
/// Collisions of random 122-bit integers are very unlikely to happen,
|
||||
/// but we allow to explicitly specify UUID in CREATE query (in particular for testing).
|
||||
/// If some UUID was already added and we are trying to add it again,
|
||||
/// this method will throw an exception.
|
||||
void addUUIDMapping(const UUID & uuid);
|
||||
|
||||
static String getPathForUUID(const UUID & uuid);
|
||||
|
||||
@ -222,7 +231,7 @@ private:
|
||||
|
||||
void loadMarkedAsDroppedTables();
|
||||
void dropTableDataTask();
|
||||
void dropTableFinally(const TableMarkedAsDropped & table) const;
|
||||
void dropTableFinally(const TableMarkedAsDropped & table);
|
||||
|
||||
static constexpr size_t reschedule_time_ms = 100;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user