Serve a cache hit with no database read or write (closes #309)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
A hit opens the variant file and nothing else: its content type comes from the file's first bytes, and a file in no output format is a miss. Its access time and hit count, and the access time of a cached source a miss uses, go without waiting to the goroutine that wrote late counts, which now writes them all once a second; each kind of access time waits in a channel of 10,000 that drops the rest. checkNegativeCache leaves expired rows to CleanExpired. The .meta files, metaCache, Lookup and golang-lru go, with their tests. Dropping the .meta skip made the variant and source reconciliation walks identical, so they share one. make bench runs a parallel cache hit benchmark on the host, outside make check. Model: opus-5-5
This commit is contained in:
@@ -140,36 +140,37 @@ var ErrProcessingTimedOut = errors.New(
|
||||
// While its source URL is in the negative cache, a cached variant is still
|
||||
// served and a variant not in the cache is still made from a cached source;
|
||||
// only a request that needs the source fetched is refused.
|
||||
//
|
||||
// A cache hit opens the variant file and makes no database call: its access
|
||||
// time and its hit count are handed to the goroutine that
|
||||
// StartPendingCountWrites starts, without waiting for it. A variant file
|
||||
// that cannot be served, such as one whose first bytes are not those of an
|
||||
// output format, is a miss, and the variant is made again.
|
||||
func (s *Service) Get(
|
||||
ctx context.Context, req *imagerequest.Request,
|
||||
) (*ImageResponse, error) {
|
||||
// Propagate AllowHTTP setting to the request
|
||||
req.AllowHTTP = s.allowHTTP
|
||||
|
||||
// Check variant cache first (disk only, no DB)
|
||||
result, err := s.cache.Lookup(ctx, req)
|
||||
if err != nil {
|
||||
s.log.Warn("cache lookup failed", "error", err)
|
||||
cacheKey := imagerequest.CacheKey(req)
|
||||
|
||||
reader, size, contentType, err := s.cache.GetVariant(cacheKey)
|
||||
if err == nil {
|
||||
s.cache.touchVariant(cacheKey)
|
||||
s.cache.IncrementStats(true, 0)
|
||||
|
||||
return &ImageResponse{
|
||||
Content: reader,
|
||||
ContentLength: size,
|
||||
ContentType: contentType,
|
||||
CacheStatus: CacheHit,
|
||||
ETag: formatETag(cacheKey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Cache hit - serve directly from disk
|
||||
if result != nil && result.Hit {
|
||||
reader, size, contentType, err := s.cache.GetVariant(result.CacheKey)
|
||||
if err != nil {
|
||||
s.log.Error("failed to get cached variant", "key", result.CacheKey, "error", err)
|
||||
// Fall through to re-process
|
||||
} else {
|
||||
// Counted also when the request context has ended meanwhile
|
||||
s.cache.IncrementStats(ctx, true, 0)
|
||||
|
||||
return &ImageResponse{
|
||||
Content: reader,
|
||||
ContentLength: size,
|
||||
ContentType: contentType,
|
||||
CacheStatus: CacheHit,
|
||||
ETag: formatETag(result.CacheKey),
|
||||
}, nil
|
||||
}
|
||||
if !errors.Is(err, ErrNotFound) {
|
||||
s.log.Warn("cached variant not served, making it again",
|
||||
"cache_key", cacheKey, "error", err)
|
||||
}
|
||||
|
||||
// Cache miss - get the variant, processed once for all the requests that
|
||||
@@ -177,7 +178,7 @@ func (s *Service) Get(
|
||||
// failed or the request context has ended meanwhile
|
||||
response, err := s.processOrWait(ctx, req)
|
||||
|
||||
s.cache.IncrementStats(ctx, false, 0)
|
||||
s.cache.IncrementStats(false, 0)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -474,7 +475,7 @@ func (s *Service) fetchAndProcess(
|
||||
fetchBytes := int64(len(sourceData))
|
||||
|
||||
// Counted also when the request context has ended meanwhile
|
||||
s.cache.IncrementUpstreamFetch(ctx, fetchBytes)
|
||||
s.cache.IncrementUpstreamFetch(fetchBytes)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read upstream response: %w", err)
|
||||
@@ -575,7 +576,7 @@ func (s *Service) processAndStore(
|
||||
processDuration := time.Since(processStart)
|
||||
|
||||
// Counted also when the request context has ended meanwhile
|
||||
s.cache.IncrementTransformCount(ctx)
|
||||
s.cache.IncrementTransformCount()
|
||||
|
||||
// Read processed content
|
||||
processedData, err := io.ReadAll(processResult.Content)
|
||||
|
||||
Reference in New Issue
Block a user