diff --git a/dbms/CMakeLists.txt b/dbms/CMakeLists.txt index 3e38c2a9fdb..f294dd84feb 100644 --- a/dbms/CMakeLists.txt +++ b/dbms/CMakeLists.txt @@ -96,6 +96,7 @@ add_headers_and_sources(dbms src/Storages/Page/V3) add_headers_and_sources(dbms src/Storages/Page/V3/LogFile) add_headers_and_sources(dbms src/Storages/Page/V3/WAL) add_headers_and_sources(dbms src/Storages/Page/V3/spacemap) +add_headers_and_sources(dbms src/Storages/Page/V3/PageDirectory) add_headers_and_sources(dbms src/Storages/Page/) add_headers_and_sources(dbms src/TiDB) add_headers_and_sources(dbms src/Client) diff --git a/dbms/src/Storages/DeltaMerge/StoragePool.cpp b/dbms/src/Storages/DeltaMerge/StoragePool.cpp index 26e89f1f7ab..7c0130392c6 100644 --- a/dbms/src/Storages/DeltaMerge/StoragePool.cpp +++ b/dbms/src/Storages/DeltaMerge/StoragePool.cpp @@ -538,10 +538,6 @@ void StoragePool::dataRegisterExternalPagesCallbacks(const ExternalPageCallbacks break; } case PageStorageRunMode::ONLY_V3: - { - data_storage_v3->registerExternalPagesCallbacks(callbacks); - break; - } case PageStorageRunMode::MIX_MODE: { // We have transformed all pages from V2 to V3 in `restore`, so @@ -564,13 +560,10 @@ void StoragePool::dataUnregisterExternalPagesCallbacks(NamespaceId ns_id) break; } case PageStorageRunMode::ONLY_V3: - { - data_storage_v3->unregisterExternalPagesCallbacks(ns_id); - break; - } case PageStorageRunMode::MIX_MODE: { - // no need unregister callback in V2. + // We have transformed all pages from V2 to V3 in `restore`, so + // only need to unregister callbacks for V3. data_storage_v3->unregisterExternalPagesCallbacks(ns_id); break; } diff --git a/dbms/src/Storages/Page/V3/PageDirectory.cpp b/dbms/src/Storages/Page/V3/PageDirectory.cpp index e1977de0f6e..79b2a1d0c89 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectory.cpp @@ -1091,8 +1091,7 @@ void PageDirectory::apply(PageEntriesEdit && edit, const WriteLimiterPtr & write { // put the new created holder into `external_ids` *holder = r.page_id; - std::lock_guard guard(external_ids_mutex); - external_ids.emplace_back(std::weak_ptr(holder)); + external_ids_by_ns.addExternalId(holder); } break; } @@ -1156,26 +1155,6 @@ void PageDirectory::gcApply(PageEntriesEdit && migrated_edit, const WriteLimiter LOG_FMT_INFO(log, "GC apply done. [edit size={}]", migrated_edit.size()); } -std::set PageDirectory::getAliveExternalIds(NamespaceId ns_id) const -{ - std::set valid_external_ids; - { - std::lock_guard guard(external_ids_mutex); - for (auto iter = external_ids.begin(); iter != external_ids.end(); /*empty*/) - { - if (auto holder = iter->lock(); holder == nullptr) - iter = external_ids.erase(iter); - else - { - if (holder->high == ns_id) - valid_external_ids.emplace(holder->low); - ++iter; - } - } - } - return valid_external_ids; -} - std::pair, PageSize> PageDirectory::getEntriesByBlobIds(const std::vector & blob_ids) const { diff --git a/dbms/src/Storages/Page/V3/PageDirectory.h b/dbms/src/Storages/Page/V3/PageDirectory.h index a1637dc8ca7..a8b4ffb0459 100644 --- a/dbms/src/Storages/Page/V3/PageDirectory.h +++ b/dbms/src/Storages/Page/V3/PageDirectory.h @@ -20,9 +20,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -370,7 +372,20 @@ class PageDirectory // When dump snapshot, we need to keep the last valid entry. Check out `tryDumpSnapshot` for the reason. PageEntriesV3 gcInMemEntries(bool return_removed_entries = true, bool keep_last_valid_var_entry = false); - std::set getAliveExternalIds(NamespaceId ns_id) const; + // Get the external id that is not deleted or being ref by another id by + // `ns_id`. + std::set getAliveExternalIds(NamespaceId ns_id) const + { + return external_ids_by_ns.getAliveIds(ns_id); + } + + // After table dropped, the `getAliveIds` with specified + // `ns_id` will not be cleaned. We need this method to + // cleanup all external id ptrs. + void unregisterNamespace(NamespaceId ns_id) + { + external_ids_by_ns.unregisterNamespace(ns_id); + } PageEntriesEdit dumpSnapshotToEdit(PageDirectorySnapshotPtr snap = nullptr); @@ -413,8 +428,7 @@ class PageDirectory mutable std::mutex snapshots_mutex; mutable std::list> snapshots; - mutable std::mutex external_ids_mutex; - mutable std::list> external_ids; + mutable ExternalIdsByNamespace external_ids_by_ns; WALStorePtr wal; const UInt64 max_persisted_log_files; diff --git a/dbms/src/Storages/Page/V3/PageDirectory/ExternalIdsByNamespace.cpp b/dbms/src/Storages/Page/V3/PageDirectory/ExternalIdsByNamespace.cpp new file mode 100644 index 00000000000..217b8896f7e --- /dev/null +++ b/dbms/src/Storages/Page/V3/PageDirectory/ExternalIdsByNamespace.cpp @@ -0,0 +1,78 @@ +// Copyright 2022 PingCAP, Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include + +namespace DB::PS::V3 +{ +void ExternalIdsByNamespace::addExternalIdUnlock(const std::shared_ptr & external_id) +{ + const NamespaceId & ns_id = external_id->high; + // create a new ExternalIds if the ns_id is not exists, else return + // the existing one. + auto [ns_iter, new_inserted] = ids_by_ns.try_emplace(ns_id, ExternalIds{}); + ns_iter->second.emplace_back(std::weak_ptr(external_id)); +} + +void ExternalIdsByNamespace::addExternalId(const std::shared_ptr & external_id) +{ + std::unique_lock map_guard(mu); + addExternalIdUnlock(external_id); +} + +std::set ExternalIdsByNamespace::getAliveIds(NamespaceId ns_id) const +{ + // Now we assume a lock among all NamespaceIds is good enough. + std::unique_lock map_guard(mu); + + std::set valid_external_ids; + auto ns_iter = ids_by_ns.find(ns_id); + if (ns_iter == ids_by_ns.end()) + return valid_external_ids; + + // Only scan the given `ns_id` + auto & external_ids = ns_iter->second; + for (auto iter = external_ids.begin(); iter != external_ids.end(); /*empty*/) + { + if (auto holder = iter->lock(); holder == nullptr) + { + // the external id has been removed from `PageDirectory`, + // cleanup the invalid weak_ptr + iter = external_ids.erase(iter); + continue; + } + else + { + valid_external_ids.emplace(holder->low); + ++iter; + } + } + // No valid external pages in this `ns_id` + if (valid_external_ids.empty()) + { + valid_external_ids.erase(ns_id); + } + return valid_external_ids; +} + +void ExternalIdsByNamespace::unregisterNamespace(NamespaceId ns_id) +{ + std::unique_lock map_guard(mu); + // free all weak_ptrs of this namespace + ids_by_ns.erase(ns_id); +} +} // namespace DB::PS::V3 diff --git a/dbms/src/Storages/Page/V3/PageDirectory/ExternalIdsByNamespace.h b/dbms/src/Storages/Page/V3/PageDirectory/ExternalIdsByNamespace.h new file mode 100644 index 00000000000..26ff9c109f4 --- /dev/null +++ b/dbms/src/Storages/Page/V3/PageDirectory/ExternalIdsByNamespace.h @@ -0,0 +1,56 @@ +// Copyright 2022 PingCAP, Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +#include + +namespace DB::PS::V3 +{ + +// A thread-safe class to manage external ids. +// Manage all external ids by NamespaceId. +class ExternalIdsByNamespace +{ +public: + ExternalIdsByNamespace() = default; + + // Add a external ids + void addExternalId(const std::shared_ptr & external_id); + // non thread-safe version, only for restore + void addExternalIdUnlock(const std::shared_ptr & external_id); + + // Get all alive external ids of given `ns_id` + // Will also cleanup the invalid external ids. + std::set getAliveIds(NamespaceId ns_id) const; + + // After table dropped, the `getAliveIds` with specified + // `ns_id` will not be cleaned. We need this method to + // cleanup all external id ptrs. + void unregisterNamespace(NamespaceId ns_id); + + DISALLOW_COPY_AND_MOVE(ExternalIdsByNamespace); + +private: + mutable std::mutex mu; + // Only store weak_ptrs. The weak_ptrs will be invalid after the external id + // in PageDirectory get removed. + using ExternalIds = std::list>; + using NamespaceMap = std::unordered_map; + mutable NamespaceMap ids_by_ns; +}; +} // namespace DB::PS::V3 diff --git a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp index aadef4d12a9..8bb75af72cb 100644 --- a/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp +++ b/dbms/src/Storages/Page/V3/PageDirectoryFactory.cpp @@ -149,7 +149,7 @@ void PageDirectoryFactory::applyRecord( if (holder) { *holder = r.page_id; - dir->external_ids.emplace_back(std::weak_ptr(holder)); + dir->external_ids_by_ns.addExternalIdUnlock(holder); } break; } @@ -162,7 +162,7 @@ void PageDirectoryFactory::applyRecord( if (holder) { *holder = r.page_id; - dir->external_ids.emplace_back(std::weak_ptr(holder)); + dir->external_ids_by_ns.addExternalIdUnlock(holder); } break; } diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp index 1bd79f54204..dba1fef7566 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.cpp +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.cpp @@ -12,14 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include +#include #include #include +#include #include #include #include #include #include #include +#include namespace DB { @@ -269,6 +273,51 @@ void PageStorageImpl::traverseImpl(const std::function"; + case GCStageType::OnlyInMem: + return " without full gc"; + case GCStageType::FullGCNothingMoved: + return " without moving any entry"; + case GCStageType::FullGC: + return ""; + } + }(); + const auto get_external_msg = [this]() -> String { + if (clean_external_page_ms == 0) + return String(""); + static constexpr double SCALE_NS_TO_MS = 1'000'000.0; + return fmt::format(" [external_callbacks={}] [external_gc={}ms] [scanner={:.2f}ms] [get_alive={:.2f}ms] [remover={:.2f}ms]", + num_external_callbacks, + clean_external_page_ms, + external_page_scan_ns / SCALE_NS_TO_MS, + external_page_get_alive_ns / SCALE_NS_TO_MS, + external_page_remove_ns / SCALE_NS_TO_MS); + }; + return fmt::format("GC finished{}." + " [total time={}ms]" + " [dump snapshots={}ms] [gc in mem entries={}ms]" + " [blobstore remove entries={}ms] [blobstore get status={}ms]" + " [get gc entries={}ms] [blobstore full gc={}ms]" + " [gc apply={}ms]" + "{}", // a placeholder for external page gc at last + stage_suffix, + total_cost_ms, + dump_snapshots_ms, + gc_in_mem_entries_ms, + blobstore_remove_entries_ms, + blobstore_get_gc_stats_ms, + full_gc_get_entries_ms, + full_gc_blobstore_copy_ms, + full_gc_apply_ms, + get_external_msg()); +} + bool PageStorageImpl::gcImpl(bool /*not_skip*/, const WriteLimiterPtr & write_limiter, const ReadLimiterPtr & read_limiter) { // If another thread is running gc, just return; @@ -276,6 +325,43 @@ bool PageStorageImpl::gcImpl(bool /*not_skip*/, const WriteLimiterPtr & write_li if (!gc_is_running.compare_exchange_strong(v, true)) return false; + const GCTimeStatistics statistics = doGC(write_limiter, read_limiter); + assert(statistics.stage != GCStageType::Unknown); // `doGC` must set the stage + LOG_DEBUG(log, statistics.toLogging()); + + return statistics.executeNextImmediately(); +} + +// Remove external pages for all tables +// TODO: `clean_external_page` for all tables may slow down the whole gc process when there are lots of table. +void PageStorageImpl::cleanExternalPage(Stopwatch & gc_watch, GCTimeStatistics & statistics) +{ + // TODO: `callbacks_mutex` is being held during the whole `cleanExternalPage`, meaning gc will block + // creating/dropping table, need to refine it later. + std::scoped_lock lock{callbacks_mutex}; + statistics.num_external_callbacks = callbacks_container.size(); + if (!callbacks_container.empty()) + { + Stopwatch external_watch; + for (const auto & [ns_id, callbacks] : callbacks_container) + { + // Note that we must call `scanner` before `getAliveExternalIds` + // Or some committed external ids is not included and we may + // remove the external page by accident with `remover`. + const auto pending_external_pages = callbacks.scanner(); + statistics.external_page_scan_ns += external_watch.elapsedFromLastTime(); + const auto alive_external_ids = page_directory->getAliveExternalIds(ns_id); + statistics.external_page_get_alive_ns += external_watch.elapsedFromLastTime(); + callbacks.remover(pending_external_pages, alive_external_ids); + statistics.external_page_remove_ns += external_watch.elapsedFromLastTime(); + } + } + + statistics.clean_external_page_ms = gc_watch.elapsedMillisecondsFromLastTime(); +} + +PageStorageImpl::GCTimeStatistics PageStorageImpl::doGC(const WriteLimiterPtr & write_limiter, const ReadLimiterPtr & read_limiter) +{ Stopwatch gc_watch; SCOPE_EXIT({ GET_METRIC(tiflash_storage_page_gc_count, type_v3).Increment(); @@ -284,18 +370,7 @@ bool PageStorageImpl::gcImpl(bool /*not_skip*/, const WriteLimiterPtr & write_li gc_is_running.compare_exchange_strong(is_running, false); }); - auto clean_external_page = [this]() { - std::scoped_lock lock{callbacks_mutex}; - if (!callbacks_container.empty()) - { - for (const auto & [ns_id, callbacks] : callbacks_container) - { - auto pending_external_pages = callbacks.scanner(); - auto alive_external_ids = getAliveExternalPageIds(ns_id); - callbacks.remover(pending_external_pages, alive_external_ids); - } - } - }; + GCTimeStatistics statistics; // 1. Do the MVCC gc, clean up expired snapshot. // And get the expired entries. @@ -303,71 +378,50 @@ bool PageStorageImpl::gcImpl(bool /*not_skip*/, const WriteLimiterPtr & write_li { GET_METRIC(tiflash_storage_page_gc_count, type_v3_mvcc_dumped).Increment(); } - const auto dump_snapshots_ms = gc_watch.elapsedMillisecondsFromLastTime(); + statistics.dump_snapshots_ms = gc_watch.elapsedMillisecondsFromLastTime(); const auto & del_entries = page_directory->gcInMemEntries(); - const auto gc_in_mem_entries_ms = gc_watch.elapsedMillisecondsFromLastTime(); + statistics.gc_in_mem_entries_ms = gc_watch.elapsedMillisecondsFromLastTime(); // 2. Remove the expired entries in BlobStore. // It won't delete the data on the disk. // It will only update the SpaceMap which in memory. blob_store.remove(del_entries); - const auto blobstore_remove_entries_ms = gc_watch.elapsedMillisecondsFromLastTime(); + statistics.blobstore_remove_entries_ms = gc_watch.elapsedMillisecondsFromLastTime(); - // 3. Analyze the status of each Blob in order to obtain the Blobs that need to do `heavy GC`. - // Blobs that do not need to do heavy GC will also do ftruncate to reduce space enlargement. - const auto & blob_need_gc = blob_store.getGCStats(); - const auto blobstore_get_gc_stats_ms = gc_watch.elapsedMillisecondsFromLastTime(); - if (blob_need_gc.empty()) - { - LOG_FMT_INFO(log, "GC finished without any blob need full gc. [total time(ms)={}]" - " [dump snapshots(ms)={}] [gc in mem entries(ms)={}]" - " [blobstore remove entries(ms)={}] [blobstore get status(ms)={}]", - gc_watch.elapsedMilliseconds(), - dump_snapshots_ms, - gc_in_mem_entries_ms, - blobstore_remove_entries_ms, - blobstore_get_gc_stats_ms); - clean_external_page(); - return false; - } - else + // 3. Analyze the status of each Blob in order to obtain the Blobs that need to do `full GC`. + // Blobs that do not need to do full GC will also do ftruncate to reduce space amplification. + const auto & blob_ids_need_gc = blob_store.getGCStats(); + statistics.blobstore_get_gc_stats_ms = gc_watch.elapsedMillisecondsFromLastTime(); + if (blob_ids_need_gc.empty()) { - GET_METRIC(tiflash_storage_page_gc_count, type_v3_bs_full_gc).Increment(blob_need_gc.size()); + cleanExternalPage(gc_watch, statistics); + statistics.stage = GCStageType::OnlyInMem; + statistics.total_cost_ms = gc_watch.elapsedMilliseconds(); + return statistics; } // Execute full gc + GET_METRIC(tiflash_storage_page_gc_count, type_v3_bs_full_gc).Increment(blob_ids_need_gc.size()); // 4. Filter out entries in MVCC by BlobId. // We also need to filter the version of the entry. // So that the `gc_apply` can proceed smoothly. - auto [blob_gc_info, total_page_size] = page_directory->getEntriesByBlobIds(blob_need_gc); - const auto gc_get_entries_ms = gc_watch.elapsedMillisecondsFromLastTime(); + auto [blob_gc_info, total_page_size] = page_directory->getEntriesByBlobIds(blob_ids_need_gc); + statistics.full_gc_get_entries_ms = gc_watch.elapsedMillisecondsFromLastTime(); if (blob_gc_info.empty()) { - LOG_FMT_INFO(log, "GC finished without any entry need be moved. [total time(ms)={}]" - " [dump snapshots(ms)={}] [in mem entries(ms)={}]" - " [blobstore remove entries(ms)={}] [blobstore get status(ms)={}]" - " [get entries(ms)={}]", - gc_watch.elapsedMilliseconds(), - dump_snapshots_ms, - gc_in_mem_entries_ms, - blobstore_remove_entries_ms, - blobstore_get_gc_stats_ms, - gc_get_entries_ms); - - clean_external_page(); - return false; + cleanExternalPage(gc_watch, statistics); + statistics.stage = GCStageType::FullGCNothingMoved; + statistics.total_cost_ms = gc_watch.elapsedMilliseconds(); + return statistics; } // 5. Do the BlobStore GC // After BlobStore GC, these entries will be migrated to a new blob. // Then we should notify MVCC apply the change. PageEntriesEdit gc_edit = blob_store.gc(blob_gc_info, total_page_size, write_limiter, read_limiter); - const auto blobstore_full_gc_ms = gc_watch.elapsedMillisecondsFromLastTime(); - if (gc_edit.empty()) - { - throw Exception("Something wrong after BlobStore GC.", ErrorCodes::LOGICAL_ERROR); - } + statistics.full_gc_blobstore_copy_ms = gc_watch.elapsedMillisecondsFromLastTime(); + RUNTIME_CHECK_MSG(!gc_edit.empty(), "Something wrong after BlobStore GC"); // 6. MVCC gc apply // MVCC will apply the migrated entries. @@ -377,24 +431,12 @@ bool PageStorageImpl::gcImpl(bool /*not_skip*/, const WriteLimiterPtr & write_li // will be remained as "read-only" files while entries in them are useless in actual. // Those BlobFiles should be cleaned during next restore. page_directory->gcApply(std::move(gc_edit), write_limiter); - const auto gc_apply_ms = gc_watch.elapsedMillisecondsFromLastTime(); - LOG_FMT_INFO(log, "GC finished. [total time(ms)={}]" - " [dump snapshots(ms)={}] [gc in mem entries(ms)={}]" - " [blobstore remove entries(ms)={}] [blobstore get status(ms)={}]" - " [get gc entries(ms)={}] [blobstore full gc(ms)={}]" - " [gc apply(ms)={}]", - gc_watch.elapsedMilliseconds(), - dump_snapshots_ms, - gc_in_mem_entries_ms, - blobstore_remove_entries_ms, - blobstore_get_gc_stats_ms, - gc_get_entries_ms, - blobstore_full_gc_ms, - gc_apply_ms); - - clean_external_page(); - - return true; + statistics.full_gc_apply_ms = gc_watch.elapsedMillisecondsFromLastTime(); + + cleanExternalPage(gc_watch, statistics); + statistics.stage = GCStageType::FullGC; + statistics.total_cost_ms = gc_watch.elapsedMilliseconds(); + return statistics; } void PageStorageImpl::registerExternalPagesCallbacks(const ExternalPageCallbacks & callbacks) @@ -409,8 +451,12 @@ void PageStorageImpl::registerExternalPagesCallbacks(const ExternalPageCallbacks void PageStorageImpl::unregisterExternalPagesCallbacks(NamespaceId ns_id) { - std::scoped_lock lock{callbacks_mutex}; - callbacks_container.erase(ns_id); + { + std::scoped_lock lock{callbacks_mutex}; + callbacks_container.erase(ns_id); + } + // clean all external ids ptrs + page_directory->unregisterNamespace(ns_id); } const String PageStorageImpl::manifests_file_name = "manifests"; diff --git a/dbms/src/Storages/Page/V3/PageStorageImpl.h b/dbms/src/Storages/Page/V3/PageStorageImpl.h index a1165d0e9b2..9bce2e5dde8 100644 --- a/dbms/src/Storages/Page/V3/PageStorageImpl.h +++ b/dbms/src/Storages/Page/V3/PageStorageImpl.h @@ -108,12 +108,14 @@ class PageStorageImpl : public DB::PageStorage #ifndef NDEBUG // Just for tests, refactor them out later + // clang-format off DB::PageStorage::SnapshotPtr getSnapshot() { return getSnapshot(""); } DB::PageEntry getEntry(PageId page_id) { return getEntryImpl(TEST_NAMESPACE_ID, page_id, nullptr); } DB::Page read(PageId page_id) { return readImpl(TEST_NAMESPACE_ID, page_id, nullptr, nullptr, true); } PageMap read(const PageIds & page_ids) { return readImpl(TEST_NAMESPACE_ID, page_ids, nullptr, nullptr, true); } PageIds read(const PageIds & page_ids, const PageHandler & handler) { return readImpl(TEST_NAMESPACE_ID, page_ids, handler, nullptr, nullptr, true); } PageMap read(const std::vector & page_fields) { return readImpl(TEST_NAMESPACE_ID, page_fields, nullptr, nullptr, true); } + // clang-format on #endif friend class PageDirectoryFactory; @@ -121,6 +123,44 @@ class PageStorageImpl : public DB::PageStorage #ifndef DBMS_PUBLIC_GTEST private: #endif + + enum class GCStageType + { + Unknown, + OnlyInMem, + FullGCNothingMoved, + FullGC, + }; + struct GCTimeStatistics + { + GCStageType stage = GCStageType::Unknown; + bool executeNextImmediately() const { return stage == GCStageType::FullGC; }; + + UInt64 total_cost_ms = 0; + + UInt64 dump_snapshots_ms = 0; + UInt64 gc_in_mem_entries_ms = 0; + UInt64 blobstore_remove_entries_ms = 0; + UInt64 blobstore_get_gc_stats_ms = 0; + // Full GC + UInt64 full_gc_get_entries_ms = 0; + UInt64 full_gc_blobstore_copy_ms = 0; + UInt64 full_gc_apply_ms = 0; + + // GC external page + UInt64 clean_external_page_ms = 0; + UInt64 num_external_callbacks = 0; + // ms is usually too big for these operation, store by ns (10^-9) + UInt64 external_page_scan_ns = 0; + UInt64 external_page_get_alive_ns = 0; + UInt64 external_page_remove_ns = 0; + + String toLogging() const; + }; + + GCTimeStatistics doGC(const WriteLimiterPtr & write_limiter, const ReadLimiterPtr & read_limiter); + void cleanExternalPage(Stopwatch & gc_watch, GCTimeStatistics & statistics); + LoggerPtr log; PageDirectoryPtr page_directory; diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp index 12e03c9ceb6..0cfed69c0f3 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_directory.cpp @@ -14,12 +14,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -33,12 +35,54 @@ #include #include +<<<<<<< HEAD +======= +#include +#include +>>>>>>> 6262e0a34d (PageStorage: Log down the gc time for external pages (#5739)) #include namespace DB { namespace PS::V3::tests { +TEST(ExternalIdsByNamespace, Simple) +{ + NamespaceId ns_id = 100; + ExternalIdsByNamespace external_ids_by_ns; + + std::atomic who(0); + + std::shared_ptr holder = std::make_shared(buildV3Id(ns_id, 50)); + + auto th_insert = std::async([&]() { + external_ids_by_ns.addExternalId(holder); + + Int32 expect = 0; + who.compare_exchange_strong(expect, 1); + }); + auto th_get_alive = std::async([&]() { + external_ids_by_ns.getAliveIds(ns_id); + Int32 expect = 0; + who.compare_exchange_strong(expect, 2); + }); + th_get_alive.wait(); + th_insert.wait(); + + { + auto ids = external_ids_by_ns.getAliveIds(ns_id); + LOG_DEBUG(&Poco::Logger::root(), "{} end first, size={}", who.load(), ids.size()); + ASSERT_EQ(ids.size(), 1); + ASSERT_EQ(*ids.begin(), 50); + } + + { + external_ids_by_ns.unregisterNamespace(ns_id); + auto ids = external_ids_by_ns.getAliveIds(ns_id); + ASSERT_EQ(ids.size(), 0); + } +} + class PageDirectoryTest : public DB::base::TiFlashStorageTestBasic { public: diff --git a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp index e019626197e..0ccdd1d9b2c 100644 --- a/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp +++ b/dbms/src/Storages/Page/V3/tests/gtest_page_storage.cpp @@ -1157,16 +1157,37 @@ try size_t times_remover_called = 0; + enum + { + STAGE_SNAP_KEEP = 1, + STAGE_SNAP_RELEASED = 2, + } test_stage; + test_stage = STAGE_SNAP_KEEP; + ExternalPageCallbacks callbacks; callbacks.scanner = []() -> ExternalPageCallbacks::PathAndIdsVec { return {}; }; - callbacks.remover = [×_remover_called](const ExternalPageCallbacks::PathAndIdsVec &, const std::set & living_page_ids) -> void { + callbacks.remover = [×_remover_called, &test_stage](const ExternalPageCallbacks::PathAndIdsVec &, const std::set & living_page_ids) -> void { times_remover_called += 1; - // 0, 1024 are still alive - EXPECT_EQ(living_page_ids.size(), 2); - EXPECT_GT(living_page_ids.count(0), 0); - EXPECT_GT(living_page_ids.count(1024), 0); + switch (test_stage) + { + case STAGE_SNAP_KEEP: + { + // 0, 1024 are still alive + EXPECT_EQ(living_page_ids.size(), 2); + EXPECT_GT(living_page_ids.count(0), 0); + EXPECT_GT(living_page_ids.count(1024), 0); + break; + } + case STAGE_SNAP_RELEASED: + { + /// After `snapshot` released, 1024 should be removed from `living` + EXPECT_EQ(living_page_ids.size(), 1); + EXPECT_GT(living_page_ids.count(0), 0); + break; + } + } }; callbacks.ns_id = TEST_NAMESPACE_ID; page_storage->registerExternalPagesCallbacks(callbacks); @@ -1208,13 +1229,7 @@ try /// After `snapshot` released, 1024 should be removed from `living` snapshot.reset(); - callbacks.remover = [×_remover_called](const ExternalPageCallbacks::PathAndIdsVec &, const std::set & living_page_ids) -> void { - times_remover_called += 1; - EXPECT_EQ(living_page_ids.size(), 1); - EXPECT_GT(living_page_ids.count(0), 0); - }; - page_storage->unregisterExternalPagesCallbacks(callbacks.ns_id); - page_storage->registerExternalPagesCallbacks(callbacks); + test_stage = STAGE_SNAP_RELEASED; { SCOPED_TRACE("gc with snapshot released"); page_storage->gc();