Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

noreply semantics with meta commands #868

Open
bisho opened this issue Feb 10, 2022 · 5 comments
Open

noreply semantics with meta commands #868

bisho opened this issue Feb 10, 2022 · 5 comments

Comments

@bisho
Copy link

bisho commented Feb 10, 2022

Describe the bug
Errors are always returned in metacommands with noreply q flag.

This is the correct described behavior according to the documentation, but I want to challenge the current behavior as it is inconsistent with previous protocols and makes implementation and usage extremely hard.

The biggest purpose of noreply flag is "fire and forget". Instead of awaiting for response, you can send something on the wire, and keep going without waiting for the roundtrip. You can, for example, implement rough counters with this, if the command is lost or causes error, not a big issue, but you don't need to wait for the roundtrip.

In previous text protocol, noreply could potentially return error responses ONLY if the command was malformed (as there is no guarantee the noreply marker could be properly processed), but any other errors like CAS check failures, add/replace existing/not existing, etc were never returned as errors. This is what the documentation says:

- "noreply" optional parameter instructs the server to not send the
  reply.  NOTE: if the request line is malformed, the server can't
  parse "noreply" option reliably.  In this case it may send the error
  to the client, and not reading it on the client side will break
  things.  Client should construct only valid requests.

meta commands on the other hand, declare:

- q: use noreply semantics for return codes.

Noreply is a method of reducing the amount of data sent back by memcached to
the client for normal responses. In the case of metaget, if an item is not
available the "VA" line is normally not sent, and the response is terminated by
"EN\r\n".

With noreply enabled, the "EN\r\n" marker is suppressed. This allows you to
pipeline several mg's together and read all of the responses without the
"EN\r\n" lines in the middle.

Errors are always returned.

Saving data sent back is arguably a strange reason to use this, as a "HD\r\n" response is a mere 4 bytes. Like I said before, the biggest use case for me is to implement fire-and-forget commands, that do not need to wait for the roundtrip to the server (at the expense of knowing about errors).

The current choice of return nothing for success, error otherwise makes implementation very hard:

  • When I send a command with norepy and another command later on the same connection, and I read an error reply, how am I suppose to know if the error is for the first or the second command?
  • I know there is the possibility to use opaque tokens so commands can be matched to responses, but having to use those to be able to use noreply: a) increases complexity of the client code and b) defeats the "reduce the amount of data send" that was originally claimed for noreply flag, as you will need to those extra bytes in every request and response.

Suggested solution:
a) Make noreply q flag behave as before. No errors other than when request is malformed will be returned.
b) If keeping backward compatibility is preferred, add a new Q flag that implements this behavior.

I think that a) makes more sense, as I highly doubt anybody is finding the current behavior for q flag usable, but I could live with a separate flag of course.

To Reproduce

# Successful request causes no reply
$ echo -ne "ms foo 3 q T0\r\n333\r\n" | nc localhost 11211
# Wrong cas token causes error response:
$ echo -ne "ms foo 3 q T0 C123\r\n333\r\n" | nc localhost 11211
EX

System Information
Tested in memcache 1.6.12

@dormando
Copy link
Member

Thanks for the writeup! Though I wish I were hearing this feedback years ago during the long open period :)

I did the new q semantics because:

  1. This wasn't as bad in binprot, but ascii 'noreply' had unfixable protocol desync bugs. If errors were injected into the stream you couldn't get back into a good state or understood where/what happened. Back when 'noreply' was being proposed this got brought up and there was a threat of a fork so I left it as a sharp edge.
  2. Meta is supposed to be a superset of the binary protocol, and the binary protocol's quiet commands only hide success. I've not actually heard any complaints about this in the 10 years it's existed!

Think I'm open to a Q flag that kills at some non-egregious errors though. I'm a little too busy to try to add that at the moment but we can leave this open until it's addressed or someone figures a patch.

bisho added a commit to RevenueCat/meta-memcache-py that referenced this issue Feb 10, 2022
## Motivation / Description
See: memcached/memcached#868

TL;DR: Memcache noreply can return error responses on
write commands, defeating the purpose of the noreply
command can causing the response parsing on the socket
to become miss-aligned.

This implements client-side no-replies. We basically do
not send the `q` flag, return immediately without waiting
for the response (so there is no roundtrip penalry) and
keep a counter of the pending, non-read responses that
we need to drop.

## Changes introduced
- No not send noreply flag on write commands
- Client-side noreply implementation to maintain the fast non-blocking
  behavior.
@dormando
Copy link
Member

I accidentally deleted a third note here:

  • The logic behind quiet giving errors sometimes is often if you don't care about errors you're often using a different command; ie set instead of cas. append/prepend missing or hitting size limits are also not very useful. Finally the mn command (NOP in binprot) is used to cap off batches of commands. NOP is how multi-get was implemented in binprot (and the same in meta); you send a bunch of mg's with the q flag and an mn at the end, and when you see the MN come back you've got all your responses.

This is less useful if you're just doing a fire-and-forget, but I'm not sure why you'd want to do that for commands where errors would matter. The original noreply in ascii was too blunt of a tool for many use cases. With mn/NOP all commands can be pipelined safely and often quietly, whereas in text only get|gat|gets with a special syntax worked.

@bisho
Copy link
Author

bisho commented Feb 11, 2022

NOP could definitely work. I'll take a look at it.

I agree normally you don't need to use flags that can cause errors with noreply. But I'm implementing a python client that supports meta commands, and I wasn't limiting when you could use noreply cas for example.

@dormando
Copy link
Member

Got it. I'll give your client a read through out of curiosity. I think noreply cas has a valid use case (batch of cas sets capped and only handle failures), which wasn't possible before, but I still like your Q idea for true fire-and-forget situations. If you don't mind I'll leave this open until I get that figured out (which could be a while).

@bisho
Copy link
Author

bisho commented Feb 11, 2022

Sure, I'm totally fine leaving this open.

This is the meta commands python client: https://github.com/RevenueCat/meta-memcache-py suggestions/comments are welcome!

bisho added a commit to RevenueCat/meta-memcache-py that referenced this issue Feb 11, 2022
## Motivation / Description
See: memcached/memcached#868

TL;DR: Memcache noreply can return error responses on
write commands, defeating the purpose of the noreply
command can causing the response parsing on the socket
to become miss-aligned.

This adds no-op meta command to no-reply write requests.
We can return immediately without waiting
for the response (so there is no roundtrip penalty) and
keep a counter of the pending expected no-op responses.

The next non-noreply command will read and ignore
responses until the no-ops have been read, and proceed
to read the right response to the new command.

## Changes introduced
- Send no-op after noreply write commands
- Response reads will drop pending responses until the no-op
  response when noops are expected in a connection.
bisho added a commit to RevenueCat/meta-memcache-py that referenced this issue Feb 11, 2022
## Motivation / Description
See: memcached/memcached#868

TL;DR: Memcache noreply can return error responses on
write commands, defeating the purpose of the noreply
command can causing the response parsing on the socket
to become miss-aligned.

This adds no-op meta command to no-reply write requests.
We can return immediately without waiting
for the response (so there is no roundtrip penalty) and
keep a counter of the pending expected no-op responses.

The next non-noreply command will read and ignore
responses until the no-ops have been read, and proceed
to read the right response to the new command.

## Changes introduced
- Send no-op after noreply write commands
- Response reads will drop pending responses until the no-op
  response when noops are expected in a connection.
bisho added a commit to RevenueCat/meta-memcache-py that referenced this issue Feb 14, 2022
* Implement noreply flag client-side for write commands

## Motivation / Description
See: memcached/memcached#868

TL;DR: Memcache noreply can return error responses on
write commands, defeating the purpose of the noreply
command can causing the response parsing on the socket
to become miss-aligned.

This adds no-op meta command to no-reply write requests.
We can return immediately without waiting
for the response (so there is no roundtrip penalty) and
keep a counter of the pending expected no-op responses.

The next non-noreply command will read and ignore
responses until the no-ops have been read, and proceed
to read the right response to the new command.

## Changes introduced
- Send no-op after noreply write commands
- Response reads will drop pending responses until the no-op
  response when noops are expected in a connection.

* Add test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants