From: Miod Vallat Date: Mon, 15 Sep 2025 13:56:20 +0000 (+0200) Subject: lmdb: allow domain notification timestamps to be kept in memory only. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6ad62703cd1301ff6cb9b658b5ae91e94cfba575;p=thirdparty%2Fpdns.git lmdb: allow domain notification timestamps to be kept in memory only. They will get synchronized on disk only when another DomainInfo field gets modified. Signed-off-by: Miod Vallat (cherry picked from commit 51d5dbc558e36f3bb6d487c5c46737e58e2f4b19) --- diff --git a/docs/backends/lmdb.rst b/docs/backends/lmdb.rst index 2f9702747..0f12e2b7c 100644 --- a/docs/backends/lmdb.rst +++ b/docs/backends/lmdb.rst @@ -133,6 +133,21 @@ Defaults to 100 on 32 bit systems, and 16000 on 64 bit systems. Instead of deleting items from the database, flag them as deleted in the item's `Lightning Stream `_ header. Only enable this if you are using Lightning Stream. +``lmdb-skip-notification-update`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. versionadded:: 5.1.0 + +- Boolean +- Default: no + +Do not update the domains table in the database when the last notification +timestamp is modified. These timestamps will only be written back to the +database when other changes to the domain (such as accounts) occur. + +**Warning**: Running with this flag enabled will cause spurious notifications +to be sent upon startup. + ``lmdb-lightning-stream`` ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/modules/lmdbbackend/lmdbbackend.cc b/modules/lmdbbackend/lmdbbackend.cc index d4d8468f9..376aa1414 100644 --- a/modules/lmdbbackend/lmdbbackend.cc +++ b/modules/lmdbbackend/lmdbbackend.cc @@ -668,6 +668,30 @@ bool LMDBBackend::upgradeToSchemav6(std::string& /* filename */) return true; } +// Serial number cache +bool LMDBBackend::SerialCache::get(domainid_t domainid, uint32_t& serial) const +{ + if (auto iter = d_serials.find(domainid); iter != d_serials.end()) { + serial = iter->second; + return true; + } + return false; +} + +void LMDBBackend::SerialCache::remove(domainid_t domainid) +{ + if (auto iter = d_serials.find(domainid); iter != d_serials.end()) { + d_serials.erase(iter); + } +} + +void LMDBBackend::SerialCache::update(domainid_t domainid, uint32_t serial) +{ + d_serials.insert_or_assign(domainid, serial); +} + +SharedLockGuarded LMDBBackend::s_notified_serial; + LMDBBackend::LMDBBackend(const std::string& suffix) { // overlapping domain ids in combination with relative names are a recipe for disaster @@ -698,6 +722,8 @@ LMDBBackend::LMDBBackend(const std::string& suffix) throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what()); } + d_skip_notification_update = mustDo("skip-notification-update"); + if (mustDo("lightning-stream")) { d_random_ids = true; d_handle_dups = true; @@ -1120,7 +1146,7 @@ void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, const std::stri } } -bool LMDBBackend::findDomain(const ZoneName& domain, DomainInfo& info) +bool LMDBBackend::findDomain(const ZoneName& domain, DomainInfo& info) const { auto rotxn = d_tdomains->getROTransaction(); auto domain_id = rotxn.get<0>(domain, info); @@ -1131,7 +1157,7 @@ bool LMDBBackend::findDomain(const ZoneName& domain, DomainInfo& info) return true; } -bool LMDBBackend::findDomain(domainid_t domainid, DomainInfo& info) +bool LMDBBackend::findDomain(domainid_t domainid, DomainInfo& info) const { auto rotxn = d_tdomains->getROTransaction(); if (!rotxn.get(domainid, info)) { @@ -1141,6 +1167,32 @@ bool LMDBBackend::findDomain(domainid_t domainid, DomainInfo& info) return true; } +void LMDBBackend::consolidateDomainInfo(DomainInfo& info) const +{ + // Update the notified_serial value if we have a cached value in memory. + if (d_skip_notification_update) { + auto container = s_notified_serial.read_lock(); + container->get(info.id, info.notified_serial); + } +} + +void LMDBBackend::writeDomainInfo(const DomainInfo& info) +{ + if (d_skip_notification_update) { + uint32_t last_notified_serial{0}; + auto container = s_notified_serial.write_lock(); + container->get(info.id, last_notified_serial); + // Only remove the in-memory value if it has not been modified since the + // DomainInfo data was set up. + if (last_notified_serial == info.notified_serial) { + container->remove(info.id); + } + } + auto txn = d_tdomains->getRWTransaction(); + txn.put(info, info.id); + txn.commit(); +} + /* Here's the complicated story. Other backends have just one transaction, which is either on or not. @@ -1752,6 +1804,10 @@ bool LMDBBackend::deleteDomain(const ZoneName& domain) commitTransaction(); // Remove zone + { + auto container = s_notified_serial.write_lock(); + container->remove(static_cast(id)); + } auto txn = d_tdomains->getRWTransaction(); txn.del(id); txn.commit(); @@ -2023,6 +2079,7 @@ bool LMDBBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool g return false; } info.backend = this; + consolidateDomainInfo(info); if (getserial) { getSerial(info); @@ -2037,10 +2094,9 @@ int LMDBBackend::genChangeDomain(const ZoneName& domain, const std::function(false); } + consolidateDomainInfo(info); func(info); - auto txn = d_tdomains->getRWTransaction(); - txn.put(info, info.id); - txn.commit(); + writeDomainInfo(info); return true; } @@ -2051,10 +2107,9 @@ int LMDBBackend::genChangeDomain(domainid_t id, const std::functiongetRWTransaction(); - txn.put(info, id); - txn.commit(); + writeDomainInfo(info); return true; } @@ -2130,6 +2185,7 @@ void LMDBBackend::getAllDomainsFiltered(vector* domains, const std:: for (auto& [k, v] : zonemap) { if (allow(v)) { + consolidateDomainInfo(v); domains->push_back(std::move(v)); } } @@ -2141,6 +2197,7 @@ void LMDBBackend::getAllDomainsFiltered(vector* domains, const std:: di.backend = this; if (allow(di)) { + consolidateDomainInfo(di); domains->push_back(di); } } @@ -2239,9 +2296,19 @@ void LMDBBackend::getUpdatedPrimaries(vector& updatedDomains, std::u void LMDBBackend::setNotified(domainid_t domain_id, uint32_t serial) { - genChangeDomain(domain_id, [serial](DomainInfo& info) { - info.notified_serial = serial; - }); + if (!d_skip_notification_update) { + genChangeDomain(domain_id, [serial](DomainInfo& info) { + info.notified_serial = serial; + }); + return; + } + + DomainInfo info; + if (findDomain(domain_id, info)) { + auto container = s_notified_serial.write_lock(); + container->update(info.id, serial); + } + // else throw something? this should be a "can't happen" situation. } class getCatalogMembersReturnFalseException : std::runtime_error @@ -3181,6 +3248,7 @@ public: declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no"); declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000"); declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no"); + declare(suffix, "skip-notification-update", "Do not update domain table upon notification", "no"); declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no"); } DNSBackend* make(const string& suffix = "") override diff --git a/modules/lmdbbackend/lmdbbackend.hh b/modules/lmdbbackend/lmdbbackend.hh index e40f5cf77..01addb7cb 100644 --- a/modules/lmdbbackend/lmdbbackend.hh +++ b/modules/lmdbbackend/lmdbbackend.hh @@ -337,8 +337,10 @@ private: int genChangeDomain(domainid_t id, const std::function& func); static void deleteDomainRecords(RecordsRWTransaction& txn, const std::string& match); - bool findDomain(const ZoneName& domain, DomainInfo& info); - bool findDomain(domainid_t domainid, DomainInfo& info); + bool findDomain(const ZoneName& domain, DomainInfo& info) const; + bool findDomain(domainid_t domainid, DomainInfo& info) const; + void consolidateDomainInfo(DomainInfo& info) const; + void writeDomainInfo(const DomainInfo& info); void getAllDomainsFiltered(vector* domains, const std::function& allow); @@ -354,6 +356,19 @@ private: static void deleteNSEC3RecordPair(const std::shared_ptr& txn, domainid_t domain_id, const DNSName& qname); void writeNSEC3RecordPair(const std::shared_ptr& txn, domainid_t domain_id, const DNSName& qname, const DNSName& ordername); + // Cache of DomainInfo notified_serial values + class SerialCache : public boost::noncopyable + { + public: + bool get(domainid_t domainid, uint32_t& serial) const; + void remove(domainid_t domainid); + void update(domainid_t domainid, uint32_t serial); + + private: + std::unordered_map d_serials; + }; + static SharedLockGuarded s_notified_serial; + ZoneName d_lookupdomain; DNSName d_lookupsubmatch; vector d_currentrrset; @@ -369,6 +384,7 @@ private: bool d_random_ids; bool d_handle_dups; bool d_views; + bool d_skip_notification_update; DTime d_dtime; // used only for logging uint64_t d_mapsize; };