Redis provides a versatile data structure called hashes to store associated field-value pairs. With the hash data structure, you can model real-world entities and their properties efficiently.
However, iterating over a large hash with millions of field-value pairs can be challenging. Fetching all elements at once with blocking commands like HGETALL and HMGET is not feasible. It can lock up the Redis instance for a considerable amount of time.
This is where the incremental cursor-based HSCAN command comes in handy. HSCAN allows fetching a few hash fields and values at a time from Redis in a non-blocking fashion.
In this comprehensive guide, we will dive deeper into the inner workings, performance analysis, and practical use cases for optimizing large hash scans with HSCAN.
Understanding Incremental Scan Algorithms
HSCAN implements a traversal algorithm called incremental scanning. Here is how it works under the hood:
-
The Redis hash is conceptually broken down into partitions. Partition size depends on the hash encoding – ziplist vs hashtable.
-
HSCAN maintains a cursor which points to the current partition position. It starts from 0.
-
Every HSCAN call returns elements from the cursor‘s partition up to the COUNT or configured size.
-
Cursor advances forward based on return sizehints. Next HSCAN picks elements from the new partition.
-
Once all partitions are scanned, cursor loops back to 0.

So HSCAN provides a moving window over the hash elements by gradually advancing the cursor.
Analysis
Since HSCAN returns a few elements per call, it minimizes redis command overheads and latency spikes. But it requires multiple round trips to fully traverse the hash.
The processor time is also lower as Redis avoids creating one huge multi-bulk reply for all hash fields.
Overall efficiency depends on:
- Hash encoding type – ziplist sizes are predetermined while hashtable partitioning is dynamic
- Degree of parallelism – running concurrent HSCANs divides work
- Number of round trips – pipelined HSCANs reduce trips substantially
Now let us analyze the performance difference between HSCAN and other Redis hash commands via benchmarks.
Benchmarking HSCAN Performance
I created a test hash named user_records with 1 million field-value pairs on Redis 6.2 instance.
The aim is to measure different methods for fetching all hash fields and values sequentially. Here is the comparison:

Observations:
-
HGETALL takes least time due to single call. But it blocks Redis during multi-bulk reply creation.
-
HMGET has double the latency since it makes separate requests for fields and values in two steps. Still blocks server.
-
Serial HSCAN takes longest overall. ButRedis remains responsive with gradual element returns.
-
Pipelined HSCAN divides latency by concurrent round trips. Emerges as optimal approach.
So for large hashes, pipelined HSCAN strikes a balance between throughput and non-blocking iteration.
Now let us explore some real-world use cases where HSCAN shines.
Use Case 1: Paginated User Profile Sharding
Modern web applications often require horizontal scaling of user profiles across Redis shards.
Let‘s see how HSCAN can efficiently shard user profile records with properties like name, location, preferences etc.
Naive Approach
- Prefix user ids while inserting data –
HMSET user:134:name Tom city Paris - Use keys* pattern to fetch shard with SCAN –
SCAN 0 MATCH user:*
This loads the entire user shard in memory before extracting profiles. Wasteful.
HSCAN Approach
- Store encoded userid within hash field –
HSET profiles 134:userid 134 - Use HSCAN with pattern match per shard –
HSCAN profiles 0 MATCH 134:*
HSCAN avoids pulling full profiles in memory. It paginates results via COUNT allowing smooth shard transitions.
Adding new fields per user also gets localized within the hash without new keys. Redis memory overhead reduces significantly for millions of small user records.
Use Case 2: Graph Activity Feed Generation
Generating aggregated activity feeds requires scanning large collections of events and entities.
If activities are modeled as hashes in Redis with details like:
HMSET activities:144 user John target Posts:526 type Comment data "Nice work!"
HMSET activities:155 user Mat target Articles:738 type Edited
An application could leverage pipelines HSCANs to group activities by type, deduplicate, analyze frequencies etc. without pulling entire data in memory.
Pattern matching helps extracting subsets of activities. For example, MATCH * type:Comment* returns only comments for analyzing participation levels across entities.
As new activities get logged, application keeps consuming incremental batches via HSCAN without serialization issues. Smooth activity feed generation.
Implementing Efficient Cursors
While HSCAN provides the primitive for incremental scanning, application developers must utilize it judiciously for efficiency.
Here are some tips:
1. Distinct App Instances
Maintain separate cursor states per app instance to allow parallel scan:

2. Cursor Storage
Preserve HSCAN cursor per instance in Redis SET – SET app1:user_scan:cursor 980. Survives restarts.
3. Scan Direction
Traverse large hash by key ranges for faster scans:

Start parallel cursors from different initial key prefixes.
4. Control Timeouts
Dynamically tune COUNT to prevent stalled iterations causing timeouts.
5. Lua HSCAN
Implement client-side cursor logic via Lua for custom datasets and sharding.
By carefully optimizing cursors, scaling HSCAN for massive hashes becomes feasible.
Analyzing HSCAN Memory Overheads
HSCAN memory consumption depends on the Redis hash encoding type:
-
Ziplist – Small hashes encode fields and values in a tightly packed contiguous structure similar to arrays. Overhead ~2 MB for 1 million field-value pairs. HSCAN partitions equal sized blocks from array.
-
Hashtable – Larger hashes use inherent key-value indexing leading to more memory for housekeeping ~10 MB for 1 million entries. HSCAN creates variable sized partitions.
For huge production hashes, retina-friendly memory efficient encodings like Redis JSON may be preferable to traditional hashes.
Extending HSCAN via Lua Scripting
Sometimes application-specific data formats require customizing default HSCAN behavior for efficiency.
For example, storing fields as field:134:userid creates redundancy during MATCH *:134:*.
Redis Lua scripting allows enhancing HSCAN capabilities:
local cursor = ARGV[1]
local pattern = ARGV[2]
local count = ARGV[3]
local scanned = redis.call(‘HSCAN‘, ‘profiles‘, cursor, ‘0‘, count)
scanned[2] = sanitize_fields(scanned[2])
return scanned
Here the Lua script sanitizes and returns clean fields to the application without Redis MATCH overheads.
Both cursor iteration and field formatting can be customized via scripting increasing efficiency substantially.
Performance Tuning Checklist
For optimal large hash scans, apply these performance tuning tips:
✅Parallelize using multiple instances and shards
✅Use pipelined HSCAN invocations
✅Tune COUNT dynamically to balance overhead
✅Maintain cursor state for resumability
✅Pre-filter datasets via scripting before scan
✅Test with different encoding types like RedisJSON
Here is a sample dashboard monitoring success rate and latency across HSCAN pipelines:

Such instrumentation ensures efficiency as data scales up.
Comparative Analysis
While HSCAN solves incremental hash fetching quite efficiently, alternative approaches may work better in certain situations:
HGETALL
Fetching the entire hash in one shot avoids iteration overhead. Performs better for smaller hashes.
But requires recalculation of hash slots when resized causing performance dips. Not dynmaic.
Keys & Values
Using SCAN for keys and MGET for values reduces memory overheads. Achieves better pipeline efficiency than pattern matched HSCAN.
However, leads to multiple network round trips between Redis and application. Additional serialization effort.
Lua Hashes
Redis Lua support for user-defined data structures like Lua HLL provides better performance for analytics via scripting.
But data resides within Lua VM increasing persistence overheads. Not naturally incremental.
So consider inherent HSCAN tradeoffs around blocking behavior, memory, round trips against application requirements while adopting.
Conclusion
To summarize, HSCAN provides non-blocking windowed iteration capability over large Redis hashes. Its incremental scanning algorithms strike a balance between latency and throughput for retrieving big hash data gradually.
Patterns like pipelined cursors, parallel instances, and Lua extensions further optimize large hash scans. Adaptive sizing, customized partitioning, directional scans make HSCAN extremely versatile.
For simple caching scenarios HGETALL may be better. Analytical workflows can leverage Lua hashes. But there is no denying HSCAN efficiency for incremental sharding of normalized data.
With meticulous tuning using provided tips, engineers can scale HSCAN to handle billions of fields and values across various products efficiently.


