From: Remi Gacogne Date: Mon, 19 Jun 2017 10:51:39 +0000 (+0200) Subject: rec: Add a NetmaskTree-based cache index for ECS entries X-Git-Tag: rec-4.1.0-alpha1~46^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e74f866a076c7f9b6ea7c308dd53ab6f0c74dd36;p=thirdparty%2Fpdns.git rec: Add a NetmaskTree-based cache index for ECS entries The main idea is not to have to go through all the netmask-specific entries for a given (qname/qtype), but to have to know quickly which netmask-specific entry is the best match. To do that we add an index containing a NetmaskTree for each (qname,qtype), and we then know quickly which entry to get from the "regular" cache. Initial benchmarking results: - inserting non-netmask-specific entries has the same performance ; - inserting netmask-specific entries is 40% slower because of the additional insertion ; - looking for a (qname/qtype) that has no netmask-specific entries remains the same ; - looking for (qname/qtype) with 65k netmask-specific entries but only matching the non-netmask one is around 2000 times faster ; - looking for (qname/qtype) with 65k netmask-specific entries and matching one is also around 2000 times faster ; - pruning the cache is a lot slower (from 11 millions/s to 1.8 millions/s) Remaining issues: - ANY queries do not use the index ; - we have to do two lookups - removal is slower, but might still be good enough - NetmaskTree.erase() does not compact the tree. Ideas that didn't seem to work out: - Storing a pointer of some kind in the NetmaskTree to save a lookup: caused issues with our generic cache management functions (moving entries to the front or to the back requires an iterator) - Keeping the NMT index in the empty Netmak entry (the non-netmask specific one) save the additional lookup when we have no ECS entries, but made cache management very awkward because we needed to keep the non-netmask specific entry around as a place holder for the ECS index even if it held no data. --- diff --git a/pdns/cachecleaner.hh b/pdns/cachecleaner.hh index 0e746fa5e0..955757876e 100644 --- a/pdns/cachecleaner.hh +++ b/pdns/cachecleaner.hh @@ -23,10 +23,10 @@ #include "lock.hh" -// this function can clean any cache that has a getTTD() method on its entries, and a 'sequence' index as its second index +// this function can clean any cache that has a getTTD() method on its entries, a preRemoval() method and a 'sequence' index as its second index // the ritual is that the oldest entries are in *front* of the sequence collection, so on a hit, move an item to the end // on a miss, move it to the beginning -template void pruneCollection(T& collection, unsigned int maxCached, unsigned int scanFraction=1000) +template void pruneCollection(C& container, T& collection, unsigned int maxCached, unsigned int scanFraction=1000) { time_t now=time(0); unsigned int toTrim=0; @@ -54,7 +54,8 @@ template void pruneCollection(T& collection, unsigned int maxCached typename sequence_t::iterator iter=sidx.begin(), eiter; for(; iter != sidx.end() && tried < lookAt ; ++tried) { - if(iter->getTTD() < now) { + if(iter->getTTD() < now) { + container.preRemoval(*iter); sidx.erase(iter++); erased++; } @@ -76,8 +77,16 @@ template void pruneCollection(T& collection, unsigned int maxCached // cout<<"Still have "< (time_t)(30)) { { WriteLock l(&s_metacachelock); - pruneCollection(s_metacache, ::arg().asNum("max-cache-entries")); + pruneCollection(*this, s_metacache, ::arg().asNum("max-cache-entries")); } { WriteLock l(&s_keycachelock); - pruneCollection(s_keycache, ::arg().asNum("max-cache-entries")); + pruneCollection(*this, s_keycache, ::arg().asNum("max-cache-entries")); } s_last_prune=time(0); } diff --git a/pdns/dnsseckeeper.hh b/pdns/dnsseckeeper.hh index 7f111ecbf7..3473a4e461 100644 --- a/pdns/dnsseckeeper.hh +++ b/pdns/dnsseckeeper.hh @@ -268,6 +268,14 @@ private: static pthread_rwlock_t s_keycachelock; static AtomicCounter s_ops; static time_t s_last_prune; + +public: + void preRemoval(const KeyCacheEntry&) + { + } + void preRemoval(const METACacheEntry&) + { + } }; class DNSPacket; diff --git a/pdns/recpacketcache.cc b/pdns/recpacketcache.cc index 6186caad14..9a1104d068 100644 --- a/pdns/recpacketcache.cc +++ b/pdns/recpacketcache.cc @@ -199,7 +199,7 @@ uint64_t RecursorPacketCache::bytes() void RecursorPacketCache::doPruneTo(unsigned int maxCached) { - pruneCollection(d_packetCache, maxCached); + pruneCollection(*this, d_packetCache, maxCached); } uint64_t RecursorPacketCache::doDump(int fd) diff --git a/pdns/recpacketcache.hh b/pdns/recpacketcache.hh index 51f4d642aa..bfa16961f2 100644 --- a/pdns/recpacketcache.hh +++ b/pdns/recpacketcache.hh @@ -103,6 +103,11 @@ private: packetCache_t d_packetCache; bool checkResponseMatches(std::pair::type::iterator, packetCache_t::index::type::iterator> range, const std::string& queryPacket, const DNSName& qname, uint16_t qtype, uint16_t qclass, time_t now, std::string* responsePacket, uint32_t* age, RecProtoBufMessage* protobufMessage); + +public: + void preRemoval(const Entry& entry) + { + } }; #endif diff --git a/pdns/recursor_cache.cc b/pdns/recursor_cache.cc index 4105055633..d64067d428 100644 --- a/pdns/recursor_cache.cc +++ b/pdns/recursor_cache.cc @@ -33,11 +33,113 @@ unsigned int MemRecursorCache::bytes() return ret; } +int32_t MemRecursorCache::handleHit(cache_t::iterator entry, const DNSName& qname, const ComboAddress& who, vector* res, vector>* signatures, std::vector>* authorityRecs, bool* variable, vState* state, bool* wasAuth) +{ + int32_t ttd = entry->d_ttd; + + if(variable && !entry->d_netmask.empty()) { + *variable = true; + } + + // cerr<<"Looking at "<d_records.size()<<" records for this name"<d_records) { + DNSRecord dr; + dr.d_name = qname; + dr.d_type = entry->d_qtype; + dr.d_class = QClass::IN; + dr.d_content = k; + dr.d_ttl = static_cast(entry->d_ttd); + dr.d_place = DNSResourceRecord::ANSWER; + res->push_back(dr); + } + } + + if(signatures) { // if you do an ANY lookup you are hosed XXXX + *signatures = entry->d_signatures; + } + + if(authorityRecs) { + *authorityRecs = entry->d_authorityRecs; + } + + if (state) { + *state = entry->d_state; + } + + if (wasAuth) { + *wasAuth = entry->d_auth; + } + + moveCacheItemToBack(d_cache, entry); + + return ttd; +} + +MemRecursorCache::cache_t::const_iterator MemRecursorCache::getEntryUsingIndex(time_t now, const DNSName &qname, uint16_t qtype, bool requireAuth, const ComboAddress& who) +{ + auto indexKey = tie(qname, qtype); + auto index = d_index.find(indexKey); + if (index != d_index.end() && !index->isEmpty()) { + /* we have netmask-specific entries, let's see if we match one */ + while (true) { + const Netmask best = index->lookupBestMatch(who); + if (best.empty()) { + /* we have nothing more specific for you */ + break; + } + + auto key = boost::make_tuple(qname, qtype, best); + auto entry = d_cache.find(key); + if (entry == d_cache.end()) { + /* index is not up-to-date */ + index->removeNetmask(best); + if (index->isEmpty()) { + d_index.erase(index); + break; + } + continue; + } + + if (entry->d_ttd > now) { + if (!requireAuth || entry->d_auth) { + return entry; + } + } + else { + /* this netmask-specific entry has expired */ + moveCacheItemToFront(d_cache, entry); + index->removeNetmask(best); + if (index->isEmpty()) { + d_index.erase(index); + break; + } + } + } + } + + /* we have nothing specific, let's see if we have a generic one */ + auto key = boost::make_tuple(qname, qtype, Netmask()); + auto entry = d_cache.find(key); + if (entry != d_cache.end()) { + if (entry->d_ttd > now) { + if (!requireAuth || entry->d_auth) { + return entry; + } + } + else { + moveCacheItemToFront(d_cache, entry); + } + } + + /* nothing for you, sorry */ + return d_cache.end(); +} + // returns -1 for no hits std::pair MemRecursorCache::getEntries(const DNSName &qname, const QType& qt) { // cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n"; - if(!d_cachecachevalid || d_cachedqname!= qname) { // cerr<<"had cache cache miss"<d_auth) return false; - return ((entry->d_qtype == qt.getCode() || qt.getCode()==QType::ANY || - (qt.getCode()==QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA))) + return ((entry->d_qtype == qt || qt == QType::ANY || + (qt == QType::ADDR && (entry->d_qtype == QType::A || entry->d_qtype == QType::AAAA))) && (entry->d_netmask.empty() || entry->d_netmask.match(who))); } @@ -64,11 +166,42 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, { time_t ttd=0; // cerr<<"looking up "<< qname<<"|"+qt.getName()<<"\n"; + if(res) { + res->clear(); + } - auto entries = getEntries(qname, qt); + const uint16_t qtype = qt.getCode(); + /* If we don't have any netmask-specific entries at all, let's just skip this + to be able to use the nice d_cachecache hack. */ + if (qtype != QType::ANY && !d_index.empty()) { + if (qtype == QType::ADDR) { + int32_t ret = -1; - if(res) - res->clear(); + auto entryA = getEntryUsingIndex(now, qname, QType::A, requireAuth, who); + if (entryA != d_cache.end()) { + ret = handleHit(entryA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth); + } + auto entryAAAA = getEntryUsingIndex(now, qname, QType::AAAA, requireAuth, who); + if (entryAAAA != d_cache.end()) { + int32_t ttdAAAA = handleHit(entryA, qname, who, res, signatures, authorityRecs, variable, state, wasAuth); + if (ret > 0) { + ret = std::min(ret, ttdAAAA); + } else { + ret = ttdAAAA; + } + } + return ret > 0 ? static_cast(ret-now) : ret; + } + else { + auto entry = getEntryUsingIndex(now, qname, qtype, requireAuth, who); + if (entry != d_cache.end()) { + return static_cast(handleHit(entry, qname, who, res, signatures, authorityRecs, variable, state, wasAuth) - now); + } + return -1; + } + } + + auto entries = getEntries(qname, qt); if(entries.first!=entries.second) { for(cache_t::const_iterator i=entries.first; i != entries.second; ++i) { @@ -78,44 +211,10 @@ int32_t MemRecursorCache::get(time_t now, const DNSName &qname, const QType& qt, continue; } - if (!entryMatches(i, qt, requireAuth, who)) + if (!entryMatches(i, qtype, requireAuth, who)) continue; - if(variable && !i->d_netmask.empty()) { - *variable=true; - } - - ttd = i->d_ttd; - - // cerr<<"Looking at "<d_records.size()<<" records for this name"<d_records.begin(); k != i->d_records.end(); ++k) { - if(res) { - DNSRecord dr; - dr.d_name = qname; - dr.d_type = i->d_qtype; - dr.d_class = QClass::IN; - dr.d_content = *k; - dr.d_ttl = static_cast(i->d_ttd); - dr.d_place = DNSResourceRecord::ANSWER; - res->push_back(dr); - } - } - - if(signatures) // if you do an ANY lookup you are hosed XXXX - *signatures=i->d_signatures; - - if(authorityRecs) // if you do an ANY lookup you are hosed here too XXXX - *authorityRecs=i->d_authorityRecs; - - moveCacheItemToBack(d_cache, i); - - if(state) { - *state = i->d_state; - } - - if(wasAuth) { - *wasAuth = i->d_auth; - } + ttd = handleHit(i, qname, who, res, signatures, authorityRecs, variable, state, wasAuth); if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done break; @@ -155,16 +254,26 @@ bool MemRecursorCache::attemptToRefreshNSTTL(const QType& qt, const vector& content, const vector>& signatures, const std::vector>& authorityRecs, bool auth, boost::optional ednsmask, vState state) +void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt, const vector& content, const vector>& signatures, const std::vector>& authorityRecs, bool auth, boost::optional ednsmask, vState state) { - d_cachecachevalid=false; - cache_t::iterator stored; + d_cachecachevalid = false; + + auto key = boost::make_tuple(qname, qt.getCode(), ednsmask ? *ednsmask : Netmask()); bool isNew = false; - auto key=boost::make_tuple(qname, qt.getCode(), ednsmask ? *ednsmask : Netmask()); - stored=d_cache.find(key); - if(stored == d_cache.end()) { - stored=d_cache.insert(CacheEntry(key,CacheEntry::records_t(), auth)).first; + cache_t::iterator stored = d_cache.find(key); + if (stored == d_cache.end()) { + stored = d_cache.insert(CacheEntry(key, CacheEntry::records_t(), auth)).first; isNew = true; + + /* don't bother building an index if we don't have any netmask-specific entries */ + if (ednsmask && !ednsmask->empty()) { + auto indexKey = boost::make_tuple(qname, qt.getCode()); + auto index = d_index.find(indexKey); + if (index == d_index.end()) { + index = d_index.insert(IndexEntry(qname, qt.getCode())).first; + } + index->addMask(*ednsmask); + } } time_t maxTTD=std::numeric_limits::max(); @@ -204,13 +313,12 @@ void MemRecursorCache::replace(time_t now, const DNSName &qname, const QType& qt //else cerr<<"\tNot nuking"<(i->d_ttl)); // XXX this does weird things if TTLs differ in the set - // cerr<<"To store: "<d_content->getZoneRepresentation()<<" with ttl/ttd "<d_ttl<<", capped at: "<d_content); + ce.d_ttd=min(maxTTD, static_cast(i.d_ttl)); // XXX this does weird things if TTLs differ in the set + // cerr<<"To store: "<getZoneRepresentation()<<" with ttl/ttd "< range; if(!sub) { - if(qtype==0xffff) - range=d_cache.equal_range(tie(name)); - else + pair indexRange; + if(qtype==0xffff) { + range = d_cache.equal_range(tie(name)); + indexRange = d_index.equal_range(tie(name)); + } + else { range=d_cache.equal_range(tie(name, qtype)); + indexRange = d_index.equal_range(tie(name, qtype)); + } for(cache_t::const_iterator i=range.first; i != range.second; ) { count++; d_cache.erase(i++); } + for(auto i = indexRange.first; i != indexRange.second; ) { + d_index.erase(i++); + } } else { for(auto iter = d_cache.lower_bound(tie(name)); iter != d_cache.end(); ) { @@ -247,6 +363,16 @@ int MemRecursorCache::doWipeCache(const DNSName& name, bool sub, uint16_t qtype) else iter++; } + for(auto iter = d_index.lower_bound(tie(name)); iter != d_index.end(); ) { + if(!iter->d_qname.isPartOf(name)) + break; + if(iter->d_qtype == qtype || qtype == 0xffff) { + d_index.erase(iter++); + } + else { + iter++; + } + } } return count; } @@ -279,22 +405,31 @@ bool MemRecursorCache::doAgeCache(time_t now, const DNSName& name, uint16_t qtyp return false; } -bool MemRecursorCache::updateValidationStatus(const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState) +bool MemRecursorCache::updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState) { bool updated = false; + uint16_t qtype = qt.getCode(); + if (qtype != QType::ANY && qtype != QType::ADDR && !d_index.empty()) { + auto entry = getEntryUsingIndex(now, qname, qtype, requireAuth, who); + if (entry == d_cache.end()) { + return false; + } + + entry->d_state = newState; + return true; + } + auto entries = getEntries(qname, qt); for(auto i = entries.first; i != entries.second; ++i) { - - if (!entryMatches(i, qt, requireAuth, who)) + if (!entryMatches(i, qtype, requireAuth, who)) continue; i->d_state = newState; updated = true; - if(qt.getCode()!=QType::ANY && qt.getCode()!=QType::ADDR) // normally if we have a hit, we are done + if(qtype != QType::ANY && qtype != QType::ADDR) // normally if we have a hit, we are done break; - } return updated; @@ -311,14 +446,14 @@ uint64_t MemRecursorCache::doDump(int fd) uint64_t count=0; time_t now=time(0); - for(auto i=sidx.cbegin(); i != sidx.cend(); ++i) { - for(auto j=i->d_records.cbegin(); j != i->d_records.cend(); ++j) { + for(const auto i : sidx) { + for(const auto j : i.d_records) { count++; try { - fprintf(fp, "%s %" PRId64 " IN %s %s ; %s\n", i->d_qname.toString().c_str(), static_cast(i->d_ttd - now), DNSRecordContent::NumberToType(i->d_qtype).c_str(), (*j)->getZoneRepresentation().c_str(), i->d_netmask.empty() ? "" : i->d_netmask.toString().c_str()); + fprintf(fp, "%s %" PRId64 " IN %s %s ; %s\n", i.d_qname.toString().c_str(), static_cast(i.d_ttd - now), DNSRecordContent::NumberToType(i.d_qtype).c_str(), j->getZoneRepresentation().c_str(), i.d_netmask.empty() ? "" : i.d_netmask.toString().c_str()); } catch(...) { - fprintf(fp, "; error printing '%s'\n", i->d_qname.empty() ? "EMPTY" : i->d_qname.toString().c_str()); + fprintf(fp, "; error printing '%s'\n", i.d_qname.empty() ? "EMPTY" : i.d_qname.toString().c_str()); } } } @@ -331,5 +466,5 @@ void MemRecursorCache::doPrune(void) d_cachecachevalid=false; unsigned int maxCached=::arg().asNum("max-cache-entries") / g_numThreads; - pruneCollection(d_cache, maxCached); + pruneCollection(*this, d_cache, maxCached); } diff --git a/pdns/recursor_cache.hh b/pdns/recursor_cache.hh index 0f3fbc91bb..1fb4374540 100644 --- a/pdns/recursor_cache.hh +++ b/pdns/recursor_cache.hh @@ -33,6 +33,7 @@ #undef L #include #include +#include #include #include #include @@ -63,7 +64,7 @@ public: int doWipeCache(const DNSName& name, bool sub, uint16_t qtype=0xffff); bool doAgeCache(time_t now, const DNSName& name, uint16_t qtype, uint32_t newTTL); - bool updateValidationStatus(const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState); + bool updateValidationStatus(time_t now, const DNSName &qname, const QType& qt, const ComboAddress& who, bool requireAuth, vState newState); uint64_t cacheHits, cacheMisses; @@ -72,24 +73,63 @@ private: struct CacheEntry { CacheEntry(const boost::tuple& key, const vector>& records, bool auth) : - d_qname(key.get<0>()), d_qtype(key.get<1>()), d_auth(auth), d_ttd(0), d_records(records), d_netmask(key.get<2>()) + d_records(records), d_qname(key.get<0>()), d_netmask(key.get<2>()), d_ttd(0), d_qtype(key.get<1>()), d_auth(auth) {} typedef vector> records_t; - vector> d_signatures; time_t getTTD() const { return d_ttd; } - DNSName d_qname; - uint16_t d_qtype; - bool d_auth; - time_t d_ttd; records_t d_records; + vector> d_signatures; std::vector> d_authorityRecs; + DNSName d_qname; Netmask d_netmask; mutable vState d_state; + time_t d_ttd; + uint16_t d_qtype; + bool d_auth; + }; + + class IndexEntry + { + public: + IndexEntry(const DNSName& qname, uint16_t qtype): d_qname(qname), d_qtype(qtype) + { + } + + Netmask lookupBestMatch(const ComboAddress& addr) const + { + Netmask result = Netmask(); + + const auto best = d_nmt.lookup(addr); + if (best != nullptr) { + result = best->first; + } + + return result; + } + + void addMask(const Netmask& nm) const + { + d_nmt.insert(nm).second = true; + } + + void removeNetmask(const Netmask& nm) const + { + d_nmt.erase(nm); + } + + bool isEmpty() const + { + return d_nmt.empty(); + } + + mutable NetmaskTree d_nmt; + DNSName d_qname; + uint16_t d_qtype; }; typedef multi_index_container< @@ -107,15 +147,46 @@ private: sequenced<> > > cache_t; + typedef multi_index_container< + IndexEntry, + indexed_by < + ordered_unique < + composite_key< + IndexEntry, + member, + member + > + > + > + > index_t; cache_t d_cache; + index_t d_index; pair d_cachecache; DNSName d_cachedqname; bool d_cachecachevalid; bool attemptToRefreshNSTTL(const QType& qt, const vector& content, const CacheEntry& stored); - bool entryMatches(cache_t::const_iterator& entry, const QType& qt, bool requireAuth, const ComboAddress& who); + bool entryMatches(cache_t::const_iterator& entry, uint16_t qt, bool requireAuth, const ComboAddress& who); std::pair getEntries(const DNSName &qname, const QType& qt); + cache_t::const_iterator getEntryUsingIndex(time_t now, const DNSName &qname, uint16_t qtype, bool requireAuth, const ComboAddress& who); + int32_t handleHit(cache_t::iterator entry, const DNSName& qname, const ComboAddress& who, vector* res, vector>* signatures, std::vector>* authorityRecs, bool* variable, vState* state, bool* wasAuth); +public: + void preRemoval(const CacheEntry& entry) + { + if (entry.d_netmask.empty()) { + return; + } + + auto key = tie(entry.d_qname, entry.d_qtype); + auto indexEntry = d_index.find(key); + if (indexEntry != d_index.end()) { + indexEntry->removeNetmask(entry.d_netmask); + if (indexEntry->isEmpty()) { + d_index.erase(indexEntry); + } + } + } }; #endif diff --git a/pdns/recursordist/negcache.cc b/pdns/recursordist/negcache.cc index c719eae85a..49f4e2448e 100644 --- a/pdns/recursordist/negcache.cc +++ b/pdns/recursordist/negcache.cc @@ -155,7 +155,7 @@ void NegCache::clear() { * \param maxEntries The maximum number of entries that may exist in the cache. */ void NegCache::prune(unsigned int maxEntries) { - pruneCollection(d_negcache, maxEntries, 200); + pruneCollection(*this, d_negcache, maxEntries, 200); } /*! diff --git a/pdns/recursordist/negcache.hh b/pdns/recursordist/negcache.hh index e82883b801..e25fe5da98 100644 --- a/pdns/recursordist/negcache.hh +++ b/pdns/recursordist/negcache.hh @@ -71,6 +71,10 @@ class NegCache : public boost::noncopyable { return d_negcache.size(); }; + void preRemoval(const NegCacheEntry& entry) + { + } + private: typedef boost::multi_index_container < NegCacheEntry, diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 95123efddc..e6065c33db 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -832,7 +832,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures); if (state != Indeterminate) { LOG(prefix<updateValidationStatus(qname, QType(QType::CNAME), d_requestor, d_requireAuthData, state); + t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_requestor, d_requireAuthData, state); } } } @@ -990,7 +990,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vectorupdateValidationStatus(sqname, sqt, d_requestor, d_requireAuthData, cachedState); + t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_requestor, d_requireAuthData, cachedState); } } }