Cache Control | Tigris Object Storage Documentation

On this page

TAG supports RFC 7234-compliant cache revalidation. Clients can control caching behavior using standard Cache-Control headers, and TAG reports cache status via the X-Cache response header.

X-Cache header reference

Value Meaning
HIT Served from cache (includes revalidation that confirmed the object is unchanged)
MISS Not in cache, fetched from upstream and now cached
REVALIDATED Revalidated with upstream, object changed, new content returned
BYPASS Cache bypassed entirely (client requested no-store)
DISABLED Caching is disabled server-side (TAG_CACHE_DISABLED=true)

Object size threshold

TAG caches objects up to a configurable size limit (cache.size_threshold, default 1 GiB). Objects larger than this threshold bypass the cache entirely: they are fetched from Tigris and streamed directly to the client without being written to disk.

Configure the threshold via the config file:

cache:
  size_threshold: 5368709120 # 5 GiB

Responses that bypass the threshold return X-Cache: MISS and are not stored. Subsequent requests for the same large object will always go to Tigris.

Cache eviction

Cached objects are evicted through two mechanisms:

If max_disk_usage_bytes is 0 (the default), LRU eviction is disabled and objects are only removed by TTL expiry or explicit invalidation.

Automatic cache invalidation

TAG automatically invalidates cached objects when they are modified through TAG:

Objects modified directly on Tigris (bypassing TAG) remain in cache until they expire (default TTL: 24 hours) or are revalidated via Cache-Control: no-cache.

Force revalidation

Send Cache-Control: no-cache or Cache-Control: max-age=0 to force TAG to check with upstream before serving a cached object. TAG sends a conditional request using the cached ETag. If the object hasn't changed, upstream returns 304 and TAG serves from cache (X-Cache: HIT). If changed, TAG streams the new content (X-Cache: REVALIDATED).

import boto3

from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:8080",
    config=Config(s3={"addressing_style": "path"}),
)

# Add Cache-Control header to HEAD requests

def add_no_cache(params, **kwargs):
    params["headers"]["Cache-Control"] = "no-cache"

s3.meta.events.register("before-sign.s3.HeadObject", add_no_cache)

response = s3.head_object(Bucket="my-bucket", Key="my-key")

print(response["ResponseMetadata"]["HTTPHeaders"].get("x-cache"))

# → HIT (object unchanged) or REVALIDATED (object changed)

If the revalidation request to upstream fails, TAG serves the stale cached copy as a fallback.

Bypass cache

Send Cache-Control: no-store to skip the cache entirely. TAG forwards the request directly to upstream and does not cache the response.

def add_no_store(params, **kwargs):
    params["headers"]["Cache-Control"] = "no-store"

s3.meta.events.register("before-sign.s3.HeadObject", add_no_store)

response = s3.head_object(Bucket="my-bucket", Key="my-key")

print(response["ResponseMetadata"]["HTTPHeaders"].get("x-cache"))

# → BYPASS

Verifying cache behavior

Check the X-Cache header to verify caching:

import boto3

from botocore.config import Config

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:8080",
    config=Config(s3={"addressing_style": "path"}),
)

# First request — fetched from Tigris
response = s3.head_object(Bucket="my-bucket", Key="my-key")
print(response["ResponseMetadata"]["HTTPHeaders"].get("x-cache"))

# → MISS

# Second request — served from cache
response = s3.head_object(Bucket="my-bucket", Key="my-key")
print(response["ResponseMetadata"]["HTTPHeaders"].get("x-cache"))

# → HIT