Why Big Companies Avoid SET for User Data: A Redis Storage Guide
The article compares storing user objects in Redis using plain SET with JSON versus using HASH fields, providing code demos, benchmark results, memory and concurrency analysis, and practical guidelines on when to choose each approach for optimal performance and safety.
Scenario Simulation
Store a user object {"id":1001,"name":"张三","age":28,"vip":true}. Two schemes are considered:
Scheme 1: Store the whole JSON string with SET user:1001 ... Scheme 2: Store each field in a Redis hash with
HSET user:1001 id 1001 name 张三 age 28 vip 1Object Definition
public class User {
private int id;
private String name;
private int age;
private boolean vip;
public String toJson() { return new Gson().toJson(this); }
public static User fromJson(String json) { return new Gson().fromJson(json, User.class); }
}String Storage Demo (JSON serialization)
public class StringStorageDemo {
private static final Jedis jedis = new Jedis("localhost");
public void saveUser(User user) {
jedis.set("user:" + user.getId(), user.toJson());
}
public User getUser(int id) {
String json = jedis.get("user:" + id);
return User.fromJson(json);
}
public void updateAge(int id, int newAge) throws Exception {
// non‑atomic read‑modify‑write
String key = "user:" + id;
User user = User.fromJson(jedis.get(key));
user.setAge(newAge);
jedis.set(key, user.toJson());
}
}Hash Storage Demo (field‑level storage)
public class HashStorageDemo {
private static final Jedis jedis = new Jedis("localhost");
private Map<String, String> toMap(User user) {
Map<String, String> map = new HashMap<>();
map.put("id", String.valueOf(user.getId()));
map.put("name", user.getName());
map.put("age", String.valueOf(user.getAge()));
map.put("vip", user.isVip() ? "1" : "0");
return map;
}
public void saveUser(User user) {
jedis.hset("user:" + user.getId(), toMap(user));
}
public User getUser(int id) {
Map<String, String> map = jedis.hgetAll("user:" + id);
return new User(
Integer.parseInt(map.get("id")),
map.get("name"),
Integer.parseInt(map.get("age")),
map.get("vip").equals("1")
);
}
public void updateAge(int id, int newAge) {
jedis.hset("user:" + id, "age", String.valueOf(newAge));
}
}Performance Test Recommendation
Run redis-benchmark -n 100000 -t set,hset to compare write performance.
Benchmark Results (10 000 operations)
Operation String Scheme Hash Scheme Improvement
Write time 4200 ms 850 ms 395%
Read time 3800 ms 650 ms 485%
Network 12 MB 2.3 MB 422%When to Use String
Simple values that need expiration
Counters or other single‑value scenarios
Storing serialized binary data
Why Hash Is Recommended
1. Memory Optimization
Redis hash uses a ziplist when field count < 512 and value size < 64 bytes; it converts to a hashtable as it grows.
Memory measured with redis-rdb-tools:
String storage ≈ 120 bytes per object
Hash storage ≈ 65 bytes per object (≈ 45 % saving)
2. Operation Efficiency
Read a single field: GET + deserialization vs HGET Modify a single field: GET → modify → SET vs HSET Batch operations: multiple commands vs HMSET/HMGET in one round‑trip
3. Concurrency Safety
# Non‑atomic (String)
GET user:1001 → modify age → SET user:1001
# Atomic (Hash)
HSET user:1001 age 294. Extensibility
Adding a new field with a hash requires a single HSET; the string approach requires a full read‑modify‑write cycle.
Key Difference Illustration
Updating a field with String:
Get the whole JSON
Deserialize to object
Modify the field
Serialize back to JSON
Store the new JSON
Updating a field with Hash: direct HSET of the changed field (network traffic reduced by > 60 %).
Concurrency risk: the multi‑step string update can cause data overwrite, whereas a single HSET is atomic.
Java Development Best Practices
Scenarios for Using Hash
Frequent partial updates (e.g., user profile changes)
Objects with more than three fields (memory advantage appears)
Need for atomic field operations
Exception Cases for Using String
void saveOrderSnapshot(Order order) {
jedis.set("order:" + order.getId(), order.toJson());
}Performance Optimization Tip
public void batchUpdate(Map<Integer, User> users) {
Pipeline pipeline = jedis.pipelined();
users.forEach((id, user) -> {
pipeline.hset("user:" + id, toMap(user));
});
pipeline.sync();
}Conclusion
Hash storage provides memory efficiency, faster single‑field operations, atomic updates, and easier schema evolution compared with storing the whole object as a string.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
