]> git.ipfire.org Git - thirdparty/squid.git/blame - src/store_digest.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / store_digest.cc
CommitLineData
8638fc66 1/*
ef57eb7b 2 * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
8638fc66 7 */
8
bbc27441
AJ
9/* DEBUG: section 71 Store Digest Manager */
10
6168bccd 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
582c2af2
FC
17#include "squid.h"
18#include "Debug.h"
a553a5a3 19#include "event.h"
582c2af2 20#include "globals.h"
8822ebee 21#include "mgr/Registration.h"
35a28a37 22#include "store_digest.h"
d6fd3381 23
b814e8d4
FC
24#if USE_CACHE_DIGESTS
25#include "CacheDigest.h"
528b2c61 26#include "HttpReply.h"
582c2af2 27#include "HttpRequest.h"
308e60be 28#include "internal.h"
528b2c61 29#include "MemObject.h"
aa839030 30#include "PeerDigest.h"
c6f15d40 31#include "refresh.h"
4d5904f7 32#include "SquidConfig.h"
985c86bc 33#include "SquidTime.h"
582c2af2 34#include "Store.h"
c8f4eac4 35#include "StoreSearch.h"
ed6e9fb9 36#include "util.h"
528b2c61 37
074d6a40 38#include <cmath>
582c2af2 39
d6fd3381 40/*
41 * local types
42 */
12784378 43
c8f4eac4 44class StoreDigestState
62e76326 45{
c8f4eac4 46
47public:
12784378 48 StoreDigestCBlock cblock;
f53969cc
SM
49 int rebuild_lock; /* bucket number */
50 StoreEntry * rewrite_lock; /* points to store entry with the digest */
c8f4eac4 51 StoreSearchPointer theSearch;
12784378 52 int rewrite_offset;
53 int rebuild_count;
b644367b 54 int rewrite_count;
c8f4eac4 55};
12784378 56
26ac0430 57typedef struct {
f53969cc
SM
58 int del_count; /* #store entries deleted from store_digest */
59 int del_lost_count; /* #store entries not found in store_digest on delete */
60 int add_count; /* #store entries accepted to store_digest */
61 int add_coll_count; /* #accepted entries that collided with existing ones */
62 int rej_count; /* #store entries not accepted to store_digest */
63 int rej_coll_count; /* #not accepted entries that collided with existing ones */
2fadd50d 64} StoreDigestStats;
6168bccd 65
462f66d2 66/* local vars */
12784378 67static StoreDigestState sd_state;
6168bccd 68static StoreDigestStats sd_stats;
12784378 69
70/* local prototypes */
6168bccd 71static void storeDigestRebuildStart(void *datanotused);
d6fd3381 72static void storeDigestRebuildResume(void);
73static void storeDigestRebuildFinish(void);
12784378 74static void storeDigestRebuildStep(void *datanotused);
d6fd3381 75static void storeDigestRewriteStart(void *);
76static void storeDigestRewriteResume(void);
b644367b 77static void storeDigestRewriteFinish(StoreEntry * e);
d6f51e3c 78static EVH storeDigestSwapOutStep;
b644367b 79static void storeDigestCBlockSwapOut(StoreEntry * e);
d6fd3381 80static void storeDigestAdd(const StoreEntry *);
12784378 81
831e953c
AJ
82/// calculates digest capacity
83static uint64_t
84storeDigestCalcCap()
85{
86 /*
87 * To-Do: Bloom proved that the optimal filter utilization is 50% (half of
88 * the bits are off). However, we do not have a formula to calculate the
89 * number of _entries_ we want to pre-allocate for.
90 */
91 const uint64_t hi_cap = Store::Root().maxSize() / Config.Store.avgObjectSize;
92 const uint64_t lo_cap = 1 + Store::Root().currentSize() / Config.Store.avgObjectSize;
93 const uint64_t e_count = StoreEntry::inUseCount();
94 uint64_t cap = e_count ? e_count : hi_cap;
95 debugs(71, 2, "have: " << e_count << ", want " << cap <<
96 " entries; limits: [" << lo_cap << ", " << hi_cap << "]");
97
98 if (cap < lo_cap)
99 cap = lo_cap;
100
101 /* do not enforce hi_cap limit, average-based estimation may be wrong
102 *if (cap > hi_cap)
103 * cap = hi_cap;
104 */
105
106 // Bug 4534: we still have to set an upper-limit at some reasonable value though.
107 // this matches cacheDigestCalcMaskSize doing (cap*bpe)+7 < INT_MAX
108 const uint64_t absolute_max = (INT_MAX -8) / Config.digest.bits_per_entry;
109 if (cap > absolute_max) {
110 debugs(71, DBG_CRITICAL, "WARNING: Cache Digest cannot store " << cap << " entries. Limiting to " << absolute_max);
111 cap = absolute_max;
112 }
113
114 return cap;
115}
116#endif /* USE_CACHE_DIGESTS */
12784378 117
8638fc66 118void
d6fd3381 119storeDigestInit(void)
8638fc66 120{
8f0386af 121 Mgr::RegisterAction("store_digest", "Store Digest", storeDigestReport, 0, 1);
d120ed12 122
6cfa8966 123#if USE_CACHE_DIGESTS
7e3ce7b9 124 if (!Config.onoff.digest_generation) {
62e76326 125 store_digest = NULL;
bf8fe701 126 debugs(71, 3, "Local cache digest generation disabled");
62e76326 127 return;
7e3ce7b9 128 }
62e76326 129
831e953c 130 const uint64_t cap = storeDigestCalcCap();
e04fc9d3 131 store_digest = new CacheDigest(cap, Config.digest.bits_per_entry);
e0236918 132 debugs(71, DBG_IMPORTANT, "Local cache digest enabled; rebuild/rewrite every " <<
bf8fe701 133 (int) Config.digest.rebuild_period << "/" <<
134 (int) Config.digest.rewrite_period << " sec");
135
12784378 136 memset(&sd_state, 0, sizeof(sd_state));
d6fd3381 137#else
138 store_digest = NULL;
bf8fe701 139 debugs(71, 3, "Local cache digest is 'off'");
d6fd3381 140#endif
8638fc66 141}
142
6168bccd 143/* called when store_rebuild completes */
8638fc66 144void
d6fd3381 145storeDigestNoteStoreReady(void)
12784378 146{
d6fd3381 147#if USE_CACHE_DIGESTS
62e76326 148
7e3ce7b9 149 if (Config.onoff.digest_generation) {
62e76326 150 storeDigestRebuildStart(NULL);
151 storeDigestRewriteStart(NULL);
7e3ce7b9 152 }
62e76326 153
d6fd3381 154#endif
155}
156
35a28a37 157//TODO: this seems to be dead code. Is it needed?
d6fd3381 158void
159storeDigestDel(const StoreEntry * entry)
160{
161#if USE_CACHE_DIGESTS
62e76326 162
7e3ce7b9 163 if (!Config.onoff.digest_generation) {
62e76326 164 return;
7e3ce7b9 165 }
62e76326 166
d6fd3381 167 assert(entry && store_digest);
bf8fe701 168 debugs(71, 6, "storeDigestDel: checking entry, key: " << entry->getMD5Text());
62e76326 169
d46a87a8 170 if (!EBIT_TEST(entry->flags, KEY_PRIVATE)) {
6fc4e508 171 if (!store_digest->contains(static_cast<const cache_key *>(entry->key))) {
5db6bf73 172 ++sd_stats.del_lost_count;
bf8fe701 173 debugs(71, 6, "storeDigestDel: lost entry, key: " << entry->getMD5Text() << " url: " << entry->url() );
62e76326 174 } else {
5db6bf73 175 ++sd_stats.del_count;
fbba122c 176 store_digest->remove(static_cast<const cache_key *>(entry->key));
bf8fe701 177 debugs(71, 6, "storeDigestDel: deled entry, key: " << entry->getMD5Text());
62e76326 178 }
d6fd3381 179 }
35a28a37 180#endif //USE_CACHE_DIGESTS
12784378 181}
182
12784378 183void
d6fd3381 184storeDigestReport(StoreEntry * e)
185{
186#if USE_CACHE_DIGESTS
62e76326 187
7e3ce7b9 188 if (!Config.onoff.digest_generation) {
62e76326 189 return;
7e3ce7b9 190 }
62e76326 191
d6fd3381 192 if (store_digest) {
62e76326 193 cacheDigestReport(store_digest, "store", e);
194 storeAppendPrintf(e, "\t added: %d rejected: %d ( %.2f %%) del-ed: %d\n",
195 sd_stats.add_count,
196 sd_stats.rej_count,
197 xpercent(sd_stats.rej_count, sd_stats.rej_count + sd_stats.add_count),
198 sd_stats.del_count);
199 storeAppendPrintf(e, "\t collisions: on add: %.2f %% on rej: %.2f %%\n",
200 xpercent(sd_stats.add_coll_count, sd_stats.add_count),
201 xpercent(sd_stats.rej_coll_count, sd_stats.rej_count));
d6fd3381 202 } else {
62e76326 203 storeAppendPrintf(e, "store digest: disabled.\n");
d6fd3381 204 }
62e76326 205
35a28a37 206#endif //USE_CACHE_DIGESTS
d6fd3381 207}
208
209/*
210 * LOCAL FUNCTIONS
211 */
212
213#if USE_CACHE_DIGESTS
214
c68e9c6b 215/* should we digest this entry? used by storeDigestAdd() */
216static int
217storeDigestAddable(const StoreEntry * e)
218{
219 /* add some stats! XXX */
220
bf8fe701 221 debugs(71, 6, "storeDigestAddable: checking entry, key: " << e->getMD5Text());
c68e9c6b 222
3900307b 223 /* check various entry flags (mimics StoreEntry::checkCachable XXX) */
62e76326 224
c68e9c6b 225 if (EBIT_TEST(e->flags, KEY_PRIVATE)) {
bf8fe701 226 debugs(71, 6, "storeDigestAddable: NO: private key");
62e76326 227 return 0;
c68e9c6b 228 }
62e76326 229
c68e9c6b 230 if (EBIT_TEST(e->flags, ENTRY_NEGCACHED)) {
bf8fe701 231 debugs(71, 6, "storeDigestAddable: NO: negative cached");
62e76326 232 return 0;
c68e9c6b 233 }
62e76326 234
c68e9c6b 235 if (EBIT_TEST(e->flags, RELEASE_REQUEST)) {
bf8fe701 236 debugs(71, 6, "storeDigestAddable: NO: release requested");
62e76326 237 return 0;
c68e9c6b 238 }
62e76326 239
c68e9c6b 240 if (e->store_status == STORE_OK && EBIT_TEST(e->flags, ENTRY_BAD_LENGTH)) {
bf8fe701 241 debugs(71, 6, "storeDigestAddable: NO: wrong content-length");
62e76326 242 return 0;
c68e9c6b 243 }
62e76326 244
c68e9c6b 245 /* do not digest huge objects */
47f6e231 246 if (e->swap_file_sz > (uint64_t )Config.Store.maxObjectSize) {
bf8fe701 247 debugs(71, 6, "storeDigestAddable: NO: too big");
62e76326 248 return 0;
c68e9c6b 249 }
62e76326 250
c68e9c6b 251 /* still here? check staleness */
252 /* Note: We should use the time of the next rebuild, not (cur_time+period) */
7e3ce7b9 253 if (refreshCheckDigest(e, Config.digest.rebuild_period)) {
4a7a3d56 254 debugs(71, 6, "storeDigestAdd: entry expires within " << Config.digest.rebuild_period << " secs, ignoring");
62e76326 255 return 0;
c68e9c6b 256 }
62e76326 257
17a80fc2 258 /*
259 * idea: how about also skipping very fresh (thus, potentially
260 * unstable) entries? Should be configurable through
261 * cd_refresh_pattern, of course.
262 */
263 /*
264 * idea: skip objects that are going to be purged before the next
265 * update.
266 */
c68e9c6b 267 return 1;
268}
269
d6fd3381 270static void
6168bccd 271storeDigestAdd(const StoreEntry * entry)
272{
6168bccd 273 assert(entry && store_digest);
c68e9c6b 274
275 if (storeDigestAddable(entry)) {
5db6bf73 276 ++sd_stats.add_count;
62e76326 277
6fc4e508 278 if (store_digest->contains(static_cast<const cache_key *>(entry->key)))
5db6bf73 279 ++sd_stats.add_coll_count;
62e76326 280
fbba122c 281 store_digest->add(static_cast<const cache_key *>(entry->key));
62e76326 282
bf8fe701 283 debugs(71, 6, "storeDigestAdd: added entry, key: " << entry->getMD5Text());
6168bccd 284 } else {
5db6bf73 285 ++sd_stats.rej_count;
62e76326 286
6fc4e508 287 if (store_digest->contains(static_cast<const cache_key *>(entry->key)))
5db6bf73 288 ++sd_stats.rej_coll_count;
6168bccd 289 }
12784378 290}
291
12784378 292/* rebuilds digest from scratch */
293static void
6168bccd 294storeDigestRebuildStart(void *datanotused)
8638fc66 295{
296 assert(store_digest);
12784378 297 /* prevent overlapping if rebuild schedule is too tight */
62e76326 298
12784378 299 if (sd_state.rebuild_lock) {
e0236918 300 debugs(71, DBG_IMPORTANT, "storeDigestRebuildStart: overlap detected, consider increasing rebuild period");
62e76326 301 return;
12784378 302 }
62e76326 303
12784378 304 sd_state.rebuild_lock = 1;
bf8fe701 305 debugs(71, 2, "storeDigestRebuildStart: rebuild #" << sd_state.rebuild_count + 1);
62e76326 306
6168bccd 307 if (sd_state.rewrite_lock) {
bf8fe701 308 debugs(71, 2, "storeDigestRebuildStart: waiting for Rewrite to finish.");
62e76326 309 return;
6168bccd 310 }
62e76326 311
6168bccd 312 storeDigestRebuildResume();
313}
314
831e953c
AJ
315/// \returns true if we actually resized the digest
316static bool
317storeDigestResize()
318{
319 const uint64_t cap = storeDigestCalcCap();
320 assert(store_digest);
321 uint64_t diff = abs(cap - store_digest->capacity);
322 debugs(71, 2, store_digest->capacity << " -> " << cap << "; change: " <<
323 diff << " (" << xpercentInt(diff, store_digest->capacity) << "%)" );
324 /* avoid minor adjustments */
325
326 if (diff <= store_digest->capacity / 10) {
327 debugs(71, 2, "small change, will not resize.");
328 return false;
329 } else {
330 debugs(71, 2, "big change, resizing.");
331 store_digest->updateCapacity(cap);
332 }
333 return true;
334}
335
6168bccd 336/* called be Rewrite to push Rebuild forward */
337static void
d6fd3381 338storeDigestRebuildResume(void)
6168bccd 339{
340 assert(sd_state.rebuild_lock);
341 assert(!sd_state.rewrite_lock);
2745fea5 342 sd_state.theSearch = Store::Root().search();
304b267e 343 /* resize or clear */
62e76326 344
304b267e 345 if (!storeDigestResize())
28faff32 346 store_digest->clear(); /* not clean()! */
62e76326 347
6168bccd 348 memset(&sd_stats, 0, sizeof(sd_stats));
62e76326 349
c43f5247 350 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep, NULL, 0.0, 1);
12784378 351}
352
353/* finishes swap out sequence for the digest; schedules next rebuild */
354static void
d6fd3381 355storeDigestRebuildFinish(void)
12784378 356{
357 assert(sd_state.rebuild_lock);
358 sd_state.rebuild_lock = 0;
5db6bf73 359 ++sd_state.rebuild_count;
bf8fe701 360 debugs(71, 2, "storeDigestRebuildFinish: done.");
7e3ce7b9 361 eventAdd("storeDigestRebuildStart", storeDigestRebuildStart, NULL, (double)
62e76326 362 Config.digest.rebuild_period, 1);
6168bccd 363 /* resume pending Rewrite if any */
62e76326 364
12784378 365 if (sd_state.rewrite_lock)
62e76326 366 storeDigestRewriteResume();
12784378 367}
368
369/* recalculate a few hash buckets per invocation; schedules next step */
370static void
371storeDigestRebuildStep(void *datanotused)
372{
c8f4eac4 373 /* TODO: call Store::Root().size() to determine this.. */
374 int count = Config.Store.objectsPerBucket * (int) ceil((double) store_hash_buckets *
375 (double) Config.digest.rebuild_chunk_percentage / 100.0);
12784378 376 assert(sd_state.rebuild_lock);
62e76326 377
bf8fe701 378 debugs(71, 3, "storeDigestRebuildStep: buckets: " << store_hash_buckets << " entries to check: " << count);
62e76326 379
c8f4eac4 380 while (count-- && !sd_state.theSearch->isDone() && sd_state.theSearch->next())
381 storeDigestAdd(sd_state.theSearch->currentItem());
62e76326 382
12784378 383 /* are we done ? */
c8f4eac4 384 if (sd_state.theSearch->isDone())
62e76326 385 storeDigestRebuildFinish();
12784378 386 else
62e76326 387 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep, NULL, 0.0, 1);
12784378 388}
389
12784378 390/* starts swap out sequence for the digest */
391static void
6168bccd 392storeDigestRewriteStart(void *datanotused)
12784378 393{
f206b652 394 RequestFlags flags;
462f66d2 395 char *url;
6168bccd 396 StoreEntry *e;
12784378 397
398 assert(store_digest);
399 /* prevent overlapping if rewrite schedule is too tight */
62e76326 400
12784378 401 if (sd_state.rewrite_lock) {
e0236918 402 debugs(71, DBG_IMPORTANT, "storeDigestRewrite: overlap detected, consider increasing rewrite period");
62e76326 403 return;
12784378 404 }
62e76326 405
bf8fe701 406 debugs(71, 2, "storeDigestRewrite: start rewrite #" << sd_state.rewrite_count + 1);
12784378 407 /* make new store entry */
a9cd5050 408 url = internalLocalUri("/squid-internal-periodic/", SBuf(StoreDigestFileName));
e857372a 409 flags.cachable = true;
c2a7cefd 410 e = storeCreateEntry(url, url, flags, Http::METHOD_GET);
28c60158 411 assert(e);
aa839030 412 sd_state.rewrite_lock = e;
bf8fe701 413 debugs(71, 3, "storeDigestRewrite: url: " << url << " key: " << e->getMD5Text());
c21ad0f5 414 HttpRequest *req = HttpRequest::CreateFromUrl(url);
b248c2a3
AJ
415 e->mem_obj->request = req;
416 HTTPMSGLOCK(e->mem_obj->request);
6168bccd 417 /* wait for rebuild (if any) to finish */
62e76326 418
6168bccd 419 if (sd_state.rebuild_lock) {
bf8fe701 420 debugs(71, 2, "storeDigestRewriteStart: waiting for rebuild to finish.");
62e76326 421 return;
6168bccd 422 }
62e76326 423
6168bccd 424 storeDigestRewriteResume();
425}
426
427static void
d6fd3381 428storeDigestRewriteResume(void)
6168bccd 429{
28c60158 430 StoreEntry *e;
6168bccd 431
432 assert(sd_state.rewrite_lock);
433 assert(!sd_state.rebuild_lock);
aa839030 434 e = sd_state.rewrite_lock;
12784378 435 sd_state.rewrite_offset = 0;
d46a87a8 436 EBIT_SET(e->flags, ENTRY_SPECIAL);
462f66d2 437 /* setting public key will purge old digest entry if any */
d88e3c49 438 e->setPublicKey();
462f66d2 439 /* fake reply */
06a5ae20 440 HttpReply *rep = new HttpReply;
955394ce 441 rep->setHeaders(Http::scOkay, "Cache Digest OK",
11992b6f
AJ
442 "application/cache-digest", (store_digest->mask_size + sizeof(sd_state.cblock)),
443 squid_curtime, (squid_curtime + Config.digest.rewrite_period) );
26ac0430 444 debugs(71, 3, "storeDigestRewrite: entry expires on " << rep->expires <<
bf8fe701 445 " (" << std::showpos << (int) (rep->expires - squid_curtime) << ")");
3900307b 446 e->buffer();
db237875 447 e->replaceHttpReply(rep);
12784378 448 storeDigestCBlockSwapOut(e);
3900307b 449 e->flush();
aa839030 450 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep, sd_state.rewrite_lock, 0.0, 1, false);
12784378 451}
452
453/* finishes swap out sequence for the digest; schedules next rewrite */
454static void
b644367b 455storeDigestRewriteFinish(StoreEntry * e)
12784378 456{
aa839030 457 assert(e == sd_state.rewrite_lock);
528b2c61 458 e->complete();
3900307b 459 e->timestampsSet();
26ac0430 460 debugs(71, 2, "storeDigestRewriteFinish: digest expires at " << e->expires <<
bf8fe701 461 " (" << std::showpos << (int) (e->expires - squid_curtime) << ")");
6168bccd 462 /* is this the write order? @?@ */
528b2c61 463 e->mem_obj->unlinkRequest();
acc5dc4c 464 e->unlock("storeDigestRewriteFinish");
feefc1d9 465 sd_state.rewrite_lock = NULL;
5db6bf73 466 ++sd_state.rewrite_count;
7e3ce7b9 467 eventAdd("storeDigestRewriteStart", storeDigestRewriteStart, NULL, (double)
62e76326 468 Config.digest.rewrite_period, 1);
6168bccd 469 /* resume pending Rebuild if any */
62e76326 470
6168bccd 471 if (sd_state.rebuild_lock)
62e76326 472 storeDigestRebuildResume();
12784378 473}
474
475/* swaps out one digest "chunk" per invocation; schedules next swap out */
476static void
52040193 477storeDigestSwapOutStep(void *data)
12784378 478{
aa839030 479 StoreEntry *e = static_cast<StoreEntry *>(data);
7e3ce7b9 480 int chunk_size = Config.digest.swapout_chunk_size;
aa839030 481 assert(e == sd_state.rewrite_lock);
12784378 482 assert(e);
12784378 483 /* _add_ check that nothing bad happened while we were waiting @?@ @?@ */
62e76326 484
831e953c 485 if (static_cast<uint32_t>(sd_state.rewrite_offset + chunk_size) > store_digest->mask_size)
62e76326 486 chunk_size = store_digest->mask_size - sd_state.rewrite_offset;
487
3900307b 488 e->append(store_digest->mask + sd_state.rewrite_offset, chunk_size);
62e76326 489
e4049756 490 debugs(71, 3, "storeDigestSwapOutStep: size: " << store_digest->mask_size <<
491 " offset: " << sd_state.rewrite_offset << " chunk: " <<
492 chunk_size << " bytes");
62e76326 493
12784378 494 sd_state.rewrite_offset += chunk_size;
62e76326 495
12784378 496 /* are we done ? */
831e953c 497 if (static_cast<uint32_t>(sd_state.rewrite_offset) >= store_digest->mask_size)
62e76326 498 storeDigestRewriteFinish(e);
12784378 499 else
aa839030 500 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep, data, 0.0, 1, false);
12784378 501}
502
503static void
b644367b 504storeDigestCBlockSwapOut(StoreEntry * e)
12784378 505{
12784378 506 memset(&sd_state.cblock, 0, sizeof(sd_state.cblock));
462f66d2 507 sd_state.cblock.ver.current = htons(CacheDigestVer.current);
508 sd_state.cblock.ver.required = htons(CacheDigestVer.required);
509 sd_state.cblock.capacity = htonl(store_digest->capacity);
510 sd_state.cblock.count = htonl(store_digest->count);
511 sd_state.cblock.del_count = htonl(store_digest->del_count);
512 sd_state.cblock.mask_size = htonl(store_digest->mask_size);
831e953c 513 sd_state.cblock.bits_per_entry = Config.digest.bits_per_entry;
6168bccd 514 sd_state.cblock.hash_func_count = (unsigned char) CacheDigestHashFuncCount;
3900307b 515 e->append((char *) &sd_state.cblock, sizeof(sd_state.cblock));
8638fc66 516}
517
d6fd3381 518#endif /* USE_CACHE_DIGESTS */
f53969cc 519