# Go Caching

> Patrones de caching y pooling en Go. Usar al implementar caches, reducir allocations, o cachear resultados costosos. Use when this capability is needed.

- Skill: `tomevault-io/go-caching` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/go-caching`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/go-caching/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/go-caching

---


# Skill: go-caching

Patrones de caching y pooling en Go con ejemplos de GoFHIR.

## Caching en GoFHIR

### 1. Expression Cache (fhirpath/cache.go)

Cache LRU para expresiones FHIRPath compiladas:

```go
// fhirpath/cache.go

type ExpressionCache struct {
    mu    sync.RWMutex
    cache map[string]*cacheEntry
    order []string  // Para tracking LRU
    limit int
}

type cacheEntry struct {
    expr     *Expression
    lastUsed time.Time
}

func NewExpressionCache(limit int) *ExpressionCache {
    return &ExpressionCache{
        cache: make(map[string]*cacheEntry),
        limit: limit,
    }
}

// Thread-safe get con actualización de LRU
func (c *ExpressionCache) Get(exprStr string) (*Expression, bool) {
    c.mu.RLock()
    entry, ok := c.cache[exprStr]
    c.mu.RUnlock()

    if ok {
        // Actualizar timestamp
        c.mu.Lock()
        entry.lastUsed = time.Now()
        c.mu.Unlock()
        return entry.expr, true
    }
    return nil, false
}

// Thread-safe put con eviction
func (c *ExpressionCache) Put(exprStr string, expr *Expression) {
    c.mu.Lock()
    defer c.mu.Unlock()

    // Evict oldest si está lleno
    if len(c.cache) >= c.limit {
        c.evictOldest()
    }

    c.cache[exprStr] = &cacheEntry{
        expr:     expr,
        lastUsed: time.Now(),
    }
}

// GetOrCompile - Pattern común
func (c *ExpressionCache) GetOrCompile(exprStr string) (*Expression, error) {
    if expr, ok := c.Get(exprStr); ok {
        return expr, nil
    }

    expr, err := Compile(exprStr)
    if err != nil {
        return nil, err
    }

    c.Put(exprStr, expr)
    return expr, nil
}
```

**Uso:**
```go
// Cache global para expresiones frecuentes
var globalCache = fhirpath.NewExpressionCache(1000)

// Evaluar con cache
expr, err := globalCache.GetOrCompile("Patient.name.family")
if err != nil {
    return err
}
result, _ := expr.Evaluate(patient)
```

### 2. Integer Cache (fhirpath/types/pool.go)

Cache de valores pequeños para evitar allocations:

```go
// fhirpath/types/pool.go

// Cache para integers -128 a 127 (como Java)
var integerCache [256]Integer

func init() {
    for i := range integerCache {
        integerCache[i] = Integer{value: int64(i - 128)}
    }
}

func NewInteger(v int64) Integer {
    // Usar cache para valores pequeños
    if v >= -128 && v < 128 {
        return integerCache[v+128]
    }
    return Integer{value: v}
}
```

### 3. Collection Pool (fhirpath/types/pool.go)

sync.Pool para colecciones reutilizables:

```go
// fhirpath/types/pool.go

var collectionPool = sync.Pool{
    New: func() interface{} {
        return make(Collection, 0, 8)  // Capacidad inicial 8
    },
}

// Obtener del pool (slice vacío pero con capacidad)
func GetCollection() Collection {
    return collectionPool.Get().(Collection)[:0]
}

// Devolver al pool
func PutCollection(c Collection) {
    // No guardar colecciones muy grandes
    if cap(c) <= 1024 {
        collectionPool.Put(c[:0])
    }
}

// Crear con capacidad específica
func NewCollectionWithCap(capacity int) Collection {
    if capacity <= 8 {
        return GetCollection()
    }
    return make(Collection, 0, capacity)
}
```

**Uso en evaluación:**
```go
func (e *Evaluator) evaluateWhere(input Collection, criteria Expression) Collection {
    result := types.GetCollection()  // Del pool
    defer func() {
        // NO devolver result al pool - se retorna al caller
    }()

    for _, item := range input {
        matches, _ := criteria.Evaluate(item)
        if matches.IsTrue() {
            result = append(result, item)
        }
    }
    return result
}
```

### 4. StructureDefinition Cache (validator/registry.go)

Cache de definiciones cargadas:

```go
// validator/registry.go

type Registry struct {
    mu          sync.RWMutex
    version     FHIRVersion
    definitions map[string]*StructureDefinition
}

func (r *Registry) GetStructureDefinition(url string) (*StructureDefinition, bool) {
    r.mu.RLock()
    defer r.mu.RUnlock()
    sd, ok := r.definitions[url]
    return sd, ok
}

func (r *Registry) LoadStructureDefinition(sd *StructureDefinition) {
    r.mu.Lock()
    defer r.mu.Unlock()
    r.definitions[sd.URL] = sd
}

// GetOrLoad con lazy loading
func (r *Registry) GetOrLoad(ctx context.Context, url string) (*StructureDefinition, error) {
    // Fast path - ya cacheado
    r.mu.RLock()
    if sd, ok := r.definitions[url]; ok {
        r.mu.RUnlock()
        return sd, nil
    }
    r.mu.RUnlock()

    // Slow path - cargar
    sd, err := r.loadFromSource(ctx, url)
    if err != nil {
        return nil, err
    }

    r.mu.Lock()
    r.definitions[url] = sd
    r.mu.Unlock()

    return sd, nil
}
```

### 5. Regex Cache (fhirpath/funcs/regex.go)

Cache para expresiones regulares compiladas:

```go
// fhirpath/funcs/regex.go

type RegexCache struct {
    mu      sync.RWMutex
    cache   map[string]*regexp.Regexp
    limit   int
    maxLen  int           // Max length de pattern
    timeout time.Duration // Timeout de compilación
}

func NewRegexCache(limit, maxLen int, timeout time.Duration) *RegexCache {
    return &RegexCache{
        cache:   make(map[string]*regexp.Regexp),
        limit:   limit,
        maxLen:  maxLen,
        timeout: timeout,
    }
}

func (c *RegexCache) Get(pattern string) (*regexp.Regexp, error) {
    // Validar longitud (seguridad)
    if len(pattern) > c.maxLen {
        return nil, fmt.Errorf("pattern too long: %d > %d", len(pattern), c.maxLen)
    }

    c.mu.RLock()
    if re, ok := c.cache[pattern]; ok {
        c.mu.RUnlock()
        return re, nil
    }
    c.mu.RUnlock()

    // Compilar con timeout
    re, err := c.compileWithTimeout(pattern)
    if err != nil {
        return nil, err
    }

    c.mu.Lock()
    if len(c.cache) < c.limit {
        c.cache[pattern] = re
    }
    c.mu.Unlock()

    return re, nil
}
```

## Patrones de Caching

### Thread-Safety

```go
// Read-heavy: usar RWMutex
type Cache struct {
    mu    sync.RWMutex
    items map[string]interface{}
}

func (c *Cache) Get(key string) (interface{}, bool) {
    c.mu.RLock()
    defer c.mu.RUnlock()
    v, ok := c.items[key]
    return v, ok
}

func (c *Cache) Set(key string, value interface{}) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.items[key] = value
}
```

### Eviction Strategy

```go
// LRU eviction
func (c *Cache) evictOldest() {
    var oldestKey string
    var oldestTime time.Time

    for key, entry := range c.cache {
        if oldestKey == "" || entry.lastUsed.Before(oldestTime) {
            oldestKey = key
            oldestTime = entry.lastUsed
        }
    }

    if oldestKey != "" {
        delete(c.cache, oldestKey)
    }
}
```

## Cuándo Usar Cada Patrón

| Patrón | Usar cuando... | Ejemplo GoFHIR |
|--------|----------------|----------------|
| LRU Cache | Resultados costosos de calcular | ExpressionCache |
| sync.Pool | Objetos frecuentes, corta vida | Collection pool |
| Value Cache | Valores inmutables pequeños | Integer cache |
| Registry Cache | Datos cargados una vez | StructureDefinitions |
| GetOrCreate | Lazy loading con cache | GetOrCompile |

## Checklist

```markdown
- [ ] ¿El cache es thread-safe?
- [ ] ¿Hay estrategia de eviction?
- [ ] ¿El límite de tamaño es configurable?
- [ ] ¿sync.Pool solo para objetos de corta vida?
- [ ] ¿Se limpian objetos antes de devolver al pool?
```

## Referencias

```text
fhirpath/cache.go            → ExpressionCache
fhirpath/types/pool.go       → Collection pool, Integer cache
fhirpath/funcs/regex.go      → RegexCache
validator/registry.go        → StructureDefinition cache
```

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/gofhir) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-13 -->

