Migrating to Familia 2.9.0
This version introduces batch iteration primitives for DataTypes, enabling efficient enumeration over large Redis collections. It also includes breaking changes to method names for clarity.
Breaking Changes
Method Renames
The multi-field update methods have been renamed to better reflect their purpose:
# Before (2.8.x)
user.batch_update(name: "Alice", email: "alice@example.com")
user.batch_fast_write(name: "Alice", email: "alice@example.com")
# After (2.9.0)
user.multi_field_update(name: "Alice", email: "alice@example.com")
user.multi_field_fast_write(name: "Alice", email: "alice@example.com")
Migration: Find and replace batch_update with multi_field_update and batch_fast_write with multi_field_fast_write.
MultiResult Namespace
MultiResult has moved into the Familia namespace:
# Before (2.8.x)
result.is_a?(MultiResult)
# After (2.9.0)
result.is_a?(Familia::MultiResult)
Migration: Replace bare MultiResult references with Familia::MultiResult.
New Features
Enumerable Integration
All collection DataTypes now include Ruby's Enumerable module, providing each_slice, lazy, map, reduce, find, and other stdlib methods:
# Lazy iteration with transformation
Org.instances.lazy.map { |id| id.upcase }.take(10).to_a
# Batch processing with each_slice
User.instances.each_slice(100) do |batch|
batch.each { |id| process(id) }
end
Filtered Iteration
Each DataType now supports type-specific filters on each:
# SortedSet: filter by score (timestamp) bounds
Org.instances.each(since: 24.hours.ago, until: Time.now) do |id|
puts id
end
# HashKey: filter by field name pattern
user.profile.each(matching: "pref_*") do |field, value|
puts "#{field}: #{value}"
end
# UnsortedSet: filter by member pattern
user..each(matching: "admin*") do |tag|
puts tag
end
each_record for Loading Horreum Instances
each_record yields fully-loaded Horreum records instead of raw IDs:
# Load records in batches of 100
Org.instances.each_record(batch_size: 100) do |org|
org.tidy! # org is a loaded Horreum instance
end
# Control pipelining depth separately from fetch batch size
Org.instances.each_record(batch_size: 500, pipeline: 50) do |org|
org.status!("active")
end
# Serial execution (no pipelining) — this is the default
Org.instances.each_record(batch_size: 100) do |org|
org.complex_operation
end
Ghost instances (keys that expired but remain in the instances sorted set) are automatically filtered and never reach the block.
BatchResult for Aggregated Operations
Familia::BatchResult aggregates results from batch operations with per-record error isolation:
result = Familia::BatchResult.collect(
Org.instances.each_record(batch_size: 100, since: 24.hours.ago)
) do |org|
org.tidy!
end
result.scanned # Total records yielded to block
result.modified # Count of truthy block returns
result.errors # Array of {id:, error:} for failed records
result.duration_ms # Total execution time
# Re-raise errors after completion
result = Familia::BatchResult.collect(enum, strict: true) { |r| r.process! }
Concurrent Mutation Behavior
When iterating with each or each_record, be aware of Redis cursor semantics:
- Items present from iteration start to end are guaranteed to be returned
- Items added or removed mid-iteration may or may not appear
- Blocks should be idempotent to handle potential duplicates
This is inherent to ZSCAN/HSCAN/SSCAN and is documented, not a bug.