1 /* caching.c : in-memory caching
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
29 #include "temp_serializer.h"
30 #include "../libsvn_fs/fs-loader.h"
32 #include "svn_config.h"
33 #include "svn_cache_config.h"
35 #include "svn_private_config.h"
37 #include "svn_pools.h"
39 #include "private/svn_debug.h"
40 #include "private/svn_subr_private.h"
42 /* Take the ORIGINAL string and replace all occurrences of ":" without
43 * limiting the key space. Allocate the result in POOL.
46 normalize_key_part(const char *original,
50 apr_size_t len = strlen(original);
51 svn_stringbuf_t *normalized = svn_stringbuf_create_ensure(len, pool);
53 for (i = 0; i < len; ++i)
58 case ':': svn_stringbuf_appendbytes(normalized, "%_", 2);
60 case '%': svn_stringbuf_appendbytes(normalized, "%%", 2);
62 default : svn_stringbuf_appendbyte(normalized, c);
66 return normalized->data;
69 /* *CACHE_TXDELTAS, *CACHE_FULLTEXTS, *CACHE_NODEPROPS flags will be set
70 according to FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to
73 Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
74 for temporary allocations. */
76 read_config(const char **cache_namespace,
77 svn_boolean_t *cache_txdeltas,
78 svn_boolean_t *cache_fulltexts,
79 svn_boolean_t *cache_nodeprops,
83 /* No cache namespace by default. I.e. all FS instances share the
84 * cached data. If you specify different namespaces, the data will
85 * share / compete for the same cache memory but keys will not match
86 * across namespaces and, thus, cached data will not be shared between
89 * Since the namespace will be concatenated with other elements to form
90 * the complete key prefix, we must make sure that the resulting string
91 * is unique and cannot be created by any other combination of elements.
94 = normalize_key_part(svn_hash__get_cstring(fs->config,
95 SVN_FS_CONFIG_FSFS_CACHE_NS,
99 /* Cache text deltas by default.
100 * They tend to be smaller and have finer granularity than fulltexts.
103 = svn_hash__get_bool(fs->config,
104 SVN_FS_CONFIG_FSFS_CACHE_DELTAS,
107 /* by default, cache fulltexts.
108 * Most SVN tools care about reconstructed file content.
109 * Thus, this is a reasonable default.
110 * SVN admin tools may set that to FALSE because fulltexts
111 * won't be re-used rendering the cache less effective
112 * by squeezing wanted data out.
115 = svn_hash__get_bool(fs->config,
116 SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
119 /* by default, cache nodeprops.
120 * Pre-1.10, this was controlled by the SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS
121 * configuration option which defaulted to TRUE.
124 = svn_hash__get_bool(fs->config,
125 SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS,
131 /* Implements svn_cache__error_handler_t
132 * This variant clears the error after logging it.
135 warn_and_continue_on_cache_errors(svn_error_t *err,
139 svn_fs_t *fs = baton;
140 (fs->warning)(fs->warning_baton, err);
141 svn_error_clear(err);
146 /* Implements svn_cache__error_handler_t
147 * This variant logs the error and passes it on to the callers.
150 warn_and_fail_on_cache_errors(svn_error_t *err,
154 svn_fs_t *fs = baton;
155 (fs->warning)(fs->warning_baton, err);
159 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
160 /* Baton to be used for the dump_cache_statistics() pool cleanup function, */
161 struct dump_cache_baton_t
163 /* the pool about to be cleaned up. Will be used for temp. allocations. */
166 /* the cache to dump the statistics for */
170 /* APR pool cleanup handler that will printf the statistics of the
171 cache referenced by the baton in BATON_VOID. */
173 dump_cache_statistics(void *baton_void)
175 struct dump_cache_baton_t *baton = baton_void;
177 apr_status_t result = APR_SUCCESS;
178 svn_cache__info_t info;
179 svn_string_t *text_stats;
180 apr_array_header_t *lines;
183 svn_error_t *err = svn_cache__get_info(baton->cache,
188 /* skip unused caches */
189 if (! err && (info.gets > 0 || info.sets > 0))
191 text_stats = svn_cache__format_info(&info, TRUE, baton->pool);
192 lines = svn_cstring_split(text_stats->data, "\n", FALSE, baton->pool);
194 for (i = 0; i < lines->nelts; ++i)
196 const char *line = APR_ARRAY_IDX(lines, i, const char *);
198 SVN_DBG(("%s\n", line));
203 /* process error returns */
206 result = err->apr_err;
207 svn_error_clear(err);
214 dump_global_cache_statistics(void *baton_void)
216 apr_pool_t *pool = baton_void;
218 svn_cache__info_t *info = svn_cache__membuffer_get_global_info(pool);
219 svn_string_t *text_stats = svn_cache__format_info(info, FALSE, pool);
220 apr_array_header_t *lines = svn_cstring_split(text_stats->data, "\n",
224 for (i = 0; i < lines->nelts; ++i)
226 const char *line = APR_ARRAY_IDX(lines, i, const char *);
228 SVN_DBG(("%s\n", line));
235 #endif /* SVN_DEBUG_CACHE_DUMP_STATS */
237 /* This function sets / registers the required callbacks for a given
238 * not transaction-specific CACHE object in FS, if CACHE is not NULL.
240 * All these svn_cache__t instances shall be handled uniformly. Unless
241 * ERROR_HANDLER is NULL, register it for the given CACHE in FS.
244 init_callbacks(svn_cache__t *cache,
246 svn_cache__error_handler_t error_handler,
251 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
253 /* schedule printing the access statistics upon pool cleanup,
254 * i.e. end of FSFS session.
256 struct dump_cache_baton_t *baton;
258 baton = apr_palloc(pool, sizeof(*baton));
260 baton->cache = cache;
262 apr_pool_cleanup_register(pool,
264 dump_cache_statistics,
265 apr_pool_cleanup_null);
269 SVN_ERR(svn_cache__set_error_handler(cache,
279 /* Sets *CACHE_P to cache instance based on provided options.
280 * Creates memcache if MEMCACHE is not NULL. Creates membuffer cache if
281 * MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
282 * MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
283 * otherwise. Use the given PRIORITY class for the new cache. If it
284 * is 0, then use the default priority class. HAS_NAMESPACE indicates
285 * whether we prefixed this cache instance with a namespace.
287 * Unless NO_HANDLER is true, register an error handler that reports errors
288 * as warnings to the FS warning callback.
290 * Cache is allocated in RESULT_POOL, temporaries in SCRATCH_POOL.
293 create_cache(svn_cache__t **cache_p,
294 svn_memcache_t *memcache,
295 svn_membuffer_t *membuffer,
297 apr_int64_t items_per_page,
298 svn_cache__serialize_func_t serializer,
299 svn_cache__deserialize_func_t deserializer,
302 apr_uint32_t priority,
303 svn_boolean_t has_namespace,
305 svn_boolean_t no_handler,
306 apr_pool_t *result_pool,
307 apr_pool_t *scratch_pool)
309 svn_cache__error_handler_t error_handler = no_handler
311 : warn_and_fail_on_cache_errors;
313 priority = SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY;
317 SVN_ERR(svn_cache__create_memcache(cache_p, memcache,
318 serializer, deserializer, klen,
319 prefix, result_pool));
320 error_handler = no_handler
322 : warn_and_continue_on_cache_errors;
326 /* We assume caches with namespaces to be relatively short-lived,
327 * i.e. their data will not be needed after a while. */
328 SVN_ERR(svn_cache__create_membuffer_cache(
329 cache_p, membuffer, serializer, deserializer,
330 klen, prefix, priority, FALSE, has_namespace,
331 result_pool, scratch_pool));
335 SVN_ERR(svn_cache__create_inprocess(
336 cache_p, serializer, deserializer, klen, pages,
337 items_per_page, FALSE, prefix, result_pool));
344 SVN_ERR(init_callbacks(*cache_p, fs, error_handler, result_pool));
350 svn_fs_fs__initialize_caches(svn_fs_t *fs,
353 fs_fs_data_t *ffd = fs->fsap_data;
354 const char *prefix = apr_pstrcat(pool,
356 "/", normalize_key_part(fs->path, pool),
359 svn_membuffer_t *membuffer;
360 svn_boolean_t no_handler = ffd->fail_stop;
361 svn_boolean_t cache_txdeltas;
362 svn_boolean_t cache_fulltexts;
363 svn_boolean_t cache_nodeprops;
364 const char *cache_namespace;
365 svn_boolean_t has_namespace;
367 /* Evaluating the cache configuration. */
368 SVN_ERR(read_config(&cache_namespace,
375 prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, SVN_VA_NULL);
376 has_namespace = strlen(cache_namespace) > 0;
378 membuffer = svn_cache__get_global_membuffer_cache();
380 /* General rules for assigning cache priorities:
382 * - Data that can be reconstructed from other elements has low prio
383 * (e.g. fulltexts etc.)
384 * - Index data required to find any of the other data has high prio
385 * (e.g. noderevs, L2P and P2L index pages)
386 * - everything else should use default prio
389 #ifdef SVN_DEBUG_CACHE_DUMP_STATS
391 /* schedule printing the global access statistics upon pool cleanup,
392 * i.e. when the repo instance gets closed / cleaned up.
395 apr_pool_cleanup_register(fs->pool,
397 dump_global_cache_statistics,
398 apr_pool_cleanup_null);
401 /* Make the cache for revision roots. For the vast majority of
402 * commands, this is only going to contain a few entries (svnadmin
403 * dump/verify is an exception here), so to reduce overhead let's
404 * try to keep it to just one page. I estimate each entry has about
405 * 130 bytes of overhead (svn_revnum_t key, ID struct, and the cache_entry);
406 * the default pool size is 8192, so about a fifty should fit comfortably.
408 SVN_ERR(create_cache(&(ffd->rev_root_id_cache),
412 svn_fs_fs__serialize_id,
413 svn_fs_fs__deserialize_id,
414 sizeof(svn_revnum_t),
415 apr_pstrcat(pool, prefix, "RRI", SVN_VA_NULL),
422 /* Rough estimate: revision DAG nodes have size around 1kBytes, so
423 * let's put 8 on a page. */
424 SVN_ERR(create_cache(&(ffd->rev_node_cache),
428 svn_fs_fs__dag_serialize,
429 svn_fs_fs__dag_deserialize,
431 apr_pstrcat(pool, prefix, "DAG", SVN_VA_NULL),
432 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
438 /* 1st level DAG node cache */
439 ffd->dag_node_cache = svn_fs_fs__create_dag_cache(fs->pool);
441 /* Very rough estimate: 1K per directory. */
442 SVN_ERR(create_cache(&(ffd->dir_cache),
446 svn_fs_fs__serialize_dir_entries,
447 svn_fs_fs__deserialize_dir_entries,
448 sizeof(pair_cache_key_t),
449 apr_pstrcat(pool, prefix, "DIR", SVN_VA_NULL),
450 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
456 /* 8 kBytes per entry (1000 revs / shared, one file offset per rev).
457 Covering about 8 pack files gives us an "o.k." hit rate. */
458 SVN_ERR(create_cache(&(ffd->packed_offset_cache),
462 svn_fs_fs__serialize_manifest,
463 svn_fs_fs__deserialize_manifest,
464 sizeof(svn_revnum_t),
465 apr_pstrcat(pool, prefix, "PACK-MANIFEST",
467 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
473 /* initialize node revision cache, if caching has been enabled */
474 SVN_ERR(create_cache(&(ffd->node_revision_cache),
477 2, 16, /* ~500 byte / entry; 32 entries total */
478 svn_fs_fs__serialize_node_revision,
479 svn_fs_fs__deserialize_node_revision,
480 sizeof(pair_cache_key_t),
481 apr_pstrcat(pool, prefix, "NODEREVS", SVN_VA_NULL),
482 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
488 /* initialize representation header cache, if caching has been enabled */
489 SVN_ERR(create_cache(&(ffd->rep_header_cache),
492 1, 200, /* ~40 bytes / entry; 200 entries total */
493 svn_fs_fs__serialize_rep_header,
494 svn_fs_fs__deserialize_rep_header,
495 sizeof(pair_cache_key_t),
496 apr_pstrcat(pool, prefix, "REPHEADER", SVN_VA_NULL),
497 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
503 /* initialize node change list cache, if caching has been enabled */
504 SVN_ERR(create_cache(&(ffd->changes_cache),
507 1, 8, /* 1k / entry; 8 entries total, rarely used */
508 svn_fs_fs__serialize_changes,
509 svn_fs_fs__deserialize_changes,
510 sizeof(pair_cache_key_t),
511 apr_pstrcat(pool, prefix, "CHANGES", SVN_VA_NULL),
518 /* if enabled, cache revprops */
519 SVN_ERR(create_cache(&(ffd->revprop_cache),
522 8, 20, /* ~400 bytes / entry, capa for ~2 packs */
523 svn_fs_fs__serialize_revprops,
524 svn_fs_fs__deserialize_revprops,
525 sizeof(pair_cache_key_t),
526 apr_pstrcat(pool, prefix, "REVPROP", SVN_VA_NULL),
527 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
528 TRUE, /* contents is short-lived */
533 /* if enabled, cache fulltext and other derived information */
536 SVN_ERR(create_cache(&(ffd->fulltext_cache),
539 0, 0, /* Do not use the inprocess cache */
540 /* Values are svn_stringbuf_t */
542 sizeof(pair_cache_key_t),
543 apr_pstrcat(pool, prefix, "TEXT", SVN_VA_NULL),
544 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
550 SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
553 0, 0, /* Do not use the inprocess cache */
554 svn_fs_fs__serialize_mergeinfo,
555 svn_fs_fs__deserialize_mergeinfo,
557 apr_pstrcat(pool, prefix, "MERGEINFO",
565 SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
568 0, 0, /* Do not use the inprocess cache */
569 /* Values are svn_stringbuf_t */
572 apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
582 ffd->fulltext_cache = NULL;
583 ffd->mergeinfo_cache = NULL;
584 ffd->mergeinfo_existence_cache = NULL;
587 /* if enabled, cache node properties */
590 SVN_ERR(create_cache(&(ffd->properties_cache),
593 0, 0, /* Do not use the inprocess cache */
594 svn_fs_fs__serialize_properties,
595 svn_fs_fs__deserialize_properties,
596 sizeof(pair_cache_key_t),
597 apr_pstrcat(pool, prefix, "PROP",
599 SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
607 ffd->properties_cache = NULL;
610 /* if enabled, cache text deltas and their combinations */
613 SVN_ERR(create_cache(&(ffd->raw_window_cache),
616 0, 0, /* Do not use the inprocess cache */
617 svn_fs_fs__serialize_raw_window,
618 svn_fs_fs__deserialize_raw_window,
619 sizeof(window_cache_key_t),
620 apr_pstrcat(pool, prefix, "RAW_WINDOW",
622 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
628 SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
631 0, 0, /* Do not use the inprocess cache */
632 svn_fs_fs__serialize_txdelta_window,
633 svn_fs_fs__deserialize_txdelta_window,
634 sizeof(window_cache_key_t),
635 apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
637 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
643 SVN_ERR(create_cache(&(ffd->combined_window_cache),
646 0, 0, /* Do not use the inprocess cache */
647 /* Values are svn_stringbuf_t */
649 sizeof(window_cache_key_t),
650 apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
652 SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
660 ffd->txdelta_window_cache = NULL;
661 ffd->combined_window_cache = NULL;
664 SVN_ERR(create_cache(&(ffd->l2p_header_cache),
667 8, 16, /* entry size varies but we must cover a
668 reasonable number of rev / pack files
669 to allow for delta chains to be walked
671 svn_fs_fs__serialize_l2p_header,
672 svn_fs_fs__deserialize_l2p_header,
673 sizeof(pair_cache_key_t),
674 apr_pstrcat(pool, prefix, "L2P_HEADER",
676 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
681 SVN_ERR(create_cache(&(ffd->l2p_page_cache),
684 8, 16, /* entry size varies but we must cover a
685 reasonable number of rev / pack files
686 to allow for delta chains to be walked
688 svn_fs_fs__serialize_l2p_page,
689 svn_fs_fs__deserialize_l2p_page,
690 sizeof(svn_fs_fs__page_cache_key_t),
691 apr_pstrcat(pool, prefix, "L2P_PAGE",
693 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
698 SVN_ERR(create_cache(&(ffd->p2l_header_cache),
701 4, 1, /* Large entries. Rarely used. */
702 svn_fs_fs__serialize_p2l_header,
703 svn_fs_fs__deserialize_p2l_header,
704 sizeof(pair_cache_key_t),
705 apr_pstrcat(pool, prefix, "P2L_HEADER",
707 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
712 SVN_ERR(create_cache(&(ffd->p2l_page_cache),
715 4, 1, /* Variably sized entries. Rarely used. */
716 svn_fs_fs__serialize_p2l_page,
717 svn_fs_fs__deserialize_p2l_page,
718 sizeof(svn_fs_fs__page_cache_key_t),
719 apr_pstrcat(pool, prefix, "P2L_PAGE",
721 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
730 /* Baton to be used for the remove_txn_cache() pool cleanup function, */
731 struct txn_cleanup_baton_t
733 /* the cache to reset */
734 svn_cache__t *txn_cache;
736 /* the position where to reset it */
737 svn_cache__t **to_reset;
739 /* pool that TXN_CACHE was allocated in */
740 apr_pool_t *txn_pool;
742 /* pool that the FS containing the TO_RESET pointer was allocator */
746 /* Forward declaration. */
748 remove_txn_cache_fs(void *baton_void);
750 /* APR pool cleanup handler that will reset the cache pointer given in
751 BATON_VOID when the TXN_POOL gets cleaned up. */
753 remove_txn_cache_txn(void *baton_void)
755 struct txn_cleanup_baton_t *baton = baton_void;
757 /* be careful not to hurt performance by resetting newer txn's caches. */
758 if (*baton->to_reset == baton->txn_cache)
760 /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
761 *baton->to_reset = NULL;
764 /* It's cleaned up now. Prevent double cleanup. */
765 apr_pool_cleanup_kill(baton->fs_pool,
767 remove_txn_cache_fs);
772 /* APR pool cleanup handler that will reset the cache pointer given in
773 BATON_VOID when the FS_POOL gets cleaned up. */
775 remove_txn_cache_fs(void *baton_void)
777 struct txn_cleanup_baton_t *baton = baton_void;
779 /* be careful not to hurt performance by resetting newer txn's caches. */
780 if (*baton->to_reset == baton->txn_cache)
782 /* This is equivalent to calling svn_fs_fs__reset_txn_caches(). */
783 *baton->to_reset = NULL;
786 /* It's cleaned up now. Prevent double cleanup. */
787 apr_pool_cleanup_kill(baton->txn_pool,
789 remove_txn_cache_txn);
794 /* This function sets / registers the required callbacks for a given
795 * transaction-specific *CACHE object in FS, if CACHE is not NULL and
796 * a no-op otherwise. In particular, it will ensure that *CACHE gets
797 * reset to NULL upon POOL or FS->POOL destruction latest.
800 init_txn_callbacks(svn_fs_t *fs,
801 svn_cache__t **cache,
806 struct txn_cleanup_baton_t *baton;
808 baton = apr_palloc(pool, sizeof(*baton));
809 baton->txn_cache = *cache;
810 baton->to_reset = cache;
811 baton->txn_pool = pool;
812 baton->fs_pool = fs->pool;
814 /* If any of these pools gets cleaned, we must reset the cache.
815 * We don't know which one will get cleaned up first, so register
816 * cleanup actions for both and during the cleanup action, unregister
817 * the respective other action. */
818 apr_pool_cleanup_register(pool,
820 remove_txn_cache_txn,
821 apr_pool_cleanup_null);
822 apr_pool_cleanup_register(fs->pool,
825 apr_pool_cleanup_null);
830 svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
834 fs_fs_data_t *ffd = fs->fsap_data;
837 /* We don't support caching for concurrent transactions in the SAME
838 * FSFS session. Maybe, you forgot to clean POOL. */
839 if (ffd->txn_dir_cache != NULL || ffd->concurrent_transactions)
841 ffd->txn_dir_cache = NULL;
842 ffd->concurrent_transactions = TRUE;
847 /* Transaction content needs to be carefully prefixed to virtually
848 eliminate any chance for conflicts. The (repo, txn_id) pair
849 should be unique but if the filesystem format doesn't store the
850 global transaction ID via the txn-current file, and a transaction
851 fails, it might be possible to start a new transaction later that
852 receives the same id. For such older formats, throw in an uuid as
853 well -- just to be sure. */
854 if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
855 prefix = apr_pstrcat(pool,
862 prefix = apr_pstrcat(pool,
866 ":", svn_uuid_generate(pool),
870 /* create a txn-local directory cache */
871 SVN_ERR(create_cache(&ffd->txn_dir_cache,
873 svn_cache__get_global_membuffer_cache(),
875 svn_fs_fs__serialize_txndir_entries,
876 svn_fs_fs__deserialize_dir_entries,
879 SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
880 TRUE, /* The TXN-ID is our namespace. */
885 /* reset the transaction-specific cache if the pool gets cleaned up. */
886 init_txn_callbacks(fs, &(ffd->txn_dir_cache), pool);
892 svn_fs_fs__reset_txn_caches(svn_fs_t *fs)
894 /* we can always just reset the caches. This may degrade performance but
895 * can never cause in incorrect behavior. */
897 fs_fs_data_t *ffd = fs->fsap_data;
898 ffd->txn_dir_cache = NULL;