UNPKG

@bcoders.gr/redis-client

Version:

High-performance Redis client with advanced compression, multi-level caching, bulk operations, and comprehensive performance monitoring. Optimized for production workloads with intelligent cache management.

414 lines (302 loc) 14.1 kB
# @bcoders.gr/redis-client A **high-performance**, self-contained Redis client with advanced compression, multi-level caching, bulk operations, and comprehensive performance monitoring. Engineered for production workloads with intelligent cache management and optimized data handling. ## 🚀 Performance Features - **Multi-level Caching**: Intelligent caching for compression, decompression, and key formatting - **Parallel Processing**: Concurrent compression/decompression for bulk operations - **Batch Operations**: Optimized pipeline processing with configurable batch sizes - **Performance Analytics**: Real-time statistics tracking with cache hit rates and timing metrics - **Memory Optimization**: Smart cache management with automatic cleanup and size limits - **High-Resolution Timing**: Precise performance measurements using `performance.now()` ## 🛠️ Core Features - **Built-in Compression**: Automatic zlib compression/decompression with caching for all stored values - **Performance Monitoring**: Comprehensive logging of slow operations with configurable thresholds - **Keyspace Event Subscriptions**: Subscribe to Redis keyspace events for real-time notifications - **Connection Management**: Robust connection handling with auto-reconnection and connection pooling - **Pipeline Support**: Advanced batch operations for improved performance - **Namespace Operations**: Easy management of key namespaces with optimized bulk operations - **Self-contained**: No external utility dependencies - all logic built-in with performance optimizations ## Installation ```bash npm install @bcoders.gr/redis-client ``` ## Basic Usage ```javascript import RedisClient from '@bcoders.gr/redis-client'; // Create a client instance with different connection options: // 1. Default local socket connection with default credentials const redis = new RedisClient('my-app'); // 2. With custom username and password const redis = new RedisClient('my-app', {}, 'admin', 'secret'); // 3. Legacy boolean option (backward compatibility) const redis = new RedisClient('my-app', false, 'user', 'pass'); // local socket const redis = new RedisClient('my-app', true, 'user', 'pass'); // remote socket // 4. Custom socket path with credentials const redis = new RedisClient('my-app', '/custom/path/redis.sock', 'admin', 'secret'); // 5. IP address connection with credentials const redis = new RedisClient('my-app', '192.168.1.100', 'user', 'password'); const redis = new RedisClient('my-app', '192.168.1.100:6380', 'admin', 'secret'); // 6. Full configuration object (overrides individual username/password) const redis = new RedisClient('my-app', { host: '192.168.1.100', port: 6379, username: 'admin', password: 'secret' }); // 7. Socket path with configuration object const redis = new RedisClient('my-app', { path: '/custom/redis.sock', username: 'admin', password: 'secret' }); // Set a value (automatically compressed) await redis.setKey('users', 'john', { name: 'John Doe', email: 'john@example.com' }); // Get a value (automatically decompressed) const user = await redis.getKey('users', 'john'); console.log(user); // { name: 'John Doe', email: 'john@example.com' } // Get an entire namespace (new in v1.2.2) const allUsers = await redis.getNamespaceSnapshot('users'); console.log(`Retrieved ${Object.keys(allUsers).length} users`); // Check if key exists const exists = await redis.checkKey('users', 'john'); // Delete a key await redis.deleteKey('users', 'john'); // Clean up redis.destroy(); ``` ## Constructor API ```javascript new RedisClient(alias, connectionOptions, username, password) ``` **Parameters:** - `alias` (string, optional): A unique identifier for this client instance. Default: `'default'` - `connectionOptions` (various types, optional): Connection configuration. Default: `{}` - **Boolean**: `true` for remote socket (`/media/redis/remote.sock`), `false` for local socket (`/media/redis/local.sock`) - **String**: Socket path (if contains `/` or `.sock`) or IP address with optional port (`192.168.1.100:6379`) - **Object**: Full configuration object with properties: - `host`: Redis server hostname (default: 'localhost') - `port`: Redis server port (default: 6379) - `path`: Unix socket path - `username`: Redis username - `password`: Redis password - `socket`: Socket configuration object - `username` (string, optional): Redis username. Default: `'root'` - `password` (string, optional): Redis password. Default: `'root'` **Note:** If `connectionOptions` is an object with `username` and `password` properties, they will override the individual `username` and `password` parameters. ## Advanced Features ### High-Performance Bulk Operations ```javascript // Optimized bulk operations with parallel compression const bulkData = { 'user1': { name: 'John', age: 30 }, 'user2': { name: 'Jane', age: 25 }, 'user3': { name: 'Bob', age: 35 } }; // Set multiple keys at once (parallel compression) await redis.setBulk('users', bulkData, 3600); // with TTL // Get multiple keys at once (parallel decompression) const users = await redis.getBulk('users', ['user1', 'user2', 'user3']); // Batched operations for large datasets const largeDataset = {}; // ... 1000+ keys await redis.setBulkBatched('dataset', largeDataset, 100); // Process in batches of 100 const results = await redis.getBulkBatched('dataset', Object.keys(largeDataset), 100); ``` ### Performance Monitoring & Analytics ```javascript // Get comprehensive performance statistics const stats = redis.getPerformanceStats(); console.log('Cache Hit Rate:', stats.cacheHitRate); // e.g., "85.2%" console.log('Total Operations:', stats.operationCount); console.log('Average Compression Time:', stats.avgCompressionTime); // Configure performance thresholds redis.performanceThresholds = { slowGet: 5, // Log GET operations slower than 5ms slowSet: 15, // Log SET operations slower than 15ms cacheSize: 3000 // Maximum cache entries }; // Reset statistics for new measurement period redis.resetPerformanceStats(); // Optimize caches manually redis.optimizeCaches(); ``` ### Advanced Operations ```javascript // Atomic counter operations await redis.incrementCounter('stats', 'page_views', 1); await redis.decrementCounter('stats', 'inventory', 5); // Execute Lua scripts for complex atomic operations const script = ` local key = KEYS[1] local increment = ARGV[1] local current = redis.call('GET', key) or 0 local new_value = current + increment redis.call('SET', key, new_value) return new_value `; const result = await redis.executeScript(script, ['counter'], [10]); ``` ### Namespace Operations ```javascript // Get all keys in a namespace const userKeys = await redis.getKeys('users'); // Get namespace size const size = await redis.getNamespaceSize('users'); // Get all keys matching pattern const allUserKeys = await redis.getNamespace('users'); ``` ### Pipeline Operations ```javascript // Create a pipeline for batch operations const pipeline = redis.createPipeline(); if (pipeline) { // Add operations to pipeline pipeline.set('key1', 'value1'); pipeline.set('key2', 'value2'); pipeline.del('oldkey'); // Execute all operations at once const results = await redis.executePipeline(pipeline); } ``` ### Keyspace Event Subscriptions ```javascript // Subscribe to keyspace events redis.on('keyspace', (event) => { console.log('Key event:', event); // { namespace: 'users', key: 'john', event: 'set' } }); await redis.subscribeToKeyspaceEvents('users'); // Now any changes to 'users:*' keys will trigger events await redis.setKey('users', 'jane', { name: 'Jane' }); // Triggers event // Unsubscribe when done await redis.unsubscribeFromNamespace('users'); ``` ### Expiration ```javascript // Set a key with TTL (time to live) await redis.setKey('session', 'token123', { userId: 456 }, 3600); // 1 hour // Set expiration on existing key await redis.expireKey('session', 'token123', 1800); // 30 minutes ``` ## Configuration ### Constructor Options ```javascript const redis = new RedisClient(alias, remote); ``` - `alias` (string): Identifier for logging and debugging (default: 'default') - `remote` (boolean): Whether to use remote socket path (default: false) ### Performance Thresholds The client automatically logs slow operations. Default thresholds: - GET operations: 10ms - SET operations: 30ms These are configurable via the `performanceThresholds` property: ```javascript redis.performanceThresholds = { slowGet: 5, // Log GET operations slower than 5ms slowSet: 20 // Log SET operations slower than 20ms }; ``` ## Connection Paths - **Local**: `/media/redis/local.sock` - **Remote**: `/media/redis/remote.sock` Both connections use: - Username: `root` - Password: `root` ## API Reference ### Performance Methods - `getPerformanceStats()` - Get comprehensive performance statistics and cache metrics - `resetPerformanceStats()` - Reset all performance counters - `clearCaches()` - Clear all internal caches manually - `optimizeCaches()` - Optimize cache sizes and remove old entries ### Key Operations - `setKey(namespace, key, value, expirationInSeconds?)` - Store a key-value pair - `getKey(namespace, key)` - Retrieve a value by key - `checkKey(namespace, key)` - Check if a key exists - `deleteKey(namespace, key, pipeline?)` - Delete a key - `expireKey(namespace, key, seconds)` - Set expiration on a key ### High-Performance Bulk Operations - `setBulk(namespace, keyValuePairs, expirationInSeconds?)` - Set multiple keys with parallel compression - `getBulk(namespace, keys)` - Get multiple keys with parallel decompression - `deleteBulk(namespace, keys)` - Delete multiple keys in a single pipeline - `setBulkBatched(namespace, keyValuePairs, batchSize?, expirationInSeconds?)` - Process large datasets in batches - `getBulkBatched(namespace, keys, batchSize?)` - Retrieve large datasets in batches ### Advanced Operations - `incrementCounter(namespace, key, increment?)` - Atomic counter increment - `decrementCounter(namespace, key, decrement?)` - Atomic counter decrement - `executeScript(script, keys?, args?)` - Execute Lua scripts for complex atomic operations ### Namespace Operations - `getNamespaceSize(namespace)` - Get number of keys in namespace - `getKeys(namespace)` - Get all keys in namespace (using SCAN) - `getNamespace(namespace)` - Get all keys matching namespace pattern - `getNamespaceSnapshot(namespace, batchSize?)` - Get complete snapshot of all keys and values from a namespace ### Connection Management - `reconnectClient()` - Manually reconnect the client - `destroy()` - Close all connections and clean up - `close()` - Close main connection only ### Pipeline Operations - `createPipeline()` - Create a new pipeline - `executePipeline(pipeline)` - Execute a pipeline ### Subscription Management - `subscribeToKeyspaceEvents(namespace)` - Subscribe to keyspace events - `unsubscribeFromNamespace(namespace)` - Unsubscribe from namespace ## Data Handling ### Compression All values are automatically compressed using zlib before storage and decompressed when retrieved. This significantly reduces memory usage for large objects. ### Serialization - Strings are stored as-is (after compression) - Objects are JSON.stringify'd before compression - Retrieved values are automatically parsed back to their original type ## Error Handling The client includes comprehensive error handling: - Connection errors are logged and trigger reconnection attempts - Operation errors are logged with context - Failed operations return `null` or `false` rather than throwing (configurable) ## Namespace Snapshot The `getNamespaceSnapshot()` method provides a complete snapshot of all keys and values from a Redis namespace in a single operation. ```javascript // Get all data from 'pair' namespace const pairData = await client.getNamespaceSnapshot('pair'); console.log(`Retrieved ${Object.keys(pairData).length} pairs`); // Sample of first few items const sampleKeys = Object.keys(pairData).slice(0, 3); sampleKeys.forEach(key => { console.log(`${key}: ${JSON.stringify(pairData[key])}`); }); // For large datasets, use a custom batch size const blockData = await client.getNamespaceSnapshot('block', 200); ``` ### Benefits of getNamespaceSnapshot - **Simplified Data Retrieval**: Get all namespace data in a single call - **Clean Result Structure**: Keys have namespace prefix removed for easier processing - **High Performance**: Uses optimized bulk batched operations under the hood - **Memory Efficient**: Processes data in batches to manage memory usage - **Automatic Type Handling**: Preserves original value types (objects, arrays, etc.) ## Performance - **Multi-level Caching**: Compression, decompression, and key formatting results are cached for optimal performance - **Parallel Processing**: Bulk operations use parallel compression/decompression for maximum throughput - **Intelligent Cache Management**: Automatic cache size management with LRU-like cleanup strategies - **High-Resolution Timing**: Precise performance measurements using `performance.now()` for accurate benchmarking - **Optimized Data Structures**: Uses efficient Set-based JSON detection and optimized hash functions - **Batch Processing**: Large datasets are processed in configurable batches to prevent memory overload - **Connection Pooling**: Extended connection caching reduces connection overhead - **Performance Analytics**: Real-time statistics tracking helps identify bottlenecks and optimization opportunities ## Testing ```bash # Run basic functionality tests npm test # Run comprehensive performance benchmarks npm run test:performance # Run race condition fix verification npm run test:fixes # Run performance benchmarks (alias) npm run benchmark ``` ## Requirements - Node.js 16+ - Redis server with Unix socket support - Redis server configured for keyspace events (for subscriptions) ## License MIT ## Contributing Issues and pull requests welcome!