From 717c878e8ac0ad390be828a318cafadcad155773 Mon Sep 17 00:00:00 2001 From: Alexander Shalamov Date: Thu, 12 Sep 2019 21:38:07 +0300 Subject: [core] Calculate size of an ambient cache without offline resources --- .../include/mbgl/storage/offline_database.hpp | 37 +++++- .../default/src/mbgl/storage/offline_database.cpp | 135 +++++++++++++++++---- 2 files changed, 147 insertions(+), 25 deletions(-) diff --git a/platform/default/include/mbgl/storage/offline_database.hpp b/platform/default/include/mbgl/storage/offline_database.hpp index 62a82f3a25..7eb546b3f6 100644 --- a/platform/default/include/mbgl/storage/offline_database.hpp +++ b/platform/default/include/mbgl/storage/offline_database.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -36,7 +35,7 @@ struct MapboxTileLimitExceededException : util::Exception { MapboxTileLimitExceededException() : util::Exception("Mapbox tile limit exceeded") {} }; -class OfflineDatabase : private util::noncopyable { +class OfflineDatabase { public: OfflineDatabase(std::string path); ~OfflineDatabase(); @@ -97,6 +96,8 @@ public: void reopenDatabaseReadOnly(bool readOnly); private: + class DatabaseSizeChangeStats; + void initialize(); void handleError(const mapbox::sqlite::Exception&, const char* action); void handleError(const util::IOException&, const char* action); @@ -150,7 +151,37 @@ private: optional offlineMapboxTileCount; - bool evict(uint64_t neededFreeSize); + bool evict(uint64_t neededFreeSize, DatabaseSizeChangeStats& stats); + + class DatabaseSizeChangeStats { + public: + explicit DatabaseSizeChangeStats(OfflineDatabase*); + + // Returns difference between current database size and + // database size at the time of creation of this object. + int64_t diff() const; + + // Returns how many bytes were released comparing to a database + // size at the time of creation of this object. + uint64_t bytesReleased() const; + + // Returns page size for the database. + uint64_t pageSize() const; + + private: + uint64_t pageSize_ = 0u; + uint64_t pageCount_ = 0u; + uint64_t initialSize_ = 0u; + OfflineDatabase* db = nullptr; + }; + + friend class DatabaseSizeChangeStats; + + // Lazily initializes currentAmbientCacheSize. + std::exception_ptr initAmbientCacheSize(); + optional currentAmbientCacheSize; + void updateAmbientCacheSize(DatabaseSizeChangeStats&); + bool autopack = true; bool readOnly = false; }; diff --git a/platform/default/src/mbgl/storage/offline_database.cpp b/platform/default/src/mbgl/storage/offline_database.cpp index 643818fc99..912b323e70 100644 --- a/platform/default/src/mbgl/storage/offline_database.cpp +++ b/platform/default/src/mbgl/storage/offline_database.cpp @@ -10,7 +10,6 @@ #include #include - namespace mbgl { OfflineDatabase::OfflineDatabase(std::string path_) @@ -311,9 +310,13 @@ std::pair OfflineDatabase::putInternal(const Resource& resource, size = compressed ? compressedData.size() : response.data->size(); } - if (evict_ && !evict(size)) { - Log::Info(Event::Database, "Unable to make space for entry"); - return { false, 0 }; + optional stats; + if (evict_) { + stats = DatabaseSizeChangeStats(this); + if (!evict(size, *stats)) { + Log::Info(Event::Database, "Unable to make space for entry"); + return {false, 0}; + } } bool inserted; @@ -329,6 +332,10 @@ std::pair OfflineDatabase::putInternal(const Resource& resource, compressed); } + if (stats) { + updateAmbientCacheSize(*stats); + } + return { inserted, size }; } @@ -943,9 +950,11 @@ std::exception_ptr OfflineDatabase::deleteRegion(OfflineRegion&& region) try { query.run(); } - evict(0); + DatabaseSizeChangeStats stats(this); + evict(0, stats); assert(db); if (autopack) vacuum(); + updateAmbientCacheSize(stats); // Ensure that the cached offlineTileCount value is recalculated. offlineMapboxTileCount = nullopt; @@ -1207,19 +1216,13 @@ T OfflineDatabase::getPragma(const char* sql) { // and as it approaches to the hard limit (i.e. the actual file size) we // delete an arbitrary number of old cache entries. The free pages approach saves // us from calling VACUUM or keeping a running total, which can be costly. -bool OfflineDatabase::evict(uint64_t neededFreeSize) { +bool OfflineDatabase::evict(uint64_t neededFreeSize, DatabaseSizeChangeStats& stats) { checkFlags(); + uint64_t ambientCacheSize = + (initAmbientCacheSize() == nullptr) ? *currentAmbientCacheSize : maximumAmbientCacheSize; + uint64_t newAmbientCacheSize = ambientCacheSize + neededFreeSize + stats.pageSize(); - uint64_t pageSize = getPragma("PRAGMA page_size"); - uint64_t pageCount = getPragma("PRAGMA page_count"); - - auto usedSize = [&] { - return pageSize * (pageCount - getPragma("PRAGMA freelist_count")); - }; - - // The addition of pageSize is a fudge factor to account for non `data` column - // size, and because pages can get fragmented on the database. - while (usedSize() + neededFreeSize + pageSize > maximumAmbientCacheSize) { + while (newAmbientCacheSize > maximumAmbientCacheSize) { // clang-format off mapbox::sqlite::Query accessedQuery{ getStatement( "SELECT max(accessed) " @@ -1275,9 +1278,11 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) { tileQuery.run(); const uint64_t tileChanges = tileQuery.changes(); + // Update current ambient cache size, based on how many bytes were released. + newAmbientCacheSize = std::max(newAmbientCacheSize - stats.bytesReleased(), 0u); + // The cached value of offlineTileCount does not need to be updated // here because only non-offline tiles can be removed by eviction. - if (resourceChanges == 0 && tileChanges == 0) { return false; } @@ -1286,18 +1291,75 @@ bool OfflineDatabase::evict(uint64_t neededFreeSize) { return true; } +std::exception_ptr OfflineDatabase::initAmbientCacheSize() { + if (!currentAmbientCacheSize) { + try { + // clang-format off + mapbox::sqlite::Query query{ getStatement( + "SELECT SUM(data) " + "FROM ( " + " SELECT SUM(IFNULL(LENGTH(data), 0) " + " + IFNULL(LENGTH(id), 0) " + " + IFNULL(LENGTH(url_template), 0) " + " + IFNULL(LENGTH(pixel_ratio), 0) " + " + IFNULL(LENGTH(x), 0) " + " + IFNULL(LENGTH(y), 0) " + " + IFNULL(LENGTH(z), 0) " + " + IFNULL(LENGTH(expires), 0) " + " + IFNULL(LENGTH(modified), 0) " + " + IFNULL(LENGTH(etag), 0) " + " + IFNULL(LENGTH(compressed), 0) " + " + IFNULL(LENGTH(accessed), 0) " + " + IFNULL(LENGTH(must_revalidate), 0) " + " ) as data " + " FROM tiles " + " LEFT JOIN region_tiles " + " ON tile_id = tiles.id " + " WHERE tile_id IS NULL " + " UNION ALL " + " SELECT SUM(IFNULL(LENGTH(data), 0) " + " + IFNULL(LENGTH(id), 0) " + " + IFNULL(LENGTH(url), 0) " + " + IFNULL(LENGTH(kind), 0) " + " + IFNULL(LENGTH(expires), 0) " + " + IFNULL(LENGTH(modified), 0) " + " + IFNULL(LENGTH(etag), 0) " + " + IFNULL(LENGTH(compressed), 0) " + " + IFNULL(LENGTH(accessed), 0) " + " + IFNULL(LENGTH(must_revalidate), 0) " + " ) as data " + " FROM resources " + " LEFT JOIN region_resources " + " ON resource_id = resources.id " + " WHERE resource_id IS NULL " + ") ") }; + // clang-format on + query.run(); + currentAmbientCacheSize = query.get(0); + } catch (const mapbox::sqlite::Exception& ex) { + handleError(ex, "cannot get current ambient cache size"); + return std::current_exception(); + } + } + + return nullptr; +} + std::exception_ptr OfflineDatabase::setMaximumAmbientCacheSize(uint64_t size) { uint64_t previousMaximumAmbientCacheSize = maximumAmbientCacheSize; + if (auto exception = initAmbientCacheSize()) { + return exception; + } + try { maximumAmbientCacheSize = size; - uint64_t databaseSize = getPragma("PRAGMA page_size") - * getPragma("PRAGMA page_count"); - - if (databaseSize > maximumAmbientCacheSize) { - evict(0); + if (*currentAmbientCacheSize > maximumAmbientCacheSize) { + DatabaseSizeChangeStats stats(this); + evict(0, stats); if (autopack) vacuum(); + updateAmbientCacheSize(stats); } return nullptr; @@ -1395,4 +1457,33 @@ void OfflineDatabase::reopenDatabaseReadOnly(bool readOnly_) { } } +OfflineDatabase::DatabaseSizeChangeStats::DatabaseSizeChangeStats(OfflineDatabase* db_) : db(db_) { + assert(db); + pageSize_ = db->getPragma("PRAGMA page_size"); + pageCount_ = db->getPragma("PRAGMA page_count"); + initialSize_ = pageSize_ * (pageCount_ - db->getPragma("PRAGMA freelist_count")); +} + +uint64_t OfflineDatabase::DatabaseSizeChangeStats::pageSize() const { + return pageSize_; +} + +int64_t OfflineDatabase::DatabaseSizeChangeStats::diff() const { + const uint64_t currentSize = + pageSize_ * (db->getPragma("PRAGMA page_count") - db->getPragma("PRAGMA freelist_count")); + return currentSize - initialSize_; +} + +uint64_t OfflineDatabase::DatabaseSizeChangeStats::bytesReleased() const { + uint64_t currentSize = pageSize_ * (pageCount_ - db->getPragma("PRAGMA freelist_count")); + return std::max(initialSize_ - currentSize, 0u); +} + +void OfflineDatabase::updateAmbientCacheSize(DatabaseSizeChangeStats& stats) { + assert(currentAmbientCacheSize); + if (currentAmbientCacheSize) { + *currentAmbientCacheSize = std::max(*currentAmbientCacheSize + stats.diff(), 0u); + } +} + } // namespace mbgl -- cgit v1.2.1