/** * Centralized Storage Tier Policy * * This module centralizes all storage tier policy decisions for the postgres.do * tiered storage architecture: * * - HOT: In-DO SQLite Blobs (sync) / Cloudflare Cache API (async) * - WARM: Durable Object storage (persistent) * - COLD: R2 object storage (archival) * * The policy module provides a single source of truth for: * - Promotion decisions (cold -> warm -> hot) * - Demotion decisions (hot -> warm -> cold) * - TTL configuration per tier * - Threshold configuration for access-based tiering * * @module @dotdo/postgres-shared/storage-policy */ /** * Storage tier identifiers * - hot: Cloudflare Cache API (FREE, fastest, ephemeral) * - warm: Durable Object Storage (fast, persistent, limited) * - cold: R2 (durable, unlimited, slower) */ export type StorageTier = 'hot' | 'warm' | 'cold'; /** * Reason for tier movement */ export type TierMovementReason = 'access_threshold' | 'time_window' | 'ttl_expired' | 'lru_eviction' | 'manual' | 'cost_optimization' | 'initial_placement'; /** * Access statistics for a page/resource */ export interface AccessStats { /** Total number of times accessed */ totalAccessCount: number; /** Number of accesses within the time window */ windowAccessCount: number; /** Timestamp of first access (ms since epoch) */ firstAccessTime: number; /** Timestamp of last access (ms since epoch) */ lastAccessTime: number; /** Size of the resource in bytes */ sizeBytes?: number; } /** * Decision about whether to promote a resource to a hotter tier */ export interface PromotionDecision { /** Whether promotion should happen */ shouldPromote: boolean; /** Target tier for promotion (if shouldPromote is true) */ targetTier?: StorageTier; /** Reason for the decision */ reason: TierMovementReason | 'no_action'; /** Human-readable explanation */ explanation?: string; } /** * Decision about whether to demote a resource to a colder tier */ export interface DemotionDecision { /** Whether demotion should happen */ shouldDemote: boolean; /** Target tier for demotion (if shouldDemote is true) */ targetTier?: StorageTier; /** Reason for the decision */ reason: TierMovementReason | 'no_action'; /** Human-readable explanation */ explanation?: string; } /** * Decision about initial tier placement for a new resource */ export interface PlacementDecision { /** Tier to place the resource in */ tier: StorageTier; /** Reason for the decision */ reason: TierMovementReason; /** Human-readable explanation */ explanation?: string; } /** * Configuration for time-to-live settings per tier */ export interface TierTTLConfig { /** TTL for hot tier in milliseconds */ hotTTLMs: number; /** TTL for warm tier in milliseconds */ warmTTLMs: number; /** TTL for cold tier in milliseconds (typically Infinity) */ coldTTLMs: number; } /** * Configuration for promotion thresholds */ export interface PromotionThresholdConfig { /** Access count threshold for cold -> warm promotion */ coldToWarmAccessCount: number; /** Access count threshold for warm -> hot promotion */ warmToHotAccessCount: number; /** Time window in milliseconds for warm -> hot promotion */ warmToHotTimeWindowMs: number; } /** * Configuration for capacity limits */ export interface CapacityConfig { /** Maximum number of pages/resources in hot tier */ maxHotEntries?: number; /** Maximum number of pages/resources in warm tier */ maxWarmEntries?: number; /** Maximum total bytes in hot tier */ maxHotBytes?: number; /** Maximum total bytes in warm tier */ maxWarmBytes?: number; } /** * Complete tier policy configuration */ export interface TierPolicyConfig { /** TTL settings per tier */ ttl: TierTTLConfig; /** Promotion thresholds */ promotion: PromotionThresholdConfig; /** Optional capacity limits */ capacity?: CapacityConfig; /** Default tier for new resources */ defaultTier?: StorageTier; } /** * Interface for tier policy implementations * * This interface allows different policy strategies to be swapped: * - DefaultTierPolicy: Balanced approach * - AggressiveCachePolicy: Maximize hot tier usage * - ConservativePolicy: Minimize tier movement * - AdaptiveTierPolicy: Learn from access patterns */ export interface TierPolicy { /** * Decide whether to promote a resource to a hotter tier * * @param currentTier - The current tier of the resource * @param stats - Access statistics for the resource * @returns Promotion decision */ shouldPromote(currentTier: StorageTier, stats: AccessStats): PromotionDecision; /** * Decide whether to demote a resource to a colder tier * * @param currentTier - The current tier of the resource * @param stats - Access statistics for the resource * @param currentTime - Current timestamp (for TTL calculation) * @returns Demotion decision */ shouldDemote(currentTier: StorageTier, stats: AccessStats, currentTime?: number): DemotionDecision; /** * Decide initial tier placement for a new resource * * @param sizeBytes - Size of the resource in bytes * @param hints - Optional hints about expected access patterns * @returns Placement decision */ getInitialPlacement(sizeBytes?: number, hints?: PlacementHints): PlacementDecision; /** * Get the TTL for a specific tier * * @param tier - The storage tier * @returns TTL in milliseconds */ getTTL(tier: StorageTier): number; /** * Get the full policy configuration */ getConfig(): TierPolicyConfig; } /** * Hints for initial placement decisions */ export interface PlacementHints { /** Expected access frequency (accesses per minute) */ expectedAccessFrequency?: number; /** Whether this is temporary data */ isTemporary?: boolean; /** Whether this is archival data */ isArchival?: boolean; /** Preferred tier (can be overridden by policy) */ preferredTier?: StorageTier; } /** * Current tier statistics for capacity-based decisions */ export interface TierStats { /** Number of entries in hot tier */ hotEntryCount: number; /** Number of entries in warm tier */ warmEntryCount: number; /** Number of entries in cold tier */ coldEntryCount: number; /** Total bytes in hot tier */ hotBytes?: number; /** Total bytes in warm tier */ warmBytes?: number; /** Total bytes in cold tier */ coldBytes?: number; } /** * Default tier policy configuration values * * These defaults are tuned for typical edge database workloads: * - Hot tier: 5 minute TTL (cache frequently accessed pages) * - Warm tier: 1 hour TTL (keep active pages in DO) * - Cold tier: No TTL (R2 is durable storage) */ export declare const DEFAULT_TIER_POLICY_CONFIG: TierPolicyConfig; /** * Aggressive caching policy - maximizes hot tier usage * * Use this when: * - You have high read-to-write ratio * - Cache API costs are not a concern (it's FREE on Cloudflare!) * - Low latency is critical */ export declare const AGGRESSIVE_CACHE_POLICY_CONFIG: TierPolicyConfig; /** * Conservative policy - minimizes tier movement * * Use this when: * - You have limited cache capacity * - Write-heavy workloads * - Data changes frequently */ export declare const CONSERVATIVE_POLICY_CONFIG: TierPolicyConfig; /** * Cost-optimized policy - balances performance and cost * * Use this when: * - You want to minimize R2 operations * - Hot tier is free (Cloudflare Cache API) * - Warm tier (DO) has storage limits */ export declare const COST_OPTIMIZED_POLICY_CONFIG: TierPolicyConfig; /** * Default tier policy implementation * * Provides a balanced approach to tier management with configurable * thresholds, TTLs, and capacity limits. * * @example * ```typescript * const policy = new DefaultTierPolicy() * * // Check if a page should be promoted * const stats: AccessStats = { * totalAccessCount: 10, * windowAccessCount: 5, * firstAccessTime: Date.now() - 60000, * lastAccessTime: Date.now(), * } * const decision = policy.shouldPromote('warm', stats) * if (decision.shouldPromote) { * console.log(`Promote to ${decision.targetTier}: ${decision.explanation}`) * } * ``` */ export declare class DefaultTierPolicy implements TierPolicy { private config; private tierStats?; constructor(config?: Partial); /** * Update tier statistics for capacity-based decisions */ setTierStats(stats: TierStats): void; /** * Decide whether to promote a resource to a hotter tier */ shouldPromote(currentTier: StorageTier, stats: AccessStats): PromotionDecision; /** * Decide whether to demote a resource to a colder tier */ shouldDemote(currentTier: StorageTier, stats: AccessStats, currentTime?: number): DemotionDecision; /** * Decide initial tier placement for a new resource */ getInitialPlacement(_sizeBytes?: number, hints?: PlacementHints): PlacementDecision; /** * Get the TTL for a specific tier */ getTTL(tier: StorageTier): number; /** * Get the full policy configuration */ getConfig(): TierPolicyConfig; /** * Get the target tier for demotion */ private getDemotionTarget; /** * Check if hot tier is at capacity */ private isHotTierAtCapacity; /** * Check if warm tier is at capacity */ private isWarmTierAtCapacity; } /** * Aggressive caching policy implementation * * Maximizes usage of the hot tier (Cloudflare Cache API) which is FREE. * This policy promotes aggressively and keeps data in cache longer. * * @example * ```typescript * const policy = new AggressiveCachePolicy() * * // Will promote after just 1-2 accesses * const decision = policy.shouldPromote('cold', { totalAccessCount: 1, ... }) * // decision.shouldPromote === true * ``` */ export declare class AggressiveCachePolicy extends DefaultTierPolicy { constructor(config?: Partial); } /** * Conservative policy implementation * * Minimizes tier movement for write-heavy workloads or when cache * capacity is limited. * * @example * ```typescript * const policy = new ConservativePolicy() * * // Requires many accesses before promotion * const decision = policy.shouldPromote('cold', { totalAccessCount: 5, ... }) * // decision.shouldPromote === false (needs 10+) * ``` */ export declare class ConservativePolicy extends DefaultTierPolicy { constructor(config?: Partial); } /** * Cost-optimized policy implementation * * Balances performance and cost by maximizing free cache usage * while minimizing expensive R2 operations. * * @example * ```typescript * const policy = new CostOptimizedPolicy() * * // Quick promotion from cold (R2) to avoid repeated R2 reads * const decision = policy.shouldPromote('cold', { totalAccessCount: 2, ... }) * // decision.shouldPromote === true * ``` */ export declare class CostOptimizedPolicy extends DefaultTierPolicy { constructor(config?: Partial); } /** * Factory function to create a tier policy from a preset name * * @param preset - Name of the preset policy * @param overrides - Optional configuration overrides * @returns Configured TierPolicy instance * * @example * ```typescript * // Use default policy * const policy = createTierPolicy('default') * * // Use aggressive caching with custom TTL * const policy = createTierPolicy('aggressive', { * ttl: { hotTTLMs: 30 * 60 * 1000 } // 30 minute hot TTL * }) * ``` */ export declare function createTierPolicy(preset?: 'default' | 'aggressive' | 'conservative' | 'cost-optimized', overrides?: Partial): TierPolicy; /** * Utility to calculate access statistics from access records * * @param accessTimes - Array of access timestamps (ms since epoch) * @param timeWindowMs - Time window for window access count calculation * @param currentTime - Current timestamp (defaults to Date.now()) * @returns Calculated access statistics */ export declare function calculateAccessStats(accessTimes: number[], timeWindowMs: number, currentTime?: number): AccessStats; /** * Merge tier policy configurations * * @param base - Base configuration * @param overrides - Override values * @returns Merged configuration */ export declare function mergeTierPolicyConfig(base: TierPolicyConfig, overrides: Partial): TierPolicyConfig; //# sourceMappingURL=storage-policy.d.ts.map