tigris-egress-optimizer
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-Pattern | Symptom | Impact |
|---|---|---|
| Downloading full objects for metadata | Using get() when head() would suffice |
High — downloads entire file |
| Re-downloading immutable assets | No caching layer for static content | High — repeated bandwidth |
| Proxying through app server | Server downloads then re-serves to client | 2x bandwidth (Tigris→server + server→client) |
| Missing Cache-Control headers | Browsers re-fetch on every page load | Very high — multiplied by users |
| Not using CDN | Private bucket for public content | Medium — single origin, no edge caching |
| Downloading full objects for thumbnails | Fetching 5MB image to show 100px thumbnail | High — 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 Type | Cache-Control |
|---|---|
| Hashed assets (JS, CSS) | public, max-age=31536000, immutable |
| Images (avatars, uploads) | public, max-age=86400 (1 day) |
| Dynamic content | private, no-cache |
| Fonts | public, 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:
- Monitor bandwidth — check Tigris dashboard for bandwidth reduction
- Compare before/after — track bandwidth for 1-2 weeks
- Set alerts — configure monitoring for unexpected bandwidth spikes
- Review periodically — new features can introduce new anti-patterns
Quick Wins Summary
| Fix | Effort | Impact |
|---|---|---|
| Add Cache-Control headers | Low | High |
| Switch to public bucket (CDN) | Low | High |
| Use presigned URLs instead of proxying | Medium | High |
Replace get() with head() for checks |
Low | Medium |
| Store thumbnails separately | Medium | High |
| Regional pinning | Low | Medium |
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
- Tigris: https://www.tigrisdata.com/docs/