Skip to content

Processor Cache & Reuse

This guide explains the difference between package functions and Processor instances, helping you make the right choice for different scenarios and achieve optimal performance.

Two Calling Modes

Package Functions (One-time Calls)

go
result, err := html.Extract(data)

Internally uses sync.Pool to manage temporary Processors. Each call borrows from the pool and returns it when done.

Use cases: Low-frequency calls (e.g., CLI tools, one-time scripts)

Lifecycle:

text
Call Extract()
  → Get Processor from sync.Pool (or create new)
  → Execute extraction
  → Return to sync.Pool

Processor Instance (Reuse Mode)

go
p, _ := html.New()
defer p.Close()

for _, page := range pages {
    result, _ := p.Extract(page)
}

Creates a standalone Processor instance with manual lifecycle management.

Use cases: High-frequency calls (e.g., web services, crawlers)

Lifecycle:

text
html.New()
  → Create Processor (cache, audit, statistics)
  → Loop calling p.Extract() (reuse cache)
  → defer p.Close()

How to Choose

ScenarioRecommendedReason
CLI tools, one-time processingPackage functionsSimple, no management needed
Web services, API backendsProcessor instanceCache acceleration, statistics monitoring
Batch crawlersProcessor instanceCache deduplication, resource control
Test codePackage functionsStateless, test isolation

Cache Mechanism

Processor instances include content-based caching. Identical HTML input won't be processed twice.

Cache Configuration

go
cfg := html.DefaultConfig()
cfg.MaxCacheEntries = 2000     // Max cache entries (0=disable)
cfg.CacheTTL = time.Hour       // Cache TTL
cfg.CacheCleanup = 5 * time.Minute // Background cleanup interval
ParameterDefaultDescription
MaxCacheEntries2000Cache capacity limit; set to 0 to disable
CacheTTL1 hourEntry expiration time
CacheCleanup5 minutesInterval for background cleanup of expired entries

Cache Key Generation

Cache keys are generated from the UTF-8 converted content:

  • Content < 64KB: Hash computed over full content
  • Content > 64KB: 5-point sampling algorithm (head + tail + uniform sampling)

Identical HTML content hits the cache on repeated calls, skipping parsing and extraction.

Monitoring Cache Hit Rate

go
p, _ := html.New()
defer p.Close()

// Process a batch of pages
for _, page := range pages {
    p.Extract(page)
}

// Get statistics
stats := p.GetStatistics()
fmt.Printf("Total processed: %d\n", stats.TotalProcessed)
fmt.Printf("Cache hits: %d\n", stats.CacheHits)
fmt.Printf("Cache misses: %d\n", stats.CacheMisses)

hitRate := float64(stats.CacheHits) / float64(stats.TotalProcessed) * 100
fmt.Printf("Hit rate: %.1f%%\n", hitRate)

Web Service Singleton

In web services, use a singleton Processor:

go
var processor *html.Processor

func init() {
    cfg := html.DefaultConfig()
    cfg.MaxCacheEntries = 5000
    cfg.CacheTTL = 30 * time.Minute
    cfg.ProcessingTimeout = 10 * time.Second

    var err error
    processor, err = html.New(cfg)
    if err != nil {
        log.Fatal(err)
    }
}

func handleExtract(w http.ResponseWriter, r *http.Request) {
    data, _ := io.ReadAll(r.Body)
    result, err := processor.Extract(data)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    json.NewEncoder(w).Encode(result)
}

Crawler Batch Processing

go
p, _ := html.New(html.DefaultConfig())
defer p.Close()

urls := crawlURLs()
pages := fetchPages(urls) // [][]byte

batch := p.ExtractBatch(pages)
fmt.Printf("Success: %d, Failed: %d\n", batch.Success, batch.Failed)

Periodic Maintenance

Long-running Processors need periodic maintenance:

go
// Periodic cache cleanup (prevent memory growth)
go func() {
    ticker := time.NewTicker(10 * time.Minute)
    for range ticker.C {
        p.ClearCache()
    }
}()

// Periodic statistics reset (preserve cache)
go func() {
    ticker := time.NewTicker(time.Hour)
    for range ticker.C {
        stats := p.GetStatistics()
        log.Printf("Processed %d times, errors %d",
            stats.TotalProcessed, stats.ErrorCount)
        p.ResetStatistics()
    }
}()

Performance Comparison

Processing the same HTML 1000 times (for reference only):

ModeFirst ProcessingCache Hit
Package functionsBaselineNo cache
Processor (no cache)≈Baseline≈Baseline
Processor (with cache)≈Baseline≈1/10 of baseline

Cache Activation

Cache only works on Processor instances. Package-level functions reuse a Processor via sync.Pool, but the pooled config disables caching (MaxCacheEntries = 0) and clears the cache on return, so caching is unavailable.

Common Pitfalls

PitfallCorrect Approach
Creating a new Processor with html.New() every callReuse the same instance
Forgetting to call p.Close()Use defer p.Close()
Expecting cache from package functionsCache only works on Processor instances
Using a closed ProcessorCheck for ErrProcessorClosed error

Next Steps