ExCache
ExCache is a simple, yet powerful caching framework for Elixir applications. It provides a fast, in-memory caching solution with TTL (Time To Live) support, comprehensive statistics, and a clean, idiomatic API.
Features
- Fast In-Memory Caching: Built on ETS (Erlang Term Storage) for optimal performance
- TTL Support: Automatic expiration of cached entries with configurable time-to-live
- Comprehensive Statistics: Real-time tracking of cache hits, misses, and operations
- Manual Cleanup: On-demand cleanup of expired entries in addition to automatic cleanup
- OTP Integration: Full GenServer-based implementation with supervisor support
- Concurrent Access: Thread-safe operations with excellent performance under load
- Flexible API: Simple put/get/delete operations with powerful fallback functionality
- Idiomatic Elixir: Follows Elixir conventions and best practices
Installation
Add ex_cache to your list of dependencies in mix.exs:
def deps do
[
{:ex_cache, "~> 0.2.0"}
]
end
Then run mix deps.get to install the dependency.
Usage
Basic Usage
# Start a cache process
{:ok, pid} = ExCache.Ets.start_link(:my_cache)
# Store a value
:ok = ExCache.Ets.put(:my_cache, :user_123, %{name: "John", email: "john@example.com"})
# Retrieve a value
user = ExCache.Ets.get(:my_cache, :user_123)
# => %{name: "John", email: "john@example.com"}
# Store with TTL (in milliseconds)
:ok = ExCache.Ets.put(:my_cache, :session_token, "abc123", ttl: 3600000) # 1 hour
# Delete a value
:ok = ExCache.Ets.del(:my_cache, :user_123)Using Fetch with Fallback
The fetch/4 function provides a powerful pattern for retrieving values with fallback logic:
# Fetch with automatic caching of computed value
user = ExCache.Ets.fetch(:my_cache, :user_456, [ttl: 300000], fn user_id ->
# This function is only called if the key is not in cache
{:commit, Database.get_user(user_id)} # Store the result in cache
end)
# Fetch without caching (ignore pattern)
config = ExCache.Ets.fetch(:my_cache, :app_config, [], fn key ->
{:ignore, File.read!("config/#{key}.json")} # Return without storing
end)Supervisor Integration
ExCache is designed to work seamlessly with OTP supervisors:
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
{ExCache.Ets, name: :my_cache},
# ... other children
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endCache Statistics
Monitor cache performance with built-in statistics:
# Get current statistics
stats = ExCache.Ets.stats(:my_cache)
# => %{
# hits: 1250,
# misses: 42,
# puts: 1320,
# deletes: 89,
# total_operations: 2701
# }
# Calculate hit rate
hit_rate = stats.hits / (stats.hits + stats.misses)
# => 0.9675 (96.75% hit rate)
# Reset statistics when needed
:ok = ExCache.Ets.reset_stats(:my_cache)Manual Cleanup
While ExCache automatically cleans up expired entries, you can also trigger manual cleanup:
# Manually clean up expired entries
{:ok, deleted_count} = ExCache.Ets.cleanup_expired(:my_cache)
IO.puts("Cleaned up #{deleted_count} expired entries")API Reference
Core Functions
put(name, key, value, opts \\ [])
Store a key-value pair in the cache.
Parameters:
name: Cache process name or PIDkey: Any term to use as the keyvalue: Any term to store as the valueopts: Keyword list of options
Options:
:ttl- Time to live in milliseconds (default::infinity)
Returns:
:ok
get(name, key)
Retrieve a value from the cache.
Parameters:
name: Cache process name or PIDkey: Key to retrieve
Returns:
value | nil(returnsnilif key doesn’t exist or has expired)
del(name, key)
Delete a key-value pair from the cache.
Parameters:
name: Cache process name or PIDkey: Key to delete
Returns:
:ok
Advanced Functions
fetch(name, key, opts \\ [], fallback)
Retrieve a value with fallback functionality.
Parameters:
name: Cache process name or PIDkey: Key to retrieveopts: Keyword list of optionsfallback: Function to call if key is not found
Options:
:ttl- Time to live for newly cached values (default::infinity)
Fallback Function: Should return either:
{:commit, value}- Store the value in cache and return it{:ignore, value}- Return the value without storing it
Returns: The cached or computed value
Statistics Functions
stats(name)
Get cache statistics.
Parameters:
name: Cache process name or PID
Returns: Map containing:
:hits- Number of cache hits:misses- Number of cache misses:puts- Number of put operations:deletes- Number of delete operations:total_operations- Total number of operations
reset_stats(name)
Reset all cache statistics to zero.
Parameters:
name: Cache process name or PID
Returns:
:ok
Maintenance Functions
cleanup_expired(name)
Manually clean up expired entries.
Parameters:
name: Cache process name or PID
Returns:
{:ok, deleted_count}wheredeleted_countis the number of entries deleted
Configuration
Cache Process Configuration
The cache process uses a timeout of 5000 milliseconds for automatic cleanup of expired entries. This is currently not configurable but provides a good balance between cleanup frequency and performance.
TTL Management
- Infinite TTL: Use
ttl: :infinityfor entries that should never expire - Short TTL: Values like
ttl: 1000(1 second) for temporary data - Long TTL: Values like
ttl: 86400000(24 hours) for long-term caching
Error Handling
ExCache gracefully handles various edge cases:
- Invalid TTL Values: Negative, zero, or invalid TTL values are treated as
:infinity - Missing Keys:
get/3anddel/3operations on non-existent keys are safe and return appropriate values - Concurrent Access: All operations are thread-safe and designed for concurrent use
Performance Considerations
Memory Usage
ExCache uses ETS tables for storage, which are kept in memory. Monitor memory usage when caching large amounts of data.
Automatic Cleanup
Expired entries are automatically cleaned up every 5 seconds. This process is efficient and doesn’t block normal operations.
Concurrency
The cache is designed for high concurrency with minimal contention between operations. All read operations are synchronous, while write operations are asynchronous.
Testing
Run the test suite:
mix testInclude property tests:
mix test test/ex_cache_property_test.exsContributing
We welcome contributions! Please see our Contributing Guidelines for details.
Development Setup
- Fork the repository
-
Clone your fork:
git clone https://github.com/your-username/ex_cache.git -
Create your feature branch:
git checkout -b feature/amazing-feature -
Install dependencies:
mix deps.get -
Run tests:
mix test -
Commit your changes:
git commit -m 'Add amazing feature' -
Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
Code Style
- Follow the existing code style
- Ensure all tests pass
- Add tests for new functionality
- Update documentation as needed
License
ExCache is released under the MIT License.
Changelog
v0.2.0 (2024-01-XX)
Added
- Comprehensive statistics tracking for cache operations
- Manual cleanup functionality for expired entries
- Enhanced TTL management with graceful error handling
- Extensive test suite including property-based tests
- Complete API documentation and usage examples
Changed
- Improved error handling for invalid TTL values
- Optimized cleanup algorithm for better performance
- Enhanced concurrent operation support
Fixed
- API return value consistency across all operations
- TTL expiration edge cases
- Memory leaks in cleanup process
v0.1.1 (2024-01-XX)
- Initial release
- Basic ETS-based caching implementation
- TTL support
- Core API functions