Josef,<br><br>I totally agree.&nbsp; There are a number of strategies to implement this on the client&#39;s side.&nbsp; Putting this policy in the server is weird.&nbsp; Once it&#39;s in, then people would want to change all those magic tunables at runtime, per key, etc.&nbsp; Including changing the ramp-up curve of likelihood of miss between soft and hard, et.c<br>
<br>Brad<br><br><div class="gmail_quote">On Sat, Jun 14, 2008 at 10:18 AM, Josef Finsel &lt;<a href="mailto:carpdeus@gmail.com">carpdeus@gmail.com</a>&gt; wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Olivier,<br><br>Sorry, I just realized I was only responding to you, not the list as well.<br><br>My thoughts are that this is a good patch but I&#39;m not sure it&#39;s appropriate for the server. There are many ways of handling this on the client side. The biggest concern I have with it is that it would add additional processing on every get. Not a great deal, perhaps but every little bit adds up over time, especially on larger sites.<br>

<br>If you&#39;re concern is related to cache stampedes, there are many ways to avoid them on the client. And you can even implement code to handle refreshing data before it expires so that it only affects one client instance.<br>

<br>But that&#39;s just my thoughts on this.<br><br>Josef<br clear="all"><br>&quot;If you see a whole thing - it seems that it&#39;s always beautiful. Planets, lives... But up close a world&#39;s all dirt and rocks. And day to day, life&#39;s a hard job, you get tired, you lose the pattern.&quot;<br>

Ursula K. Le Guin
<br><div><div></div><div class="Wj3C7c"><br><div class="gmail_quote">On Sat, Jun 14, 2008 at 10:37 AM, Olivier Poitrey &lt;<a href="mailto:rs@dailymotion.com" target="_blank">rs@dailymotion.com</a>&gt; wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">

If a key is very popular in the cache, when this key will expire,<br>
a lot of different clients will get an expired answer from the cache<br>
at the same time and they will gather the data from the source all<br>
at the same time. This isn&#39;t efficient, especially when the computation<br>
of the information is expensive and can takes some time.<br>
<br>
This patch adds an argument to memcached to activate soft expiration<br>
period to keys with expiration. For instance, if the soft timeout is<br>
set to 30 seconds and a key have a TTL of 50 seconds, after 20 seconds<br>
of life in the cache, memcached will start to return empty answers for<br>
this key for a small part of the queries. The more the key will be close<br>
to its real expiration time, the more important will be the propability<br>
for memcached to return an empty answer.<br>
<br>
This way, if a key is very popular, a small number of clients will<br>
see it as expired while every others will see its value as normal.<br>
This small number of clients will gather the data from the source<br>
and update the key value and chances are they will finish this<br>
before the real key expiration time.<br>
<br>
NOTE: this soft timeout is a server wide value, future version<br>
of this patch could extend the protocol in order to let clients<br>
choose this value on a per key basis.<br>
---<br>
&nbsp;items.c &nbsp; &nbsp; | &nbsp; &nbsp;7 +++++++<br>
&nbsp;memcached.c | &nbsp; 14 +++++++++++++-<br>
&nbsp;memcached.h | &nbsp; &nbsp;1 +<br>
&nbsp;3 files changed, 21 insertions(+), 1 deletions(-)<br>
<br>
diff --git a/items.c b/items.c<br>
index 88c92f6..b15325d 100644<br>
--- a/items.c<br>
+++ b/items.c<br>
@@ -434,6 +434,13 @@ item *do_item_get_notedeleted(const char *key, const size_t nkey, bool *delete_l<br>
 &nbsp; &nbsp; &nbsp; &nbsp; it = NULL;<br>
 &nbsp; &nbsp; }<br>
<br>
+ &nbsp; &nbsp;if (it != NULL &amp;&amp; settings.soft_timeout &gt; 0 &amp;&amp; it-&gt;exptime != 0 &amp;&amp;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;(it-&gt;exptime - current_time) &lt;= settings.soft_timeout) {<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;if (rand() % settings.soft_timeout &gt;= (it-&gt;exptime - current_time)) {<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;it = NULL;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;}<br>
+ &nbsp; &nbsp;}<br>
+<br>
 &nbsp; &nbsp; if (it != NULL) {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; it-&gt;refcount++;<br>
 &nbsp; &nbsp; &nbsp; &nbsp; DEBUG_REFCNT(it, &#39;+&#39;);<br>
diff --git a/memcached.c b/memcached.c<br>
index 27456be..6efb47d 100644<br>
--- a/memcached.c<br>
+++ b/memcached.c<br>
@@ -177,6 +177,7 @@ static void settings_init(void) {<br>
 &nbsp; &nbsp; settings.managed = false;<br>
 &nbsp; &nbsp; settings.factor = 1.25;<br>
 &nbsp; &nbsp; settings.chunk_size = 48; &nbsp; &nbsp; &nbsp; &nbsp; /* space for a modest key and value */<br>
+ &nbsp; &nbsp;settings.soft_timeout = 0; &nbsp; &nbsp; &nbsp; &nbsp;/* number of seconds before hard timeout the soft timeout will start */<br>
&nbsp;#ifdef USE_THREADS<br>
 &nbsp; &nbsp; settings.num_threads = 4;<br>
&nbsp;#else<br>
@@ -1065,6 +1066,7 @@ static void process_stat(conn *c, token_t *tokens, const size_t ntokens) {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; pos += sprintf(pos, &quot;STAT bytes_written %llu\r\n&quot;, stats.bytes_written);<br>
 &nbsp; &nbsp; &nbsp; &nbsp; pos += sprintf(pos, &quot;STAT limit_maxbytes %llu\r\n&quot;, (uint64_t) settings.maxbytes);<br>
 &nbsp; &nbsp; &nbsp; &nbsp; pos += sprintf(pos, &quot;STAT threads %u\r\n&quot;, settings.num_threads);<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;pos += sprintf(pos, &quot;STAT soft_timeout %d\r\n&quot;, settings.soft_timeout);<br>
 &nbsp; &nbsp; &nbsp; &nbsp; pos += sprintf(pos, &quot;END&quot;);<br>
 &nbsp; &nbsp; &nbsp; &nbsp; STATS_UNLOCK();<br>
 &nbsp; &nbsp; &nbsp; &nbsp; out_string(c, temp);<br>
@@ -2671,6 +2673,13 @@ static void usage(void) {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;-P &lt;file&gt; &nbsp; &nbsp; save PID in &lt;file&gt;, only used with -d option\n&quot;<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;-f &lt;factor&gt; &nbsp; chunk size growth factor, default 1.25\n&quot;<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;-n &lt;bytes&gt; &nbsp; &nbsp;minimum space allocated for key+value+flags, default 48\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot;-S &lt;num&gt; &nbsp; &nbsp; &nbsp;Number of seconds before the hard expires to start using\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;soft expiration. When an item is in soft expiration status,\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;memcached will tell to SOME clients the item is expired before\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;its actual expiration. The more the item is close to its actual expiration\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time, the more the probability that memcache tell a client the item is\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;expired. This allow smooth cache refill when a popular key is about to\n&quot;<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &quot; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;expire.\n&quot;<br>
<br>
&nbsp;#if defined(HAVE_GETPAGESIZES) &amp;&amp; defined(HAVE_MEMCNTL)<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;-L &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Try to use large memory pages (if available). Increasing\n&quot;<br>
@@ -2861,7 +2870,7 @@ int main (int argc, char **argv) {<br>
 &nbsp; &nbsp; setbuf(stderr, NULL);<br>
<br>
 &nbsp; &nbsp; /* process arguments */<br>
- &nbsp; &nbsp;while ((c = getopt(argc, argv, &quot;a:bp:s:U:m:Mc:khirvdl:u:P:f:s:n:t:D:L&quot;)) != -1) {<br>
+ &nbsp; &nbsp;while ((c = getopt(argc, argv, &quot;a:bp:s:U:m:Mc:khirvdl:u:P:f:s:n:t:D:S:L&quot;)) != -1) {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; switch (c) {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; case &#39;a&#39;:<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* access for unix domain socket, as octal mask (like chmod)*/<br>
@@ -2952,6 +2961,9 @@ int main (int argc, char **argv) {<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;<br>
&nbsp;#endif<br>
+ &nbsp; &nbsp; &nbsp; &nbsp;case &#39;S&#39;:<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;settings.soft_timeout = atoi(optarg);<br>
+ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break;<br>
 &nbsp; &nbsp; &nbsp; &nbsp; default:<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fprintf(stderr, &quot;Illegal argument \&quot;%c\&quot;\n&quot;, c);<br>
 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return 1;<br>
diff --git a/memcached.h b/memcached.h<br>
index ffbe880..ae11c72 100644<br>
--- a/memcached.h<br>
+++ b/memcached.h<br>
@@ -96,6 +96,7 @@ struct settings {<br>
 &nbsp; &nbsp; int num_threads; &nbsp; &nbsp; &nbsp; &nbsp;/* number of libevent threads to run */<br>
 &nbsp; &nbsp; char prefix_delimiter; &nbsp;/* character that marks a key prefix (for stats) */<br>
 &nbsp; &nbsp; int detail_enabled; &nbsp; &nbsp; /* nonzero if we&#39;re collecting detailed stats */<br>
+ &nbsp; &nbsp;int soft_timeout;<br>
&nbsp;};<br>
<br>
&nbsp;extern struct stats stats;<br>
<font color="#888888">--<br>
<a href="http://1.5.4.4" target="_blank">1.5.4.4</a><br>
<br>
</font></blockquote></div><br>
</div></div></blockquote></div><br>