-
Notifications
You must be signed in to change notification settings - Fork 3.3k
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
meta command follow ups #546
Comments
Trying to implement a simplified client with this in a few more languages. Just ran into something backwards. For sending flags to the server, capital letter flags always consume a token. Up until now I was writing the clients with tokens being passed around blindly... but this has accidentally made parsing the value part of the response impossible. In all my other tests I'd been looking for Clients either need to test for "was there a v flag?", and if so, "which token position is the size?", or blind parse by looking for
Hmm. Another possibility would be to always have capital means "consumes a token". So response flags get capitalized when they have a token. l -> L, etc. and request flags get lowercased. N -> n. Downside is this reduces the number of possible flags to just 26 without using special characters. metaget, with the most flags, has 14ish. The W/X/Z flags can be swapped for |
9 flags currently free Known commands not used yet:
that's to get to parity with binprot. so perhaps 3 more flags for delta, though N could be reused for the init since it means the same thing. Current incr/decr is specialized: That should leave 12 for future expansion. Right now it's convenient to be able have a shared token pre/post parser for all meta commands, so not having overlap is nice. However, if CAPS means has a token (supplied or returned), lower doesn't, then the preparser doesn't necessarily have to be shared, then each command can have 26 flags. The shared preparser in memcached is nice since it sets bits for whether or not to leave the item locked during internal fetch... however so far the preparser is only useful for metaget. I can't see much reason for clients to care about flags.. if they're pre-parsing response flags into a map/dict/hash and you're in a typed language you'd want to know ahead of time if it's a number or string, but even that has limited usefulness. |
Think the problem here stems from flags being reflected in the response rather than representing what the tokens are, or strictly from extra data being returned. building a response this way could suck in some languages. In C you're going to end up with a copy somewhere... in the server I reserve space at the front of the return buffer, then write extra flags in backwards. If building the response flags forwards I couldn't do that without a pre-parser, which honestly there already is one so it's probably fine to just handle that in the pre-parser. I wonder if this would end up painful in any immutable language. :\ ... but I can't seem to remember why I decided to reflect everything. I think earlier in the design nothing was reflected, but I added the flag return possibly only to make return parsing easier plus have the out of band data for WIN/STALE/WON tokens. Edit: Why am I so dumb? Of course this doesn't still work: WIN/STALE/WON still don't consume tokens, so response parsing still can't be blind. If this is done on top of the CAPS fix it would work 100%? Wonder if I could leave the flags in for responses but basically kill all of them off: "0" or "Z" just means no extra flags in response, then tokens match what was sent implicitly. This would be fine but clients still can't autoparse since the flag <-> token state is entirely in the client. Wireshark could parse it only if it understood all flags and saw both request + response. My ancient "protocol V3" suggestion had flags prepending tokens: Might just try the code for:
where a [flags] of 0 or Z means no extra flags. If [flags] were instead a token with "e[extra/bulk flags]" they could be appended which should make client code the simplest and remove my weird "front pointer" and backwards walk bullshittery. with 's' tagged to the size we could keep with just benefits:
|
is... 5 extra bytes.
is 1 extra byte ( The only single flags that get returned that mean anything are W / X / Z. flags like q/u/etc get eaten. all other flags return a token. Meaning there isn't much point in flag compression for return:
(with size being redundant here; you probably only need it when not fetching the value) The simplest approach would just be to always space delim flags for both request/response. I'll try adding an 'e' flag for client side compression and see how that complicates the parsing code. I expect a few more bare response flags to be added over time, but not enough to make a huge dent in bytes-on-wire :/ Avoiding mirroring the flags in the response saves a few bytes in the response overall, so it's a wash anyway. doing the flag token prepend will make code a bit longer. might be able to be clever about prepending the token or at least use a macro. |
dormando#3 fix is being tested here. on top of the network-readbuf branch to avoid a merge nightmare |
With the previous change to flags, command parsing is now fully agnostic to the commands used... ie; a client can have convenience functions for collapsing/aliasing flags, but it doesn't need anything beyond a basic "send" and maybe a "send but it's a set" function. Different commands use unique 2-char codes. The last thing I was thinking about (while doing the mctester work) was if the return codes could be collapsed further:
then prefixes for SE/CL/ER are for full strings of SERVER_ERROR, ERROR, CLIENT_ERROR... which are unavoidable as the protocol is live-switchable with originall text. NF == Not_Found Honestly I think adding an "OK" to replace ST, DE, maybe MN and maybe HD would be enough. We can always add codes when absolutely necessary, but any remaining commands to implement can likely use one of the existing codes, with flag sprinkles for command specific information. In the test client code, all of the above are parsed the same way already. It should be possible to add the ma (arithmetic) command without adding any client code. Edit: Think I want to keep |
#612 easy to talk myself into this one. |
need to add a new do_store_item routine or adjust it somehow... in binprot, if you send an ADD with a CAS it'll change that to a SET.. whereas doing so doesn't actually make sense (ADD with CAS will fail if the item exists, and if the item doesn't exist). REPLACE sort of works (same effect as CAS), but in the ADD case it should autofail but doesn't. in do_store_item CAS is a specific subcommand (NREAD_CAS). Except for append/prepend, which does a quick CAS check if there is one on the item. Auditing for calls to Could refactor to check for a CAS value at top of function and set an enum properly, then reorganize the various sub routines. This could change behavior slightly but means we can get the CAS counters for all use cases. |
Well I refactored If a CAS is supplied to metaset, it flips the current command from
This just sort of sucks because |
Organizing thoughts for ma (meta arithmetic):
should be trivial to add modes for multiplication and division, maybe shifting. need more feedback on how people could benefit from expanding the counter system. ascii requires the incr/decr with add fallback for initial values. binprot fixed that by allowing an initial value with a seeded expiration time and CAS twiddles. meta will allow touch + binprot parity. there's no win/recache/etc routine here. edit: code's coming along. had to do an extra loop post-delta to get cas/TTL/etc return to work... so the code's not as copypasta as the other commands. need to make a decision re: value return. If requested, do the full VA format or add as a return token since it's always a short numeric? Leaning toward VA but going to sleep on it then finish things. |
At this point we have binary protocol parity, and the further fixes noted here can be done incrementally. |
@shivnagarajan are you still working on / interested in this, by chance? I'm working on the C client issue now. |
I've got some changes in |
👋 I'm experimenting with converting https://github.com/petergoldstein/dalli (the most popular ruby client, only supports the binary protocol) to the meta commands protocol. It's actually fairly easy and I got almost everything working. However there is a major feature missing to have proper compatibility with the binary protocol, it's to return the CAS value on Is this something you are still planning to add? Another more minor missing feature is the NOOP command. Dalli uses it to mark the end of a multi-get pipeline. Without it I have no idea how I could use the |
Will the meta no-op be sufficient for this? |
@shivnagarajan 🤦 it totally is, I simply somehow managed not to see it... |
@casperisfine CAS from ms is something I punted on temporarily because the code structure didn't make it easy. That's the third TODO item down in the top post in this issue. I haven't gone back to do it yet :( RE the other thing, MN is exactly what you want as noted :) |
Yes I saw it. I just wanted to communicate it's importance 😉 |
Got it :) I'm a bit slammed right now but will revisit when I can. |
@casperisfine do you make use of this feature or is it mostly for feature parity in the client? |
We don't even use dalli right now, nor the binary protocol, we're actually still mostly on the old ASCII protocol using a libmemcached binding, so no we don't use this feature, and if we were really committed to use the meta commands ASAP we could do without it. But our goal is more to get rid of our libmemcached binding, so in this context going directly to the newer protocol, with Dalli as an API to have compatibility with many other modern libraries is appealing. That's why I'm looking for feature parity. I'd bet most software would work fine without it, but "eventually compatible" is a bit of a hard sell... Either way there is no rush. |
That's good to know, thanks! I need to hear from people to figure out priorities :) I'm working on a C client thing that I'll share soon too. |
I'm throwing an attempt in at making a new C client: https://github.com/dormando/mcmc I'm hammering out the "make it work" parts now, which it's mostly there. I'll get a few limited tests going then immediately start using it on a project I'm working on now. The API should evolve into something stable and we can release it as a full C client base others can quickly use in language wrappings. libmemcached is a lot of code and completely unmaintained. It includes client/server protocols, stats parsers (that're constantly out of sync), mallocs and holds onto read/write buffers. It also doesn't integrate into event loops at all; async mode uses poll so far as I can tell. mcmc is completely agnostic to how you use it: it should be trivial to write tiny wrappers to embed it into any event loop. It's also tiny. @shivnagarajan @casperisfine this may not solve any of your problems, or it might. I'm not expecting a long development time since the client is highly simplified. There's no binary protocol. I've iterated on the API a dozen times already, and I could probably write a synchronous client wrapper in most languages in a couple days at most. (not that I'm volunteering to do so; just that it should be easy) |
Thanks for the link. However the meta commands protocol is simple enough that I'd rather implement it directly in Ruby than to bind a C library. At least until I get to the benchmark stage. |
I don't expect you to use it for dalli, but wasn't there another client in use that depended on libmemcached? |
The one we currently use, but we'd like to simply get rid of it as it's not really maintained anymore: https://rubygems.org/gems/memcached. And we're only using it because of historical reasons, all our newer apps use Dalli. And in the Ruby community as a whole, Dalli is really the standard. |
Ahhah. I must've misremembered it as a python client. Yeah that's great, do it :) |
Couple annoying things:
|
Well that got dropped... I'd like to stabilize meta and mark it as final ASAP. My list of changes after having played with it for a while:
Looks like I had some code level follow-ups from above that I'd forgotten about. LOGGER, refcount checks, etc. I also want to change the internal parser but am struggling to come up with an optimal approach. It's a hot path or I wouldn't try so hard I think it's safe to make these changes since the meta protocol is still marked as EXPERIMENTAL in the documentation, but I worry it's been out there long enough that someone's relying on it. I may just have to be bold in this one case. |
Most of these changes are in. Keeping this issue open a while longer until the top two TODO items get done. |
My 2 cents is that yes, EN should absolutely return O and k if requested. I'm writing a non-blocking client and the fact that the O flag is ignore by EN responses came as a show stopping shock. If the change is truly non-breaking, please consider having EN respect at least the O flag, and I should think k flag as well for consistency's sake. |
You're two years late, but yes looks like I forgot to add that in :) It's not a breaking change and looks like I'd already planned on doing it, so I'll add it as soon as I can. There're some cases in the code where it's not trivial to add the flags. |
#938 will probably close out this issue when merged. |
Meta features (#484) are out in 1.5.19. There'll be a trickle of changes hopefully with every release to smooth things out. Keeping track of them with this issue.
v
if supplied cas matches?)edit: removed a bunch of completed items/notes.
edit2: (final?) todo list.
edit3: updated TODO list again.
The text was updated successfully, but these errors were encountered: