]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
lmdb: allow domain notification timestamps to be kept in memory only.
authorMiod Vallat <miod.vallat@powerdns.com>
Mon, 15 Sep 2025 13:56:20 +0000 (15:56 +0200)
committerMiod Vallat <miod.vallat@powerdns.com>
Fri, 19 Sep 2025 10:40:21 +0000 (12:40 +0200)
They will get synchronized on disk only when another DomainInfo field
gets modified.

Signed-off-by: Miod Vallat <miod.vallat@powerdns.com>
docs/backends/lmdb.rst
modules/lmdbbackend/lmdbbackend.cc
modules/lmdbbackend/lmdbbackend.hh

index 2f9702747a54a0ed342604346317c58a4e5fe194..0f12e2b7c9ecb734ef5fa84214b7cb1ef341bc4a 100644 (file)
@@ -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 <https://doc.powerdns.com/lightningstream>`_ 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``
 ^^^^^^^^^^^^^^^^^^^^^^^^^
 
index 73e292fe68146d93984c3aed2bbacc3889ae0613..1961dc7e9a6622b7648116ab3e22a8dbc451cf36 100644 (file)
@@ -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::SerialCache> 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;
@@ -1185,7 +1211,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);
@@ -1196,7 +1222,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)) {
@@ -1206,6 +1232,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.
 
@@ -1798,6 +1850,10 @@ bool LMDBBackend::deleteDomain(const ZoneName& domain)
     commitTransaction();
 
     // Remove zone
+    {
+      auto container = s_notified_serial.write_lock();
+      container->remove(static_cast<domainid_t>(id));
+    }
     auto txn = d_tdomains->getRWTransaction();
     txn.del(id);
     txn.commit();
@@ -2089,6 +2145,7 @@ bool LMDBBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool g
     return false;
   }
   info.backend = this;
+  consolidateDomainInfo(info);
 
   if (getserial) {
     getSerial(info);
@@ -2103,10 +2160,9 @@ bool LMDBBackend::genChangeDomain(const ZoneName& domain, const std::function<vo
   if (!findDomain(domain, info)) {
     return false;
   }
+  consolidateDomainInfo(info);
   func(info);
-  auto txn = d_tdomains->getRWTransaction();
-  txn.put(info, info.id);
-  txn.commit();
+  writeDomainInfo(info);
   return true;
 }
 
@@ -2117,10 +2173,9 @@ bool LMDBBackend::genChangeDomain(domainid_t id, const std::function<void(Domain
   if (!findDomain(id, info)) {
     return false;
   }
+  consolidateDomainInfo(info);
   func(info);
-  auto txn = d_tdomains->getRWTransaction();
-  txn.put(info, id);
-  txn.commit();
+  writeDomainInfo(info);
   return true;
 }
 
@@ -2196,6 +2251,7 @@ void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::
 
     for (auto& [k, v] : zonemap) {
       if (allow(v)) {
+        consolidateDomainInfo(v);
         domains->push_back(std::move(v));
       }
     }
@@ -2207,6 +2263,7 @@ void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::
       di.backend = this;
 
       if (allow(di)) {
+        consolidateDomainInfo(di);
         domains->push_back(di);
       }
     }
@@ -2305,9 +2362,19 @@ void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& 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
@@ -3358,6 +3425,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
index 740a323df817e49d91dd97835729988dcd2270a0..3c3c26315869005051c7bdb7516aee24a423b657 100644 (file)
@@ -339,8 +339,10 @@ private:
   bool genChangeDomain(domainid_t id, const std::function<void(DomainInfo&)>& func);
   static void deleteDomainRecords(RecordsRWTransaction& txn, const std::string& match, QType qtype = QType::ANY);
 
-  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<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow);
 
@@ -359,6 +361,19 @@ private:
 
   string directBackendCmd_list(std::vector<string>& argv);
 
+  // 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<domainid_t, uint32_t> d_serials;
+  };
+  static SharedLockGuarded<SerialCache> s_notified_serial;
+
   ZoneName d_lookupdomain;
   DNSName d_lookupsubmatch;
   vector<LMDBResourceRecord> d_currentrrset;
@@ -374,6 +389,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;
 };