Introduction
Redis is a fast, in-memory, key-value data store used widely for caching, session storage, and real-time data processing. While Redis traditionally handles data as simple strings (with structures like lists, sets, and hashes), it has evolved to support more complex data types, especially JSON, which is often the format of choice for modern applications dealing with complex nested data structures.
This article will explore two ways to handle JSON data in Redis using RedisString and RedisJSON. We will cover the fundamental differences between these approaches, why RedisJSON is preferable for large JSON objects, and provide real-world use cases to highlight when and why you should consider RedisJSON. The purpose is to measure the performance, data handling capabilities, and potential use cases for each approach.
What is RedisString?
RedisString is the most basic and commonly used data type in Redis. It stores data as a simple string value, allowing for GET, SET, and more operations. You can store JSON data inside a RedisString by serializing the JSON object into a string format.
What is RedisJSON?
RedisJSON is a Redis module that provides native support for storing, querying, and manipulating JSON data inside Redis. It allows you to store JSON documents and perform fine-grained operations on specific fields without needing to retrieve the entire document. RedisJSON is purpose-built to handle large JSON data efficiently and support complex JSON structures like nested objects, arrays, and primitives.
Now, let us understand everything with a POC.
Table of Contents
- Project Setup
- Prerequisites
- Running the ASP.NET Core Web API Project
- Available Endpoints
- Analyzing Performance Results
- Conclusion
Project Setup
Please find the attachment zip file with the following.
- Source Code: The source code has both RedisString and RedisJSON APIs available.
- Test_Result_Images: The performance comparison, including key metrics (latency and data received).
- JSON File: JSON file to test code on your local machine.
- Instructions to Run the Project: Txt file with instructions to run the project.
Prerequisites
- Redis Server installed and running (version x.x.x)
- .NET Core SDK installed
ASP.NET Core Web API Project
You will need the following dependencies in your ASP.NET Core Web API project.
- StackExchange.Redis for RedisString operations.
- NRedisStack for RedisJSON operations.
- Run the project
Available Endpoints
Your Web API exposes several endpoints to perform different operations with both RedisString and RedisJSON. Here are the key endpoints and how to use them.
- Upload JSON file: We will first upload a JSON file and then perform all the operations to see the difference.
- RedisString: POST /api/redisString/upload-file
- RedisJSON: POST /api/redisJson/upload-file
- Clean Cache: This endpoint is used to clean the cache.
- RedisString: POST /api/redisString/CleanCache
- RedisJSON: POST /api/redisJson/CleanCache
- get-data: This endpoint is used to get all the JSON data.
- RedisString: POST /api/redisString/get-data
- RedisJSON: POST /api/redisJson/get-data
- get-dataById: This endpoint is used to get data by id.
- RedisString: POST /api/redisString/get-dataById
- RedisJSON: POST /api/redisJson/get-dataById
- check-KeyExists: This endpoint is used to check if the Redis key exists or not.
- RedisString: POST /api/redisString/check-KeyExists
- RedisJSON: POST /api/redisJson/check-KeyExists
Analyzing Performance Results
First, upload the JSON file using the upload-file endpoint. This endpoint will upload the file to the project directory and save the data into the cache.
Using RedisString
[HttpPost("upload-file")]
public async Task<IActionResult> UploadJson([FromForm] IFormFile jsonFile)
{
if (jsonFile == null || jsonFile.Length == 0)
{
return BadRequest("Invalid file or no file was uploaded.");
}
var fileExtension = Path.GetExtension(jsonFile.FileName);
if (fileExtension.ToLower() != ".json")
{
return BadRequest("Only JSON files are allowed.");
}
if (jsonFile.ContentType != "application/json")
{
return BadRequest("File content type is not valid. Only JSON files are allowed.");
}
try
{
var jsonFiles = Directory.GetFiles(_uploadDirectory, "*.json");
foreach (var file in jsonFiles)
{
System.IO.File.Delete(file);
}
// Save the file to the project directory
var filePath = Path.Combine(_uploadDirectory, jsonFile.FileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await jsonFile.CopyToAsync(fileStream);
}
// Read the content from the file and cache it using Redis
var jsonData = await System.IO.File.ReadAllTextAsync(filePath);
// Deserialize the JSON to validate its format
var data = JsonSerializer.Deserialize<object>(jsonData);
// Get the file size (in MB)
var fileSizeKB = jsonFile.Length / (1024.0 * 1024.0);
await _redisString.UploadFile();
return Ok(new
{
Message = "File uploaded successfully",
FileSizeMB = fileSizeKB
});
}
catch (JsonException)
{
return BadRequest("Invalid JSON format.");
}
catch (Exception)
{
return StatusCode(500, "Internal server error.");
}
}
public async Task UploadFile()
{
// Remove Redis cache
await _cache.RemoveAsync(cacheKey);
// Cache miss, read from JSON file
var data = await ReadJsonFileAsync();
if (data.ActionAccessRight.Count > 0)
{
// Save the data into RedisString
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(data.ActionAccessRight));
}
}
In the above two methods, we have uploaded the JSON file into the project directory and saved the data into Redis using redisString.
Output
Using RedisJSON
[HttpPost("upload-file")]
public async Task<IActionResult> UploadJson([FromForm] IFormFile jsonFile)
{
if (jsonFile == null || jsonFile.Length == 0)
{
return BadRequest("Invalid file or no file was uploaded.");
}
var fileExtension = Path.GetExtension(jsonFile.FileName);
if (fileExtension.ToLower() != ".json")
{
return BadRequest("Only JSON files are allowed.");
}
if (jsonFile.ContentType != "application/json")
{
return BadRequest("File content type is not valid. Only JSON files are allowed.");
}
try
{
var jsonFiles = Directory.GetFiles(_uploadDirectory, "*.json");
foreach (var file in jsonFiles)
{
System.IO.File.Delete(file);
}
// Save the file to the project directory
var filePath = Path.Combine(_uploadDirectory, jsonFile.FileName);
using (var fileStream = new FileStream(filePath, FileMode.Create))
{
await jsonFile.CopyToAsync(fileStream);
}
// Read the content from the file and cache it using Redis
var jsonData = await System.IO.File.ReadAllTextAsync(filePath);
// Deserialize the JSON to validate its format
var data = JsonSerializer.Deserialize<object>(jsonData);
// Get the file size (in MB)
var fileSizeKB = jsonFile.Length / (1024.0 * 1024.0);
await _accessRepository.UploadFile();
return Ok(new
{
Message = "File uploaded successfully",
FileSizeMB = fileSizeKB
});
}
catch (JsonException)
{
return BadRequest("Invalid JSON format.");
}
catch (Exception)
{
return StatusCode(500, "Internal server error.");
}
}
public async Task UploadFile()
{
// Remove Redis JSON
var res = await _redisDb.JSON().ForgetAsync(cacheKey, path: "$");
// Cache miss, read from JSON file
var data = await ReadJsonFileAsync();
if (data.ActionAccessRight.Count > 0)
{
// Save the data into RedisJSON using root path
_redisDb.JSON().Set(cacheKey, "$", data, When.Always);
}
}
In the above two methods, we have uploaded the file into the project directory and saved the data into redisJSON using the root path.
Output
Now, we will use two endpoints, get-dataById, and check-keyExists, to check the performance in both cases.
get-dataById
Using RedisString
public async Task<(List<ActionAccessRight> AccessRights, TimeSpan Latency, int DataSize)> GetAccessRightByIdAsync(long menuId, long accessRightId)
{
List<ActionAccessRight> accessRights;
TimeSpan redisLatency = new TimeSpan();
var stopwatch = new Stopwatch();
stopwatch.Start();
// Get data from Redis
var result = await _cache.GetStringAsync(cacheKey);
var res = JsonSerializer.Deserialize<List<ActionAccessRight>>(result.ToString());
// Get the data for menuId and accessRightId using LINQ
accessRights = res.Where(a => a.MenuId == menuId && a.AccessRightId == accessRightId).ToList();
stopwatch.Stop();
redisLatency = stopwatch.Elapsed;
// Data size processed
int dataSize = (int)ConvertBytesToKB((result?.ToString()?.Length ?? 0) * sizeof(char));
return (accessRights, redisLatency, dataSize);
}
Output
As you can see, the latencyinmilliseconds and dataReceivedFromRedisInKb. Now, we will check in the case of RedisJson.
Using RedisJSON
public async Task<(List<ActionAccessRight> AccessRights, TimeSpan Latency, int DataSize)> GetAccessRightByIdAsync(long menuId, long accessRightId)
{
List<ActionAccessRight> accessRights = null;
var stopwatch = new Stopwatch();
TimeSpan redisLatency = new TimeSpan();
var jsonPath = $"$..[?(@.MenuId=={menuId} && @.AccessRightId=={accessRightId})]";
stopwatch.Start();
var result = await _redisDb.JSON().GetAsync(cacheKey, path: jsonPath);
stopwatch.Stop();
redisLatency = stopwatch.Elapsed;
// Data size processed
int dataSize = (int)ConvertBytesToKB((result?.ToString()?.Length ?? 0) * sizeof(char));
if (!result.IsNull)
{
accessRights = JsonSerializer.Deserialize<List<ActionAccessRight>>(result.ToString());
}
return (accessRights, redisLatency, dataSize);
}
Output
Now, we will see all the metrics using the table given below.
|
RedisString |
RedisJson |
Latency(ms) |
472 |
305 |
Data Received(Kb) |
37196 |
50 |
Now we will move to another endpoint check-Keyexists.
check-KeyExists
Using RedisString
public async Task<(bool AccessRights, TimeSpan Latency, int DataSize)> CheckAccessRightByIdAsync(long menuId, long accessRightId)
{
List<ActionAccessRight> accessRights = null;
var stopwatch = new Stopwatch();
TimeSpan redisLatency = new TimeSpan();
try
{
stopwatch.Start();
var result = await _cache.GetStringAsync(cacheKey);
var res = JsonSerializer.Deserialize<List<ActionAccessRight>>(result.ToString());
accessRights = res.Where(a => a.MenuId == menuId && a.AccessRightId == accessRightId).ToList();
stopwatch.Stop();
redisLatency = stopwatch.Elapsed;
// Data size processed
int dataSize = (int)ConvertBytesToKB((result?.ToString()?.Length ?? 0) * sizeof(char));
if (accessRights.Count == 0)
{
return (false, redisLatency, dataSize);
}
return (true, redisLatency, dataSize);
}
catch (Exception)
{
return (false, redisLatency, 0);
}
}
Output
As you can see, the latencyInMilliseconds and dataReceivedFromRedisInKb. Now, we will check in the case of RedisJson.
Using RedisJSON
public async Task<(bool AccessRights, TimeSpan Latency, int DataSize)> CheckAccessRightByIdAsync(long menuId, long accessRightId)
{
List<ActionAccessRight> accessRights = null;
var stopwatch = new Stopwatch();
TimeSpan redisLatency = new TimeSpan();
try
{
var jsonPath = $"$..[?(@.MenuId=={menuId} && @.AccessRightId=={accessRightId})]";
stopwatch.Start();
var result = await _redisDb.JSON().TypeAsync(cacheKey, path: jsonPath);
stopwatch.Stop();
redisLatency = stopwatch.Elapsed;
// Data size processed
int dataSize = (int)ConvertBytesToKB((result?.ToString()?.Length ?? 0) * sizeof(char));
if (result.Length == 0)
{
return (false, redisLatency, dataSize);
}
return (true, redisLatency, dataSize);
}
catch (Exception)
{
return (false, redisLatency, 0);
}
}
Output
Now, we will see all the metrics using the table given below.
|
RedisString |
RedisJson |
Latency(ms) |
487 |
393 |
Data Received(Kb) |
37196 |
0 |
Conclusion
By running this ASP.NET Core Web API project, you can clearly see the performance advantages of RedisJSON over RedisString, especially when dealing with large or complex JSON data. RedisString is simple to use but quickly becomes inefficient for large-scale JSON handling due to its lack of partial updates, higher memory consumption, and increased network overhead. RedisJSON, with its ability to update and retrieve parts of a JSON document, provides a much more efficient solution.
Key Takeaways
- RedisString is suitable for simple use cases but lacks efficiency when handling large or frequently updated JSON objects.
- RedisJSON offers partial updates, more efficient memory usage, atomic operations, and faster queries, making it ideal for modern applications dealing with complex, nested data structures.
By understanding and analyzing the performance of both RedisString and RedisJSON through this project, you can make more informed decisions about how to store and manage JSON data in Redis, depending on your specific application needs.
Thank You, and Stay Tuned for More!