New feature: refresh [patch]...
Sean Chittenden
sean at chittenden.org
Wed Nov 24 12:15:28 PST 2004
Howdy. Sessions, the bane of most web application developers'
existence. How to tell when they expire and how many of them happen in
a day. Most people use log analysis to figure this out in the form of
IP tracking, but this falls apart with proxies, nevermind that it
happens only once a day. One could log every hit in a database... but
we won't go there, that's just atrociously slow and doesn't scale.
memcached nearly solves this in its current form, but it uses an
absolute expiration (which is what's needed for nearly everything but
sessions).
That said, I've added a new command to memcached(8) called "refresh".
refresh behaves *exactly* the same as get, except that it refreshes the
expiration of the item based off of the original expiration time
provided by the client. For example:
> % telnet localhost 11211
> add ses 0 10 1
> 1
> STORED
> refresh ses
> VALUE ses 0 1
> 1
> END
> [wait 9 seconds]
> refresh ses
> VALUE ses 0 1
> 1
> END
> [wait 9 seconds]
> refresh ses
> VALUE ses 0 1
> 1
> END
> [etc...]
> quit
On cache lookup failure, assuming that memcached hasn't pushed the
session out, the client can now log a new session. It's not perfect,
but it's infinitely better than the current state of affairs. I'm sure
there are possibly other uses for this beyond sessions, but that's what
I had in mind when I thumped this addition out. If someone has a
better name, please let me know. :) I have updated the C api to
include this new functionality, but haven't posted it yet because this
hasn't been accepted into the tree yet. If someone wants the updated C
API, please let me know offlist.
There is a small problem with the stats (I think memory is being
clobbered somewhere... stack problem?). It looks like the stats
structure is getting stomped on someplace, but I haven't figured out
where yet. :( Operationally, this works without a hitch and I've
benched the hell out of it without incident. It just looks like a
stats problem to me, though I haven't figured out how or why. Here's a
transcript:
> % telnet localhost 11211
> stats
> STAT pid 6233
> STAT uptime 6
> STAT time 1101325249
> STAT version 1.1.11
> STAT rusage_user 0.010000
> STAT rusage_system 0.030000
> STAT curr_items 0
> STAT total_items 0
> STAT bytes 0
> STAT curr_connections 1
> STAT total_connections 2
> STAT connection_structures 2
Does it preallocate a connection structure? There is only one
connection to the backend at the moment. Just an oddity that's always
caught my attention.
> STAT cmd_get 0
> STAT cmd_refresh 0
> STAT cmd_set 0
> STAT get_hits 0
> STAT get_misses 0
> STAT refresh_hits 0
> STAT refresh_misses 0
> STAT bytes_read 7
"stats\n" should only be six bytes, not seven. Something seems fishy
here... use of sizeof being mixed up with strlen(3)?
> STAT bytes_written 0
> STAT limit_maxbytes 67108864
> END
> add foo 0 10 1
> 1
> STORED
> get foo
> VALUE foo 0 1
> 1
> END
> stats
> STAT pid 6233
> STAT uptime 18
> STAT time 1101325261
> STAT version 1.1.11
> STAT rusage_user 0.020000
> STAT rusage_system 0.040000
> STAT curr_items 1
> STAT total_items 1
> STAT bytes 43
> STAT curr_connections 1
> STAT total_connections 2
> STAT connection_structures 2
> STAT cmd_get 1
> STAT cmd_refresh 0
> STAT cmd_set 1
> STAT get_hits 1
> STAT get_misses 0
> STAT refresh_hits 0
> STAT refresh_misses 0
> STAT bytes_read 42
> STAT bytes_written 502
> STAT limit_maxbytes 67108864
> END
... so far so good...
> refresh foo
> VALUE foo 0 1
> 1
> END
> stats
> STAT pid 6233
> STAT uptime 26
> STAT time 1101325269
> STAT version 1.1.11
> STAT rusage_user 0.020000
> STAT rusage_system 0.040000
> STAT curr_items 1
> STAT total_items 1
> STAT bytes 43
> STAT curr_connections 1
> STAT total_connections 2
> STAT connection_structures 2
> STAT cmd_get 1
> STAT cmd_refresh 2
??? That's not right... only one refresh command has been issued.
> STAT cmd_set 1
> STAT get_hits 1
> STAT get_misses 0
> STAT refresh_hits 1
> STAT refresh_misses 1
And there haven't been any cache misses. :(
> STAT bytes_read 62
> STAT bytes_written 1001
> STAT limit_maxbytes 67108864
> END
> quit
But if you look at the patch, it's stupid simple and 1:1 emulates how
"get" updates its stats. I'm hard pressed to think it's a bug in my
patch, but it very likely could be. I just don't see how it's
possible. Anyway, here's the patch. Feedback is very welcome. I
threw in a few whitespace cleanups along the way. cvs di -b is
probably going to be a bit more useful than cvs di to see the actual
changes w/o whitespace foo clogging up the unified output. If you add:
;; (setq font-lock-mode-maximum-decoration t)
;; (require 'font-lock)
(if (>= emacs-major-version 21)
(setq-default show-trailing-whitespace t))
to your ~/.emacs file and you'll see what I was cleaning up. -sc
-------------- next part --------------
Index: memcached.h
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/memcached.h,v
retrieving revision 1.21
diff -u -b -r1.21 memcached.h
--- memcached.h 24 Feb 2004 23:42:02 -0000 1.21
+++ memcached.h 24 Nov 2004 20:11:58 -0000
@@ -15,9 +15,12 @@
unsigned int total_conns;
unsigned int conn_structs;
unsigned int get_cmds;
+ unsigned int refresh_cmds;
unsigned int set_cmds;
unsigned int get_hits;
unsigned int get_misses;
+ unsigned int refresh_hits;
+ unsigned int refresh_misses;
time_t started; /* when the process was started */
unsigned long long bytes_read;
unsigned long long bytes_written;
@@ -51,6 +54,7 @@
int nbytes; /* size of data */
time_t time; /* least recent access */
time_t exptime; /* expire time */
+ time_t expire; /* Original expiration from client: used by refresh */
unsigned char it_flags; /* ITEM_* above */
unsigned char slabs_clsid;
unsigned char nkey; /* key length, with terminating null and padding */
@@ -77,6 +81,8 @@
#define NREAD_ADD 1
#define NREAD_SET 2
#define NREAD_REPLACE 3
+#define NREAD_GET 4
+#define NREAD_REFRESH 5
typedef struct {
int sfd;
Index: memcached.c
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/memcached.c,v
retrieving revision 1.52
diff -u -b -r1.52 memcached.c
--- memcached.c 23 Nov 2004 12:25:40 -0000 1.52
+++ memcached.c 24 Nov 2004 20:11:58 -0000
@@ -73,15 +73,14 @@
}
void stats_init(void) {
- stats.curr_items = stats.total_items = stats.curr_conns = stats.total_conns = stats.conn_structs = 0;
- stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = 0;
- stats.curr_bytes = stats.bytes_read = stats.bytes_written = 0;
+ memset(&stats, 0, sizeof(struct stats));
stats.started = time(0);
}
void stats_reset(void) {
stats.total_items = stats.total_conns = 0;
- stats.get_cmds = stats.set_cmds = stats.get_hits = stats.get_misses = 0;
+ stats.get_cmds = stats.refresh_cmds = stats.set_cmds = stats.get_hits =
+ stats.refresh_hits = stats.get_misses = stats.refresh_misses = 0;
stats.bytes_read = stats.bytes_written = 0;
}
@@ -338,10 +337,13 @@
pos += sprintf(pos, "STAT curr_connections %u\r\n", stats.curr_conns - 1); /* ignore listening conn */
pos += sprintf(pos, "STAT total_connections %u\r\n", stats.total_conns);
pos += sprintf(pos, "STAT connection_structures %u\r\n", stats.conn_structs);
- pos += sprintf(pos, "STAT cmd_get %u\r\n", stats.get_cmds);
pos += sprintf(pos, "STAT cmd_set %u\r\n", stats.set_cmds);
+ pos += sprintf(pos, "STAT cmd_get %u\r\n", stats.get_cmds);
pos += sprintf(pos, "STAT get_hits %u\r\n", stats.get_hits);
pos += sprintf(pos, "STAT get_misses %u\r\n", stats.get_misses);
+ pos += sprintf(pos, "STAT cmd_refresh %u\r\n", stats.refresh_cmds);
+ pos += sprintf(pos, "STAT refresh_hits %u\r\n", stats.refresh_hits);
+ pos += sprintf(pos, "STAT refresh_misses %u\r\n", stats.refresh_misses);
pos += sprintf(pos, "STAT bytes_read %llu\r\n", stats.bytes_read);
pos += sprintf(pos, "STAT bytes_written %llu\r\n", stats.bytes_written);
pos += sprintf(pos, "STAT limit_maxbytes %u\r\n", settings.maxbytes);
@@ -516,7 +518,6 @@
out_string(c, "CLIENT_ERROR bad command line format");
return;
}
- expire = realtime(expire);
it = item_alloc(key, flags, expire, len+2);
if (it == 0) {
out_string(c, "SERVER_ERROR out of memory");
@@ -597,7 +598,8 @@
return;
}
- if (strncmp(command, "get ", 4) == 0) {
+ if ((strncmp(command, "get ", 4) == 0 && (comm = NREAD_GET)) ||
+ (strncmp(command, "refresh ", 8) == 0 && (comm = NREAD_REFRESH))) {
char *start = command + 4;
char key[251];
@@ -608,7 +610,14 @@
while(sscanf(start, " %250s%n", key, &next) >= 1) {
start+=next;
+ switch (comm) {
+ case NREAD_GET:
stats.get_cmds++;
+ break;
+ case NREAD_REFRESH:
+ stats.refresh_cmds++;
+ break;
+ }
it = assoc_find(key);
if (it && (it->it_flags & ITEM_DELETED)) {
it = 0;
@@ -630,12 +639,30 @@
c->ilist = new_list;
} else break;
}
+
+ item_update(it);
+ switch (comm) {
+ case NREAD_GET:
stats.get_hits++;
+ break;
+ case NREAD_REFRESH:
+ it->exptime = realtime(it->expire);
+ stats.refresh_hits++;
+ break;
+ }
it->refcount++;
- item_update(it);
*(c->ilist + i) = it;
i++;
- } else stats.get_misses++;
+ } else {
+ switch (comm) {
+ case NREAD_GET:
+ stats.get_misses++;
+ break;
+ case NREAD_REFRESH:
+ stats.refresh_misses++;
+ break;
+ }
+ }
}
c->icurr = c->ilist;
c->ileft = i;
Index: items.c
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/items.c,v
retrieving revision 1.23
diff -u -b -r1.23 items.c
--- items.c 13 Sep 2004 22:31:53 -0000 1.23
+++ items.c 24 Nov 2004 20:11:58 -0000
@@ -96,7 +96,8 @@
it->nkey = len;
it->nbytes = nbytes;
strcpy(ITEM_key(it), key);
- it->exptime = exptime;
+ it->expire = exptime;
+ it->exptime = realtime(exptime);
it->flags = flags;
return it;
}
Index: doc/protocol.txt
===================================================================
RCS file: /home/cvspub/wcmtools/memcached/doc/protocol.txt,v
retrieving revision 1.3
diff -u -b -r1.3 protocol.txt
--- doc/protocol.txt 1 Dec 2003 21:43:35 -0000 1.3
+++ doc/protocol.txt 24 Nov 2004 20:11:58 -0000
@@ -177,13 +177,19 @@
The retrieval command looks like this:
-get <key>*\r\n
+<cmd> <key>*\r\n
+
+- <cmd> can be either "get" or "refresh"
- <key>* means one or more key strings separated by whitespace.
After this command, the client expects zero or more items, each of
-which is received as a text line followed by a data block. After all
-the items have been transmitted, the server sends the string
+which is received as a text line followed by a data block. Unlike the
+"get" <cmd>, "refresh" resets the expiration time to be now + whatever
+the expiration time was set to (ex: assuming memcached(8) is not
+memory constrained, a cache lookup failure means a session expired or
+doesn't exist). After all the items have been transmitted, the server
+sends the string:
"END\r\n"
@@ -224,9 +230,10 @@
the client wishes the server to refuse "add" and "replace" commands
with this key. For this amount of item, the item is put into a
delete queue, which means that it won't possible to retrieve it by
- the "get" command, but "add" and "replace" command with this key
- will also fail (the "set" command will succeed, however). After the
- time passes, the item is finally deleted from server memory.
+ the "get" or "refresh" command, but "add" and "replace" command with
+ this key will also fail (the "set" command will succeed,
+ however). After the time passes, the item is finally deleted from
+ server memory.
The parameter <time> is optional, and, if absent, defaults to 0
(which means that the item will be deleted immediately and further
@@ -341,12 +348,17 @@
the server started running
connection_structures 32u Number of connection structures allocated
by the server
-cmd_get 32u Cumulative number of retrieval requests
+cmd_get 32u Cumulative number of "get" requests
+cmd_refresh 32u Cumulative number of "refresh" requests
cmd_set 32u Cumulative number of storage requests
get_hits 32u Number of keys that have been requested and
- found present
+ found present with the "get" command
get_misses 32u Number of items that have been requested
- and not found
+ and not found with "get" command
+refresh_hits 32u Number of keys that have been requested and
+ found present with the "refresh" command
+refresh_misses 32u Number of items that have been requested
+ and not found with the "refresh" command
bytes_read 64u Total number of bytes read by this server
from network
bytes_written 64u Total number of bytes sent by this server to
-------------- next part --------------
--
Sean Chittenden
More information about the memcached
mailing list