From: Miod Vallat Date: Fri, 26 Sep 2025 09:18:02 +0000 (+0200) Subject: Add a pdns_control flush command, to instruct backends to write in-flight data. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=636cb1d7f103c4bb3c67a946aff7095e071ba73e;p=thirdparty%2Fpdns.git Add a pdns_control flush command, to instruct backends to write in-flight data. Signed-off-by: Miod Vallat --- diff --git a/docs/backends/lmdb.rst b/docs/backends/lmdb.rst index ad5c3818f..18741ad16 100644 --- a/docs/backends/lmdb.rst +++ b/docs/backends/lmdb.rst @@ -150,7 +150,9 @@ other changes to the domain (such as accounts) occur. This setting is also available in version 4.9.9. **Warning**: Running with this flag disabled will cause spurious notifications -to be sent upon startup. +to be sent upon startup, unless a ``flush'' command is sent using +:doc:`pdns_control <../manpages/pdns_control.1>` before stopping the +PowerDNS Authoritative Server. ``lmdb-lightning-stream`` ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/manpages/pdns_control.1.rst b/docs/manpages/pdns_control.1.rst index 0ffc838b5..f4ab9a9d7 100644 --- a/docs/manpages/pdns_control.1.rst +++ b/docs/manpages/pdns_control.1.rst @@ -74,6 +74,13 @@ cycle Restart the nameserver so it reloads its configuration. Only works when the server is running in guardian mode. +flush +^^^^^ + +Flush all backend data to stable storage. +There is usually no such data during regular operation, unless the LMDB backend +is used with :ref:`setting-lmdb-write-notification-update` set to ``no``. + list ^^^^ diff --git a/modules/lmdbbackend/lmdbbackend.cc b/modules/lmdbbackend/lmdbbackend.cc index 44041b25d..270ba1778 100644 --- a/modules/lmdbbackend/lmdbbackend.cc +++ b/modules/lmdbbackend/lmdbbackend.cc @@ -669,6 +669,8 @@ bool LMDBBackend::upgradeToSchemav6(std::string& /* filename */) } // Serial number cache + +// Retrieve the serial number entry for the given domain, if any bool LMDBBackend::SerialCache::get(domainid_t domainid, uint32_t& serial) const { if (auto iter = d_serials.find(domainid); iter != d_serials.end()) { @@ -678,6 +680,7 @@ bool LMDBBackend::SerialCache::get(domainid_t domainid, uint32_t& serial) const return false; } +// Remove the serial number entry for the given domain void LMDBBackend::SerialCache::remove(domainid_t domainid) { if (auto iter = d_serials.find(domainid); iter != d_serials.end()) { @@ -685,11 +688,25 @@ void LMDBBackend::SerialCache::remove(domainid_t domainid) } } +// Create or update the serial number entry for the given domain void LMDBBackend::SerialCache::update(domainid_t domainid, uint32_t serial) { d_serials.insert_or_assign(domainid, serial); } +// Return the contents of the first element and remove it +bool LMDBBackend::SerialCache::pop(domainid_t& domainid, uint32_t& serial) +{ + auto iter = d_serials.begin(); + if (iter == d_serials.end()) { + return false; + } + domainid = iter->first; + serial = iter->second; + (void)d_serials.erase(iter); + return true; +} + SharedLockGuarded LMDBBackend::s_notified_serial; LMDBBackend::LMDBBackend(const std::string& suffix) @@ -3392,6 +3409,42 @@ void LMDBBackend::rectifyZoneHook(domainid_t domain_id, bool before) const LMDBBackend::deleteDomainRecords(*d_rwtxn, order(domain_id), QType::NSEC3); } +void LMDBBackend::flush() +{ + if (d_write_notification_update) { + return; // no data needs to be synchronized + } + + // We flush in chunks of 10 domains, in order not to keep the serial number + // cache locked for too long. + while (true) { + unsigned int done = 0; + auto container = s_notified_serial.write_lock(); + for (; done < 10; ++done) { + domainid_t domid{}; + uint32_t serial{}; + if (!container->pop(domid, serial)) { + break; + } + DomainInfo info; + if (findDomain(domid, info)) { + info.notified_serial = serial; + auto txn = d_tdomains->getRWTransaction(); + txn.put(info, info.id); + txn.commit(); + } + else { + // Domain has been removed. This should not happen because deletion + // is supposed to take care of removing the entry here too. + // Is it worth logging something here? + } + } + if (done == 0) { + break; // no more work to do! + } + } +} + class LMDBFactory : public BackendFactory { public: diff --git a/modules/lmdbbackend/lmdbbackend.hh b/modules/lmdbbackend/lmdbbackend.hh index 14c78c7f1..7c929c713 100644 --- a/modules/lmdbbackend/lmdbbackend.hh +++ b/modules/lmdbbackend/lmdbbackend.hh @@ -166,6 +166,8 @@ public: bool updateEmptyNonTerminals(domainid_t domain_id, set& insert, set& erase, bool remove) override; + void flush() override; + // other string directBackendCmd(const string& query) override; @@ -365,6 +367,7 @@ private: bool get(domainid_t domainid, uint32_t& serial) const; void remove(domainid_t domainid); void update(domainid_t domainid, uint32_t serial); + bool pop(domainid_t& domainid, uint32_t& serial); private: std::unordered_map d_serials; diff --git a/pdns/auth-main.cc b/pdns/auth-main.cc index ce1c9f2df..e15cc8cf7 100644 --- a/pdns/auth-main.cc +++ b/pdns/auth-main.cc @@ -1456,6 +1456,7 @@ int main(int argc, char** argv) DynListener::registerExitFunc("QUIT", &DLRQuitHandler); DynListener::registerFunc("CCOUNTS", &DLCCHandler, "get cache statistics"); DynListener::registerFunc("CURRENT-CONFIG", &DLCurrentConfigHandler, "retrieve the current configuration", "[diff]"); + DynListener::registerFunc("FLUSH", &DLFlushHandler, "flush backend data"); DynListener::registerFunc("LIST-ZONES", &DLListZones, "show list of zones", "[primary|secondary|native|consumer|producer]"); DynListener::registerFunc("NOTIFY", &DLNotifyHandler, "queue a notification", ""); DynListener::registerFunc("NOTIFY-HOST", &DLNotifyHostHandler, "notify host for specific zone", " "); diff --git a/pdns/dnsbackend.hh b/pdns/dnsbackend.hh index 588795062..999559f8b 100644 --- a/pdns/dnsbackend.hh +++ b/pdns/dnsbackend.hh @@ -531,6 +531,10 @@ public: return get(rec); } + virtual void flush() + { + } + protected: bool mustDo(const string& key); const string& getArg(const string& key); diff --git a/pdns/dynhandler.cc b/pdns/dynhandler.cc index 212a59242..8403e6f73 100644 --- a/pdns/dynhandler.cc +++ b/pdns/dynhandler.cc @@ -407,6 +407,14 @@ string DLListZones(const vector& parts, Utility::pid_t /* ppid */) return ret.str(); } +string DLFlushHandler(const vector& /*parts*/, Utility::pid_t /*ppid*/) +{ + UeberBackend B; // NOLINT(readability-identifier-length) + B.flush(); + g_log<&parts, Utility::pid_t ppid); string DLCurrentConfigHandler(const vector&parts, Utility::pid_t ppid); +string DLFlushHandler(const vector&parts, Utility::pid_t ppid); string DLListZones(const vector&parts, Utility::pid_t ppid); string DLNotifyHandler(const vector&parts, Utility::pid_t ppid); string DLNotifyHostHandler(const vector&parts, Utility::pid_t ppid); diff --git a/pdns/ueberbackend.cc b/pdns/ueberbackend.cc index 064219410..e6270b190 100644 --- a/pdns/ueberbackend.cc +++ b/pdns/ueberbackend.cc @@ -1056,6 +1056,13 @@ unsigned int UeberBackend::getCapabilities() return capabilities; } +void UeberBackend::flush() +{ + for (auto& backend : backends) { + backend->flush(); + } +} + AtomicCounter UeberBackend::handle::instances(0); UeberBackend::handle::handle() diff --git a/pdns/ueberbackend.hh b/pdns/ueberbackend.hh index db0681e76..213754a6e 100644 --- a/pdns/ueberbackend.hh +++ b/pdns/ueberbackend.hh @@ -149,6 +149,8 @@ public: unsigned int getCapabilities(); + void flush(); + private: handle d_handle; vector d_answers;