AWS Logo
Menu
📉 Reduce Amazon MemoryDB Latency with Smarter  Patterns

📉 Reduce Amazon MemoryDB Latency with Smarter Patterns

Learn how to observe latency for reads and writes in MemoryDB so you can react to anomalies and optimize your apps.

Matheus Guimaraes
Amazon Employee
Published Mar 26, 2025
When using Redis-compatible engines like Amazon MemoryDB, your access patterns can directly impact latency. Even subtle inefficiencies can slow down your app.
In this short demo, I’ll show how tweaking your Redis commands can have an impact on latency so you can make the most of MemoryDB’s lightning-fast performance.
We’ll have a look at an inefficient way to write and read to the database before learning how to optimize those and then checking SuccessfulWriteRequestsLatency and SuccessfulReadRequestsLatency,two key metrics that you can access via Amazon CloudWatch, to verify that it worked.
đŸ§Ș This demo uses .NET 9 and StackExchange.Redis, and uses Valkey running on Amazon MemoryDB.

đŸ› ïž The Setup

I have created a very simple app to perform the reads and writes. I encourage you to download the code so you can follow along and poke around.
đŸ’ŸDownload the full code here.
We’re going to simulate two common Redis patterns:
  1. Inefficient write: Sending 100 write operations with large, verbose payloads (e.g., detailed player activity logs in JSON).
  2. Efficient write: Sending the same 100 operations, but using much smaller, compact payloads that reduce memory and processing overhead.
And then:
  1. Inefficient read: Pulling an entire hash with HGETALL.
  2. Efficient read: Fetching only what’s needed by selecting specific fields with theHMGETcommand.
I’m not going to be following good architectural practices here as this is meant to be a very simple demo. I have a post coming up soon which goes through the whole process of creating a .NET 9 application from scratch with real-world best architectural practices so stay tuned for that if you’re interested.
For this one, I only have two files in my application: Program.cs and ValkeyClient.cs.
Program.cs contains all the application running logic. First thing we do is create a unique id so we can provide some isolation for the data we store and read every time we run the app.
To help with our benchmarking, we add the ability to pass in arguments to force the app to run in either efficient or inefficient mode.
It runs in “inefficient” mode by default, but we can switch to effective mode by appending the word “efficient” when we run the app.
The MemoryDB Valkey endpoint is hard-coded (don’t try this at home, kids! And by “home” I mean “production” 😄) and I also instantiate a ValkeyClient directly instead of using dependency injection for simplicity.
As for the RunInefficientMode() and RunEfficientMode() they both use the ValkeyClient to call similar named methods that actually do all the work. We do, however, wrap them in a method called MeasureAsync() so we can benchmark the application performance of those operations and print them to the screen for monitoring.
ValkeyClient.cs, as the name suggests, is where the magic happens.
We start by connecting to MemoryDB and making the Valkey database available through a private IDatabase object that we can use to perform the data operations.
Now, we just need to implement the business logic for making inefficient and efficient reads and writes. Let’s start with the writing operation.

Optimizing writes

In this scenario, I’m simulating a very common inefficiency in distributed caching: storing large, verbose JSON documents unnecessarily.
Let’s pretend that we’re tracking a player’s activity in a video game. We store a detailed object for every action which includes not just the action and timestamp, but also metadata like inventory, location, session ID, server info, and so on. That’s a lot of data!
Let’s simulate that:
So we run a loop that writes 100 such entries to MemoryDB using StringSetAsync. This means the server has to:
  1. Parse large payloads
  2. Allocate memory
  3. Perform processing for each field in the value
And since Valkey, like Redis, is single-threaded for command execution, every write still waits its turn in the event loop.
Now imagine we simplify the payload. We drop unnecessary fields and just store what the app really needs — like an ID, timestamp, and damage value. So we write the efficient version like this:
We still perform 100 write operations — same command, same frequency — but the server has less data to process. And guess what? That matters.

Optimizing reads

One of the most common anti-patterns when reading data is overfetching. That just means getting more data than you need which creates unnecessary operational overhead and increased transfer time for downstream clients. It’s quite easy to make that mistake in Valkey because of how simple the commands are.
Let’s use a Redis hash this time to illustrate this. This is a method that just generates test data for our read operations:
We just add 200 entries to a hash each with a key and value of simple strings that have the index value attached to them.
📝Remember! Don’t confuse Redis hashes with a HashSet<string>! They’re very different data structures! Redis hashes are more similar to Dictionary<string,string> in C#.
Now, for the inefficient reads we’re going to use HGETALL which is a Redis command that gets every field and value in a hash. This may have its place in certain solutions where you do want to cache full records, but that should be the exception, not the rule.
One line of code? Yep! Beautiful on the outside, not so much on the inside. đŸ‘ș When you do this, the database has to fetch the entire hash from memory, serialize and send all its contents over the network, and finally wait for your client to parse the full response.
Let’s see how we can optimize this:
Still pretty simple, right? For this example, we assume we only need “field1” and “field3”. We only have an extra step which is to adds those to a string array and then pass it in with our read request to only get the value for those back from Valkey.
Ok, let’s put all this into action.

Running the app

One thing to always remember with Amazon MemoryDB is that you can only connect to it from within the same VPC. This is by design.
That means that if you want to see the app running you need to deploy it on something like an Amazon EC2 instance running within the same VPC as your MemoryDB cluster to see things working which is what I’m doing.
Like I said in the beginning, I’m not going to cover that here, however, here are the steps I’m taking which could be helpful if you’re getting your head around these things:
  1. Publish the app using dotnet publish
  2. Zip up the folder with the published code using your favorite tool (I just use Powershell Compress-Archive)
  3. Copy the zip up to Amazon S3
  4. Log into your EC2 instance
  5. Download the zip file from S3 into a folder in your EC2 local storage
  6. Run the app (make sure you got .NET 9 installed if you’re using my code)
You should see something beautiful like this:
Or, running in efficient mode, it should look like this:

📊 What to Watch in CloudWatch

MemoryDB exposes many metrics that can help you keep an eye on the database performance and overall health. For latency, there are two specific metrics of interest available: SuccessfulWriteRequestsLatency and SuccessfulReadRequestsLatency.
Note that only successful requests are counted, hence their names. If you want to keep track of failed requests then you can use the ErrosCount metric.
📝Remember! Both SuccessfulWriteRequestsLatency and SuccessfulReadRequestsLatency are only available if you’re running Valkey engine 7.2 and above.
These metrics won’t show up unless there is some read and write activity going on so I run the app in inefficient mode first, then I do another run in efficient mode so I can compare them.
Let’s check out the results!
I log into the AWS Console, navigate to Amazon CloudWatch and find the “All Metrics” menu. The home screen shows an empty graph at the top and an interface at the bottom where you can pick metrics to display or search for them.
📝Remember! You can also access SuccessfulWriteRequestsLatency and SuccessfulReadRequestsLatency metrics programatically either via the AWS CLI or using an AWS SDK.
I can see there are 249 metrics reported from MemoryDB databases in the Oregon region (us-west-2) which is the one I’m using.
I could browse through all the MemoryDB metrics , but I’ll just do a simple search instead. I type “successful” and hit enter. It lists a few places where metrics matching that name either wholly or partially can be found.
Let’s have a look inside MemoryDB > Node Metrics.
By default, the metrics won’t be selected so you have to tick the boxes next to their name to add them to the graph. Notice the Period option. I make sure to set that to 1 minute since I ran the application 1 minute apart to make the comparison easier.
At 12:30 I ran the application in inefficient mode, and at 12:31 I ran it in efficient mode, so let’s see how our code optimizations impacted the server-side latency.
Annnd
I’m happy to verify that it is exactly as expected! đŸ„ł
Of course, the difference is negligible in this case because I have a very limited amount of both data and simultaneous reads and writes, but the lesson remains the same no matter what size of application you are developing.
In summary, if you suspect you have performance bottlenecks, optmize your app to follow Valkey best practices (which, incidentally, are the same as Redis) and keep an eye on the SuccessfulWriteRequestsLatency**** and SuccessfulReadRequestsLatency metrics to measure the impact on server-side database performance.

🚀 Try It Yourself

I made this demo as a simple .NET 9 console app so you can play around with it or use it as a starting point for your needs. Download the code on my github.
 

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.

Comments