[PATCH 1/2] Add mdelete command to delete several keys at a time.

Tomash Brechko tomash.brechko at gmail.com
Tue Nov 13 13:28:50 UTC 2007


---
 trunk/server/doc/protocol.txt |   29 ++++++++-
 trunk/server/memcached.c      |  152 +++++++++++++++++++++++++++++++++++++---
 trunk/server/memcached.h      |    2 +-
 trunk/server/t/mdelete.t      |   54 +++++++++++++++
 4 files changed, 223 insertions(+), 14 deletions(-)
 create mode 100644 trunk/server/t/mdelete.t

diff --git a/trunk/server/doc/protocol.txt b/trunk/server/doc/protocol.txt
index 589d25c..3bd6318 100644
--- a/trunk/server/doc/protocol.txt
+++ b/trunk/server/doc/protocol.txt
@@ -243,7 +243,8 @@ deleted by a client).
 Deletion
 --------
 
-The command "delete" allows for explicit deletion of items:
+The command "delete" allows for explicit deletion of items (one at a
+time):
 
 delete <key> [<time>] [noreply]\r\n
 
@@ -272,6 +273,32 @@ The response line to this command can be one of:
 - "NOT_FOUND\r\n" to indicate that the item with this key was not
   found.
 
+The command "mdelete" allows for explicit deletion of items (several
+at a time):
+
+mdelete <time> [no]reply <key>*\r\n
+
+- <time> has the same meaning as for 'delete', and applies to all
+  keys.
+
+- "noreply" has the same meaning as for 'delete'.  Since this
+  parameter is mandatory, there is also its opposite "reply".
+
+- <key>* means one or more key strings separated by whitespace.
+
+The response of this command is one line for every key plus the end
+line:
+
+  VALUE key1 DELETED\r\n
+  VALUE key2 NOT_FOUND\r\n
+  VALUE very_..._long_..._key CLIENT_ERROR key too long\r\n
+  ...
+  END\r\n
+
+There may be other per-key errors.  Alternatively this command may
+return one line error response (that doesn't begin with VALUE), as
+'delete' does.
+
 See the "flush_all" command below for immediate invalidation
 of all existing items.
 
diff --git a/trunk/server/memcached.c b/trunk/server/memcached.c
index 974eb81..4c42235 100644
--- a/trunk/server/memcached.c
+++ b/trunk/server/memcached.c
@@ -812,6 +812,10 @@ typedef struct token_s {
 
 #define MAX_TOKENS 8
 
+
+#define STRING_WITH_LEN(str)  (str), (sizeof(str) - 1)
+
+
 /*
  * Tokenize the command string by replacing whitespace with '\0' and update
  * the token array tokens with pointer to start of each token and length.
@@ -1430,6 +1434,31 @@ char *do_add_delta(item *it, const bool incr, const int64_t delta, char *buf) {
     return buf;
 }
 
+static inline
+const char *
+do_delete_command(conn *c, const char *key, size_t nkey, time_t exptime)
+{
+    item *it;
+
+    if (settings.detail_enabled) {
+        stats_prefix_record_delete(key);
+    }
+
+    it = item_get(key, nkey);
+    if (it) {
+        if (exptime == 0) {
+            item_unlink(it);
+            item_remove(it);      /* release our reference */
+            return "DELETED";
+        } else {
+            /* our reference will be transfered to the delete queue */
+            return defer_delete(it, exptime);
+        }
+    } else {
+        return "NOT_FOUND";
+    }
+}
+
 static void process_delete_command(conn *c, token_t *tokens, const size_t ntokens) {
     char *key;
     size_t nkey;
@@ -1460,31 +1489,128 @@ static void process_delete_command(conn *c, token_t *tokens, const size_t ntoken
         }
     }
 
-    if (settings.detail_enabled) {
-        stats_prefix_record_delete(key);
+    out_string(c, do_delete_command(c, key, nkey, exptime));
+}
+
+
+/*
+  Multi-delete command.  The syntax is
+
+    mdelete <time> [no]reply <key>*\r\n
+
+  The reply contains one line for every key with the syntax
+
+    VALUE <key> <result>\r\n
+
+  where <result> is DELETED|NOT_FOUND|CLIENT_ERROR <msg>|SERVER_ERROR <msg>.
+  There is END\r\n line at the end.
+
+  Alternatively this command may reply with a single line error
+  response.
+*/
+static
+void
+process_mdelete_command(conn *c, token_t *tokens, size_t ntokens)
+{
+    token_t *key_token = tokens + 3;
+    time_t exptime;
+
+    assert(c != NULL);
+
+    if (strcmp(tokens[2].value, "noreply") == 0) {
+        c->noreply = true;
+    } else if (strcmp(tokens[2].value, "reply") != 0) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
     }
 
-    it = item_get(key, nkey);
-    if (it) {
-        if (exptime == 0) {
-            item_unlink(it);
-            item_remove(it);      /* release our reference */
-            out_string(c, "DELETED");
+    if (settings.managed && !test_bucket(c))
+        return;
+
+    exptime = strtol(tokens[1].value, NULL, 10);
+    if (errno == ERANGE) {
+        out_string(c, "CLIENT_ERROR bad command line format");
+        return;
+    }
+
+    do {
+        while (key_token->length != 0) {
+            const char *key = key_token->value;
+            size_t nkey = key_token->length;
+            const char *reply;
+            size_t len;
+
+            if (!c->noreply) {
+                if (add_iov(c, STRING_WITH_LEN("VALUE ")) != 0 ||
+                    add_iov(c, key, nkey) != 0 ||
+                    add_iov(c, STRING_WITH_LEN(" ")) != 0) {
+                    break;
+                }
+            }
+
+            if (key_token->length > KEY_MAX_LENGTH) {
+                /*
+                  Note: unlike get command, we do not stop processing
+                  on error.  Instead, there's one-to-one
+                  correspondence between the key and the line in the
+                  reply.
+                */
+                if (!c->noreply) {
+                    if (add_iov(c,
+                                STRING_WITH_LEN("CLIENT_ERROR"
+                                                " key too long\r\n")) != 0) {
+                        break;
+                    }
+                }
+                key_token++;
+                continue;
+            }
+            reply = do_delete_command(c, key, nkey, exptime);
+            if (!c->noreply) {
+                len = strlen(reply);
+                if (add_iov(c, reply, len) != 0 ||
+                    add_iov(c, STRING_WITH_LEN("\r\n")) != 0) {
+                    break;
+                }
+            }
+            key_token++;
+        }
+
+        /*
+         * If the command string hasn't been fully processed, get the next set
+         * of tokens.
+         */
+        if (key_token->value != NULL) {
+            ntokens = tokenize_command(key_token->value, tokens, MAX_TOKENS);
+            key_token = tokens;
+        }
+    } while (key_token->value != NULL);
+
+    if (!c->noreply) {
+        if (key_token->value == NULL &&
+            add_iov(c, STRING_WITH_LEN("END\r\n")) == 0) {
+            conn_set_state(c, conn_write);
         } else {
-            /* our reference will be transfered to the delete queue */
-            out_string(c, defer_delete(it, exptime));
+            /*
+              If the loop was terminated because of out-of-memory, it
+              is not reliable to add END\r\n to the buffer, because it
+              might not end in \r\n.  So we send SERVER_ERROR instead.
+            */
+            out_string(c, "SERVER_ERROR out of memory");
         }
     } else {
-        out_string(c, "NOT_FOUND");
+        c->noreply = false;
+        conn_set_state(c, conn_read);
     }
 }
 
+
 /*
  * Adds an item to the deferred-delete list so it can be reaped later.
  *
  * Returns the result to send to the client.
  */
-char *do_defer_delete(item *it, time_t exptime)
+const char *do_defer_delete(item *it, time_t exptime)
 {
     if (delcurr >= deltotal) {
         item **new_delete = realloc(todelete, sizeof(item *) * deltotal * 2);
@@ -1711,6 +1837,8 @@ static void process_command(conn *c, char *command) {
 #endif
     } else if ((ntokens == 3 || ntokens == 4) && (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0)) {
         process_verbosity_command(c, tokens, ntokens);
+    } else if (ntokens >= 5 && strcmp(tokens[COMMAND_TOKEN].value, "mdelete") == 0) {
+        process_mdelete_command(c, tokens, ntokens);
     } else {
         out_string(c, "ERROR");
     }
diff --git a/trunk/server/memcached.h b/trunk/server/memcached.h
index 501e431..f8fc827 100644
--- a/trunk/server/memcached.h
+++ b/trunk/server/memcached.h
@@ -225,7 +225,7 @@ extern volatile rel_time_t current_time;
 
 conn *do_conn_from_freelist();
 bool do_conn_add_to_freelist(conn *c);
-char *do_defer_delete(item *item, time_t exptime);
+const char *do_defer_delete(item *item, time_t exptime);
 void do_run_deferred_deletes(void);
 char *do_add_delta(item *item, const bool incr, const int64_t delta, char *buf);
 int do_store_item(item *item, int comm);
diff --git a/trunk/server/t/mdelete.t b/trunk/server/t/mdelete.t
new file mode 100644
index 0000000..a0af4e2
--- /dev/null
+++ b/trunk/server/t/mdelete.t
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+use strict;
+use Test::More tests => 18;
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use MemcachedTest;
+
+my $server = new_memcached();
+my $sock = $server->sock;
+
+
+print $sock "mdelete\r\n";
+is(scalar <$sock>, "ERROR\r\n");
+
+print $sock "mdelete 0\r\n";
+is(scalar <$sock>, "ERROR\r\n");
+
+print $sock "mdelete 0 reply\r\n";
+is(scalar <$sock>, "ERROR\r\n");
+
+print $sock "set foo 0 0 1 noreply\r\n1\r\n";
+print $sock "mdelete 0 reply foo\r\n";
+is(scalar <$sock>, "VALUE foo DELETED\r\n");
+is(scalar <$sock>, "END\r\n");
+
+foreach my $i (1, 2, 5, 6) {
+    print $sock "set foo$i 0 0 1 noreply\r\n1\r\n";
+}
+print $sock "set bar 0 0 1 noreply\r\n1\r\n";
+
+my @keys = ((map{ "foo$_" } (1..7)), "x" x 251, "bar", "bar");
+print $sock "mdelete 0 reply @keys\r\n";
+is(scalar <$sock>, "VALUE $keys[0] DELETED\r\n");
+is(scalar <$sock>, "VALUE $keys[1] DELETED\r\n");
+is(scalar <$sock>, "VALUE $keys[2] NOT_FOUND\r\n");
+is(scalar <$sock>, "VALUE $keys[3] NOT_FOUND\r\n");
+is(scalar <$sock>, "VALUE $keys[4] DELETED\r\n");
+is(scalar <$sock>, "VALUE $keys[5] DELETED\r\n");
+is(scalar <$sock>, "VALUE $keys[6] NOT_FOUND\r\n");
+is(scalar <$sock>, "VALUE $keys[7] CLIENT_ERROR key too long\r\n");
+is(scalar <$sock>, "VALUE $keys[8] DELETED\r\n");
+is(scalar <$sock>, "VALUE $keys[9] NOT_FOUND\r\n");
+is(scalar <$sock>, "END\r\n");
+
+
+# Test delayed mdelete.
+print $sock "set foo 0 0 1 noreply\r\n1\r\n";
+print $sock "set bar 0 0 1 noreply\r\n1\r\n";
+print $sock "mdelete 10 noreply foo bar\r\n";
+print $sock "add foo 0 0 1\r\n1\r\n";
+is(scalar <$sock>, "NOT_STORED\r\n");
+print $sock "add bar 0 0 1\r\n1\r\n";
+is(scalar <$sock>, "NOT_STORED\r\n");
-- 
1.5.3.5.529.ge3d6d


More information about the memcached mailing list