Skip to main content

Overview

The CacheService provides a thread-safe abstraction over BoltDB cache operations. It manages post metadata, search indexes, HTML content, and hash tracking for incremental builds. Source: builder/services/interfaces.go:26-58

Interface

type CacheService interface {
    // Read operations
    GetPost(id string) (*cache.PostMeta, error)
    ListAllPosts() ([]string, error)
    GetPostByPath(path string) (*cache.PostMeta, error)
    GetPostsByIDs(ids []string) (map[string]*cache.PostMeta, error)
    GetPostsByTemplate(templatePath string) ([]string, error)
    GetSearchRecords(ids []string) (map[string]*cache.SearchRecord, error)
    GetSearchRecord(id string) (*cache.SearchRecord, error)
    GetHTMLContent(post *cache.PostMeta) ([]byte, error)
    GetSocialCardHash(path string) (string, error)
    GetGraphHash() (string, error)
    GetWasmHash() (string, error)
    GetPostsMetadataByVersion(version string) ([]cache.PostListMeta, error)
    
    // Write operations
    StoreHTML(content []byte) (string, error)
    StoreHTMLForPost(post *cache.PostMeta, content []byte) error
    BatchCommit(posts []*cache.PostMeta, records map[string]*cache.SearchRecord, deps map[string]*cache.Dependencies) error
    DeletePost(postID string) error
    SetSocialCardHash(path, hash string) error
    SetGraphHash(hash string) error
    SetWasmHash(hash string) error
    
    // Dirty tracking
    MarkDirty(postID string)
    IsDirty(postID string) bool
    
    // Lifecycle
    Stats() (*cache.CacheStats, error)
    IncrementBuildCount() error
    Close() error
}

Read Operations

GetPost

Retrieves post metadata by ID.
id
string
required
Post ID (BLAKE3 hash of normalized path)
PostMeta
*cache.PostMeta
Post metadata including title, tags, HTML content, and hashes
PostID
string
Unique post identifier
Path
string
Relative path from content directory
ContentHash
string
BLAKE3 hash of frontmatter
BodyHash
string
BLAKE3 hash of body content (v1.2.1)
InlineHTML
[]byte
HTML content for posts < 32KB
HTMLHash
string
Content-addressed hash for large HTML (> 32KB)

GetPostsByIDs

Batch retrieves multiple posts in a single transaction.
ids
[]string
required
Array of post IDs to fetch
posts
map[string]*cache.PostMeta
Map of post ID to metadata

GetSearchRecord

Retrieves pre-computed search data for a post.
id
string
required
Post ID
SearchRecord
*cache.SearchRecord
Search index data
BM25Data
map[string]int
Word frequency map for BM25 scoring
DocLen
int
Document length (tokenized word count)
NormalizedTitle
string
Lowercase title for case-insensitive search

GetHTMLContent

Retrieves rendered HTML content for a post.
post
*cache.PostMeta
required
Post metadata containing either inline HTML or content hash
html
[]byte
Rendered HTML content
Small posts (< 32KB) store HTML inline in metadata. Large posts use content-addressed storage.

Write Operations

StoreHTMLForPost

Stores rendered HTML for a post, automatically choosing inline or content-addressed storage.
post
*cache.PostMeta
required
Post metadata to update
content
[]byte
required
Rendered HTML content

BatchCommit

Commits multiple posts, search records, and dependencies in a single transaction.
posts
[]*cache.PostMeta
required
Array of post metadata to store
records
map[string]*cache.SearchRecord
required
Map of post ID to search record
deps
map[string]*cache.Dependencies
required
Map of post ID to dependencies (tags, templates)

DeletePost

Removes a post from cache (used when files are deleted).
postID
string
required
Post ID to delete

Hash Tracking

GetSocialCardHash / SetSocialCardHash

Tracks social card image generation state.
path
string
required
Post path
hash
string
Frontmatter hash (for Set operation)

GetWasmHash / SetWasmHash

Tracks WASM search engine compilation state.
hash
string
Directory hash of cmd/search, builder/search, builder/models

Dirty Tracking

MarkDirty

Marks a post as needing re-render (used in watch mode).
postID
string
required
Post ID to mark dirty

IsDirty

Checks if a post needs re-rendering.
postID
string
required
Post ID to check
dirty
bool
True if post needs re-rendering

Implementation

The default implementation is cacheServiceImpl with thread-safe dirty tracking:
type cacheServiceImpl struct {
    manager *cache.Manager
    logger  *slog.Logger
    dirty   sync.Map  // Thread-safe dirty tracking
}

func NewCacheService(manager *cache.Manager, logger *slog.Logger) CacheService
Source: builder/services/cache_service.go:10-24

Usage Example

From builder/run/build.go:178-240:
// Batch fetch all posts for fast rehydration
ids, _ := b.cacheService.ListAllPosts()
cachedPosts, _ := b.cacheService.GetPostsByIDs(ids)
searchRecords, _ := b.cacheService.GetSearchRecords(ids)

for _, id := range ids {
    cached := cachedPosts[id]
    searchMeta := searchRecords[id]
    
    // Reconstruct post from cache
    post := models.PostMetadata{
        Title: cached.Title,
        Link:  cached.Link,
        // ...
    }
}
From builder/services/post_service.go:522-546:
// Store new posts in batch
newMeta := &cache.PostMeta{
    PostID: postID,
    Path: relPath,
    BodyHash: bodyHash,
    ContentHash: frontmatterHash,
    // ...
}

err := s.cache.StoreHTMLForPost(newMeta, []byte(htmlContent))

// Batch commit at end of processing
err = s.cache.BatchCommit(newPostsMeta, newSearchRecords, newDeps)

Performance Features

In-Memory LRU Cache (v1.2.1)

Hot post metadata is cached in memory with 5-minute TTL:
// First access: reads from BoltDB
post, _ := cacheService.GetPost(id)

// Subsequent accesses within 5 minutes: served from memory
post, _ := cacheService.GetPost(id)  // ~76x faster

Body Hash Tracking (v1.2.1)

Separate body content hashing ensures body-only changes are detected:
type PostMeta struct {
    ContentHash string  // Frontmatter hash
    BodyHash    string  // Body content hash (NEW in v1.2.1)
}

Batch Operations

Avoids N+1 queries by fetching multiple posts in single transaction:
// BAD: N queries
for _, id := range ids {
    post, _ := cacheService.GetPost(id)
}

// GOOD: 1 query
posts, _ := cacheService.GetPostsByIDs(ids)

Build docs developers (and LLMs) love