Skip to content

Processor 재사용과 캐시 ​

이 가이드는 패키지 함수와 Processor 인스턴스의 차이를 설명하여, 다양한 시나리오에서 올바른 선택과 최적의 성능을 얻는 데 도움을 줍니다.

두 가지 호출 모드 ​

패키지 함수 (일회성 호출) ​

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

내부적으로 sync.Pool로 임시 Processor 를 관리하며, 매 호출 시 풀에서 가져오고 사용 후 반환합니다.

적용 시나리오: 저빈도 호출 (예: CLI 도구, 일회성 스크립트)

라이프사이클:

text
Extract() 호출
  → sync.Pool 에서 Processor 획득 (또는 새로 생성)
  → 추출 실행
  → sync.Pool 로 반환

Processor 인스턴스 (재사용 모드) ​

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

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

독립적인 Processor 인스턴스를 생성하고, 라이프사이클을 수동으로 관리합니다.

적용 시나리오: 고빈도 호출 (예: 웹 서비스, 크롤러)

라이프사이클:

text
html.New()
  → Processor 생성 (캐시, 감사, 통계)
  → 루프에서 p.Extract() 호출 (캐시 재사용)
  → defer p.Close()

선택 방법 ​

시나리오추천 방식이유
CLI 도구, 단일 처리패키지 함수간단하고 직관적, 관리 불필요
웹 서비스, API 백엔드Processor 인스턴스캐시 가속, 통계 모니터링
배치 크롤러Processor 인스턴스캐시 중복 제거, 리소스 제어 가능
테스트 코드패키지 함수상태 없음, 테스트 격리

캐시 메커니즘 ​

Processor 인스턴스는 콘텐츠 기반 캐시를 내장합니다. 동일한 HTML 입력은 반복해서 처리되지 않습니다.

캐시 설정 ​

go
cfg := html.DefaultConfig()
cfg.MaxCacheEntries = 2000     // 최대 캐시 항목 수 (0=비활성화)
cfg.CacheTTL = time.Hour       // 캐시 유효 기간
cfg.CacheCleanup = 5 * time.Minute // 백그라운드 정리 간격
매개변수기본값설명
MaxCacheEntries2000캐시 용량 상한, 0 으로 설정하면 캐시 비활성화
CacheTTL1 시간항목 만료 시간
CacheCleanup5 분백그라운드에서 만료된 항목을 정리하는 간격

캐시 키 생성 ​

캐시 키는 인코딩 변환 후 UTF-8 콘텐츠를 기반으로 생성됩니다:

  • 64KB 미만의 콘텐츠: 전체 콘텐츠의 해시 계산
  • 64KB 이상의 콘텐츠: 5 점 샘플링 알고리즘 사용 (헤더 + 푸터 + 균등 샘플링)

동일한 HTML 콘텐츠는 반복 호출 시 캐시에 직접 적중하여 파싱과 추출 단계를 건너뜁니다.

캐시 적중률 모니터링 ​

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

// 페이지 배치 처리
for _, page := range pages {
    p.Extract(page)
}

// 통계 가져오기
stats := p.GetStatistics()
fmt.Printf("총 처리: %d\n", stats.TotalProcessed)
fmt.Printf("캐시 적중: %d\n", stats.CacheHits)
fmt.Printf("캐시 미스: %d\n", stats.CacheMisses)

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

추천 패턴 ​

웹 서비스 싱글톤 ​

웹 서비스에서는 싱글톤 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)
}

크롤러 배치 처리 ​

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

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

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

정기 유지보수 ​

장기 실행되는 Processor 는 정기적인 유지보수가 필요합니다:

go
// 정기 캐시 정리 (메모리 증가 방지)
go func() {
    ticker := time.NewTicker(10 * time.Minute)
    for range ticker.C {
        p.ClearCache()
    }
}()

// 정기 통계 초기화 (캐시는 유지)
go func() {
    ticker := time.NewTicker(time.Hour)
    for range ticker.C {
        stats := p.GetStatistics()
        log.Printf("처리 %d회, 오류 %d회",
            stats.TotalProcessed, stats.ErrorCount)
        p.ResetStatistics()
    }
}()

성능 비교 ​

동일한 HTML 을 1000 회 반복 처리 (참고용):

모드첫 처리캐시 적중
패키지 함수기준캐시 없음
Processor(캐시 없음)약 기준약 기준
Processor(캐시 있음)약 기준약 기준의 1/10

캐시 적용 조건

캐시는 Processor 인스턴스에서만 적용됩니다. 패키지 함수는 sync.Pool로 Processor 를 재사용하지만 풀 설정이 캐시를 비활성화하고 (MaxCacheEntries = 0) 반환 시 캐시를 비우므로 캐시를 사용할 수 없습니다.

일반적인 오해 ​

오해올바른 방법
매번 html.New()로 Processor 생성동일한 인스턴스를 재사용
p.Close() 호출 누락defer p.Close() 사용
패키지 함수에 캐시 기대캐시는 Processor 인스턴스에서만 적용
종료 후 Processor 계속 사용ErrProcessorClosed 오류 확인

다음 단계 ​