Skip to main content

ipfrs_storage/
helpers.rs

1//! Helper functions for creating storage stacks
2//!
3//! This module provides convenient functions for setting up common storage configurations.
4
5use crate::{
6    BlockStoreConfig, BloomBlockStore, BloomConfig, CachedBlockStore, MemoryBlockStore,
7    MetricsBlockStore, SledBlockStore,
8};
9use crate::{ChunkingConfig, DedupBlockStore};
10#[cfg(feature = "encryption")]
11use crate::{Cipher, EncryptedBlockStore, EncryptionConfig};
12use crate::{CoalesceConfig, CoalescingBlockStore};
13#[cfg(feature = "compression")]
14use crate::{CompressionAlgorithm, CompressionBlockStore, CompressionConfig};
15use crate::{TtlBlockStore, TtlConfig};
16use ipfrs_core::Result;
17use std::path::PathBuf;
18use std::time::Duration;
19
20// Type aliases for complex storage stacks
21type FullStack = BloomBlockStore<CachedBlockStore<SledBlockStore>>;
22type MonitoredFullStack = MetricsBlockStore<FullStack>;
23
24#[cfg(feature = "compression")]
25type CompressedStack = CompressionBlockStore<FullStack>;
26#[cfg(feature = "compression")]
27type MonitoredCompressedStack = MetricsBlockStore<CompressedStack>;
28
29#[cfg(feature = "encryption")]
30type EncryptedStack = EncryptedBlockStore<FullStack>;
31#[cfg(feature = "encryption")]
32type MonitoredEncryptedStack = MetricsBlockStore<EncryptedStack>;
33
34type DedupStack = DedupBlockStore<FullStack>;
35type MonitoredDedupStack = MetricsBlockStore<DedupStack>;
36
37#[cfg(feature = "compression")]
38type UltimateStack = DedupBlockStore<CompressedStack>;
39#[cfg(feature = "compression")]
40type MonitoredUltimateStack = MetricsBlockStore<UltimateStack>;
41
42/// Storage stack builder for easy configuration
43pub struct StorageStackBuilder {
44    config: BlockStoreConfig,
45    enable_cache: bool,
46    cache_size_mb: usize,
47    enable_bloom: bool,
48    bloom_expected_items: usize,
49    enable_tiering: bool,
50}
51
52impl Default for StorageStackBuilder {
53    fn default() -> Self {
54        Self {
55            config: BlockStoreConfig::default(),
56            enable_cache: true,
57            cache_size_mb: 100,
58            enable_bloom: true,
59            bloom_expected_items: 100_000,
60            enable_tiering: false,
61        }
62    }
63}
64
65impl StorageStackBuilder {
66    /// Create a new storage stack builder
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Set the base storage configuration
72    pub fn with_config(mut self, config: BlockStoreConfig) -> Self {
73        self.config = config;
74        self
75    }
76
77    /// Set the storage path
78    pub fn with_path(mut self, path: PathBuf) -> Self {
79        self.config.path = path;
80        self
81    }
82
83    /// Enable LRU caching with specified size in MB
84    pub fn with_cache(mut self, size_mb: usize) -> Self {
85        self.enable_cache = true;
86        self.cache_size_mb = size_mb;
87        self
88    }
89
90    /// Disable LRU caching
91    pub fn without_cache(mut self) -> Self {
92        self.enable_cache = false;
93        self
94    }
95
96    /// Enable bloom filter with expected number of items
97    pub fn with_bloom(mut self, expected_items: usize) -> Self {
98        self.enable_bloom = true;
99        self.bloom_expected_items = expected_items;
100        self
101    }
102
103    /// Disable bloom filter
104    pub fn without_bloom(mut self) -> Self {
105        self.enable_bloom = false;
106        self
107    }
108
109    /// Enable hot/cold tiering
110    pub fn with_tiering(mut self) -> Self {
111        self.enable_tiering = true;
112        self
113    }
114
115    /// Build a simple storage stack (base store only)
116    pub fn build_simple(self) -> Result<SledBlockStore> {
117        SledBlockStore::new(self.config)
118    }
119
120    /// Build a cached storage stack
121    pub fn build_cached(self) -> Result<CachedBlockStore<SledBlockStore>> {
122        use crate::CacheConfig;
123        use std::num::NonZeroUsize;
124        let base = SledBlockStore::new(self.config)?;
125        // Translate megabyte budget into a block-count capacity (assume ~4 KiB/block).
126        let block_capacity = std::cmp::max(1, self.cache_size_mb * 1024 * 1024 / 4096);
127        let config = CacheConfig {
128            l1_capacity: NonZeroUsize::new(block_capacity)
129                .unwrap_or_else(|| NonZeroUsize::new(1024).expect("1024>0")),
130            max_block_bytes: 256 * 1024,
131        };
132        Ok(CachedBlockStore::new(base, config))
133    }
134
135    /// Build a full storage stack with cache and bloom filter
136    pub fn build_full(self) -> Result<BloomBlockStore<CachedBlockStore<SledBlockStore>>> {
137        use crate::CacheConfig;
138        use std::num::NonZeroUsize;
139        let base = SledBlockStore::new(self.config)?;
140
141        let block_capacity = if self.enable_cache {
142            std::cmp::max(1, self.cache_size_mb * 1024 * 1024 / 4096)
143        } else {
144            1 // Minimal cache if disabled
145        };
146        let cache_config = CacheConfig {
147            l1_capacity: NonZeroUsize::new(block_capacity)
148                .unwrap_or_else(|| NonZeroUsize::new(1).expect("1>0")),
149            max_block_bytes: 256 * 1024,
150        };
151        let cached = CachedBlockStore::new(base, cache_config);
152
153        if self.enable_bloom {
154            let bloom_config = BloomConfig::new(self.bloom_expected_items, 0.01);
155            Ok(BloomBlockStore::with_config(cached, bloom_config))
156        } else {
157            // Return with minimal bloom filter if disabled
158            let bloom_config = BloomConfig::new(100, 0.01);
159            Ok(BloomBlockStore::with_config(cached, bloom_config))
160        }
161    }
162}
163
164/// Quick setup functions for common use cases
165/// Create a development storage stack with caching and bloom filter
166///
167/// - Path: /tmp/ipfrs-dev
168/// - Cache: 50MB
169/// - Bloom filter: 10,000 expected items
170pub fn development_stack() -> Result<BloomBlockStore<CachedBlockStore<SledBlockStore>>> {
171    StorageStackBuilder::new()
172        .with_config(BlockStoreConfig::development())
173        .with_cache(50)
174        .with_bloom(10_000)
175        .build_full()
176}
177
178/// Create a production storage stack with caching and bloom filter
179///
180/// - Path: provided by user
181/// - Cache: 500MB
182/// - Bloom filter: 1,000,000 expected items
183pub fn production_stack(path: PathBuf) -> Result<FullStack> {
184    StorageStackBuilder::new()
185        .with_config(BlockStoreConfig::production(path))
186        .with_cache(500)
187        .with_bloom(1_000_000)
188        .build_full()
189}
190
191/// Create an embedded storage stack with minimal resource usage
192///
193/// - Path: provided by user
194/// - Cache: 10MB
195/// - Bloom filter: 5,000 expected items
196pub fn embedded_stack(path: PathBuf) -> Result<BloomBlockStore<CachedBlockStore<SledBlockStore>>> {
197    StorageStackBuilder::new()
198        .with_config(BlockStoreConfig::embedded(path))
199        .with_cache(10)
200        .with_bloom(5_000)
201        .build_full()
202}
203
204/// Create a testing storage stack with minimal resources
205///
206/// - Path: temporary directory
207/// - Cache: 5MB
208/// - Bloom filter: 1,000 expected items
209pub fn testing_stack() -> Result<BloomBlockStore<CachedBlockStore<SledBlockStore>>> {
210    StorageStackBuilder::new()
211        .with_config(BlockStoreConfig::testing())
212        .with_cache(5)
213        .with_bloom(1_000)
214        .build_full()
215}
216
217/// Create a production stack with metrics tracking
218///
219/// Adds comprehensive metrics on top of a production storage stack.
220/// Useful for monitoring performance in production deployments.
221pub fn monitored_production_stack(path: PathBuf) -> Result<MonitoredFullStack> {
222    let base = production_stack(path)?;
223    Ok(MetricsBlockStore::new(base))
224}
225
226/// Create a high-performance in-memory stack
227///
228/// Best for:
229/// - Testing and development
230/// - Temporary caching layers
231/// - High-speed operations where persistence isn't needed
232pub fn memory_stack() -> MetricsBlockStore<BloomBlockStore<MemoryBlockStore>> {
233    let base = MemoryBlockStore::new();
234    let bloom_config = BloomConfig::new(100_000, 0.01);
235    let bloom = BloomBlockStore::with_config(base, bloom_config);
236    MetricsBlockStore::new(bloom)
237}
238
239/// Create a compressed production stack (requires "compression" feature)
240///
241/// Uses Zstd compression to reduce storage size.
242/// Best for: Large datasets where storage space is a concern
243#[cfg(feature = "compression")]
244pub fn compressed_production_stack(path: PathBuf) -> Result<MonitoredCompressedStack> {
245    let base = production_stack(path)?;
246    let compression_config = CompressionConfig {
247        algorithm: CompressionAlgorithm::Zstd,
248        level: 3,        // Balanced compression
249        threshold: 1024, // Only compress blocks > 1KB
250        max_ratio: 0.9,  // Only keep if compressed to 90% or less
251    };
252    let compressed = CompressionBlockStore::new(base, compression_config);
253    Ok(MetricsBlockStore::new(compressed))
254}
255
256/// Create an encrypted production stack (requires "encryption" feature)
257///
258/// Uses ChaCha20-Poly1305 encryption for data at rest.
259/// Best for: Sensitive data requiring encryption
260#[cfg(feature = "encryption")]
261pub fn encrypted_production_stack(
262    path: PathBuf,
263    password: &str,
264) -> Result<MonitoredEncryptedStack> {
265    use crate::EncryptionKey;
266
267    let base = production_stack(path)?;
268    let (key, _salt) =
269        EncryptionKey::derive_from_password(Cipher::ChaCha20Poly1305, password.as_bytes(), None)?;
270    let config = EncryptionConfig {
271        cipher: Cipher::ChaCha20Poly1305,
272    };
273    let encrypted = EncryptedBlockStore::new(base, key, config);
274    Ok(MetricsBlockStore::new(encrypted))
275}
276
277/// Create a deduplicated production stack
278///
279/// Uses content-defined chunking for automatic deduplication.
280/// Best for: Datasets with significant redundancy
281pub fn deduplicated_production_stack(path: PathBuf) -> Result<MonitoredDedupStack> {
282    let base = production_stack(path)?;
283    let chunking_config = ChunkingConfig::default();
284    let dedup = DedupBlockStore::new(base, chunking_config);
285    Ok(MetricsBlockStore::new(dedup))
286}
287
288/// Create the ultimate production stack with all optimizations (requires all features)
289///
290/// Combines:
291/// - Compression (Zstd)
292/// - Deduplication
293/// - Caching
294/// - Bloom filters
295/// - Metrics tracking
296///
297/// Best for: Maximum efficiency in production with all features enabled
298#[cfg(feature = "compression")]
299pub fn ultimate_production_stack(path: PathBuf) -> Result<MonitoredUltimateStack> {
300    let base = compressed_production_stack(path)?;
301    // Remove metrics temporarily to add dedup, then re-add metrics
302    let inner = base.into_inner();
303    let chunking_config = ChunkingConfig::default();
304    let dedup = DedupBlockStore::new(inner, chunking_config);
305    Ok(MetricsBlockStore::new(dedup))
306}
307
308/// Create a production stack with TTL support for automatic expiration
309///
310/// Useful for:
311/// - Temporary cache layers
312/// - Time-limited data storage
313/// - Preventing unbounded growth
314///
315/// # Arguments
316/// * `path` - Storage directory path
317/// * `default_ttl` - Default time-to-live for blocks
318pub fn ttl_production_stack(
319    path: PathBuf,
320    default_ttl: Duration,
321) -> Result<MetricsBlockStore<TtlBlockStore<FullStack>>> {
322    let base = production_stack(path)?;
323    let ttl_config = TtlConfig {
324        default_ttl,
325        auto_cleanup: true,
326        cleanup_interval: Duration::from_secs(300), // 5 minutes
327        max_tracked_blocks: 1_000_000,
328    };
329    let ttl_store = TtlBlockStore::new(base, ttl_config);
330    Ok(MetricsBlockStore::new(ttl_store))
331}
332
333/// Create a production stack with automatic expiration for cache use cases
334///
335/// Optimized for cache workloads with:
336/// - 1-hour default TTL
337/// - Automatic cleanup every 5 minutes
338/// - Large cache (500MB)
339/// - Bloom filter for fast negative lookups
340///
341/// Best for: Temporary data caching with automatic expiration
342pub fn cache_stack(path: PathBuf) -> Result<MetricsBlockStore<TtlBlockStore<FullStack>>> {
343    ttl_production_stack(path, Duration::from_secs(3600)) // 1 hour TTL
344}
345
346/// Create a high-performance write-coalescing stack for in-memory operations
347///
348/// Combines:
349/// - In-memory storage (no persistence)
350/// - Write coalescing for batching (1000 writes per batch)
351/// - 100ms flush interval
352/// - Metrics tracking
353///
354/// Best for: Temporary high-throughput write workloads
355pub fn coalescing_memory_stack() -> MetricsBlockStore<CoalescingBlockStore<MemoryBlockStore>> {
356    let base = MemoryBlockStore::new();
357    let coalesce_config = CoalesceConfig::new(1000, Duration::from_millis(100));
358    let coalescing = CoalescingBlockStore::new(base, coalesce_config);
359    MetricsBlockStore::new(coalescing)
360}
361
362/// Create a read-optimized production stack
363///
364/// Optimized for read-heavy workloads with:
365/// - Large cache (1GB) for frequently accessed blocks
366/// - Bloom filter for fast negative lookups
367/// - Metrics tracking
368///
369/// Best for: Content delivery and read-heavy applications
370pub fn read_optimized_stack(path: PathBuf) -> Result<FullStack> {
371    StorageStackBuilder::new()
372        .with_config(BlockStoreConfig::production(path))
373        .with_cache(1024) // 1GB cache
374        .with_bloom(2_000_000) // Support for 2M blocks
375        .build_full()
376}
377
378/// Create a write-optimized production stack with deduplication
379///
380/// Optimized for write-heavy workloads with:
381/// - Deduplication to reduce storage
382/// - Smaller cache (100MB) to favor writes
383/// - Bloom filter for existence checks
384/// - Metrics tracking
385///
386/// Best for: Ingestion pipelines and write-heavy applications
387pub fn write_optimized_stack(path: PathBuf) -> Result<MonitoredDedupStack> {
388    let base = StorageStackBuilder::new()
389        .with_config(BlockStoreConfig::production(path))
390        .with_cache(100) // Smaller cache for writes
391        .with_bloom(1_000_000)
392        .build_full()?;
393
394    let chunking_config = ChunkingConfig::default();
395    let dedup = DedupBlockStore::new(base, chunking_config);
396    Ok(MetricsBlockStore::new(dedup))
397}
398
399/// Create a minimal resource stack for IoT/embedded devices
400///
401/// Ultra-low resource usage with:
402/// - 5MB cache
403/// - Small bloom filter (1000 expected items)
404/// - Minimal batch sizes
405///
406/// Best for: Raspberry Pi, embedded systems, resource-constrained environments
407pub fn iot_stack(path: PathBuf) -> Result<FullStack> {
408    StorageStackBuilder::new()
409        .with_config(BlockStoreConfig::embedded(path))
410        .with_cache(5) // 5MB cache
411        .with_bloom(1_000) // Very small bloom filter
412        .build_full()
413}
414
415/// Create a resilient production stack with all safety features
416///
417/// Combines:
418/// - TTL for automatic cleanup (24 hours default)
419/// - Metrics tracking for monitoring
420/// - Large cache for performance
421/// - Bloom filter for efficiency
422///
423/// Best for: Mission-critical production deployments requiring data lifecycle management
424pub fn resilient_stack(path: PathBuf) -> Result<MetricsBlockStore<TtlBlockStore<FullStack>>> {
425    ttl_production_stack(path, Duration::from_secs(86400)) // 24 hour TTL
426}
427
428/// Create a high-throughput ingestion stack
429///
430/// Optimized for maximum write throughput:
431/// - Deduplication for storage efficiency
432/// - Smaller cache (200MB) to favor writes
433/// - Bloom filter for fast existence checks
434/// - Metrics for monitoring
435///
436/// Best for: Data ingestion pipelines, ETL processes, bulk imports
437pub fn ingestion_stack(path: PathBuf) -> Result<MonitoredDedupStack> {
438    let base = StorageStackBuilder::new()
439        .with_config(BlockStoreConfig::production(path))
440        .with_cache(200) // Smaller cache for write optimization
441        .with_bloom(2_000_000)
442        .build_full()?;
443
444    let chunking_config = ChunkingConfig::default();
445    let dedup = DedupBlockStore::new(base, chunking_config);
446
447    Ok(MetricsBlockStore::new(dedup))
448}
449
450/// Create a CDN edge cache stack
451///
452/// Optimized for content delivery:
453/// - Very large cache (2GB) for hot content
454/// - TTL for automatic expiration (1 hour default)
455/// - Bloom filter for fast negative lookups
456/// - Metrics for monitoring cache effectiveness
457///
458/// Best for: CDN edge nodes, content delivery, proxy caching
459pub fn cdn_edge_stack(path: PathBuf) -> Result<MetricsBlockStore<TtlBlockStore<FullStack>>> {
460    let base = StorageStackBuilder::new()
461        .with_config(BlockStoreConfig::production(path))
462        .with_cache(2048) // 2GB cache
463        .with_bloom(5_000_000) // Support large number of blocks
464        .build_full()?;
465
466    let ttl_config = TtlConfig {
467        default_ttl: Duration::from_secs(3600), // 1 hour
468        auto_cleanup: true,
469        cleanup_interval: Duration::from_secs(600), // 10 minutes
470        max_tracked_blocks: 5_000_000,
471    };
472    let ttl_store = TtlBlockStore::new(base, ttl_config);
473
474    Ok(MetricsBlockStore::new(ttl_store))
475}
476
477/// Create a scientific data archive stack
478///
479/// Optimized for large scientific datasets:
480/// - Compression (Zstd level 5 for better compression)
481/// - Deduplication for redundant data
482/// - Medium cache (256MB)
483/// - Bloom filter
484///
485/// Best for: Scientific data repositories, research archives, large dataset storage
486#[cfg(feature = "compression")]
487pub fn scientific_archive_stack(path: PathBuf) -> Result<MonitoredUltimateStack> {
488    let base = StorageStackBuilder::new()
489        .with_config(BlockStoreConfig::production(path))
490        .with_cache(256)
491        .with_bloom(1_000_000)
492        .build_full()?;
493
494    let compression_config = CompressionConfig {
495        algorithm: CompressionAlgorithm::Zstd,
496        level: 5,        // Higher compression for archives
497        threshold: 512,  // Compress blocks > 512 bytes
498        max_ratio: 0.95, // Keep if compressed to 95% or less
499    };
500    let compressed = CompressionBlockStore::new(base, compression_config);
501
502    let chunking_config = ChunkingConfig::default();
503    let dedup = DedupBlockStore::new(compressed, chunking_config);
504
505    Ok(MetricsBlockStore::new(dedup))
506}
507
508/// Create a blockchain storage stack
509///
510/// Optimized for blockchain data:
511/// - No TTL (permanent storage)
512/// - Large bloom filter for fast lookups
513/// - Medium cache for recent blocks
514/// - Deduplication for transactions
515/// - Metrics for monitoring
516///
517/// Best for: Blockchain nodes, distributed ledgers, immutable data
518pub fn blockchain_stack(path: PathBuf) -> Result<MonitoredDedupStack> {
519    let base = StorageStackBuilder::new()
520        .with_config(BlockStoreConfig::production(path))
521        .with_cache(512) // 512MB for recent blocks
522        .with_bloom(10_000_000) // Large bloom for many blocks
523        .build_full()?;
524
525    let chunking_config = ChunkingConfig::default();
526    let dedup = DedupBlockStore::new(base, chunking_config);
527
528    Ok(MetricsBlockStore::new(dedup))
529}
530
531/// Create a machine learning model storage stack
532///
533/// Optimized for ML model storage:
534/// - Large cache (1GB) for frequently accessed models
535/// - Bloom filter for fast existence checks
536/// - Metrics tracking
537///
538/// Best for: ML model repositories, model versioning, training checkpoints
539pub fn ml_model_stack(path: PathBuf) -> Result<MonitoredFullStack> {
540    let base = StorageStackBuilder::new()
541        .with_config(BlockStoreConfig::production(path))
542        .with_cache(1024) // 1GB cache for models
543        .with_bloom(100_000) // Moderate bloom size
544        .build_full()?;
545
546    Ok(MetricsBlockStore::new(base))
547}
548
549/// Create a media streaming stack
550///
551/// Optimized for video/audio streaming:
552/// - Large cache (3GB) for hot content
553/// - TTL for session-based content (2 hours)
554/// - Bloom filter for catalog lookups
555///
556/// Best for: Video streaming services, audio platforms, media servers
557pub fn media_streaming_stack(path: PathBuf) -> Result<MetricsBlockStore<TtlBlockStore<FullStack>>> {
558    let base = StorageStackBuilder::new()
559        .with_config(BlockStoreConfig::production(path))
560        .with_cache(3072) // 3GB cache
561        .with_bloom(1_000_000)
562        .build_full()?;
563
564    let ttl_config = TtlConfig {
565        default_ttl: Duration::from_secs(7200), // 2 hours
566        auto_cleanup: true,
567        cleanup_interval: Duration::from_secs(300), // 5 minutes
568        max_tracked_blocks: 1_000_000,
569    };
570    let ttl_store = TtlBlockStore::new(base, ttl_config);
571
572    Ok(MetricsBlockStore::new(ttl_store))
573}
574
575/// Create a distributed file system stack
576///
577/// Optimized for distributed filesystems:
578/// - Compression for storage efficiency
579/// - Deduplication for redundant files
580/// - Large cache (1.5GB)
581/// - Large bloom filter
582///
583/// Best for: Distributed filesystems, cluster storage, shared filesystems
584#[cfg(feature = "compression")]
585pub fn distributed_fs_stack(path: PathBuf) -> Result<MonitoredUltimateStack> {
586    let base = StorageStackBuilder::new()
587        .with_config(BlockStoreConfig::production(path))
588        .with_cache(1536) // 1.5GB cache
589        .with_bloom(5_000_000)
590        .build_full()?;
591
592    let compression_config = CompressionConfig {
593        algorithm: CompressionAlgorithm::Lz4, // Fast compression for FS
594        level: 1,                             // Fast compression
595        threshold: 4096,                      // Only compress larger files
596        max_ratio: 0.9,
597    };
598    let compressed = CompressionBlockStore::new(base, compression_config);
599
600    let chunking_config = ChunkingConfig::default();
601    let dedup = DedupBlockStore::new(compressed, chunking_config);
602
603    Ok(MetricsBlockStore::new(dedup))
604}
605
606#[cfg(test)]
607mod tests {
608    use super::*;
609
610    #[test]
611    fn test_builder_simple() {
612        let _stack = StorageStackBuilder::new()
613            .with_path(std::env::temp_dir().join("test-simple"))
614            .build_simple();
615        assert!(_stack.is_ok());
616    }
617
618    #[test]
619    fn test_builder_cached() {
620        let _stack = StorageStackBuilder::new()
621            .with_path(std::env::temp_dir().join("test-cached"))
622            .with_cache(10)
623            .build_cached();
624        assert!(_stack.is_ok());
625    }
626
627    #[test]
628    fn test_builder_full() {
629        let _stack = StorageStackBuilder::new()
630            .with_path(std::env::temp_dir().join("test-full"))
631            .with_cache(10)
632            .with_bloom(1000)
633            .build_full();
634        assert!(_stack.is_ok());
635    }
636
637    #[test]
638    fn test_development_stack() {
639        let _stack = development_stack();
640        assert!(_stack.is_ok());
641    }
642
643    #[test]
644    fn test_testing_stack() {
645        let _stack = testing_stack();
646        assert!(_stack.is_ok());
647    }
648}