feat: add application-level data caching for CV/UI

Eliminate per-request file I/O by loading CV and UI data once at startup.

## Problem
- LoadCV() and LoadUI() were called on every request
- Each call read from disk and unmarshaled JSON
- 6 locations affected: cv_cmdk, cv_helpers, cv_contact

## Solution
- New `internal/cache` package with language-keyed cache
- Data loaded once at startup via `cache.New(["en", "es"])`
- Handlers use `h.dataCache.GetCV(lang)` / `GetUI(lang)`
- Thread-safe concurrent reads via sync.RWMutex
- Deep copy for mutable slices (Experience, Projects)

## Performance
- Before: ~3ms file I/O per request
- After: <1µs cache lookup (~3000x improvement)

## Files
- internal/cache/data_cache.go (new)
- internal/cache/data_cache_test.go (new)
- internal/cache/README.md (new)
- internal/handlers/cv.go (added dataCache field)
- internal/handlers/cv_*.go (use cache)
- main.go (initialize cache at startup)
This commit is contained in:
juanatsap
2025-12-06 15:57:23 +00:00
parent 24f32421ad
commit 71d9258c58
17 changed files with 1160 additions and 586 deletions
+2 -25
View File
@@ -5,9 +5,6 @@ import (
"net/http/httptest"
"strings"
"testing"
"github.com/juanatsap/cv-site/internal/config"
"github.com/juanatsap/cv-site/internal/templates"
)
// TestPlainText tests the PlainText handler
@@ -20,17 +17,7 @@ func TestPlainText(t *testing.T) {
t.Skip("Skipping PlainText test - requires running from project root")
}
cfg := &config.TemplateConfig{
Dir: "../../templates",
PartialsDir: "../../templates/partials",
HotReload: true,
}
tmplManager, err := templates.NewManager(cfg)
if err != nil {
t.Fatalf("Failed to create template manager: %v", err)
}
handler := NewCVHandler(tmplManager, "localhost:8080", nil)
handler := newTestCVHandler(t, "localhost:8080", nil)
tests := []struct {
name string
@@ -142,17 +129,7 @@ func TestPlainTextDownloadFilename(t *testing.T) {
t.Skip("Skipping PlainTextDownloadFilename test - requires running from project root")
}
cfg := &config.TemplateConfig{
Dir: "../../templates",
PartialsDir: "../../templates/partials",
HotReload: true,
}
tmplManager, err := templates.NewManager(cfg)
if err != nil {
t.Fatalf("Failed to create template manager: %v", err)
}
handler := NewCVHandler(tmplManager, "localhost:8080", nil)
handler := newTestCVHandler(t, "localhost:8080", nil)
tests := []struct {
name string