{"id":29481001,"url":"https://github.com/unkn0wn-root/kioshun","last_synced_at":"2025-08-21T09:08:06.504Z","repository":{"id":303816113,"uuid":"1016673085","full_name":"unkn0wn-root/kioshun","owner":"unkn0wn-root","description":"Fast, sharded in-memory cache with AdmissionLFU/LRU/LFU/FIFO eviction, http middleware and object pooling.","archived":false,"fork":false,"pushed_at":"2025-08-13T20:53:32.000Z","size":389,"stargazers_count":60,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-13T22:26:22.741Z","etag":null,"topics":["cache","cache-storage","cachemanager","caching","caching-library","go","golang","in-memory","in-memory-cache","in-memory-caching"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/unkn0wn-root.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-09T11:00:12.000Z","updated_at":"2025-08-13T20:53:35.000Z","dependencies_parsed_at":"2025-07-27T11:23:58.891Z","dependency_job_id":"7839a7bf-f9f8-4f21-88e1-a109ce81662c","html_url":"https://github.com/unkn0wn-root/kioshun","commit_stats":null,"previous_names":["unkn0wn-root/cago","unkn0wn-root/kioshun"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/unkn0wn-root/kioshun","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unkn0wn-root%2Fkioshun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unkn0wn-root%2Fkioshun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unkn0wn-root%2Fkioshun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unkn0wn-root%2Fkioshun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/unkn0wn-root","download_url":"https://codeload.github.com/unkn0wn-root/kioshun/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/unkn0wn-root%2Fkioshun/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271455216,"owners_count":24762701,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","cache-storage","cachemanager","caching","caching-library","go","golang","in-memory","in-memory-cache","in-memory-caching"],"created_at":"2025-07-14T23:55:15.233Z","updated_at":"2025-08-21T09:08:06.497Z","avatar_url":"https://github.com/unkn0wn-root.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.JPG\" alt=\"Kioshun Logo\" width=\"200\"/\u003e\n\n  # Kioshun - In-Memory Cache for Go\n\n  *\"kee-oh-shoon\" /kiːoʊʃuːn/*\n\n  [![Go Version](https://img.shields.io/badge/Go-1.21+-blue.svg)](https://golang.org)\n  [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)\n  [![CI](https://github.com/unkn0wn-root/kioshun/actions/workflows/test.yml/badge.svg)](https://github.com/unkn0wn-root/kioshun/actions)\n\n\n  *Thread-safe, sharded in-memory cache for Go*\n\u003c/div\u003e\n\n## Table of Contents\n\n- [Architecture](#architecture)\n  - [Sharded Design](#sharded-design)\n  - [Evictions Implementation](#eviction-policy-implementation)\n  - [Eviction Policies](#eviction-policies)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n- [API Reference](#api-reference)\n- [HTTP Middleware](#http-middleware)\n- [Cache Invalidation Setup](#cache-invalidation-setup)\n- [Benchmark Results](_benchmarks/benchmarks.md)\n\n\n## Architecture\n\n### Sharded Design\n\n\n```\n┌──────────────────────────────────────────────────────────────────────────┐\n│                            Kioshun Cache                                 │\n├─────────────────┬─────────────────┬─────────────────┬─────────────────────┤\n│     Shard 0     │     Shard 1     │     Shard 2     │  ...  │   Shard N   │\n│   RWMutex       │   RWMutex       │   RWMutex       │       │   RWMutex   │\n│                 │                 │                 │       │             │\n│ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │       │ ┌─────────┐ │\n│ │ Hash Map    │ │ │ Hash Map    │ │ │ Hash Map    │ │       │ │Hash Map │ │\n│ │ K -\u003e Item   │ │ │ K -\u003e Item   │ │ │ K -\u003e Item   │ │       │ │K -\u003e Item│ │\n│ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │       │ └─────────┘ │\n│                 │                 │                 │       │             │\n│ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │       │ ┌─────────┐ │\n│ │ LRU List    │ │ │ LRU List    │ │ │ LRU List    │ │       │ │LRU List │ │\n│ │ head ←→ tail│ │ │ head ←→ tail│ │ │ head ←→ tail│ │       │ │head↔tail│ │\n│ │ (sentinel)  │ │ │ (sentinel)  │ │ │ (sentinel)  │ │       │ │(sentinel│ │\n│ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │       │ └─────────┘ │\n│                 │                 │                 │       │             │\n│ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │       │ ┌─────────┐ │\n│ │LFU FreqList │ │ │LFU FreqList │ │ │LFU FreqList │ │       │ │LFU Freq │ │\n│ │(if LFU mode)│ │ │(if LFU mode)│ │ │(if LFU mode)│ │       │ │(LFU only│ │\n│ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │       │ └─────────┘ │\n│                 │                 │                 │       │             │\n│ ┌─────────────┐ │ ┌─────────────┐ │ ┌─────────────┐ │       │ ┌─────────┐ │\n│ │Admission    │ │ │Admission    │ │ │Admission    │ │       │ │Admission│ │\n│ │Filter       │ │ │Filter       │ │ │Filter       │ │       │ │Filter   │ │\n│ │(AdmLFU only)│ │ │(AdmLFU only)│ │ │(AdmLFU only)│ │       │ │(AdmLFU) │ │\n│ └─────────────┘ │ └─────────────┘ │ └─────────────┘ │       │ └─────────┘ │\n│                 │                 │                 │       │             │\n│ Stats Counter   │ Stats Counter   │ Stats Counter   │       │ Stats       │\n└─────────────────┴─────────────────┴─────────────────┴─────────────────────┘\n```\n\n- Keys distributed across shards using FNV-1a hash functions with bit mixing\n- Each shard maintains independent read-write mutex\n- Shard count auto-detected based on CPU cores (default: 4×CPU cores, max 256)\n- Object pooling reduces GC pressure\n\n### Eviction Policy Implementation\n\n#### LRU (Least Recently Used)\n**Implementation**: Circular doubly-linked list with sentinel nodes\n- **Access**: Move item to head position in O(1) - bypasses null checks using sentinels\n- **Eviction**: Remove least recent item (tail.prev) in O(1)\n- **Memory**: Minimal overhead per item (2 pointers: prev, next)\n- head ←→ item1 ←→ item2 ←→ ... ←→ itemN ←→ tail (sentinels)\n\n**LRU Operations:**\n```go\n// O(1) access update - no null checks needed due to sentinels\nmoveToLRUHead(item):\n  if head.next == item: return  // already at head\n  remove item from current position\n  insert item after head sentinel\n\n// O(1) eviction - always valid due to sentinel structure\nevictLRU():\n  victim = tail.prev           // LRU item\n  removeFromLRU(victim)        // unlink from list\n  delete from hashmap          // remove from shard.data\n```\n\n#### LFU (Least Frequently Used) - **Frequency-Node Architecture**\n**Implementation**: Doubly-linked list of frequency buckets, each containing items with same access count\n- **Access**: Increment frequency and move item to next bucket in O(1) amortized\n- **Eviction**: Remove item from lowest frequency bucket in O(1)\n- **Memory**: Additional frequency tracking per item + bucket management\n\n**LFU Structure:**\n```\nFrequency Buckets (sorted by access count):\n┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐\n│ Freq: 1  │←→  │ Freq: 2  │←→  │ Freq: 5  │←→  │ Freq: 8  │\n│ item1    │    │ item3    │    │ item2    │    │ item4    │\n│ item5    │    │ item7    │    │          │    │          │\n└──────────┘    └──────────┘    └──────────┘    └──────────┘\n```\n\n**LFU Operations:**\n```go\n// O(1) amortized frequency increment\nincrement(item):\n  currentBucket = itemFreq[item]\n  newFreq = currentBucket.freq + 1\n\n  // Move to next bucket or create new one\n  if nextBucket.freq == newFreq:\n    target = nextBucket\n  else:\n    target = createBucket(newFreq) // splice after current\n\n  move item from currentBucket to target\n  if currentBucket.isEmpty(): removeBucket(currentBucket)\n\n// O(1) eviction from lowest frequency bucket\nevictLFU():\n  lowestBucket = head.next     // first non-sentinel bucket\n  victim = any item from lowestBucket.items\n  remove victim from bucket and hashmap\n```\n\n#### FIFO (First In, First Out)\n**Implementation**: Uses LRU list structure but treats it as insertion-order queue\n- **Access**: No position updates on access (maintains insertion order)\n- **Eviction**: Remove oldest inserted item (at tail.prev) in O(1)\n\n#### AdmissionLFU (Adaptive Admission-controlled Least Frequently Used) - **Default**\n**Implementation**: Approximate LFU with admission control and scan resistance\n- **Access**: Update frequency counter in O(1) using Count-Min Sketch, no heap maintenance\n- **Eviction**: Random sampling (default 5 items, max 20) with LFU selection in O(k) where k = sample size\n- **Admission Control**: Multi-layered frequency-based admission with adaptive thresholds\n- **Scan Resistance**: Detects and adapts to scanning workloads automatically\n- Frequency bloom filter (10×shard size) + doorkeeper bloom filter (1/8 size) + workload detector per shard\n\n**AdmissionLFU Architecture:**\n```\n┌─────────────────────────────────────────────────────────────────────────────────┐\n│                          Adaptive AdmissionLFU Shard                            │\n├─────────────────────────────┬───────────────────────────────────────────────────┤\n│ Adaptive Admission Filter   │                Cache Items                        │\n│  ┌─────────────────────────┐│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐                  │\n│  │  Frequency Bloom Filter ││  │Item │ │Item │ │Item │ │Item │ ...              │\n│  │  Count-Min Sketch       ││  │Freq:│ │Freq:│ │Freq:│ │Freq:│                  │\n│  │  4-bit counters x10*cap ││  │  5  │ │  12 │ │  3  │ │  8  │                  │\n│  │  Hash: 4 functions      ││  └─────┘ └─────┘ └─────┘ └─────┘                  │\n│  │  Auto-aging threshold   ││                                                   │\n│  └─────────────────────────┘│  Random Sample 5 → Compare Frequencies            │\n│  ┌─────────────────────────┐│  Victim Selection: Min(freq, lastAccess)          │\n│  │    Doorkeeper Filter    ││  ↓                                                │\n│  │    Bloom Filter         ││  Adaptive Admission Decision                      │\n│  │    Size: cap/8          ││                                                   │\n│  │    Hash: 3 functions    ││  Normal Mode:                                     │\n│  │    Reset: 1min interval ││  • freq ≥ 3: Always admit                         │\n│  └─────────────────────────┘│  • freq \u003e victim: Always admit                    │\n│  ┌─────────────────────────┐│  • freq = victim \u0026 freq \u003e 0: Recency-based        │\n│  │   Workload Detector     ││  • else: Adaptive probability 5-95%               │\n│  │   Sequential patterns   ││                                                   │\n│  │   Admission rate track  ││  Scan Mode (detected patterns):                   │\n│  │   Miss streak tracking  ││  • Recency-based admission                        │\n│  │   Adaptive probability  ││  • Lower admission threshold                      │\n│  └─────────────────────────┘│  • Protect against cache pollution                │\n└─────────────────────────────┴───────────────────────────────────────────────────┘\n```\n\n**Admission Control Components:**\n\n1. **Frequency Estimation (Count-Min Sketch)**:\n   - 4-bit counters packed in uint64 arrays (16 counters per word)\n   - 4 hash functions with xxHash64-based avalanche mixing\n   - Automatic aging: counters halved when total increments ≥ size × 10\n   - Size: 10× shard capacity counters (min 1024 per shard)\n\n2. **Doorkeeper Bloom Filter**:\n   - 3 hash functions for recent access tracking\n   - Size: 1/8 of frequency filter size for memory efficiency\n   - Periodic reset every 1 minute (configurable via AdmissionResetInterval)\n   - Immediate admission for items in doorkeeper (bypass frequency check)\n\n3. **Workload Detector (Scan Resistance)**:\n   - Tracks admission rate (admissions/second) with threshold of 100/sec\n   - Detects sequential access patterns using circular buffer of recent keys\n   - Monitors consecutive miss streaks with threshold of 50 misses\n   - Adapts admission strategy during detected scanning workloads\n\n4. **Adaptive Admission Algorithm**:\n   ```\n   Phase 1: Doorkeeper Check\n   if in_doorkeeper(key):\n       refresh_doorkeeper(key)\n       return ADMIT\n\n   Phase 2: Scan Detection\n   if detect_scan(key):\n       if victim_age \u003e 100ms:\n           return ADMIT    // Prefer recent items during scan\n       else:\n           return ADMIT if hash(key) % 100 \u003c min_probability(5%)\n\n   Phase 3: Update Frequency \u0026 Doorkeeper\n   new_freq = frequency_filter.increment(key)\n   doorkeeper.add(key)\n\n   Phase 4: Adaptive Admission Decision\n   if new_freq \u003e= 3:                    // High frequency guarantee\n       return ADMIT\n   elif new_freq \u003e victim_frequency:    // Better than victim\n       return ADMIT\n   elif new_freq == victim_frequency \u0026\u0026 new_freq \u003e 0:  // Recency tie-breaking\n       return ADMIT if victim_age \u003e 1_second\n   else:                                // Adaptive probabilistic admission\n       probability = adaptive_probability(5-95%) - (victim_frequency * 10)\n       return ADMIT if hash(key) % 100 \u003c max(probability, 5%)\n\n   Phase 5: Probability Adjustment\n   adjust_probability_based_on_eviction_pressure()\n   ```\n\n5. **Adaptive Probability Control**:\n   - Dynamic admission probability between 5% and 95%\n   - Decreases probability (-5%) when eviction rate \u003e 100/second\n   - Increases probability (+5%) when eviction rate \u003c 10/second\n   - Eviction pressure monitored every second\n\n### Eviction Policies\n\n```go\nconst (\n    LRU        EvictionPolicy = iota // Least Recently Used\n    LFU                              // Least Frequently Used\n    FIFO                             // First In, First Out\n    AdmissionLFU                     // Sampled LFU with admission control (default)\n)\n```\n\n## Installation\n\n```bash\ngo get github.com/unkn0wn-root/kioshun\n```\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"time\"\n\n    cache \"github.com/unkn0wn-root/kioshun\"\n)\n\nfunc main() {\n    // Create cache with default configuration\n    c := cache.NewWithDefaults[string, string]()\n    defer c.Close()\n\n    // Set with default TTL (30 min)\n    c.Set(\"user:123\", \"David Nice\", cache.DefaultExpiration)\n\n    // Set with no expiration\n    c.Set(\"user:123\", \"David Nice\", cache.NoExpiration)\n\n    // Set value with custom TTL\n    c.Set(\"user:123\", \"David Nice\", 5*time.Minute)\n\n    // Get value\n    if value, found := c.Get(\"user:123\"); found {\n        fmt.Printf(\"User: %s\\n\", value)\n    }\n\n    // Get cache statistics\n    stats := c.Stats()\n    fmt.Printf(\"Hit ratio: %.2f%%\\n\", stats.HitRatio*100)\n}\n```\n\n## Configuration\n\n### Basic Configuration\n\n```go\nconfig := cache.Config{\n    MaxSize:         100000,           // Maximum number of items\n    ShardCount:      16,               // Number of shards (0 = auto-detect)\n    CleanupInterval: 5 * time.Minute,  // Cleanup frequency\n    DefaultTTL:      30 * time.Minute, // Default expiration time\n    EvictionPolicy:  cache.AdmissionLFU, // Eviction algorithm (default)\n    StatsEnabled:    true,             // Enable statistics collection\n}\n\ncache := cache.New[string, any](config)\n```\n\n## API Reference\n\n### Core\n\n```go\n// Set stores a value with TTL\ncache.Set(key, value, ttl time.Duration) error\n\n// Get retrieves a value\ncache.Get(key) (value, found bool)\n\n// GetWithTTL retrieves a value with remaining TTL\ncache.GetWithTTL(key) (value, ttl time.Duration, found bool)\n\n// Delete removes a key\ncache.Delete(key) bool\n\n// Exists checks if a key exists without updating access time\ncache.Exists(key) bool\n\n// Clear removes all items\ncache.Clear()\n\n// Size returns current item count\ncache.Size() int64\n\n// Stats returns performance statistics\ncache.Stats() Stats\n\n// Close gracefully shuts down the cache\ncache.Close() error\n```\n\n### Advanced\n\n```go\n// Set with expiration callback\ncache.SetWithCallback(key, value, ttl, callback func(key, value)) error\n\n// Get all keys (expensive operation)\ncache.Keys() []K\n\n// Trigger manual cleanup\ncache.TriggerCleanup()\n```\n\n### Statistics\n\n```go\ntype Stats struct {\n    Hits        int64   // Cache hits\n    Misses      int64   // Cache misses\n    Evictions   int64   // LRU evictions\n    Expirations int64   // TTL expirations\n    Size        int64   // Current items\n    Capacity    int64   // Maximum items\n    HitRatio    float64 // Hit ratio (0.0-1.0)\n    Shards      int     // Number of shards\n}\n```\n\n## HTTP Middleware\n\n### HTTP Caching\n\n```go\nconfig := cache.DefaultMiddlewareConfig() // default uses FIFO\nconfig.DefaultTTL = 5 * time.Minute\nconfig.MaxSize = 100000\nconfig.ShardCount = 16\n\nmiddleware := cache.NewHTTPCacheMiddleware(config)\ndefer middleware.Close()\n\n// IMPORTANT: Enable invalidation if needed\n// middleware.SetKeyGenerator(cache.KeyWithoutQuery())\n\n// Use with any HTTP framework\nhttp.Handle(\"/api/users\", middleware.Middleware(usersHandler))\n```\n\n### Framework Compatibility\n\n```go\n// Standard net/http\nhttp.Handle(\"/api/users\", middleware.Middleware(handler))\n\n// Gin Framework\nrouter.Use(gin.WrapH(middleware.Middleware(http.DefaultServeMux)))\n\n// Echo Framework\ne.Use(echo.WrapMiddleware(middleware.Middleware))\n\n// Chi Router\nr.Use(middleware.Middleware)\n\n// Gorilla Mux\nr.Use(middleware.Middleware)\n```\n\n### Middleware Configuration\n\n```go\napiConfig := cache.DefaultMiddlewareConfig() // default config uses FIFO\napiConfig.MaxSize = 50000\napiConfig.ShardCount = 32\napiConfig.DefaultTTL = 10 * time.Minute\napiConfig.MaxBodySize = 5 * 1024 * 1024 // 5MB\n\napiMiddleware := cache.NewHTTPCacheMiddleware(apiConfig)\n// Enable invalidation for API endpoints\napiMiddleware.SetKeyGenerator(cache.KeyWithoutQuery())\n\n// User-specific caching\nuserMiddleware := cache.NewHTTPCacheMiddleware(config)\nuserMiddleware.SetKeyGenerator(cache.KeyWithUserID(\"X-User-ID\"))\n// Note: User-specific caching uses different key format - invalidation works differently\n\n// Content-type based caching with different TTLs\ncontentMiddleware := cache.NewHTTPCacheMiddleware(config)\ncontentMiddleware.SetKeyGenerator(cache.KeyWithoutQuery()) // Enable invalidation\ncontentMiddleware.SetCachePolicy(cache.CacheByContentType(map[string]time.Duration{\n    \"application/json\": 5 * time.Minute,\n    \"text/html\":       10 * time.Minute,\n    \"image/\":          1 * time.Hour,\n}, 2*time.Minute))\n\n// Size-based conditional caching\nconditionalMiddleware := cache.NewHTTPCacheMiddleware(config)\nconditionalMiddleware.SetKeyGenerator(cache.KeyWithoutQuery()) // Enable invalidation\nconditionalMiddleware.SetCachePolicy(cache.CacheBySize(100, 1024*1024, 3*time.Minute))\n\n// Configure eviction policy\ncacheConfig := cache.DefaultMiddlewareConfig()\ncacheConfig.EvictionPolicy = cache.FIFO\ncacheConfig.MaxSize = 100000\ncacheConfig.DefaultTTL = 5 * time.Minute\n\nmiddleware := cache.NewHTTPCacheMiddleware(cacheConfig)\n\n```\n\n### Built-in Key Generators\n\n```go\n// Default key generator (method + URL + vary headers)\nmiddleware.SetKeyGenerator(cache.DefaultKeyGenerator)\n\n// User-specific keys\nmiddleware.SetKeyGenerator(cache.KeyWithUserID(\"X-User-ID\"))\n\n// Custom vary headers\nmiddleware.SetKeyGenerator(cache.KeyWithVaryHeaders([]string{\"Accept\", \"Authorization\"}))\n\n// Ignore query parameters\nmiddleware.SetKeyGenerator(cache.KeyWithoutQuery())\n```\n\n### Eviction Policies\n\nThe middleware supports different eviction algorithms that can be configured based on diffrent access patterns:\n\n```go\nconfig := cache.DefaultMiddlewareConfig()\n\n// FIFO (First In, First Out)\nconfig.EvictionPolicy = cache.FIFO\n\n// LRU (Least Recently Used)\nconfig.EvictionPolicy = cache.LRU\n\n// LFU (Least Frequently Used)\nconfig.EvictionPolicy = cache.LFU\n\n// AdmissionLFU - Approximate LFU with Admission Control\nconfig.EvictionPolicy = cache.AdmissionLFU\n```\n\n### Built-in Cache Policies\n\n```go\n// Always cache successful responses\nmiddleware.SetCachePolicy(cache.AlwaysCache(5 * time.Minute))\n\n// Never cache\nmiddleware.SetCachePolicy(cache.NeverCache())\n\n// Content-type based caching\nmiddleware.SetCachePolicy(cache.CacheByContentType(map[string]time.Duration{\n    \"application/json\": 5 * time.Minute,\n    \"text/html\":       10 * time.Minute,\n}, 2*time.Minute))\n\n// Size-based caching\nmiddleware.SetCachePolicy(cache.CacheBySize(100, 1024*1024, 3*time.Minute))\n```\n\n### Monitoring\n\n```go\n// Cache hit/miss callbacks\nmiddleware.OnHit(func(key string) {\n    fmt.Printf(\"Cache hit: %s\\n\", key)\n})\n\nmiddleware.OnMiss(func(key string) {\n    fmt.Printf(\"Cache miss: %s\\n\", key)\n})\n\nmiddleware.OnSet(func(key string, ttl time.Duration) {\n    fmt.Printf(\"Cache set: %s (TTL: %v)\\n\", key, ttl)\n})\n\n// Get cache statistics\nstats := middleware.Stats()\nfmt.Printf(\"Hit ratio: %.2f%%\\n\", stats.HitRatio*100)\n```\n\n### Cache Management\n\n```go\n// Get cache statistics\nstats := middleware.Stats()\n\n// Clear all cached responses\nmiddleware.Clear()\n\n// Invalidate by function\nmiddleware.InvalidateByFunc(func(key string) bool {\n    return strings.Contains(key, \"user\")\n})\n\n// Close middleware\nmiddleware.Close()\n```\n\n## Cache Invalidation Setup\n\n**Cache invalidation by URL pattern requires specific key generator configuration.**\n\n### The Problem\n\nThe default key generator uses MD5 hashing which makes pattern-based invalidation impossible:\n\n```go\n// DEFAULT SETUP - Invalidation won't work\nconfig := cache.DefaultMiddlewareConfig()\nmiddleware := cache.NewHTTPCacheMiddleware(config)\n\n// This returns 0 removed entries because keys are hashed\nremoved := middleware.Invalidate(\"/api/users/*\") // Returns 0\n```\n\n**Why it fails:**\n- Default keys: `\"a1b2c3d4e5f6...\"` (MD5 hash)\n- Pattern matching needs: `\"GET:/api/users/123\"` (readable path)\n- Hash loses original URL information\n\n### The Solution\n\nUse path-based key generators for invalidation to work:\n\n```go\n// CORRECT SETUP - Invalidation works\nconfig := cache.DefaultMiddlewareConfig()\nmiddleware := cache.NewHTTPCacheMiddleware(config)\n\n// CRITICAL: Set path-based key generator\nmiddleware.SetKeyGenerator(cache.KeyWithoutQuery())\n\n// Now invalidation works\nremoved := middleware.Invalidate(\"/api/users/*\") // Returns actual count\n```\n\n### Key Generator Comparison\n\n| Key Generator | Example Key | Invalidation | Use Case |\n|---------------|-------------|--------------|----------|\n| `DefaultKeyGenerator` | `\"a1b2c3d4...\"` | ❌ **Broken** | No invalidation needed |\n| `KeyWithoutQuery()` | `\"GET:/api/users\"` | ✅ **Works** | **Recommended for invalidation** |\n| `PathBasedKeyGenerator` | `\"GET:/api/users\"` | ✅ **Works** | Simple path-based caching |\n| `KeyWithVaryHeaders()` | `\"a1b2c3d4...\"` | ❌ **Broken** | Custom headers + security |\n\n### Complete Working Example\n\n```go\npackage main\n\nimport (\n    \"encoding/json\"\n    \"fmt\"\n    \"net/http\"\n    \"time\"\n\n    \"github.com/unkn0wn-root/kioshun\"\n)\n\nfunc main() {\n    // Setup middleware\n    config := cache.DefaultMiddlewareConfig()\n    config.DefaultTTL = 10 * time.Minute\n\n    middleware := cache.NewHTTPCacheMiddleware(config)\n    defer middleware.Close()\n\n\n    // Enable invalidation\n    middleware.SetKeyGenerator(cache.KeyWithoutQuery())\n\n    // Setup handlers\n    http.Handle(\"/api/users\", middleware.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        w.Header().Set(\"Content-Type\", \"application/json\")\n        json.NewEncoder(w).Encode(map[string]any{\n            \"users\": []string{\"alice\", \"bob\", \"charlie\"},\n            \"cached_at\": time.Now(),\n        })\n    })))\n\n    http.Handle(\"/api/users/\", middleware.Middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        w.Header().Set(\"Content-Type\", \"application/json\")\n        json.NewEncoder(w).Encode(map[string]any{\n            \"user\": \"user-data\",\n            \"cached_at\": time.Now(),\n        })\n    })))\n\n    // Invalidation endpoint\n    http.HandleFunc(\"/admin/invalidate\", func(w http.ResponseWriter, r *http.Request) {\n        if r.Method != http.MethodPost {\n            http.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n            return\n        }\n\n        pattern := r.URL.Query().Get(\"pattern\")\n        if pattern == \"\" {\n            http.Error(w, \"pattern parameter required\", http.StatusBadRequest)\n            return\n        }\n\n        // This now works!\n        removed := middleware.Invalidate(pattern)\n\n        w.Header().Set(\"Content-Type\", \"application/json\")\n        json.NewEncoder(w).Encode(map[string]any{\n            \"message\": fmt.Sprintf(\"Invalidated %d entries\", removed),\n            \"pattern\": pattern,\n        })\n    })\n\n    fmt.Println(\"Server starting on :8080\")\n    fmt.Println(\"\\nTest caching:\")\n    fmt.Println(\"  curl http://localhost:8080/api/users\")\n    fmt.Println(\"  curl http://localhost:8080/api/users/123\")\n    fmt.Println(\"\\nTest invalidation:\")\n    fmt.Println(\"  curl -X POST 'http://localhost:8080/admin/invalidate?pattern=/api/users/*'\")\n\n    http.ListenAndServe(\":8080\", nil)\n}\n```\n\n### When to Use Each Approach\n\n**Use `KeyWithoutQuery()` when:**\n- You need pattern-based invalidation\n- Query parameters don't affect response content\n- You want readable cache keys for debugging\n\n**Use `DefaultKeyGenerator` when:**\n- You don't need pattern invalidation\n- Query parameters affect response content\n- You only use `Clear()` for cache management\n\n\n### HTTP Compliance\n\nThe middleware automatically handles:\n- **Cache-Control** headers (max-age, no-cache, no-store, private)\n- **Expires** headers (RFC1123 format)\n- **ETag** generation and validation\n- **Vary** headers for content negotiation\n- **X-Cache** headers (HIT/MISS status)\n- **X-Cache-Date** and **X-Cache-Age** headers\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funkn0wn-root%2Fkioshun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Funkn0wn-root%2Fkioshun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Funkn0wn-root%2Fkioshun/lists"}