106 lines
3.6 KiB
Markdown
106 lines
3.6 KiB
Markdown
|
|
---
|
||
|
|
title: Choose JSON vs Hash vs String Appropriately
|
||
|
|
impact: MEDIUM
|
||
|
|
impactDescription: Optimal data model for your use case
|
||
|
|
tags: json, hash, string, data-structures, documents
|
||
|
|
description: Choose JSON vs Hash vs String Appropriately
|
||
|
|
alwaysApply: true
|
||
|
|
---
|
||
|
|
|
||
|
|
## Choose JSON vs Hash vs String Appropriately
|
||
|
|
|
||
|
|
Redis offers three ways to store structured data: JSON, Hash, and serialized strings. Each has distinct trade-offs around atomic partial operations and indexability.
|
||
|
|
|
||
|
|
| Feature | JSON | Hash | String (serialized JSON) |
|
||
|
|
|---------|------|------|--------------------------|
|
||
|
|
| **Structure** | Nested objects and arrays | Flat key-value pairs | Any structure |
|
||
|
|
| **Atomic partial reads** | Yes (`$.field`) | Yes (`HGET`) | No (must fetch entire value) |
|
||
|
|
| **Atomic partial writes** | Yes (`JSON.SET $.field`) | Yes (`HSET`) | No (must rewrite entire value) |
|
||
|
|
| **RQE indexing** | Yes | Yes | No |
|
||
|
|
| **Geospatial indexing** | Yes | Yes | No |
|
||
|
|
| **Memory efficiency** | Higher overhead | More efficient | Most compact |
|
||
|
|
| **Field-level expiration** | No | Yes (HEXPIRE) | No |
|
||
|
|
|
||
|
|
**When to use each:**
|
||
|
|
- **JSON**: Nested structures with atomic partial updates and indexing needs
|
||
|
|
- **Hash**: Flat objects with atomic field access, field-level expiration, or memory efficiency
|
||
|
|
- **String**: Simple caching where you always read/write the entire object and don't need indexing
|
||
|
|
|
||
|
|
**Correct:** Use JSON for nested structures with atomic partial updates.
|
||
|
|
|
||
|
|
**Python** (redis-py):
|
||
|
|
```python
|
||
|
|
# JSON supports nested structures and atomic deep updates
|
||
|
|
redis.json().set("user:1001", "$", {
|
||
|
|
"name": "Alice",
|
||
|
|
"preferences": {"theme": "dark", "notifications": True}
|
||
|
|
})
|
||
|
|
|
||
|
|
# Atomic update of nested field - no read-modify-write needed
|
||
|
|
redis.json().set("user:1001", "$.preferences.theme", "light")
|
||
|
|
```
|
||
|
|
|
||
|
|
**Java** (Jedis):
|
||
|
|
```java
|
||
|
|
import redis.clients.jedis.UnifiedJedis;
|
||
|
|
import redis.clients.jedis.json.Path2;
|
||
|
|
import org.json.JSONObject;
|
||
|
|
|
||
|
|
try (UnifiedJedis jedis = new UnifiedJedis("redis://localhost:6379")) {
|
||
|
|
JSONObject user = new JSONObject();
|
||
|
|
user.put("name", "Alice");
|
||
|
|
user.put("preferences", new JSONObject().put("theme", "dark"));
|
||
|
|
|
||
|
|
jedis.jsonSet("user:1001", new Path2("$"), user);
|
||
|
|
|
||
|
|
// Atomic update of nested field
|
||
|
|
jedis.jsonSet("user:1001", new Path2("$.preferences.theme"), "light");
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Correct:** Use Hash for flat objects with atomic field access.
|
||
|
|
|
||
|
|
**Python** (redis-py):
|
||
|
|
```python
|
||
|
|
# Hash is efficient for flat data with atomic field operations
|
||
|
|
redis.hset("session:abc", mapping={
|
||
|
|
"user_id": "1001",
|
||
|
|
"created_at": "2024-01-01",
|
||
|
|
"ip": "192.168.1.1"
|
||
|
|
})
|
||
|
|
|
||
|
|
# Atomic field read and update
|
||
|
|
ip = redis.hget("session:abc", "ip")
|
||
|
|
redis.hset("session:abc", "ip", "10.0.0.1")
|
||
|
|
```
|
||
|
|
|
||
|
|
**Correct:** Use String for simple caching without partial updates.
|
||
|
|
|
||
|
|
**Python** (redis-py):
|
||
|
|
```python
|
||
|
|
import json
|
||
|
|
|
||
|
|
# String is fine when you always read/write the entire object
|
||
|
|
# and don't need indexing or partial updates
|
||
|
|
config = {"feature_flags": {"dark_mode": True}, "version": "1.0"}
|
||
|
|
redis.set("config:app", json.dumps(config), ex=3600)
|
||
|
|
|
||
|
|
# Must fetch and parse entire object
|
||
|
|
config = json.loads(redis.get("config:app"))
|
||
|
|
```
|
||
|
|
|
||
|
|
**Incorrect:** Using String when you need atomic partial updates.
|
||
|
|
|
||
|
|
**Python** (redis-py):
|
||
|
|
```python
|
||
|
|
import json
|
||
|
|
|
||
|
|
# BAD: Must fetch, parse, modify, serialize, and rewrite entire object
|
||
|
|
data = json.loads(redis.get("user:1001"))
|
||
|
|
data["preferences"]["theme"] = "light" # Not atomic!
|
||
|
|
redis.set("user:1001", json.dumps(data))
|
||
|
|
# Another client could have modified the object between GET and SET
|
||
|
|
```
|
||
|
|
|
||
|
|
Reference: [Data Type Comparison](https://redis.io/docs/latest/develop/data-types/compare-data-types/#documents)
|