Skip to content

Commit d9ece78

Browse files
committed
Implement 'noreply' option for update commands. (Tomash Brechko <[email protected]>)
Commands add, set, replace, append, prepend, cas, delete, incr, decr, flush_all, verbosity can take last optional parameter, 'noreply', which instructs the server to not send the reply. Add benchmark script for noreply parameter. git-svn-id: http://code.sixapart.com/svn/memcached/trunk/server@708 b0b603af-a30f-0410-a34e-baf09ae79d0b
1 parent 57e3367 commit d9ece78

File tree

5 files changed

+185
-28
lines changed

5 files changed

+185
-28
lines changed

devtools/bench_noreply.pl

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#! /usr/bin/perl
2+
#
3+
use warnings;
4+
use strict;
5+
6+
use IO::Socket::INET;
7+
8+
use FindBin;
9+
10+
@ARGV == 1 or @ARGV == 2
11+
or die "Usage: $FindBin::Script HOST:PORT [COUNT]\n";
12+
13+
# Note that it's better to run the test over the wire, because for
14+
# localhost the task may become CPU bound.
15+
my $addr = $ARGV[0];
16+
my $count = $ARGV[1] || 10_000;
17+
18+
my $sock = IO::Socket::INET->new(PeerAddr => $addr,
19+
Timeout => 3);
20+
die "$!\n" unless $sock;
21+
22+
23+
# By running 'noreply' test first we also ensure there are no reply
24+
# packets left in the network.
25+
foreach my $noreply (1, 0) {
26+
use Time::HiRes qw(gettimeofday tv_interval);
27+
28+
print "'noreply' is ", $noreply ? "enabled" : "disabled", ":\n";
29+
my $param = $noreply ? 'noreply' : '';
30+
my $start = [gettimeofday];
31+
foreach (1 .. $count) {
32+
print $sock "add foo 0 0 1 $param\r\n1\r\n";
33+
scalar<$sock> unless $noreply;
34+
print $sock "set foo 0 0 1 $param\r\n1\r\n";
35+
scalar<$sock> unless $noreply;
36+
print $sock "replace foo 0 0 1 $param\r\n1\r\n";
37+
scalar<$sock> unless $noreply;
38+
print $sock "append foo 0 0 1 $param\r\n1\r\n";
39+
scalar<$sock> unless $noreply;
40+
print $sock "prepend foo 0 0 1 $param\r\n1\r\n";
41+
scalar<$sock> unless $noreply;
42+
print $sock "incr foo 1 $param\r\n";
43+
scalar<$sock> unless $noreply;
44+
print $sock "decr foo 1 $param\r\n";
45+
scalar<$sock> unless $noreply;
46+
print $sock "delete foo $param\r\n";
47+
scalar<$sock> unless $noreply;
48+
}
49+
my $end = [gettimeofday];
50+
printf("update commands: %.2f secs\n\n", tv_interval($start, $end));
51+
}

doc/protocol.txt

+35-18
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,10 @@ Storage commands
126126

127127
First, the client sends a command line which looks like this:
128128

129-
<command name> <key> <flags> <exptime> <bytes> [<cas unqiue>]\r\n
129+
<command name> <key> <flags> <exptime> <bytes> [noreply]\r\n
130+
cas <key> <flags> <exptime> <bytes> <cas unqiue> [noreply]\r\n
130131

131-
- <command name> is "set", "add", "replace", "append", "prepend", or "cas"
132+
- <command name> is "set", "add", "replace", "append" or "prepend"
132133

133134
"set" means "store this data".
134135

@@ -174,6 +175,12 @@ First, the client sends a command line which looks like this:
174175
Clients should use the value returned from the "gets" command
175176
when issuing "cas" updates.
176177

178+
- "noreply" optional parameter instructs the server to not send the
179+
reply. NOTE: if the request line is malformed, the server can't
180+
parse "noreply" option reliably. In this case it may send the error
181+
to the client, and not reading it on the client side will break
182+
things. Client should construct only valid requests.
183+
177184
After this line, the client sends the data block:
178185

179186
<data block>\r\n
@@ -245,7 +252,7 @@ Deletion
245252

246253
The command "delete" allows for explicit deletion of items:
247254

248-
delete <key> <time>\r\n
255+
delete <key> [<time>] [noreply]\r\n
249256

250257
- <key> is the key of the item the client wishes the server to delete
251258

@@ -261,6 +268,10 @@ delete <key> <time>\r\n
261268
(which means that the item will be deleted immediately and further
262269
storage commands with this key will succeed).
263270

271+
- "noreply" optional parameter instructs the server to not send the
272+
reply. See the note in Storage commands regarding malformed
273+
requests.
274+
264275
The response line to this command can be one of:
265276

266277
- "DELETED\r\n" to indicate success
@@ -285,17 +296,21 @@ non-existent key exists with value 0; instead, they will fail.
285296

286297
The client sends the command line:
287298

288-
incr <key> <value>\r\n
299+
incr <key> <value> [noreply]\r\n
289300

290301
or
291302

292-
decr <key> <value>\r\n
303+
decr <key> <value> [noreply]\r\n
293304

294305
- <key> is the key of the item the client wishes to change
295306

296307
- <value> is the amount by which the client wants to increase/decrease
297308
the item. It is a decimal representation of a 64-bit unsigned integer.
298309

310+
- "noreply" optional parameter instructs the server to not send the
311+
reply. See the note in Storage commands regarding malformed
312+
requests.
313+
299314
The response will be one of:
300315

301316
- "NOT_FOUND\r\n" to indicate the item with this value was not found
@@ -399,16 +414,17 @@ Other commands
399414
--------------
400415

401416
"flush_all" is a command with an optional numeric argument. It always
402-
succeeds, and the server sends "OK\r\n" in response. Its effect is to
403-
invalidate all existing items immediately (by default) or after the
404-
expiration specified. After invalidation none of the items will be returned
405-
in response to a retrieval command (unless it's stored again under the
406-
same key *after* flush_all has invalidated the items). flush_all doesn't
407-
actually free all the memory taken up by existing items; that will
408-
happen gradually as new items are stored. The most precise definition
409-
of what flush_all does is the following: it causes all items whose
410-
update time is earlier than the time at which flush_all was set to be
411-
executed to be ignored for retrieval purposes.
417+
succeeds, and the server sends "OK\r\n" in response (unless "noreply"
418+
is given as the last parameter). Its effect is to invalidate all
419+
existing items immediately (by default) or after the expiration
420+
specified. After invalidation none of the items will be returned in
421+
response to a retrieval command (unless it's stored again under the
422+
same key *after* flush_all has invalidated the items). flush_all
423+
doesn't actually free all the memory taken up by existing items; that
424+
will happen gradually as new items are stored. The most precise
425+
definition of what flush_all does is the following: it causes all
426+
items whose update time is earlier than the time at which flush_all
427+
was set to be executed to be ignored for retrieval purposes.
412428

413429
The intent of flush_all with a delay, was that in a setting where you
414430
have a pool of memcached servers, and you need to flush all content,
@@ -431,9 +447,10 @@ In response, the server sends
431447
"VERSION <version>\r\n", where <version> is the version string for the
432448
server.
433449

434-
"verbosity" is a command with a numeric argument. It always
435-
succeeds, and the server sends "OK\r\n" in response. Its effect is to
436-
set the verbosity level of the logging output.
450+
"verbosity" is a command with a numeric argument. It always succeeds,
451+
and the server sends "OK\r\n" in response (unless "noreply" is given
452+
as the last parameter). Its effect is to set the verbosity level of
453+
the logging output.
437454

438455
"quit" is a command with no arguments:
439456

memcached.c

+45-10
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@ conn *conn_new(const int sfd, const int init_state, const int event_flags,
372372
c->bucket = -1;
373373
c->gen = 0;
374374

375+
c->noreply = false;
376+
375377
event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
376378
event_base_set(base, &c->event);
377379
c->ev_flags = event_flags;
@@ -733,6 +735,12 @@ static void out_string(conn *c, const char *str) {
733735

734736
assert(c != NULL);
735737

738+
if (c->noreply) {
739+
c->noreply = false;
740+
conn_set_state(c, conn_read);
741+
return;
742+
}
743+
736744
if (settings.verbose > 1)
737745
fprintf(stderr, ">%d %s\n", c->sfd, str);
738746

@@ -898,7 +906,7 @@ typedef struct token_s {
898906
#define KEY_TOKEN 1
899907
#define KEY_MAX_LENGTH 250
900908

901-
#define MAX_TOKENS 7
909+
#define MAX_TOKENS 8
902910

903911
/*
904912
* Tokenize the command string by replacing whitespace with '\0' and update
@@ -968,6 +976,23 @@ static void write_and_free(conn *c, char *buf, int bytes) {
968976
}
969977
}
970978

979+
static inline void set_noreply_maybe(conn *c, token_t *tokens, size_t ntokens)
980+
{
981+
int noreply_index = ntokens - 2;
982+
983+
/*
984+
NOTE: this function is not the first place where we are going to
985+
send the reply. We could send it instead from process_command()
986+
if the request line has wrong number of tokens. However parsing
987+
malformed line for "noreply" option is not reliable anyway, so
988+
it can't be helped.
989+
*/
990+
if (tokens[noreply_index].value
991+
&& strcmp(tokens[noreply_index].value, "noreply") == 0) {
992+
c->noreply = true;
993+
}
994+
}
995+
971996
inline static void process_stats_detail(conn *c, const char *command) {
972997
assert(c != NULL);
973998

@@ -1347,6 +1372,8 @@ static void process_update_command(conn *c, token_t *tokens, const size_t ntoken
13471372

13481373
assert(c != NULL);
13491374

1375+
set_noreply_maybe(c, tokens, ntokens);
1376+
13501377
if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
13511378
out_string(c, "CLIENT_ERROR bad command line format");
13521379
return;
@@ -1418,6 +1445,8 @@ static void process_arithmetic_command(conn *c, token_t *tokens, const size_t nt
14181445

14191446
assert(c != NULL);
14201447

1448+
set_noreply_maybe(c, tokens, ntokens);
1449+
14211450
if(tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) {
14221451
out_string(c, "CLIENT_ERROR bad command line format");
14231452
return;
@@ -1514,6 +1543,8 @@ static void process_delete_command(conn *c, token_t *tokens, const size_t ntoken
15141543

15151544
assert(c != NULL);
15161545

1546+
set_noreply_maybe(c, tokens, ntokens);
1547+
15171548
if (settings.managed) {
15181549
int bucket = c->bucket;
15191550
if (bucket == -1) {
@@ -1535,7 +1566,7 @@ static void process_delete_command(conn *c, token_t *tokens, const size_t ntoken
15351566
return;
15361567
}
15371568

1538-
if(ntokens == 4) {
1569+
if(ntokens == (c->noreply ? 5 : 4)) {
15391570
exptime = strtol(tokens[2].value, NULL, 10);
15401571

15411572
if(errno == ERANGE) {
@@ -1598,6 +1629,8 @@ static void process_verbosity_command(conn *c, token_t *tokens, const size_t nto
15981629

15991630
assert(c != NULL);
16001631

1632+
set_noreply_maybe(c, tokens, ntokens);
1633+
16011634
level = strtoul(tokens[1].value, NULL, 10);
16021635
settings.verbose = level > MAX_VERBOSITY_LEVEL ? MAX_VERBOSITY_LEVEL : level;
16031636
out_string(c, "OK");
@@ -1635,7 +1668,7 @@ static void process_command(conn *c, char *command) {
16351668

16361669
process_get_command(c, tokens, ntokens, false);
16371670

1638-
} else if (ntokens == 6 &&
1671+
} else if ((ntokens == 6 || ntokens == 7) &&
16391672
((strcmp(tokens[COMMAND_TOKEN].value, "add") == 0 && (comm = NREAD_ADD)) ||
16401673
(strcmp(tokens[COMMAND_TOKEN].value, "set") == 0 && (comm = NREAD_SET)) ||
16411674
(strcmp(tokens[COMMAND_TOKEN].value, "replace") == 0 && (comm = NREAD_REPLACE)) ||
@@ -1644,23 +1677,23 @@ static void process_command(conn *c, char *command) {
16441677

16451678
process_update_command(c, tokens, ntokens, comm, false);
16461679

1647-
} else if (ntokens == 7 && (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0 && (comm = NREAD_CAS))) {
1680+
} else if ((ntokens == 7 || ntokens == 8) && (strcmp(tokens[COMMAND_TOKEN].value, "cas") == 0 && (comm = NREAD_CAS))) {
16481681

16491682
process_update_command(c, tokens, ntokens, comm, true);
16501683

1651-
} else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) {
1684+
} else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "incr") == 0)) {
16521685

16531686
process_arithmetic_command(c, tokens, ntokens, 1);
16541687

16551688
} else if (ntokens >= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "gets") == 0)) {
16561689

16571690
process_get_command(c, tokens, ntokens, true);
16581691

1659-
} else if (ntokens == 4 && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) {
1692+
} else if ((ntokens == 4 || ntokens == 5) && (strcmp(tokens[COMMAND_TOKEN].value, "decr") == 0)) {
16601693

16611694
process_arithmetic_command(c, tokens, ntokens, 0);
16621695

1663-
} else if (ntokens >= 3 && ntokens <= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "delete") == 0)) {
1696+
} else if (ntokens >= 3 && ntokens <= 5 && (strcmp(tokens[COMMAND_TOKEN].value, "delete") == 0)) {
16641697

16651698
process_delete_command(c, tokens, ntokens);
16661699

@@ -1729,11 +1762,13 @@ static void process_command(conn *c, char *command) {
17291762

17301763
process_stat(c, tokens, ntokens);
17311764

1732-
} else if (ntokens >= 2 && ntokens <= 3 && (strcmp(tokens[COMMAND_TOKEN].value, "flush_all") == 0)) {
1765+
} else if (ntokens >= 2 && ntokens <= 4 && (strcmp(tokens[COMMAND_TOKEN].value, "flush_all") == 0)) {
17331766
time_t exptime = 0;
17341767
set_current_time();
17351768

1736-
if(ntokens == 2) {
1769+
set_noreply_maybe(c, tokens, ntokens);
1770+
1771+
if(ntokens == (c->noreply ? 3 : 2)) {
17371772
settings.oldest_live = current_time - 1;
17381773
item_flush_expired();
17391774
out_string(c, "OK");
@@ -1798,7 +1833,7 @@ static void process_command(conn *c, char *command) {
17981833
#else
17991834
out_string(c, "CLIENT_ERROR Slab reassignment not supported");
18001835
#endif
1801-
} else if (ntokens == 3 && (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0)) {
1836+
} else if ((ntokens == 3 || ntokens == 4) && (strcmp(tokens[COMMAND_TOKEN].value, "verbosity") == 0)) {
18021837
process_verbosity_command(c, tokens, ntokens);
18031838
} else {
18041839
out_string(c, "ERROR");

memcached.h

+1
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ struct conn {
220220
int bucket; /* bucket number for the next command, if running as
221221
a managed instance. -1 (_not_ 0) means invalid. */
222222
int gen; /* generation requested for the bucket */
223+
bool noreply; /* True if the reply should not be sent. */
223224
conn *next; /* Used for generating a list of conn structures */
224225
};
225226

t/noreply.t

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/perl
2+
3+
use strict;
4+
use Test::More tests => 10;
5+
use FindBin qw($Bin);
6+
use lib "$Bin/lib";
7+
use MemcachedTest;
8+
9+
10+
my $server = new_memcached();
11+
my $sock = $server->sock;
12+
13+
14+
# Test that commands can take 'noreply' parameter.
15+
print $sock "flush_all noreply\r\n";
16+
print $sock "flush_all 0 noreply\r\n";
17+
18+
print $sock "verbosity 0 noreply\r\n";
19+
20+
print $sock "add noreply:foo 0 0 1 noreply\r\n1\r\n";
21+
mem_get_is($sock, "noreply:foo", "1");
22+
23+
print $sock "set noreply:foo 0 0 1 noreply\r\n2\r\n";
24+
mem_get_is($sock, "noreply:foo", "2");
25+
26+
print $sock "replace noreply:foo 0 0 1 noreply\r\n3\r\n";
27+
mem_get_is($sock, "noreply:foo", "3");
28+
29+
print $sock "append noreply:foo 0 0 1 noreply\r\n4\r\n";
30+
mem_get_is($sock, "noreply:foo", "34");
31+
32+
print $sock "prepend noreply:foo 0 0 1 noreply\r\n5\r\n";
33+
my @result = mem_gets($sock, "noreply:foo");
34+
ok($result[1] eq "534");
35+
36+
print $sock "cas noreply:foo 0 0 1 $result[0] noreply\r\n6\r\n";
37+
mem_get_is($sock, "noreply:foo", "6");
38+
39+
print $sock "incr noreply:foo 3 noreply\r\n";
40+
mem_get_is($sock, "noreply:foo", "9");
41+
42+
print $sock "decr noreply:foo 2 noreply\r\n";
43+
mem_get_is($sock, "noreply:foo", "7");
44+
45+
print $sock "delete noreply:foo noreply\r\n";
46+
mem_get_is($sock, "noreply:foo");
47+
48+
# Test that delete accepts both <time> and 'noreply'.
49+
print $sock "add noreply:foo 0 0 1 noreply\r\n1\r\n";
50+
print $sock "delete noreply:foo 10 noreply\r\n";
51+
print $sock "add noreply:foo 0 0 1 noreply\r\n1\r\n";
52+
# undef result means we couldn't add an entry because the key is locked.
53+
mem_get_is($sock, "noreply:foo");

0 commit comments

Comments
 (0)