2 * $Id: store_digest.cc,v 1.31 1998/11/12 06:28:28 wessels Exp $
4 * DEBUG: section 71 Store Digest Manager
5 * AUTHOR: Alex Rousskov
7 * SQUID Internet Object Cache http://squid.nlanr.net/Squid/
8 * ----------------------------------------------------------
10 * Squid is the result of efforts by numerous individuals from the
11 * Internet community. Development is led by Duane Wessels of the
12 * National Laboratory for Applied Network Research and funded by the
13 * National Science Foundation. Squid is Copyrighted (C) 1998 by
14 * Duane Wessels and the University of California San Diego. Please
15 * see the COPYRIGHT file for full details. Squid incorporates
16 * software developed and/or copyrighted by other sources. Please see
17 * the CREDITS file for full details.
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
37 * TODO: We probably do not track all the cases when
38 * storeDigestNoteStoreReady() must be called; this may prevent
39 * storeDigestRebuild/write schedule to be activated
51 StoreDigestCBlock cblock
;
52 int rebuild_lock
; /* bucket number */
53 StoreEntry
*rewrite_lock
; /* store entry with the digest */
61 int del_count
; /* #store entries deleted from store_digest */
62 int del_lost_count
; /* #store entries not found in store_digest on delete */
63 int add_count
; /* #store entries accepted to store_digest */
64 int add_coll_count
; /* #accepted entries that collided with existing ones */
65 int rej_count
; /* #store entries not accepted to store_digest */
66 int rej_coll_count
; /* #not accepted entries that collided with existing ones */
70 * local constants (many of these are good candidates for SquidConfig
73 /* #bits per entry in store digest */
74 static const int StoreDigestBitsPerEntry
= 5;
75 /* how often we want to rebuild the digest, in seconds */
76 static const time_t StoreDigestRebuildPeriod
= 60 * 60;
77 /* how often we want to rewrite the digest after rebuild, in seconds */
78 static const int StoreDigestRewritePeriod
= 60 * 60;
79 /* how many bytes to swap out at a time */
80 static const int StoreDigestSwapOutChunkSize
= SM_PAGE_SIZE
;
81 /* portion (0,1] of a hash table to be rescanned at a time */
82 static const double StoreDigestRebuildChunkPercent
= 0.10;
85 static StoreDigestState sd_state
;
86 static StoreDigestStats sd_stats
;
88 /* local prototypes */
89 static void storeDigestRebuildStart(void *datanotused
);
90 static void storeDigestRebuildResume(void);
91 static void storeDigestRebuildFinish(void);
92 static void storeDigestRebuildStep(void *datanotused
);
93 static void storeDigestRewriteStart(void *);
94 static void storeDigestRewriteResume(void);
95 static void storeDigestRewriteFinish(StoreEntry
* e
);
96 static EVH storeDigestSwapOutStep
;
97 static void storeDigestCBlockSwapOut(StoreEntry
* e
);
98 static int storeDigestCalcCap(void);
99 static int storeDigestResize(void);
100 static void storeDigestAdd(const StoreEntry
*);
102 #endif /* USE_CACHE_DIGESTS */
109 storeDigestInit(void)
111 #if USE_CACHE_DIGESTS
112 const int cap
= storeDigestCalcCap();
113 store_digest
= cacheDigestCreate(cap
, StoreDigestBitsPerEntry
);
114 debug(71, 1) ("Local cache digest enabled; rebuild/rewrite every %d/%d sec\n",
115 StoreDigestRebuildPeriod
, StoreDigestRewritePeriod
);
116 memset(&sd_state
, 0, sizeof(sd_state
));
117 cachemgrRegister("store_digest", "Store Digest",
118 storeDigestReport
, 0, 1);
121 debug(71, 3) ("Local cache digest is 'off'\n");
125 /* called when store_rebuild completes */
127 storeDigestNoteStoreReady(void)
129 #if USE_CACHE_DIGESTS
130 storeDigestRebuildStart(NULL
);
131 storeDigestRewriteStart(NULL
);
136 storeDigestDel(const StoreEntry
* entry
)
138 #if USE_CACHE_DIGESTS
139 assert(entry
&& store_digest
);
140 debug(71, 6) ("storeDigestDel: checking entry, key: %s\n",
141 storeKeyText(entry
->key
));
142 if (!EBIT_TEST(entry
->flags
, KEY_PRIVATE
)) {
143 if (!cacheDigestTest(store_digest
, entry
->key
)) {
144 sd_stats
.del_lost_count
++;
145 debug(71, 6) ("storeDigestDel: lost entry, key: %s url: %s\n",
146 storeKeyText(entry
->key
), storeUrl(entry
));
148 sd_stats
.del_count
++;
149 cacheDigestDel(store_digest
, entry
->key
);
150 debug(71, 6) ("storeDigestDel: deled entry, key: %s\n",
151 storeKeyText(entry
->key
));
158 storeDigestReport(StoreEntry
* e
)
160 #if USE_CACHE_DIGESTS
162 cacheDigestReport(store_digest
, "store", e
);
163 storeAppendPrintf(e
, "\t added: %d rejected: %d ( %.2f %%) del-ed: %d\n",
166 xpercent(sd_stats
.rej_count
, sd_stats
.rej_count
+ sd_stats
.add_count
),
168 storeAppendPrintf(e
, "\t collisions: on add: %.2f %% on rej: %.2f %%\n",
169 xpercent(sd_stats
.add_coll_count
, sd_stats
.add_count
),
170 xpercent(sd_stats
.rej_coll_count
, sd_stats
.rej_count
));
172 storeAppendPrintf(e
, "store digest: disabled.\n");
181 #if USE_CACHE_DIGESTS
183 /* should we digest this entry? used by storeDigestAdd() */
185 storeDigestAddable(const StoreEntry
* e
)
187 /* add some stats! XXX */
189 debug(71, 6) ("storeDigestAddable: checking entry, key: %s\n",
190 storeKeyText(e
->key
));
192 /* check various entry flags (mimics storeCheckCachable XXX) */
193 if (!EBIT_TEST(e
->flags
, ENTRY_CACHABLE
)) {
194 debug(71, 6) ("storeDigestAddable: NO: not cachable\n");
197 if (EBIT_TEST(e
->flags
, KEY_PRIVATE
)) {
198 debug(71, 6) ("storeDigestAddable: NO: private key\n");
201 if (EBIT_TEST(e
->flags
, ENTRY_NEGCACHED
)) {
202 debug(71, 6) ("storeDigestAddable: NO: negative cached\n");
205 if (EBIT_TEST(e
->flags
, RELEASE_REQUEST
)) {
206 debug(71, 6) ("storeDigestAddable: NO: release requested\n");
209 if (e
->store_status
== STORE_OK
&& EBIT_TEST(e
->flags
, ENTRY_BAD_LENGTH
)) {
210 debug(71, 6) ("storeDigestAddable: NO: wrong content-length\n");
213 /* do not digest huge objects */
214 if (e
->swap_file_sz
> Config
.Store
.maxObjectSize
) {
215 debug(71, 6) ("storeDigestAddable: NO: too big\n");
218 /* still here? check staleness */
219 /* Note: We should use the time of the next rebuild, not (cur_time+period) */
220 if (refreshCheckDigest(e
, StoreDigestRebuildPeriod
)) {
221 debug(71, 6) ("storeDigestAdd: entry expires within %d secs, ignoring\n",
222 StoreDigestRebuildPeriod
);
225 /* idea: how about also skipping very fresh (thus, potentially unstable)
226 * entries? Should be configurable through cd_refresh_pattern, of course */
232 storeDigestAdd(const StoreEntry
* entry
)
234 assert(entry
&& store_digest
);
236 if (storeDigestAddable(entry
)) {
237 sd_stats
.add_count
++;
238 if (cacheDigestTest(store_digest
, entry
->key
))
239 sd_stats
.add_coll_count
++;
240 cacheDigestAdd(store_digest
, entry
->key
);
241 debug(71, 6) ("storeDigestAdd: added entry, key: %s\n",
242 storeKeyText(entry
->key
));
244 sd_stats
.rej_count
++;
245 if (cacheDigestTest(store_digest
, entry
->key
))
246 sd_stats
.rej_coll_count
++;
250 /* rebuilds digest from scratch */
252 storeDigestRebuildStart(void *datanotused
)
254 assert(store_digest
);
255 /* prevent overlapping if rebuild schedule is too tight */
256 if (sd_state
.rebuild_lock
) {
257 debug(71, 1) ("storeDigestRebuildStart: overlap detected, consider increasing rebuild period\n");
260 sd_state
.rebuild_lock
= 1;
261 debug(71, 2) ("storeDigestRebuildStart: rebuild #%d\n", sd_state
.rebuild_count
+ 1);
262 if (sd_state
.rewrite_lock
) {
263 debug(71, 2) ("storeDigestRebuildStart: waiting for Rewrite to finish.\n");
266 storeDigestRebuildResume();
269 /* called be Rewrite to push Rebuild forward */
271 storeDigestRebuildResume(void)
273 assert(sd_state
.rebuild_lock
);
274 assert(!sd_state
.rewrite_lock
);
275 sd_state
.rebuild_offset
= 0;
276 /* resize or clear */
277 if (!storeDigestResize())
278 cacheDigestClear(store_digest
); /* not clean()! */
279 memset(&sd_stats
, 0, sizeof(sd_stats
));
280 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep
, NULL
, 0.0, 1);
283 /* finishes swap out sequence for the digest; schedules next rebuild */
285 storeDigestRebuildFinish(void)
287 assert(sd_state
.rebuild_lock
);
288 sd_state
.rebuild_lock
= 0;
289 sd_state
.rebuild_count
++;
290 debug(71, 2) ("storeDigestRebuildFinish: done.\n");
291 eventAdd("storeDigestRebuildStart", storeDigestRebuildStart
, NULL
, (double) StoreDigestRebuildPeriod
, 1);
292 /* resume pending Rewrite if any */
293 if (sd_state
.rewrite_lock
)
294 storeDigestRewriteResume();
297 /* recalculate a few hash buckets per invocation; schedules next step */
299 storeDigestRebuildStep(void *datanotused
)
301 int bcount
= (int) ceil(store_hash_buckets
* StoreDigestRebuildChunkPercent
);
302 assert(sd_state
.rebuild_lock
);
303 if (sd_state
.rebuild_offset
+ bcount
> store_hash_buckets
)
304 bcount
= store_hash_buckets
- sd_state
.rebuild_offset
;
305 debug(71, 3) ("storeDigestRebuildStep: buckets: %d offset: %d chunk: %d buckets\n",
306 store_hash_buckets
, sd_state
.rebuild_offset
, bcount
);
308 hash_link
*link_ptr
= hash_get_bucket(store_table
, sd_state
.rebuild_offset
);
309 for (; link_ptr
; link_ptr
= link_ptr
->next
) {
310 storeDigestAdd((StoreEntry
*) link_ptr
);
312 sd_state
.rebuild_offset
++;
315 if (sd_state
.rebuild_offset
>= store_hash_buckets
)
316 storeDigestRebuildFinish();
318 eventAdd("storeDigestRebuildStep", storeDigestRebuildStep
, NULL
, 0.0, 1);
322 /* starts swap out sequence for the digest */
324 storeDigestRewriteStart(void *datanotused
)
330 assert(store_digest
);
331 /* prevent overlapping if rewrite schedule is too tight */
332 if (sd_state
.rewrite_lock
) {
333 debug(71, 1) ("storeDigestRewrite: overlap detected, consider increasing rewrite period\n");
336 debug(71, 2) ("storeDigestRewrite: start rewrite #%d\n", sd_state
.rewrite_count
+ 1);
337 /* make new store entry */
338 url
= internalLocalUri("/squid-internal-periodic/", StoreDigestUrlPath
);
339 flags
= null_request_flags
;
341 sd_state
.rewrite_lock
= e
= storeCreateEntry(url
, url
, flags
, METHOD_GET
);
342 assert(sd_state
.rewrite_lock
);
343 cbdataAdd(sd_state
.rewrite_lock
, MEM_DONTFREE
);
344 debug(71, 3) ("storeDigestRewrite: url: %s key: %s\n", url
, storeKeyText(e
->key
));
345 e
->mem_obj
->request
= requestLink(urlParse(METHOD_GET
, url
));
346 /* wait for rebuild (if any) to finish */
347 if (sd_state
.rebuild_lock
) {
348 debug(71, 2) ("storeDigestRewriteStart: waiting for rebuild to finish.\n");
351 storeDigestRewriteResume();
355 storeDigestRewriteResume(void)
357 StoreEntry
*e
= sd_state
.rewrite_lock
;
359 assert(sd_state
.rewrite_lock
);
360 assert(!sd_state
.rebuild_lock
);
361 sd_state
.rewrite_offset
= 0;
362 EBIT_SET(e
->flags
, ENTRY_SPECIAL
);
363 /* setting public key will purge old digest entry if any */
364 storeSetPublicKey(e
);
366 httpReplyReset(e
->mem_obj
->reply
);
367 httpReplySetHeaders(e
->mem_obj
->reply
, 1.0, 200, "Cache Digest OK",
368 "application/cache-digest", store_digest
->mask_size
+ sizeof(sd_state
.cblock
),
369 squid_curtime
, squid_curtime
+ StoreDigestRewritePeriod
);
370 debug(71, 3) ("storeDigestRewrite: entry expires on %s\n", mkrfc1123(e
->mem_obj
->reply
->expires
));
372 httpReplySwapOut(e
->mem_obj
->reply
, e
);
373 storeDigestCBlockSwapOut(e
);
375 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep
, sd_state
.rewrite_lock
, 0.0, 1);
378 /* finishes swap out sequence for the digest; schedules next rewrite */
380 storeDigestRewriteFinish(StoreEntry
* e
)
382 assert(e
== sd_state
.rewrite_lock
);
384 storeTimestampsSet(e
);
385 debug(71, 2) ("storeDigestRewriteFinish: digest expires on %s (%d)\n",
386 mkrfc1123(e
->expires
), e
->expires
);
387 /* is this the write order? @?@ */
388 requestUnlink(e
->mem_obj
->request
);
389 e
->mem_obj
->request
= NULL
;
390 storeUnlockObject(e
);
392 * note, it won't really get free()'d here because we used
393 * MEM_DONTFREE in the call to cbdataAdd().
395 cbdataFree(sd_state
.rewrite_lock
);
396 sd_state
.rewrite_lock
= e
= NULL
;
397 sd_state
.rewrite_count
++;
398 eventAdd("storeDigestRewriteStart", storeDigestRewriteStart
, NULL
, (double) StoreDigestRewritePeriod
, 1);
399 /* resume pending Rebuild if any */
400 if (sd_state
.rebuild_lock
)
401 storeDigestRebuildResume();
404 /* swaps out one digest "chunk" per invocation; schedules next swap out */
406 storeDigestSwapOutStep(void *data
)
408 StoreEntry
*e
= data
;
409 int chunk_size
= StoreDigestSwapOutChunkSize
;
411 assert(e
== sd_state
.rewrite_lock
);
412 /* _add_ check that nothing bad happened while we were waiting @?@ @?@ */
413 if (sd_state
.rewrite_offset
+ chunk_size
> store_digest
->mask_size
)
414 chunk_size
= store_digest
->mask_size
- sd_state
.rewrite_offset
;
415 storeAppend(e
, store_digest
->mask
+ sd_state
.rewrite_offset
, chunk_size
);
416 debug(71, 3) ("storeDigestSwapOutStep: size: %d offset: %d chunk: %d bytes\n",
417 store_digest
->mask_size
, sd_state
.rewrite_offset
, chunk_size
);
418 sd_state
.rewrite_offset
+= chunk_size
;
420 if (sd_state
.rewrite_offset
>= store_digest
->mask_size
)
421 storeDigestRewriteFinish(e
);
423 eventAdd("storeDigestSwapOutStep", storeDigestSwapOutStep
, e
, 0.0, 1);
427 storeDigestCBlockSwapOut(StoreEntry
* e
)
429 memset(&sd_state
.cblock
, 0, sizeof(sd_state
.cblock
));
430 sd_state
.cblock
.ver
.current
= htons(CacheDigestVer
.current
);
431 sd_state
.cblock
.ver
.required
= htons(CacheDigestVer
.required
);
432 sd_state
.cblock
.capacity
= htonl(store_digest
->capacity
);
433 sd_state
.cblock
.count
= htonl(store_digest
->count
);
434 sd_state
.cblock
.del_count
= htonl(store_digest
->del_count
);
435 sd_state
.cblock
.mask_size
= htonl(store_digest
->mask_size
);
436 sd_state
.cblock
.bits_per_entry
= (unsigned char) StoreDigestBitsPerEntry
;
437 sd_state
.cblock
.hash_func_count
= (unsigned char) CacheDigestHashFuncCount
;
438 storeAppend(e
, (char *) &sd_state
.cblock
, sizeof(sd_state
.cblock
));
441 /* calculates digest capacity */
443 storeDigestCalcCap(void)
446 * To-Do: Bloom proved that the optimal filter utilization is 50% (half of
447 * the bits are off). However, we do not have a formula to calculate the
448 * number of _entries_ we want to pre-allocate for.
450 const int hi_cap
= Config
.Swap
.maxSize
/ Config
.Store
.avgObjectSize
;
451 const int lo_cap
= 1 + store_swap_size
/ Config
.Store
.avgObjectSize
;
452 const int e_count
= memInUse(MEM_STOREENTRY
);
453 int cap
= e_count
? e_count
: hi_cap
;
454 debug(71, 2) ("storeDigestCalcCap: have: %d, want %d entries; limits: [%d, %d]\n",
455 e_count
, cap
, lo_cap
, hi_cap
);
458 /* do not enforce hi_cap limit, average-based estimation may be wrong
465 /* returns true if we actually resized the digest */
467 storeDigestResize(void)
469 const int cap
= storeDigestCalcCap();
471 assert(store_digest
);
472 diff
= abs(cap
- store_digest
->capacity
);
473 debug(71, 2) ("storeDigestResize: %d -> %d; change: %d (%d%%)\n",
474 store_digest
->capacity
, cap
, diff
,
475 xpercentInt(diff
, store_digest
->capacity
));
476 /* avoid minor adjustments */
477 if (diff
<= store_digest
->capacity
/ 10) {
478 debug(71, 2) ("storeDigestResize: small change, will not resize.\n");
481 debug(71, 2) ("storeDigestResize: big change, resizing.\n");
482 cacheDigestChangeCap(store_digest
, cap
);
487 #endif /* USE_CACHE_DIGESTS */