Squid Web Cache master
Loading...
Searching...
No Matches
store_digest.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
3 *
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
7 */
8
9/* DEBUG: section 71 Store Digest Manager */
10
11/*
12 * TODO: We probably do not track all the cases when
13 * storeDigestNoteStoreReady() must be called; this may prevent
14 * storeDigestRebuild/write schedule to be activated
15 */
16
17#include "squid.h"
18#include "debug/Stream.h"
19#include "event.h"
20#include "globals.h"
21#include "mgr/Registration.h"
22#include "store_digest.h"
23
24#if USE_CACHE_DIGESTS
25#include "CacheDigest.h"
26#include "HttpReply.h"
27#include "HttpRequest.h"
28#include "internal.h"
29#include "MemObject.h"
30#include "PeerDigest.h"
31#include "refresh.h"
32#include "SquidConfig.h"
33#include "Store.h"
34#include "StoreSearch.h"
35#include "util.h"
36
37#include <cmath>
38#include <limits>
39
40/*
41 * local types
42 */
43
56
58{
59public:
60 int del_count = 0; /* #store entries deleted from store_digest */
61 int del_lost_count = 0; /* #store entries not found in store_digest on delete */
62 int add_count = 0; /* #store entries accepted to store_digest */
63 int add_coll_count = 0; /* #accepted entries that collided with existing ones */
64 int rej_count = 0; /* #store entries not accepted to store_digest */
65 int rej_coll_count = 0; /* #not accepted entries that collided with existing ones */
66};
67
68/* local vars */
71
72/* local prototypes */
73static void storeDigestRebuildStart(void *datanotused);
74static void storeDigestRebuildResume(void);
75static void storeDigestRebuildFinish(void);
76static void storeDigestRebuildStep(void *datanotused);
77static void storeDigestRewriteStart(void *);
78static void storeDigestRewriteResume(void);
82static void storeDigestAdd(const StoreEntry *);
83
85static uint64_t
87{
88 /*
89 * To-Do: Bloom proved that the optimal filter utilization is 50% (half of
90 * the bits are off). However, we do not have a formula to calculate the
91 * number of _entries_ we want to pre-allocate for.
92 */
93 const uint64_t hi_cap = Store::Root().maxSize() / Config.Store.avgObjectSize;
94 const uint64_t lo_cap = 1 + Store::Root().currentSize() / Config.Store.avgObjectSize;
95 const uint64_t e_count = StoreEntry::inUseCount();
96 uint64_t cap = e_count ? e_count : hi_cap;
97 debugs(71, 2, "have: " << e_count << ", want " << cap <<
98 " entries; limits: [" << lo_cap << ", " << hi_cap << "]");
99
100 if (cap < lo_cap)
101 cap = lo_cap;
102
103 /* do not enforce hi_cap limit, average-based estimation may be wrong
104 *if (cap > hi_cap)
105 * cap = hi_cap;
106 */
107
108 const auto bpe = Config.digest.bits_per_entry;
109 Assure(bpe);
110
111 // Digest recipients recalculate mask size using received capacity and bpe
112 // values. Limit sent capacity value to keep legacy digest recipients safe.
113 const auto safeMaskSizeMax = CacheDigest::CalcMaskSize(std::numeric_limits<uint64_t>::max(), bpe); // absolute maximum
114 const auto safeCapMax = uint64_t(safeMaskSizeMax) * 8 / bpe;
115 const auto safeCap = std::min(cap, safeCapMax);
116 if (cap > safeCap) {
117 const auto absolute_max = safeCap; // diff reducer
118 static time_t last_loud = 0;
119 if (last_loud < squid_curtime - 86400) {
120 debugs(71, DBG_IMPORTANT, "WARNING: Cache Digest cannot store " << cap << " entries. Limiting to " << absolute_max);
121 last_loud = squid_curtime;
122 } else {
123 debugs(71, 3, "WARNING: Cache Digest cannot store " << cap << " entries. Limiting to " << absolute_max);
124 }
125 }
126
127 return safeCap;
128}
129#endif /* USE_CACHE_DIGESTS */
130
131void
133{
134 Mgr::RegisterAction("store_digest", "Store Digest", storeDigestReport, 0, 1);
135
136#if USE_CACHE_DIGESTS
138 store_digest = nullptr;
139 debugs(71, 3, "Local cache digest generation disabled");
140 return;
141 }
142
143 const uint64_t cap = storeDigestCalcCap();
145 debugs(71, DBG_IMPORTANT, "Local cache digest enabled; rebuild/rewrite every " <<
146 (int) Config.digest.rebuild_period << "/" <<
147 (int) Config.digest.rewrite_period << " sec");
148
150#else
151 store_digest = nullptr;
152 debugs(71, 3, "Local cache digest is 'off'");
153#endif
154}
155
156/* called when store_rebuild completes */
157void
159{
160#if USE_CACHE_DIGESTS
161
165 }
166
167#endif
168}
169
170//TODO: this seems to be dead code. Is it needed?
171void
173{
174#if USE_CACHE_DIGESTS
175
177 return;
178 }
179
180 assert(entry && store_digest);
181 debugs(71, 6, "storeDigestDel: checking entry, key: " << entry->getMD5Text());
182
183 if (!EBIT_TEST(entry->flags, KEY_PRIVATE)) {
184 if (!store_digest->contains(static_cast<const cache_key *>(entry->key))) {
186 debugs(71, 6, "storeDigestDel: lost entry, key: " << entry->getMD5Text() << " url: " << entry->url() );
187 } else {
189 store_digest->remove(static_cast<const cache_key *>(entry->key));
190 debugs(71, 6, "storeDigestDel: deled entry, key: " << entry->getMD5Text());
191 }
192 }
193#else
194 (void)entry;
195#endif //USE_CACHE_DIGESTS
196}
197
198void
200{
201#if USE_CACHE_DIGESTS
202
204 return;
205 }
206
207 if (store_digest) {
208 static const SBuf label("store");
210 storeAppendPrintf(e, "\t added: %d rejected: %d ( %.2f %%) del-ed: %d\n",
215 storeAppendPrintf(e, "\t collisions: on add: %.2f %% on rej: %.2f %%\n",
218 } else {
219 storeAppendPrintf(e, "store digest: disabled.\n");
220 }
221#else
222 (void)e;
223#endif //USE_CACHE_DIGESTS
224}
225
226/*
227 * LOCAL FUNCTIONS
228 */
229
230#if USE_CACHE_DIGESTS
231
232/* should we digest this entry? used by storeDigestAdd() */
233static int
235{
236 /* add some stats! XXX */
237
238 debugs(71, 6, "storeDigestAddable: checking entry, key: " << e->getMD5Text());
239
240 /* check various entry flags (mimics StoreEntry::checkCachable XXX) */
241
242 if (EBIT_TEST(e->flags, KEY_PRIVATE)) {
243 debugs(71, 6, "storeDigestAddable: NO: private key");
244 return 0;
245 }
246
248 debugs(71, 6, "storeDigestAddable: NO: negative cached");
249 return 0;
250 }
251
253 debugs(71, 6, "storeDigestAddable: NO: release requested");
254 return 0;
255 }
256
258 debugs(71, 6, "storeDigestAddable: NO: wrong content-length");
259 return 0;
260 }
261
262 /* do not digest huge objects */
263 if (e->swap_file_sz > (uint64_t )Config.Store.maxObjectSize) {
264 debugs(71, 6, "storeDigestAddable: NO: too big");
265 return 0;
266 }
267
268 /* still here? check staleness */
269 // Include hittingRequiresCollapsing() entries: They are fresh _now_, but we
270 // check future freshness. They should not have enough info to judge future
271 // freshness since we are still waiting for their response headers, but
272 // admins might configure Squid to consider such entries fresh anyway.
273 /* Note: We should use the time of the next rebuild, not (cur_time+period) */
275 debugs(71, 6, "storeDigestAdd: entry expires within " << Config.digest.rebuild_period << " secs, ignoring");
276 return 0;
277 }
278
279 /*
280 * idea: how about also skipping very fresh (thus, potentially
281 * unstable) entries? Should be configurable through
282 * cd_refresh_pattern, of course.
283 */
284 /*
285 * idea: skip objects that are going to be purged before the next
286 * update.
287 */
288 return 1;
289}
290
291static void
293{
294 assert(entry && store_digest);
295
296 if (storeDigestAddable(entry)) {
298
299 if (store_digest->contains(static_cast<const cache_key *>(entry->key)))
301
302 store_digest->add(static_cast<const cache_key *>(entry->key));
303
304 debugs(71, 6, "storeDigestAdd: added entry, key: " << entry->getMD5Text());
305 } else {
307
308 if (store_digest->contains(static_cast<const cache_key *>(entry->key)))
310 }
311}
312
313/* rebuilds digest from scratch */
314static void
316{
318 /* prevent overlapping if rebuild schedule is too tight */
319
321 debugs(71, DBG_IMPORTANT, "storeDigestRebuildStart: overlap detected, consider increasing rebuild period");
322 return;
323 }
324
326 debugs(71, 2, "storeDigestRebuildStart: rebuild #" << sd_state.rebuild_count + 1);
327
329 debugs(71, 2, "storeDigestRebuildStart: waiting for Rewrite to finish.");
330 return;
331 }
332
334}
335
337static bool
339{
340 const uint64_t cap = storeDigestCalcCap();
342 uint64_t diff;
343 if (cap > store_digest->capacity)
344 diff = cap - store_digest->capacity;
345 else
346 diff = store_digest->capacity - cap;
347 debugs(71, 2, store_digest->capacity << " -> " << cap << "; change: " <<
348 diff << " (" << xpercentInt(diff, store_digest->capacity) << "%)" );
349 /* avoid minor adjustments */
350
351 if (diff <= store_digest->capacity / 10) {
352 debugs(71, 2, "small change, will not resize.");
353 return false;
354 } else {
355 debugs(71, 2, "big change, resizing.");
357 }
358 return true;
359}
360
361/* called be Rewrite to push Rebuild forward */
362static void
364{
368 /* resize or clear */
369
370 if (!storeDigestResize())
371 store_digest->clear(); /* not clean()! */
372
374
375 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep, nullptr, 0.0, 1);
376}
377
378/* finishes swap out sequence for the digest; schedules next rebuild */
379static void
381{
385 debugs(71, 2, "storeDigestRebuildFinish: done.");
386 eventAdd("storeDigestRebuildStart", storeDigestRebuildStart, nullptr, (double)
388 /* resume pending Rewrite if any */
389
392}
393
394/* recalculate a few hash buckets per invocation; schedules next step */
395static void
397{
398 /* TODO: call Store::Root().size() to determine this.. */
399 int count = Config.Store.objectsPerBucket * (int) ceil((double) store_hash_buckets *
402
403 debugs(71, 3, "storeDigestRebuildStep: buckets: " << store_hash_buckets << " entries to check: " << count);
404
405 while (count-- && !sd_state.theSearch->isDone() && sd_state.theSearch->next())
407
408 /* are we done ? */
411 else
412 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep, nullptr, 0.0, 1);
413}
414
415/* starts swap out sequence for the digest */
416static void
418{
420 /* prevent overlapping if rewrite schedule is too tight */
421
423 debugs(71, DBG_IMPORTANT, "storeDigestRewrite: overlap detected, consider increasing rewrite period");
424 return;
425 }
426
427 debugs(71, 2, "storeDigestRewrite: start rewrite #" << sd_state.rewrite_count + 1);
428
429 const char *url = internalLocalUri("/squid-internal-periodic/", SBuf(StoreDigestFileName));
430 const auto mx = MasterXaction::MakePortless<XactionInitiator::initCacheDigest>();
431 auto req = HttpRequest::FromUrlXXX(url, mx);
432
433 RequestFlags flags;
434 flags.cachable.support(); // prevent RELEASE_REQUEST in storeCreateEntry()
435
436 StoreEntry *e = storeCreateEntry(url, url, flags, Http::METHOD_GET);
437 assert(e);
439 debugs(71, 3, "storeDigestRewrite: url: " << url << " key: " << e->getMD5Text());
440 e->mem_obj->request = req;
441
442 /* wait for rebuild (if any) to finish */
444 debugs(71, 2, "storeDigestRewriteStart: waiting for rebuild to finish.");
445 return;
446 }
447
449}
450
451static void
453{
454 StoreEntry *e;
455
461 /* setting public key will mark the old digest entry for removal once unlocked */
462 e->setPublicKey();
463 if (const auto oldEntry = sd_state.publicEntry) {
464 oldEntry->release(true);
465 sd_state.publicEntry = nullptr;
466 oldEntry->unlock("storeDigestRewriteResume");
467 }
468 assert(e->locked());
470 /* fake reply */
471 HttpReply *rep = new HttpReply;
472 rep->setHeaders(Http::scOkay, "Cache Digest OK",
473 "application/cache-digest", (store_digest->mask_size + sizeof(sd_state.cblock)),
475 debugs(71, 3, "storeDigestRewrite: entry expires on " << rep->expires <<
476 " (" << std::showpos << (int) (rep->expires - squid_curtime) << ")");
477 e->buffer();
478 e->replaceHttpReply(rep);
480 e->flush();
481 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep, sd_state.rewrite_lock, 0.0, 1, false);
482}
483
484/* finishes swap out sequence for the digest; schedules next rewrite */
485static void
487{
489 e->complete();
490 e->timestampsSet();
491 debugs(71, 2, "storeDigestRewriteFinish: digest expires at " << e->expires <<
492 " (" << std::showpos << (int) (e->expires - squid_curtime) << ")");
493 /* is this the write order? @?@ */
495 sd_state.rewrite_lock = nullptr;
497 eventAdd("storeDigestRewriteStart", storeDigestRewriteStart, nullptr, (double)
499 /* resume pending Rebuild if any */
500
503}
504
505/* swaps out one digest "chunk" per invocation; schedules next swap out */
506static void
508{
509 StoreEntry *e = static_cast<StoreEntry *>(data);
510 int chunk_size = Config.digest.swapout_chunk_size;
512 assert(e);
513 /* _add_ check that nothing bad happened while we were waiting @?@ @?@ */
514
515 if (static_cast<uint32_t>(sd_state.rewrite_offset + chunk_size) > store_digest->mask_size)
517
519
520 debugs(71, 3, "storeDigestSwapOutStep: size: " << store_digest->mask_size <<
521 " offset: " << sd_state.rewrite_offset << " chunk: " <<
522 chunk_size << " bytes");
523
524 sd_state.rewrite_offset += chunk_size;
525
526 /* are we done ? */
527 if (static_cast<uint32_t>(sd_state.rewrite_offset) >= store_digest->mask_size)
529 else
530 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep, data, 0.0, 1, false);
531}
532
533static void
547
548#endif /* USE_CACHE_DIGESTS */
549
#define Assure(condition)
Definition Assure.h:35
void cacheDigestReport(CacheDigest *cd, const SBuf &label, StoreEntry *e)
time_t squid_curtime
class SquidConfig Config
#define assert(EX)
Definition assert.h:17
void updateCapacity(uint64_t newCapacity)
changes mask size to fit newCapacity, resets bits to 0
char * mask
Definition CacheDigest.h:58
static uint32_t CalcMaskSize(uint64_t cap, uint8_t bpe)
uint64_t del_count
Definition CacheDigest.h:56
uint64_t count
Definition CacheDigest.h:55
uint32_t mask_size
Definition CacheDigest.h:59
void add(const cache_key *key)
uint64_t capacity
Definition CacheDigest.h:57
bool contains(const cache_key *key) const
void clear()
reset the digest mask and counters
void remove(const cache_key *key)
void setHeaders(Http::StatusCode status, const char *reason, const char *ctype, int64_t clen, time_t lmt, time_t expires)
Definition HttpReply.cc:170
time_t expires
Definition HttpReply.h:44
static HttpRequest * FromUrlXXX(const char *url, const MasterXaction::Pointer &, const HttpRequestMethod &method=Http::METHOD_GET)
HttpRequestPointer request
Definition MemObject.h:205
void unlinkRequest()
Definition MemObject.h:56
SupportOrVeto cachable
whether the response may be stored in the cache
Definition SBuf.h:94
int objectsPerBucket
struct SquidConfig::@96 digest
time_t rebuild_period
int64_t avgObjectSize
size_t swapout_chunk_size
struct SquidConfig::@90 onoff
int rebuild_chunk_percentage
int digest_generation
time_t rewrite_period
int64_t maxObjectSize
struct SquidConfig::@88 Store
unsigned char bits_per_entry
Definition PeerDigest.h:34
unsigned char hash_func_count
Definition PeerDigest.h:35
StoreEntry * publicEntry
points to the previous store entry with the digest
int rebuild_lock
bucket number
StoreDigestCBlock cblock
StoreSearchPointer theSearch
StoreEntry * rewrite_lock
points to store entry with the digest
uint16_t flags
Definition Store.h:231
int locked() const
Definition Store.h:145
int unlock(const char *context)
Definition store.cc:469
const char * url() const
Definition store.cc:1566
void complete()
Definition store.cc:1031
time_t expires
Definition Store.h:225
void flush() override
Definition store.cc:1612
bool timestampsSet()
Definition store.cc:1387
const char * getMD5Text() const
Definition store.cc:207
static size_t inUseCount()
Definition store.cc:199
void replaceHttpReply(const HttpReplyPointer &, const bool andStartWriting=true)
Definition store.cc:1705
MemObject * mem_obj
Definition Store.h:220
void append(char const *, int) override
Appends a c-string to existing packed data.
Definition store.cc:803
void buffer() override
Definition store.cc:1601
bool setPublicKey(const KeyScope keyScope=ksDefault)
Definition store.cc:575
uint64_t swap_file_sz
Definition Store.h:229
virtual void next(void(callback)(void *cbdata), void *cbdata)=0
virtual StoreEntry * currentItem()=0
virtual bool isDone() const =0
uint64_t maxSize() const override
StoreSearch * search()
uint64_t currentSize() const override
current size
short int required
Definition PeerDigest.h:21
short int current
Definition PeerDigest.h:20
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define EBIT_SET(flag, bit)
Definition defines.h:65
#define EBIT_TEST(flag, bit)
Definition defines.h:67
@ ENTRY_BAD_LENGTH
Definition enums.h:109
@ ENTRY_SPECIAL
Definition enums.h:79
@ KEY_PRIVATE
Definition enums.h:97
@ RELEASE_REQUEST
prohibits making the key public
Definition enums.h:93
@ ENTRY_NEGCACHED
Definition enums.h:107
void eventAdd(const char *name, EVH *func, void *arg, double when, int weight, bool cbdata)
Definition event.cc:107
void EVH(void *)
Definition event.h:18
int store_hash_buckets
int CacheDigestHashFuncCount
const char * StoreDigestFileName
CacheDigest * store_digest
char * internalLocalUri(const char *dir, const SBuf &name)
Definition internal.cc:139
@ scOkay
Definition StatusCode.h:27
@ METHOD_GET
Definition MethodType.h:25
void RegisterAction(char const *action, char const *desc, OBJH *handler, Protected, Atomic, Format)
Controller & Root()
safely access controller singleton
Version const CacheDigestVer
int refreshCheckDigest(const StoreEntry *entry, time_t delta)
Definition refresh.cc:617
unsigned char cache_key
Store key.
Definition forward.h:29
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition store.cc:855
StoreEntry * storeCreateEntry(const char *url, const char *logUrl, const RequestFlags &flags, const HttpRequestMethod &method)
Definition store.cc:759
static StoreDigestStats sd_stats
static void storeDigestRebuildStart(void *datanotused)
static EVH storeDigestSwapOutStep
static void storeDigestRebuildResume(void)
static void storeDigestCBlockSwapOut(StoreEntry *e)
void storeDigestDel(const StoreEntry *entry)
static void storeDigestAdd(const StoreEntry *)
static void storeDigestRewriteFinish(StoreEntry *e)
static void storeDigestRebuildStep(void *datanotused)
void storeDigestNoteStoreReady(void)
static void storeDigestRewriteStart(void *)
void storeDigestReport(StoreEntry *e)
void storeDigestInit(void)
static uint64_t storeDigestCalcCap()
calculates digest capacity
static void storeDigestRebuildFinish(void)
static int storeDigestAddable(const StoreEntry *e)
static void storeDigestRewriteResume(void)
static bool storeDigestResize()
static StoreDigestState sd_state
void EVH void double
Definition stub_event.cc:16
void unsigned int
Definition stub_fd.cc:16
double xpercent(double part, double whole)
Definition util.cc:40
int xpercentInt(double part, double whole)
Definition util.cc:46