tigris-egress-optimizer

v2026.09.24

Use when diagnosing high storage costs, reducing egress/bandwidth, optimizing data transfer — covers caching, CDN, presigned URLs, Cache-Control headers

GitHub
Install command
npx skhub add tigrisdata/tigris-egress-optimizer
Markdown
SKILL.md

Tigris Egress Optimizer

Diagnose and fix excessive storage egress (network data transfer) costs. Follow this 4-step framework to identify anti-patterns, apply fixes, and verify savings.

Most high egress bills come from the application fetching more data than it uses — not from infrastructure issues.

Prerequisites

Before doing anything else, install the Tigris CLI if it's not already available:

tigris help || npm install -g @tigrisdata/cli

If you need to install it, tell the user: "I'm installing the Tigris CLI (@tigrisdata/cli) so we can work with Tigris object storage."


Step 1: Diagnose

Identify which buckets and access patterns consume the most bandwidth.

Check Bandwidth Metrics

# Check bucket usage
tigris usage

# List objects by size (find large files being served frequently)
tigris ls t3://my-bucket --recursive -l | sort -k3 -rn | head -20

Questions to Answer

  • Which buckets have the highest bandwidth?
  • Are large files being downloaded repeatedly?
  • Are objects being accessed through the app server or directly?
  • Are there objects being downloaded that are never displayed to users?

Step 2: Analyze Codebase

Check your application code for these common anti-patterns:

Anti-Pattern Checklist

Anti-PatternSymptomImpact
Downloading full objects for metadataUsing get() when head() would sufficeHigh — downloads entire file
Re-downloading immutable assetsNo caching layer for static contentHigh — repeated bandwidth
Proxying through app serverServer downloads then re-serves to client2x bandwidth (Tigris→server + server→client)
Missing Cache-Control headersBrowsers re-fetch on every page loadVery high — multiplied by users
Not using CDNPrivate bucket for public contentMedium — single origin, no edge caching
Downloading full objects for thumbnailsFetching 5MB image to show 100px thumbnailHigh — 50x more data than needed

Code Search

Look for these patterns in your codebase:

# Downloads that should be head() calls
grep -rn "get(" --include="*.ts" --include="*.js" | grep -v "test"

# Missing cache headers on put()
grep -rn "put(" --include="*.ts" --include="*.js" | grep -v "cacheControl\|Cache-Control"

# Server-side file proxying
grep -rn "pipe\|stream\|createReadStream" --include="*.ts" --include="*.js"

Step 3: Fix

Fix 1: Use head() Instead of get() for Metadata

// Before — downloads entire file just to check if it exists
const result = await get("avatars/user-123.jpg", "file");
if (result.error) console.log("not found");

// After — only fetches metadata (size, contentType, modified)
const result = await head("avatars/user-123.jpg");
if (result.error) console.log("not found");
console.log(result.data?.size, result.data?.contentType);

Fix 2: Add Cache-Control Headers

// Set cache headers on upload
await put("assets/logo.png", file, {
  access: "public",
  contentType: "image/png",
});

Recommended cache values:

Content TypeCache-Control
Hashed assets (JS, CSS)public, max-age=31536000, immutable
Images (avatars, uploads)public, max-age=86400 (1 day)
Dynamic contentprivate, no-cache
Fontspublic, max-age=31536000, immutable

Fix 3: Use Public Buckets for CDN

Tigris public buckets automatically serve from the nearest global edge — no separate CDN setup needed.

# Make bucket public
tigris buckets create my-public-assets --public

# Or update existing bucket
tigris buckets update my-bucket --public

Public bucket URLs are served from Tigris's global edge network. This eliminates the need for CloudFront, Cloudflare, or other CDN layers for basic static asset delivery.

Fix 4: Use Presigned URLs (Skip Server Proxy)

// Before — server downloads file, then sends to client (2x egress)
app.get("/download/:path", async (req, res) => {
  const result = await get(req.params.path, "stream");
  result.data.pipe(res);
});

// After — redirect client to download directly from Tigris (1x egress)
app.get("/download/:path", async (req, res) => {
  const result = await getPresignedUrl(req.params.path, {
    operation: "get",
    expiresIn: 300,
  });
  res.redirect(result.data!.url);
});

Fix 5: Store Thumbnails Separately

// Before — serving 5MB original for a 100px avatar
<img src="/api/files/avatars/user-123.jpg" width="100" />

// After — generate and store thumbnail on upload
import sharp from "sharp";

const thumb = await sharp(originalBuffer)
  .resize(100, 100, { fit: "cover" })
  .jpeg({ quality: 80 })
  .toBuffer();

await put("avatars/user-123-thumb.jpg", thumb, {
  access: "public",
  contentType: "image/jpeg",
});
// Serve the 5KB thumbnail instead of the 5MB original

Fix 6: Client-Side Caching with ETags

// Server returns ETag on first request
app.get("/api/config", async (req, res) => {
  const result = await head("config/app.json");
  const etag = result.data?.modified?.toISOString();

  if (req.headers["if-none-match"] === etag) {
    return res.status(304).end(); // No body sent, no egress
  }

  const file = await get("config/app.json", "string");
  res.set("ETag", etag);
  res.json(JSON.parse(file.data));
});

Fix 7: Regional Pinning

Keep data close to compute to reduce cross-region transfer:

# Pin bucket to specific regions
tigris buckets create my-bucket --locations us-east-1,eu-west-1

Step 4: Verify

After applying fixes:

  1. Monitor bandwidth — check Tigris dashboard for bandwidth reduction
  2. Compare before/after — track bandwidth for 1-2 weeks
  3. Set alerts — configure monitoring for unexpected bandwidth spikes
  4. Review periodically — new features can introduce new anti-patterns

Quick Wins Summary

FixEffortImpact
Add Cache-Control headersLowHigh
Switch to public bucket (CDN)LowHigh
Use presigned URLs instead of proxyingMediumHigh
Replace get() with head() for checksLowMedium
Store thumbnails separatelyMediumHigh
Regional pinningLowMedium

Critical Rules

Always: Use public buckets for content served to users | Set Cache-Control headers on every upload | Use presigned URLs for private file downloads | Use head() for existence/metadata checks

Never: Proxy files through your app server when presigned URLs work | Serve original images when thumbnails suffice | Skip caching for immutable assets | Ignore bandwidth metrics


Related Skills

  • tigris-image-optimization — Generate thumbnails and variants
  • tigris-static-assets — CDN delivery with cache headers
  • tigris-lifecycle-management — Move cold data to cheaper tiers

Official Documentation

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

skills/tigris-egress-optimizer

Default branch

main

Latest commit

668ea42

Tree SHA

0e26190