Skip to content

Update RESP3 output#1170

Merged
badrishc merged 62 commits intomicrosoft:mainfrom
prvyk:respoutputobject
May 9, 2025
Merged

Update RESP3 output#1170
badrishc merged 62 commits intomicrosoft:mainfrom
prvyk:respoutputobject

Conversation

@prvyk
Copy link
Copy Markdown
Contributor

@prvyk prvyk commented Apr 11, 2025

This PR corrects many cases of RESP2 output in RESP3 mode. It does four things:

A) In the parsing layer, many cases were updated to use RESP3 NULLs.

B) Storage session and Tsavorite usually did not know the version so couldn't adjust output.
Set protocol version at the storage session's functionsstate, this is passed along to Operate().
Various cases of NULL/Double/Map/Set output are corrected.

C) Internals often converted to use an output struct to make output code much less noisy. Compared to previous code, I suspect the struct allocates slightly more (padding?) but faster (once).

D) Small miscellaneous RESP2 output fixes.

[Original PR used GarnetObjectStoreOutput, hence badrishc's comment, later we tried RespInputFlag before settling on this approach]

@badrishc
Copy link
Copy Markdown
Collaborator

badrishc commented Apr 11, 2025

Before we get too far along, I want to flag that this is not compatible with what input and output mean for Tsavorite. All state that we want to pass from application (Garnet session) level to Tsavorite has to be captured in "input". Output corresponds to all state that is returned by the Tsavorite call to the Garnet session (or in future, to a programmatic invocation of the API). The protocol version does not belong to this category, since it is defined at the input level (at the RESP Server Session) and has nothing to do with output state from the Tsavorite call.

@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 11, 2025

Before we get too far along, I want to flag that this is not compatible with what input and output mean for Tsavorite....

That why I asked.. Ok, what if I use RespInputFlags for this?

I could move the SetGet to a lower value, add a Resp3 flag at same bit, and use that for all this. (FlagMask at RespInputHeader will be edited to use new flag, in order to keep same value).

The SetGet flag bit is unused for objects, so I can use that while keeping the 'only the last 3 bits are available' thing for objects.

@prvyk prvyk changed the title Pass RESP3 flag in GarnetObjectStoreOutput, Create RESP output struct in server code and use it to emit correct output. Pass RESP3 flag in RespInputFlags, Create RESP output struct in server code and use it to emit correct output. Apr 11, 2025
@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 13, 2025

On commit 2e3e517: Most GarnetApi gets ObjectInput/RawStringInput from caller and we could set the flag there. Some calls do not, and instead set up the input object internally. These did not know the RESP protocol version and so didn't know to put it in the input. Turns out this barely affects a standard case of client connecting via RESP - nearly all functions in use pass input, only test code/custom functions/LCS seem to act this way.

Instead of modifying all these APIs, this uses a variable on the storage session. The underlying assumption is that a storage session is never shared between client sessions, so we could just update the storage session each time it changes. The current SWAPDB implementation is limited to a single client, so it's not a problem. Note that the version supplied in the input object (in apis where it's available) always 'wins'.

@prvyk prvyk changed the title Pass RESP3 flag in RespInputFlags, Create RESP output struct in server code and use it to emit correct output. Update RESP3 output Apr 14, 2025
@badrishc
Copy link
Copy Markdown
Collaborator

badrishc commented Apr 15, 2025

Been thinking about this. The changes here have become way too invasive, so we need to think of a simpler approach. The observation is that protocol version is really a slow-changing item (set at the session level) and it is unrelated to individual request calls. So, it does not belong in "input" at all and we are unnecessarily sending this byte back and and forth for every single request call.

In fact, for AOF replay at the secondary, when the commands are being processed, we want the backend (we refer to ISessionFunctions as the backend) to not produce output at all, since it is only replaying commands to update the database, so in future we might use a "null protocol" with something like RespProtocolVersion = byte.MaxValue for such sessions.

With this in mind, we can follow this approach:

  1. Add a byte RespProtocolVersion field to FunctionsState which the backend can access.
  2. Constructor to StorageSession gets this field which it directly passes to FunctionsState instead of keeping in StorageSession.
  3. Undo the changes to input of main and object stores (and the places we were using input.item1 to send the protocol version).
  4. A change to the version due to e.g., HELLO will call something like storageSession.UpdateProtocolVersion which will in turn update FunctionsState.

@prvyk prvyk force-pushed the respoutputobject branch from 7572985 to 283bf51 Compare April 15, 2025 12:15
@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 15, 2025

With this in mind, we can follow this approach:

  1. Add a byte RespProtocolVersion field to FunctionsState which the backend can access.
    ...

Done, also sends the protocol version to Operate(), so we don't need input.arg1 stuff.

There are many more places that need adjustments than the one that was using arg1, and having a null protocol would require all of the operations to be aware, so there should be some standard way to pass the protocol version along to the function.

@prvyk prvyk marked this pull request as ready for review April 16, 2025 01:39
@badrishc
Copy link
Copy Markdown
Collaborator

Adding a rough extension idea based on GarnetObjectStoreRespOutput, to refactor the backend state:

https://github.com/microsoft/garnet/compare/badrishc/object-store-ideas?expand=1

Whether input should be part of OperateParams is debatable.

Other thing in this proposal: we keep public ObjectOutputHeader OtherOutput; as a field in GarnetObjectStoreOutput, instead of trying to wedge it into the byte stream of SpanByteAndMemory.

@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 16, 2025

Adding a rough extension idea based on GarnetObjectStoreRespOutput, to refactor the backend state:

https://github.com/microsoft/garnet/compare/badrishc/object-store-ideas?expand=1

Whether input should be part of OperateParams is debatable.

Other thing in this proposal: we keep public ObjectOutputHeader OtherOutput; as a field in GarnetObjectStoreOutput, instead of trying to wedge it into the byte stream of SpanByteAndMemory.

Some form of output consolidation - a wrapper around RespWriteUtils to 'write RESP to memory' - is useful elsewhere too, there's use in LCS here, and in the future IRespSerializable's ToRespFormat() serializations could use a form of this. But that requires making the consolidation independent of the object store.

@badrishc
Copy link
Copy Markdown
Collaborator

Adding a rough extension idea based on GarnetObjectStoreRespOutput, to refactor the backend state:
https://github.com/microsoft/garnet/compare/badrishc/object-store-ideas?expand=1
Whether input should be part of OperateParams is debatable.
Other thing in this proposal: we keep public ObjectOutputHeader OtherOutput; as a field in GarnetObjectStoreOutput, instead of trying to wedge it into the byte stream of SpanByteAndMemory.

Some form of output consolidation - a wrapper around RespWriteUtils to 'write RESP to memory' - is useful elsewhere too, there's use in LCS here, and in the future IRespSerializable's ToRespFormat() serializations could use a form of this. But that requires making the consolidation independent of the object store.

Right. the struct RespMemoryWriter could be a field in OperateParams and dedidated to memory writing, simply moving fields from OperateParams to RespMemoryWriter.

@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 16, 2025

I think something like this may be desirable (maybe there could be an interface to emit byte[] directly):

--- a/libs/server/Resp/RespCommandsInfo.cs
+++ b/libs/server/Resp/RespCommandsInfo.cs
@@ -374,65 +375,73 @@ namespace Garnet.server
         /// <returns>Serialized value</returns>
         public string ToRespFormat()
         {
-            if (string.IsNullOrWhiteSpace(this.Name))
-                return "$-1\r\n";
+            var output = new SpanByteAndMemory(null); // example
 
-            var sb = new StringBuilder();
-            sb.Append("*10\r\n");
+            using (var outputResp = new GarnetObjectStoreRespOutput(2, ref output))
+            {
+                if (string.IsNullOrWhiteSpace(this.Name))
+                {
+                    outputResp.WriteNull();
+                }
+                else
+                {
+                    outputResp.WriteArrayLength(10);
                     // 1) Name
-            sb.Append($"${this.Name.Length}\r\n{this.Name}\r\n");
+                    outputResp.WriteAsciiBulkString(this.Name);
                     // 2) Arity
-            sb.Append($":{this.Arity}\r\n");
+                    outputResp.WriteInt32(this.Arity);
                     // 3) Flags
-            sb.Append($"*{this.respFormatFlags?.Length ?? 0}\r\n");
+                    outputResp.WriteArrayLength(this.respFormatFlags?.Length ?? 0);
                     if (this.respFormatFlags != null && this.respFormatFlags.Length > 0)
                     {
                         foreach (var flag in this.respFormatFlags)
-                    sb.Append($"+{flag}\r\n");
+                            outputResp.WriteSimpleString(flag);
                     }
 
                     // 4) First key
-            sb.Append($":{this.FirstKey}\r\n");
+                    outputResp.WriteInt32(this.FirstKey);
                     // 5) Last key
-            sb.Append($":{this.LastKey}\r\n");
+                    outputResp.WriteInt32(this.LastKey);
                     // 6) Step
-            sb.Append($":{this.Step}\r\n");
+                    outputResp.WriteInt32(this.Step);
                     // 7) ACL categories
-            sb.Append($"*{this.respFormatAclCategories?.Length ?? 0}\r\n");
+                    outputResp.WriteArrayLength(this.respFormatAclCategories?.Length ?? 0);
                     if (this.respFormatAclCategories != null && this.respFormatAclCategories.Length > 0)
                     {
                         foreach (var aclCat in this.respFormatAclCategories)
-                    sb.Append($"+@{aclCat}\r\n");
+                            outputResp.WriteSimpleString('@' + aclCat);
                     }
 
                     // 8) Tips
                     var tipCount = this.Tips?.Length ?? 0;
-            sb.Append($"*{tipCount}\r\n");
+                    outputResp.WriteArrayLength(tipCount);
                     if (this.Tips != null && tipCount > 0)
                     {
                         foreach (var tip in this.Tips)
-                    sb.Append($"${tip.Length}\r\n{tip}\r\n");
+                            outputResp.WriteAsciiBulkString(tip);
                     }
 
                     // 9) Key specifications
                     var ksCount = this.KeySpecifications?.Length ?? 0;
-            sb.Append($"*{ksCount}\r\n");
+                    outputResp.WriteArrayLength(ksCount);
                     if (this.KeySpecifications != null && ksCount > 0)
                     {
                         foreach (var ks in this.KeySpecifications)
-                    sb.Append(ks.RespFormat);
+                            outputResp.WriteAsciiDirect(ks.RespFormat);
                     }
 
                     // 10) SubCommands
                     var subCommandCount = this.SubCommands?.Length ?? 0;
-            sb.Append($"*{subCommandCount}\r\n");
+                    outputResp.WriteArrayLength(subCommandCount);
                     if (this.SubCommands != null && subCommandCount > 0)
                     {
                         foreach (var subCommand in SubCommands)
-                    sb.Append(subCommand.RespFormat);
+                            outputResp.WriteAsciiDirect(subCommand.RespFormat);
+                    }
                 }
 
-            return sb.ToString();
+                return output.ToString();
+            }
         }
     }
 }
\ No newline at end of file

@badrishc
Copy link
Copy Markdown
Collaborator

Adding a rough extension idea based on GarnetObjectStoreRespOutput, to refactor the backend state:
https://github.com/microsoft/garnet/compare/badrishc/object-store-ideas?expand=1
Whether input should be part of OperateParams is debatable.
Other thing in this proposal: we keep public ObjectOutputHeader OtherOutput; as a field in GarnetObjectStoreOutput, instead of trying to wedge it into the byte stream of SpanByteAndMemory.

Some form of output consolidation - a wrapper around RespWriteUtils to 'write RESP to memory' - is useful elsewhere too, there's use in LCS here, and in the future IRespSerializable's ToRespFormat() serializations could use a form of this. But that requires making the consolidation independent of the object store.

Right. the struct RespMemoryWriter could be a field in OperateParams and dedidated to memory writing, simply moving fields from OperateParams to RespMemoryWriter.

Would this be possible to add to this PR?

@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 16, 2025

There are several ideas here:

  1. Update Operate() parameters - doable, note however we create the new struct for all operations, even when they don't output anything.

  2. RespMemoryWriter - That's a good idea, I'll move it to Garnet.common too.

  3. Move ObjectOutputHeader outside.

Problem is ObjectOutputHeader was used in Disposal, and it seems to need the same curr, end pointers as normal output? Passing these along (in the Dispose) requires a wrapper, and we're back at having some way to have these together (e.g. exposing RespMemoryWriter's pointer to surrounding struct/class).

e.g.
4ebf2cf

(Following this, I can take what's left of GarnetObjectStoreRespOutput, turn it into the OperateParams struct and do the rest of it).

@badrishc
Copy link
Copy Markdown
Collaborator

  1. Update Operate() parameters - doable, note however we create the new struct for all operations, even when they don't output anything.

What fraction of the object store calls don't output anything? Wouldn't most calls have to output something? We will need to see if the struct introduces any overhead.

@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 16, 2025

  1. Update Operate() parameters - doable, note however we create the new struct for all operations, even when they don't output anything.

What fraction of the object store calls don't output anything? Wouldn't most calls have to output something? We will need to see if the struct introduces any overhead.

In List, 50% of operations. In Hash about 40%. Set about a third. In SortedSet/Geo, about a fifth.

The ones that don't output almost always set an integer result1 and then output that in the parsing layer.

@prvyk prvyk force-pushed the respoutputobject branch 2 times, most recently from d1d12c5 to 89d1fae Compare April 18, 2025 07:45
@prvyk prvyk force-pushed the respoutputobject branch from 1117121 to c6f8092 Compare April 23, 2025 19:51
@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 24, 2025

I think OperateParams belongs in a different PR, it's a too much churn here.

I've been adding RESP3 to COMMAND DOCS/INFO output. It works, the only remaining issue there is that the previous code had a cache of sorts for every command serialization and this one doesn't. IMHO the cache is mostly useless and the caller can do it if they really care. There may be a tiny benefit in caching entire COMMAND DOCS output (about 160K RESP2 uncompressed. redis-cli always fetches it first thing). It's pretty quick as is though - faster than it was, turns out the gain of avoiding strings outweighs the cache, and most first-run time was on constructing the command docs which are later reused - and I'm not sure where to place such a cache (RespServerSession? IGarnetServer?).

Otherwise the PR is ready for review.

@badrishc
Copy link
Copy Markdown
Collaborator

badrishc commented Apr 25, 2025

I agree OperateParams should be separate (if we decide that is worth it). However, a PR with the below change needs to get in before this one:

We keep public ObjectOutputHeader OtherOutput; as a byte-sized field in GarnetObjectStoreOutput, instead of trying to wedge it into the byte stream of SpanByteAndMemory.

With this change, the current PR can become much cleaner, e.g., GarnetObjectStoreRespOutput would no longer need the outputHeader field, GetResult1, etc.. In fact, we might not even need this type and could directly use RespMemoryWriter.

I wanted to do this but have been swamped for the last few days. Will try to get to it asap (around May 1). Feel free to take a stab at it if you wish, but I do understand this is a bit invasive.

@prvyk prvyk force-pushed the respoutputobject branch from b0a9db0 to c4b33b1 Compare April 26, 2025 10:51
@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 26, 2025

Did the ObjectOutputHeader change. A great idea as usual. I didn't understand earlier why the code has to emit an extra header, now I get the adhoc union code. Removing this makes the code far cleaner, and no need for GarnetObjectStoreRespOutput anymore.

When something like dotnet/csharplang#8928 goes in, the union could be implemented cleanly if desired.

@prvyk prvyk force-pushed the respoutputobject branch from 5a74d93 to d54fe33 Compare April 30, 2025 18:28
@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented Apr 30, 2025

I have been using the time to add some more cases to RESP3 update. The PR never claimed to be a complete RESP3 update, and it still isn't. Aside from whatever I've missed:

  • Some cluster commands use Map or Verbatim strings. Currently it's not clear how to get the current protocol version there.

  • INFO/certain CLIENT commands should use verbatim strings. Trying this blew up for some reason, it's not so important.

  • Custom functions/procedures. Technically don't have a spec so not much needed beyond maybe making the protocol version more easily available (anything using CustomProcedureInput has an easy way to do it, but others not so much).

  • Modules. This is more specified. For example, Json module has a RESP3 spec. JSON Module rewrite with custom JSONPath implementation #974 deals with that module, most we can even achieve here is being clear how a module knows the session's protocol version.

(After some thought, doing custom functions and modules is better as part of a future PR, since it changes function signatures others may rely on.)

I thought of three options for solving custom functions and modules:
A) Replace IMemoryOwner and MemoryResult output with RespMemoryWriter. ( 1e91d25)
The RespMemoryWriter solution has the advantages of giving the module quite a bit of output functionality for free (we can actually delete custom output code), but it's slightly less flexible with memory management custom code can do, so I'm not sure this is the right way to go.

B) Replace just IMemoryOwner (7d12671).
This is less invasive than the previous, with the same tradeoffs. I think this is better since custom procedures can already access respvesion directly via customProcInput, while passing the ref tuple for functions was already passing a ref struct in disguise.

C) Just pass the protocol version and let them decide how to go along it. This is basically the previous commit, with us keeping the custom output code and instead modifying about the same function signatures.

None of these are blockers IMHO.

@badrishc
Copy link
Copy Markdown
Collaborator

badrishc commented May 4, 2025

Great work. Have you checked BDN performance for any regression compared to main?

@prvyk
Copy link
Copy Markdown
Contributor Author

prvyk commented May 5, 2025

Running BDN takes a lot of time, and I'm not sure the delta measures more than the temperature of the laptop when it's run... (Also, the HMSet memory result is not replicated in a separate run).

It would be nice if someone could set up a more complete and more objective comparison. @darrenge can you please assist here?

Before:

Method Job Runtime Params Mean Error StdDev Median Gen0 Gen1 Gen2 Allocated
HSetDel .NET 9 .NET 9.0 ACL 105.545 us 0.9589 us 0.8500 us 105.385 us 1.4648 - - 16000 B
HExists .NET 9 .NET 9.0 ACL 12.716 us 0.1432 us 0.1195 us 12.687 us - - - -
HGet .NET 9 .NET 9.0 ACL 12.792 us 0.1288 us 0.1205 us 12.747 us - - - -
HGetAll .NET 9 .NET 9.0 ACL 11.803 us 0.1516 us 0.1344 us 11.742 us - - - -
HIncrby .NET 9 .NET 9.0 ACL 14.862 us 0.1288 us 0.1075 us 14.826 us - - - -
HIncrbyFloat .NET 9 .NET 9.0 ACL 15.454 us 0.0817 us 0.0724 us 15.447 us - - - -
HKeys .NET 9 .NET 9.0 ACL 13.558 us 0.2035 us 0.1700 us 13.502 us - - - -
HLen .NET 9 .NET 9.0 ACL 11.861 us 0.0547 us 0.0485 us 11.845 us - - - -
HMGet .NET 9 .NET 9.0 ACL 14.452 us 0.2747 us 0.2698 us 14.450 us - - - -
HMSet .NET 9 .NET 9.0 ACL 14.122 us 0.1922 us 0.1704 us 14.071 us - - - -
HRandField .NET 9 .NET 9.0 ACL 14.213 us 0.1499 us 0.1251 us 14.173 us - - - -
HScan .NET 9 .NET 9.0 ACL 5.215 us 0.0659 us 0.0584 us 5.190 us 0.0687 - - 704 B
HSetNx .NET 9 .NET 9.0 ACL 14.281 us 0.2706 us 0.3221 us 14.242 us - - - -
HStrLen .NET 9 .NET 9.0 ACL 15.048 us 0.0739 us 0.0577 us 15.047 us - - - -
HVals .NET 9 .NET 9.0 ACL 14.250 us 0.1296 us 0.1082 us 14.205 us - - - -
HSetDel .NET 9 .NET 9.0 AOF 158.907 us 3.1642 us 4.6381 us 159.352 us 1.4648 - - 16000 B
HExists .NET 9 .NET 9.0 AOF 55.448 us 1.0949 us 1.7989 us 54.995 us 0.6104 - - 6400 B
HGet .NET 9 .NET 9.0 AOF 48.666 us 2.4924 us 7.3490 us 43.643 us 0.7324 0.0610 0.0610 -
HGetAll .NET 9 .NET 9.0 AOF 46.716 us 0.7377 us 0.6160 us 46.925 us 0.3052 - - 3200 B
HIncrby .NET 9 .NET 9.0 AOF 67.126 us 1.2703 us 0.9918 us 67.061 us 0.8545 - - 9600 B
HIncrbyFloat .NET 9 .NET 9.0 AOF 93.071 us 1.1743 us 1.0984 us 93.215 us 1.3428 - - 14400 B
HKeys .NET 9 .NET 9.0 AOF 45.653 us 0.6710 us 0.6277 us 45.455 us 0.3052 - - 3200 B
HLen .NET 9 .NET 9.0 AOF 37.295 us 0.3211 us 0.2847 us 37.335 us 0.3052 - - 3200 B
HMGet .NET 9 .NET 9.0 AOF 47.621 us 0.9369 us 0.9201 us 47.295 us 0.9155 - - 9600 B
HMSet .NET 9 .NET 9.0 AOF 67.383 us 1.1009 us 0.9193 us 67.427 us 1.5869 - - 16000 B
HRandField .NET 9 .NET 9.0 AOF 52.447 us 1.0439 us 1.1603 us 52.473 us 0.8545 - - 8800 B
HScan .NET 9 .NET 9.0 AOF 5.376 us 0.0755 us 0.0706 us 5.395 us 0.0687 - - 704 B
HSetNx .NET 9 .NET 9.0 AOF 56.112 us 0.6446 us 0.5714 us 56.094 us 1.2207 0.0610 0.0610 -
HStrLen .NET 9 .NET 9.0 AOF 43.499 us 0.5502 us 0.4877 us 43.301 us 0.7324 0.1221 0.1221 6400 B
HVals .NET 9 .NET 9.0 AOF 46.422 us 0.9177 us 0.9424 us 46.216 us 0.3052 - - 3200 B
HSetDel .NET 9 .NET 9.0 None 103.939 us 0.7351 us 0.6876 us 103.762 us 1.4648 - - 16000 B
HExists .NET 9 .NET 9.0 None 40.549 us 0.6565 us 0.5482 us 40.663 us 0.6104 - - 6400 B
HGet .NET 9 .NET 9.0 None 41.210 us 0.5763 us 0.5109 us 41.082 us 0.6104 - - 6400 B
HGetAll .NET 9 .NET 9.0 None 46.395 us 0.7857 us 0.7349 us 46.190 us 0.3052 - - 3200 B
HIncrby .NET 9 .NET 9.0 None 56.259 us 0.4938 us 0.4124 us 56.267 us 0.9155 - - 9600 B
HIncrbyFloat .NET 9 .NET 9.0 None 83.186 us 0.7480 us 0.6997 us 83.301 us 1.3428 - - 14400 B
HKeys .NET 9 .NET 9.0 None 46.376 us 0.4244 us 0.3762 us 46.464 us 0.3052 - - 3200 B
HLen .NET 9 .NET 9.0 None 37.041 us 0.2860 us 0.2233 us 37.119 us 0.3052 - - 3200 B
HMGet .NET 9 .NET 9.0 None 46.627 us 0.6291 us 0.5577 us 46.415 us 0.9155 - - 9600 B
HMSet .NET 9 .NET 9.0 None 60.402 us 0.5389 us 0.4207 us 60.292 us 1.5259 - - 16000 B
HRandField .NET 9 .NET 9.0 None 53.326 us 0.9333 us 0.8730 us 53.359 us 0.8545 - - 8800 B
HScan .NET 9 .NET 9.0 None 5.205 us 0.0986 us 0.0770 us 5.200 us 0.0687 - - 704 B
HSetNx .NET 9 .NET 9.0 None 50.122 us 0.5357 us 0.4474 us 50.114 us 0.9155 - - 9600 B
HStrLen .NET 9 .NET 9.0 None 42.998 us 0.8225 us 0.8078 us 42.809 us 0.7324 0.1221 0.1221 6400 B
HVals .NET 9 .NET 9.0 None 47.032 us 0.5039 us 0.4208 us 47.145 us 0.3052 - - 3200 B

After:

Method Job Runtime Params Mean Error StdDev Gen0 Gen1 Gen2 Allocated
HSetDel .NET 9 .NET 9.0 ACL 108.273 us 2.0462 us 1.9140 us 1.8311 0.2441 0.2441 16000 B
HExists .NET 9 .NET 9.0 ACL 12.937 us 0.0900 us 0.0798 us - - - -
HGet .NET 9 .NET 9.0 ACL 12.827 us 0.0498 us 0.0442 us - - - -
HGetAll .NET 9 .NET 9.0 ACL 12.330 us 0.2101 us 0.1755 us - - - -
HIncrby .NET 9 .NET 9.0 ACL 15.185 us 0.2430 us 0.2273 us - - - -
HIncrbyFloat .NET 9 .NET 9.0 ACL 15.816 us 0.3106 us 0.2906 us - - - -
HKeys .NET 9 .NET 9.0 ACL 13.745 us 0.0796 us 0.0665 us - - - -
HLen .NET 9 .NET 9.0 ACL 12.198 us 0.1318 us 0.1168 us - - - -
HMGet .NET 9 .NET 9.0 ACL 14.471 us 0.1852 us 0.1546 us - - - -
HMSet .NET 9 .NET 9.0 ACL 14.694 us 0.1649 us 0.1377 us - - - -
HRandField .NET 9 .NET 9.0 ACL 14.410 us 0.0850 us 0.0753 us - - - -
HScan .NET 9 .NET 9.0 ACL 5.333 us 0.0946 us 0.0839 us 0.0687 - - 704 B
HSetNx .NET 9 .NET 9.0 ACL 14.483 us 0.2394 us 0.2240 us - - - -
HStrLen .NET 9 .NET 9.0 ACL 15.445 us 0.2039 us 0.1808 us - - - -
HVals .NET 9 .NET 9.0 ACL 14.588 us 0.1449 us 0.1356 us - - - -
HSetDel .NET 9 .NET 9.0 AOF 127.948 us 2.4804 us 2.8564 us 1.4648 - - 16000 B
HExists .NET 9 .NET 9.0 AOF 42.402 us 0.8374 us 0.9308 us 0.6714 0.0610 0.0610 -
HGet .NET 9 .NET 9.0 AOF 43.578 us 0.8368 us 0.7827 us 0.7324 0.0610 0.0610 -
HGetAll .NET 9 .NET 9.0 AOF 48.289 us 0.8931 us 0.9171 us 0.3052 - - 3200 B
HIncrby .NET 9 .NET 9.0 AOF 69.686 us 1.0463 us 1.7767 us 0.8545 - - 9600 B
HIncrbyFloat .NET 9 .NET 9.0 AOF 95.762 us 1.8758 us 1.7546 us 1.3428 - - 14400 B
HKeys .NET 9 .NET 9.0 AOF 49.292 us 0.5884 us 0.5216 us 0.3052 - - 3200 B
HLen .NET 9 .NET 9.0 AOF 39.065 us 0.6105 us 0.5710 us 0.3052 - - 3200 B
HMGet .NET 9 .NET 9.0 AOF 50.057 us 0.9883 us 1.2137 us 0.9155 - - 9600 B
HMSet .NET 9 .NET 9.0 AOF 70.610 us 1.3265 us 1.3622 us 1.4648 - - 16000 B
HRandField .NET 9 .NET 9.0 AOF 54.158 us 0.9774 us 0.9143 us 0.8545 - - 8800 B
HScan .NET 9 .NET 9.0 AOF 5.598 us 0.0977 us 0.0914 us 0.0687 - - 704 B
HSetNx .NET 9 .NET 9.0 AOF 59.686 us 1.1842 us 2.3650 us 0.8545 - - 9600 B
HStrLen .NET 9 .NET 9.0 AOF 45.878 us 0.9143 us 1.0162 us 0.6104 - - 6400 B
HVals .NET 9 .NET 9.0 AOF 48.692 us 0.8459 us 0.7499 us 0.3052 - - 3200 B
HSetDel .NET 9 .NET 9.0 None 108.564 us 1.2737 us 1.1914 us 1.4648 - - 16000 B
HExists .NET 9 .NET 9.0 None 41.687 us 0.5581 us 0.5482 us 0.7324 0.0610 0.0610 -
HGet .NET 9 .NET 9.0 None 43.019 us 0.8317 us 0.7373 us 0.7324 0.0610 0.0610 -
HGetAll .NET 9 .NET 9.0 None 46.886 us 0.5259 us 0.4662 us 0.3052 - - 3200 B
HIncrby .NET 9 .NET 9.0 None 57.208 us 0.6685 us 0.6253 us 0.9155 - - 9600 B
HIncrbyFloat .NET 9 .NET 9.0 None 84.955 us 1.0450 us 0.9775 us 1.3428 - - 14400 B
HKeys .NET 9 .NET 9.0 None 47.660 us 0.8902 us 0.7891 us 0.3052 - - 3200 B
HLen .NET 9 .NET 9.0 None 38.863 us 0.5973 us 0.5295 us 0.3052 - - 3200 B
HMGet .NET 9 .NET 9.0 None 48.820 us 0.7732 us 0.7233 us 0.9155 - - 9600 B
HMSet .NET 9 .NET 9.0 None 60.392 us 1.0650 us 0.9441 us 1.6479 0.0610 0.0610 113330 B
HRandField .NET 9 .NET 9.0 None 52.589 us 0.6633 us 0.5880 us 0.8545 - - 8800 B
HScan .NET 9 .NET 9.0 None 5.430 us 0.0722 us 0.0603 us 0.0687 - - 704 B
HSetNx .NET 9 .NET 9.0 None 50.393 us 0.9529 us 0.9359 us 0.9155 - - 9600 B
HStrLen .NET 9 .NET 9.0 None 43.947 us 0.8549 us 0.9845 us 0.6104 - - 6400 B
HVals .NET 9 .NET 9.0 None 48.116 us 0.9229 us 0.8633 us 0.3052 - - 3200 B

prvyk added 26 commits May 9, 2025 13:41
…Then remove GarnetObjectStoreRespOutput and convert all uses to RespMemoryWriter.
Convert GEOSEARCHSTORE implementation to use RespMemoryWriter.
(All sites calling TryReadErrorAsString already checked for '-' character, it's fine to return false there)
…Base.

Allow long cursor for object SCAN commands.
@prvyk prvyk force-pushed the respoutputobject branch from 6da1d7e to 0fb8743 Compare May 9, 2025 10:42
@badrishc badrishc merged commit 33ab11e into microsoft:main May 9, 2025
26 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Jul 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants