]>
Commit | Line | Data |
---|---|---|
bf269e28 RG |
1 | /* |
2 | * This file is part of PowerDNS or dnsdist. | |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of version 2 of the GNU General Public License as | |
7 | * published by the Free Software Foundation. | |
8 | * | |
9 | * In addition, for the avoidance of any doubt, permission is granted to | |
10 | * link this program with OpenSSL and to (re)distribute the binaries | |
11 | * produced as the result of such linking. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License | |
19 | * along with this program; if not, write to the Free Software | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
22 | #ifdef HAVE_CONFIG_H | |
23 | #include "config.h" | |
24 | #endif | |
25 | ||
26 | #include "auth-querycache.hh" | |
27 | #include "logger.hh" | |
28 | #include "statbag.hh" | |
29 | #include "cachecleaner.hh" | |
30 | extern StatBag S; | |
31 | ||
32 | const unsigned int AuthQueryCache::s_mincleaninterval, AuthQueryCache::s_maxcleaninterval; | |
33 | ||
bf269e28 RG |
34 | AuthQueryCache::AuthQueryCache(size_t mapsCount): d_lastclean(time(nullptr)) |
35 | { | |
36 | d_maps.resize(mapsCount); | |
37 | for(auto& mc : d_maps) { | |
38 | pthread_rwlock_init(&mc.d_mut, 0); | |
39 | } | |
40 | ||
41 | S.declare("query-cache-hit","Number of hits on the query cache"); | |
42 | S.declare("query-cache-miss","Number of misses on the query cache"); | |
43 | S.declare("query-cache-size", "Number of entries in the query cache"); | |
44 | S.declare("deferred-cache-inserts","Amount of cache inserts that were deferred because of maintenance"); | |
45 | S.declare("deferred-cache-lookup","Amount of cache lookups that were deferred because of maintenance"); | |
46 | ||
47 | d_statnumhit=S.getPointer("query-cache-hit"); | |
48 | d_statnummiss=S.getPointer("query-cache-miss"); | |
49 | d_statnumentries=S.getPointer("query-cache-size"); | |
50 | } | |
51 | ||
52 | AuthQueryCache::~AuthQueryCache() | |
53 | { | |
54 | try { | |
55 | vector<WriteLock*> locks; | |
56 | for(auto& mc : d_maps) { | |
57 | locks.push_back(new WriteLock(&mc.d_mut)); | |
58 | } | |
59 | for(auto wl : locks) { | |
60 | delete wl; | |
61 | } | |
62 | } | |
63 | catch(...) { | |
64 | } | |
65 | } | |
66 | ||
67 | // called from ueberbackend | |
68 | bool AuthQueryCache::getEntry(const DNSName &qname, const QType& qtype, vector<DNSZoneRecord>& value, int zoneID) | |
69 | { | |
70 | cleanupIfNeeded(); | |
71 | ||
72 | time_t now = time(nullptr); | |
73 | uint16_t qt = qtype.getCode(); | |
74 | auto& mc = getMap(qname); | |
75 | { | |
76 | TryReadLock rl(&mc.d_mut); | |
77 | if(!rl.gotIt()) { | |
78 | S.inc("deferred-cache-lookup"); | |
79 | return false; | |
80 | } | |
81 | ||
82 | return getEntryLocked(mc.d_map, qname, qt, value, zoneID, now); | |
83 | } | |
84 | } | |
85 | ||
86 | void AuthQueryCache::insert(const DNSName &qname, const QType& qtype, const vector<DNSZoneRecord>& value, uint32_t ttl, int zoneID) | |
87 | { | |
88 | cleanupIfNeeded(); | |
89 | ||
90 | if(!ttl) | |
91 | return; | |
92 | ||
93 | time_t now = time(nullptr); | |
94 | CacheEntry val; | |
95 | val.created = now; | |
96 | val.ttd = now + ttl; | |
97 | val.qname = qname; | |
98 | val.qtype = qtype.getCode(); | |
99 | val.drs = value; | |
100 | val.zoneID = zoneID; | |
101 | ||
102 | auto& mc = getMap(val.qname); | |
103 | ||
104 | { | |
105 | TryWriteLock l(&mc.d_mut); | |
106 | if(!l.gotIt()) { | |
107 | S.inc("deferred-cache-inserts"); | |
108 | return; | |
109 | } | |
110 | ||
111 | bool inserted; | |
112 | cmap_t::iterator place; | |
113 | tie(place, inserted) = mc.d_map.insert(val); | |
114 | ||
115 | if (!inserted) { | |
116 | mc.d_map.replace(place, val); | |
117 | } | |
118 | else { | |
119 | (*d_statnumentries)++; | |
120 | } | |
121 | } | |
122 | } | |
123 | ||
124 | bool AuthQueryCache::getEntryLocked(cmap_t& map, const DNSName &qname, uint16_t qtype, vector<DNSZoneRecord>& value, int zoneID, time_t now) | |
125 | { | |
126 | auto& idx = boost::multi_index::get<HashTag>(map); | |
127 | auto iter = idx.find(tie(qname, qtype, zoneID)); | |
128 | ||
54d1705f KM |
129 | if (iter == idx.end()) { |
130 | (*d_statnummiss)++; | |
bf269e28 | 131 | return false; |
54d1705f | 132 | } |
bf269e28 | 133 | |
54d1705f KM |
134 | if (iter->ttd < now) { |
135 | (*d_statnummiss)++; | |
bf269e28 | 136 | return false; |
54d1705f | 137 | } |
bf269e28 RG |
138 | |
139 | value = iter->drs; | |
54d1705f | 140 | (*d_statnumhit)++; |
bf269e28 RG |
141 | return true; |
142 | } | |
143 | ||
144 | map<char,uint64_t> AuthQueryCache::getCounts() | |
145 | { | |
146 | uint64_t queryCacheEntries=0, negQueryCacheEntries=0; | |
147 | ||
148 | for(auto& mc : d_maps) { | |
149 | ReadLock l(&mc.d_mut); | |
150 | ||
151 | for(cmap_t::const_iterator iter = mc.d_map.begin() ; iter != mc.d_map.end(); ++iter) { | |
152 | if(iter->drs.empty()) | |
153 | negQueryCacheEntries++; | |
154 | else | |
155 | queryCacheEntries++; | |
156 | } | |
157 | } | |
158 | map<char,uint64_t> ret; | |
159 | ||
160 | ret['!']=negQueryCacheEntries; | |
161 | ret['Q']=queryCacheEntries; | |
162 | return ret; | |
163 | } | |
164 | ||
165 | /* clears the entire cache. */ | |
166 | uint64_t AuthQueryCache::purge() | |
167 | { | |
168 | d_statnumentries->store(0); | |
169 | ||
170 | return purgeLockedCollectionsVector(d_maps); | |
171 | } | |
172 | ||
173 | uint64_t AuthQueryCache::purgeExact(const DNSName& qname) | |
174 | { | |
175 | auto& mc = getMap(qname); | |
176 | uint64_t delcount = purgeExactLockedCollection(mc, qname); | |
177 | ||
178 | *d_statnumentries -= delcount; | |
179 | ||
180 | return delcount; | |
181 | } | |
182 | ||
183 | /* purges entries from the querycache. If match ends on a $, it is treated as a suffix */ | |
184 | uint64_t AuthQueryCache::purge(const string &match) | |
185 | { | |
186 | uint64_t delcount = 0; | |
187 | ||
188 | if(ends_with(match, "$")) { | |
189 | delcount = purgeLockedCollectionsVector(d_maps, match); | |
190 | *d_statnumentries -= delcount; | |
191 | } | |
192 | else { | |
193 | delcount = purgeExact(DNSName(match)); | |
194 | } | |
195 | ||
196 | return delcount; | |
197 | } | |
198 | ||
199 | void AuthQueryCache::cleanup() | |
200 | { | |
201 | uint64_t maxCached = d_maxEntries; | |
202 | uint64_t cacheSize = *d_statnumentries; | |
203 | uint64_t totErased = 0; | |
204 | ||
205 | totErased = pruneLockedCollectionsVector(d_maps, maxCached, cacheSize); | |
206 | ||
207 | *d_statnumentries -= totErased; | |
e6a9dde5 | 208 | DLOG(g_log<<"Done with cache clean, cacheSize: "<<*d_statnumentries<<", totErased"<<totErased<<endl); |
bf269e28 RG |
209 | } |
210 | ||
211 | /* the logic: | |
212 | after d_nextclean operations, we clean. We also adjust the cleaninterval | |
213 | a bit so we slowly move it to a value where we clean roughly every 30 seconds. | |
214 | ||
215 | If d_nextclean has reached its maximum value, we also test if we were called | |
216 | within 30 seconds, and if so, we skip cleaning. This means that under high load, | |
217 | we will not clean more often than every 30 seconds anyhow. | |
218 | */ | |
219 | ||
220 | void AuthQueryCache::cleanupIfNeeded() | |
221 | { | |
222 | if (d_ops++ == d_nextclean) { | |
223 | time_t now = time(nullptr); | |
224 | int timediff = max((int)(now - d_lastclean), 1); | |
225 | ||
e6a9dde5 | 226 | DLOG(g_log<<"cleaninterval: "<<d_cleaninterval<<", timediff: "<<timediff<<endl); |
bf269e28 RG |
227 | |
228 | if (d_cleaninterval == s_maxcleaninterval && timediff < 30) { | |
229 | d_cleanskipped = true; | |
230 | d_nextclean += d_cleaninterval; | |
231 | ||
e6a9dde5 | 232 | DLOG(g_log<<"cleaning skipped, timediff: "<<timediff<<endl); |
bf269e28 RG |
233 | |
234 | return; | |
235 | } | |
236 | ||
237 | if(!d_cleanskipped) { | |
238 | d_cleaninterval=(int)(0.6*d_cleaninterval)+(0.4*d_cleaninterval*(30.0/timediff)); | |
239 | d_cleaninterval=std::max(d_cleaninterval, s_mincleaninterval); | |
240 | d_cleaninterval=std::min(d_cleaninterval, s_maxcleaninterval); | |
241 | ||
e6a9dde5 | 242 | DLOG(g_log<<"new cleaninterval: "<<d_cleaninterval<<endl); |
bf269e28 RG |
243 | } else { |
244 | d_cleanskipped = false; | |
245 | } | |
246 | ||
247 | d_nextclean += d_cleaninterval; | |
248 | d_lastclean=now; | |
249 | cleanup(); | |
250 | } | |
251 | } |