[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