Date: Thu, 28 Jun 2012 22:17:05 +0200 From: Stefan Esser <se@freebsd.org> To: FreeBSD developers <hackers@FreeBSD.org> Subject: [PATCH] Fix for negative cacheing problem in NSCD Message-ID: <4FECBBC1.3000800@freebsd.org> In-Reply-To: <4FEAA3C1.2040807@freebsd.org> References: <CAGE5yCqM4OwUyvW1OW1vz7dP2G0zTQCU4P99bJdVASndg8SpAw@mail.gmail.com> <4FEAA3C1.2040807@freebsd.org>
next in thread | previous in thread | raw e-mail | index | archive | help
This is a multi-part message in MIME format. --------------040406000400060803010701 Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: 7bit The Name Service Cache Daemon (NSCD) is useful, but may get in your way if you modify data that is cached and have the cache return stale data until expiry. One common scenario is an installation script that creates the required user account, but first checks whether the account name is currently unused. The query result (user does not exist) will be cached by NSCD and will be returned even after the user has been created in the password database (or in LDAP or whatever). One solution is to signal changes of the database to NSCD, but that requires an interfaces between the programs that modify possibly cached data and NSCD. A short timeout for negative query results is possible, but limits the effectiveness of the cache. I propose a different solution, which completely fixes the behavior in the scenario given above: I have prepared a patch to NSCD that allows to specify a threshold for negative queries. Requests for negative entries are only answered from the cache after the specified number of attempts has been made to get the result from the underlying files/databases. A high number does effectively disable negative caching, but I doubt that more than 3 is useful under normal conditions (this allows for 2 failures before the negative entry is trusted). Rate limiting of queries with negative results continues to work with this patch, since at most the specified number of retries is made within the TTL of the cache entry. A patch against -CURRENT is attached. It does not change the default behavior in any way, so I think it would be save to commit to HEAD, with possible MFC (since nothing changes unless you use the new parameters). This patch has been tested with the hosts database and verified to work as expected in my test cases. To activate a negative query threshold you have to add lines like the following to ncsd.conf:: negative-confidence-threshold hosts 3 negative-confidence-threshold passwd 2 negative-confidence-threshold group 2 A value of 2 will generally be sufficient. In the scenario where you query the user DB to check that an account is free to use, there is exactly one pro query with negative reply, before the user is added and the query succeeds. A second probe would cache the negative reply. Please let me know, whether it helps in situations with wrong negative cacheing. I'm also interested in any problems you observe with the patch. It would be great, if a native speaker had a look at the patch for the man-page since I'm not convinced it is correct and easy to understand. And finally: Please suggest a better name for the parameter ... I do not like "negative-confidence-threshold", but did not find any better phrase. Regards, STefan PS: The patch contains an undocumented option to also set the positive confidence threshold. Entries are put into the cache but the original data sources are still queried, if the results do not match for at least the number of successive queries specified. Not sure whether this is useful, it was fall-out of the negative threshold change ... One possible use case is DNS based load-balancing. If NSCD is configured with "positive-confidence-threshold hosts 3" then it will require 3 identical replies for a host name before it is willing to cache that reply. Any host with fixed IP will soon be resolved from the cache, but hosts with changing IP will continue to be queried via DNS (until the specified number of identical results is received). Hmmm, I'm not sure whether this works as advertised, since any change in the returned data (including TTL) would cause the counter to start over ... PPS: I have not tested multipart queries; they should continue to work as before (with a fixed threshold of 1). I have not verified that this is the correct behavior, and have run out of time for today ... --------------040406000400060803010701 Content-Type: text/plain; charset=windows-1252; name="nscd-Negative-Threshold.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="nscd-Negative-Threshold.patch" Index: cachelib.h =================================================================== --- cachelib.h (revision 237713) +++ cachelib.h (working copy) @@ -92,6 +92,7 @@ size_t satisf_elemsize; /* if entry size is exceeded, * this number of elements will be left, * others will be deleted */ + int confidence_threshold; /* number matching replies required */ struct timeval max_lifetime; /* if 0 then no check is made */ enum cache_policy_t policy; /* policy used for transformations */ }; @@ -116,6 +117,7 @@ size_t value_size; struct cache_policy_item_ *fifo_policy_item; + int confidence; /* incremented for each verification */ }; struct cache_ht_item_ { Index: config.c =================================================================== --- config.c (revision 237713) +++ config.c (working copy) @@ -209,6 +209,7 @@ positive_params.max_elemsize = DEFAULT_POSITIVE_ELEMENTS_SIZE; positive_params.satisf_elemsize = DEFAULT_POSITIVE_ELEMENTS_SIZE / 2; positive_params.max_lifetime.tv_sec = DEFAULT_POSITIVE_LIFETIME; + positive_params.confidence_threshold = DEFAULT_POSITIVE_CONF_THRESH; positive_params.policy = CPT_LRU; memcpy(&negative_params, &positive_params, @@ -216,6 +217,7 @@ negative_params.max_elemsize = DEFAULT_NEGATIVE_ELEMENTS_SIZE; negative_params.satisf_elemsize = DEFAULT_NEGATIVE_ELEMENTS_SIZE / 2; negative_params.max_lifetime.tv_sec = DEFAULT_NEGATIVE_LIFETIME; + negative_params.confidence_threshold = DEFAULT_NEGATIVE_CONF_THRESH; negative_params.policy = CPT_FIFO; memset(&default_common_timeout, 0, sizeof(struct timeval)); Index: config.h =================================================================== --- config.h (revision 237713) +++ config.h (working copy) @@ -44,9 +44,11 @@ #define DEFAULT_POSITIVE_ELEMENTS_SIZE (2048) #define DEFAULT_POSITIVE_LIFETIME (3600) +#define DEFAULT_POSITIVE_CONF_THRESH (1) #define DEFAULT_NEGATIVE_ELEMENTS_SIZE (2048) #define DEFAULT_NEGATIVE_LIFETIME (60) +#define DEFAULT_NEGATIVE_CONF_THRESH (1) /* (2) ??? */ #define DEFAULT_MULTIPART_ELEMENTS_SIZE (1024 * 8) #define DEFAULT_MULITPART_SESSIONS_SIZE (1024) Index: nscd.conf.5 =================================================================== --- nscd.conf.5 (revision 237713) +++ nscd.conf.5 (working copy) @@ -107,6 +107,16 @@ The value should be a prime number for optimum performance. You should only change this value when the number of cached elements is significantly (5-10 times) greater than the default hash table size (257). +.It Va negative-confidence-threshold Oo Ar cachename Oc Op Ar value +The number of times a query must have failed before the cache accepts +that the element can not be found. This makes it possible to check for +the existence of an element, before this element is created in one of +the data sources queried by +.Xr nscd 8 . +If this parameter is at its default value of 1, then the negative +result of the first request will be considered valid for the duration +of the TTL. A value of 2 will require two failed queries for the same +element, before this information is returned by the cache. .It Va keep-hot-count Oo Ar cachename Oc Op Ar value The size limit of the cache with the given .Ar cachename . Index: parser.c =================================================================== --- parser.c (revision 237713) +++ parser.c (working copy) @@ -167,6 +167,41 @@ TRACE_OUT(set_negative_time_to_live); } +static void +set_positive_confidence_threshold(struct configuration *config, + const char *entry_name, int conf_thresh) +{ + struct configuration_entry *entry; + + TRACE_IN(set_positive_conf_thresh); + assert(conf_thresh > 0); + assert(entry_name != NULL); + + entry = find_create_entry(config, entry_name); + assert(entry != NULL); + memcpy(&entry->positive_cache_params.confidence_threshold, + &conf_thresh, sizeof(conf_thresh)); + + TRACE_OUT(set_positive_conf_thresh); +} + +static void +set_negative_confidence_threshold(struct configuration *config, + const char *entry_name, int conf_thresh) +{ + struct configuration_entry *entry; + + TRACE_IN(set_negative_conf_thresh); + assert(conf_thresh > 0); + assert(entry_name != NULL); + entry = find_create_entry(config, entry_name); + assert(entry != NULL); + memcpy(&entry->negative_cache_params.confidence_threshold, + &conf_thresh, sizeof(conf_thresh)); + + TRACE_OUT(set_negative_conf_thresh); +} + /* * Hot count is actually the elements size limit. */ @@ -393,6 +428,12 @@ fields[1], value); continue; } else if ((field_count == 3) && + (strcmp(fields[0], "positive-confidence-threshold") == 0) && + ((value = get_number(fields[2], 1, -1)) != -1)) { + set_positive_confidence_threshold(config, + fields[1], value); + continue; + } else if ((field_count == 3) && (strcmp(fields[0], "positive-policy") == 0) && (check_cachename(fields[1]) == 0) && ((value = get_policy(fields[2])) != -1)) { @@ -416,6 +457,12 @@ fields[1], value); continue; } else if ((field_count == 3) && + (strcmp(fields[0], "negative-confidence-threshold") == 0) && + ((value = get_number(fields[2], 1, -1)) != -1)) { + set_negative_confidence_threshold(config, + fields[1], value); + continue; + } else if ((field_count == 3) && (strcmp(fields[0], "negative-policy") == 0) && (check_cachename(fields[1]) == 0) && ((value = get_policy(fields[2])) != -1)) { Index: cachelib.c =================================================================== --- cachelib.c (revision 237713) +++ cachelib.c (working copy) @@ -726,6 +726,12 @@ TRACE_OUT(cache_read); return (-1); } + /* pretend that entry was not found if confidence is below threshold*/ + if (find_res->confidence < + common_entry->common_params.confidence_threshold) { + TRACE_OUT(cache_read); + return (-1); + } if ((common_entry->common_params.max_lifetime.tv_sec != 0) || (common_entry->common_params.max_lifetime.tv_usec != 0)) { @@ -826,6 +832,24 @@ item = HASHTABLE_GET_ENTRY(&(common_entry->items), hash); find_res = HASHTABLE_ENTRY_FIND(cache_ht_, item, &item_data); if (find_res != NULL) { + if (find_res->confidence < common_entry->common_params.confidence_threshold) { + /* duplicate entry is no error, if confidence is low */ + if ((find_res->value_size == value_size) && + (memcmp(find_res->value, value, value_size) == 0)) { + /* increase confidence on exact match (key and values) */ + find_res->confidence++; + } else { + /* create new entry with low confidence, if value changed */ + free(item_data.value); + item_data.value = malloc(value_size); + assert(item_data.value != NULL); + memcpy(item_data.value, value, value_size); + item_data.value_size = value_size; + find_res->confidence = 1; + } + TRACE_OUT(cache_write); + return (0); + } TRACE_OUT(cache_write); return (-1); } @@ -839,6 +863,8 @@ memcpy(item_data.value, value, value_size); item_data.value_size = value_size; + item_data.confidence = 1; + policy_item = common_entry->policies[0]->create_item_func(); policy_item->key = item_data.key; policy_item->key_size = item_data.key_size; --------------040406000400060803010701--
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?4FECBBC1.3000800>