TL;DR

  • Compression isn’t free. With a cold CDN cache, every Imgproxy rule made total load time worse — between −113% and −154%.
  • Warm cache flips three of four tiers. Medium, Small, and Thumbnail finally beat the Akamai path. Large still ends up bigger than the original.
  • Pearson r between compression ratio and time-savings drops as files shrink — 0.78 → 0.47 from Large to Thumbnail. Large-tier compression is the most predictable, but its actual gain is the worst.
  • Single action rule that fell out of the data: if original_size > 3 MB → route to Imgproxy; else → serve origin directly via Akamai. Cuts the long tail of compression-induced regressions without giving up the wins on large files.

If you operate a CDN + dynamic-image-pipeline stack and you’ve ever assumed “compress everything, it’s safer” — this is the data that pushed us off that default.


Background

Part 1 of this series established that once the CloudFront cache is warm, CloudFront → Imgproxy → S3 is comparable to Akamai → S3 on raw download speed.

But raw download speed isn’t the point. Imgproxy exists to compress images so the user gets a smaller file faster. The question this post answers is the obvious follow-up:

Does compressing actually make pages load faster — and if so, for which files?

I treated this as an experiment, not an opinion: design a compression policy, run it against real S3 traffic, log timings, and let the numbers decide.


Goals

  1. Produce compressed outputs at 50 KB, 250 KB, 500 KB, and 1 MB tiers.
  2. Skip compression for any file already below a tier’s target — never let the policy make files bigger.
  3. Support PNG, JPG, AVIF, WEBP — pick the right format per source.
  4. Emit multiple resolutions so the frontend can pick by network conditions.

Compression design

Step 1: Inspect file size and format

  • Pull the original from Akamai → S3, then check size and format.
  • If the file is already smaller than a tier’s target (e.g. trying to hit 250 KB on a 70 KB file), skip that tier and move to a tighter one (50 KB).
  • Otherwise, compress or transcode.

Step 2: Pick a format strategy

Priority:

  1. JPG (JPEG) — best for photos and rich color; lossy but compresses hard.
  2. AVIF / WEBP — even higher compression, near-lossless; ideal for modern browsers.
  3. PNG — keep when transparency matters; lossless but weak compression.

Routing:

  • PNG → JPG: opaque PNG that’s too large — flip to JPG.
  • AVIF → AVIF: already small; just adjust quality.
  • JPG → JPG: drop quality.
  • GIF → WEBP: 30–50% smaller on animated content.

Step 3: Compression parameters

Rules of thumb:

  • JPG: quality=70-80 is the sweet spot; 70 still looks fine and ships substantially less data.
  • PNG: lossless, or transcode to JPG when transparency doesn’t matter.
  • WEBP / AVIF: quality=60-70 keeps quality high while files stay tiny.

The parameter table we landed on:

PNG

Original sizeParameters
> 1 MBq:80/rs:fit:1920:1080
500 KB – 1 MBq:70/rs:fit:1280:720
250 KB – 500 KBq:60/rs:fit:800:600
50 KB – 250 KBq:50/rs:fit:400:300

JPEG / JPG

Original sizeParameters
> 1 MBq:80/mb:1000000/rs:fit:1920:1080
500 KB – 1 MBq:70/mb:500000/rs:fit:1280:720
250 KB – 500 KBq:60/mb:250000/rs:fit:800:600
50 KB – 250 KBq:50/mb:25000/rs:fit:400:300

WEBP

  • Baseline: q:80, mb:250000.
  • Tier adjustments mirror JPEG.

Step 4: Multi-version output

Each source image fans out into:

  • original (untouched)
  • large (1 MB)
  • medium (500 KB)
  • small (250 KB)
  • thumbnail (50 KB)

Each variant lives at a distinct S3 path so the frontend can pick.


Execution

1. Audit S3

Pulled every user_upload from the staging bucket and counted formats.

Fig 1. File types in the S3 user_upload bucket

Fig 1. File types in the S3 user_upload bucket

Table 1. File type counts

Table 1. Counts by file type

2. Align with frontend

All uploads are normalized to .png upstream — so the dataset skews PNG.

3. Apply the size guard

Files only hit Imgproxy if their original size exceeds the tier target. Otherwise: skip.

  • PNG 241 KB → skip > 1 MB and 500 KB – 1 MB.
  • JPEG 13 MB → all four tiers run.
  • WEBP 80 KB → only 50 KB – 250 KB.

4. Metrics

For every file, against every rule:

  • cdn_s3 size and time.
  • Per-rule cdn_imgproxy size and time (blank if skipped).
  • Download each variant 3× and average.
  • Compute compression ratio: cdn_s3 vs each rule.
  • Compute time reduction: cdn_s3 vs each rule.

5. Two runs

Two passes total. The first deliberately starts cold so the CDN cache hasn’t warmed up; the second runs on the same dataset after caches are populated.


Experiment 1 — cold cache

Fig 2 shows the first-pass data across all four compression tiers.

Fig 2. First-run compression dashboard: counts, time savings, ratios, regression cases

Fig 2. First-run dashboard — compression counts (top-left), files that loaded faster (top-middle), compression-ratio distribution (top-right), time-reduction rate (bottom-left), regression cases (bottom-right)

Compression counts (top-left)

All four tiers compressed successfully — no negative-ratio outliers. Large matched the fewest files (only the largest originals qualify); Thumbnail matched the most.

Files that loaded faster (top-middle)

Every tier sat below 50% — meaning fewer than half the compressed files actually loaded faster than the Akamai path.

Compression ratios (top-right)

Large’s distribution drifts into the negative — some files come out bigger after the Large rule. The other tiers hit clean positive ratios.

Time reduction (bottom-left) — the counterintuitive result

RuleTime reductionEquivalent to
Large-154.4%~2.5× slower
Medium-113.0%~2.1× slower
Small-114.5%~2.1× slower
Thumbnail-118.6%~2.2× slower

Definition:

time_reduction = (results['cdn_s3_time'] - avg_time) / results['cdn_s3_time'] * 100

That is, (S3 mean time − Imgproxy variant mean time) / S3 mean time × 100.

Read: the Imgproxy processing overhead more than wiped out the file-size savings. Smaller files arrived slower.

Plausible drivers:

  1. Imgproxy adds CPU time per request.
  2. CDN cache miss penalty on first request.
  3. The cdn_s3 originals were already warm in the CDN.
  4. Imgproxy server location / capacity bottleneck.

Regression-case scatter (bottom-right)

Drilling into the cases where compression made things worse:

  • X: original file size (KB).
  • Y: load-time increase (%).
  • Color: compression ratio (yellow → green → purple, high → low).

Pattern:

  • Trend line slopes downward (red).
  • The cluster sits between 0 and 3 MB.
  • Load-time penalty stretches 0–600%.

Finding:

  • Smaller originals (< 2 MB) see the biggest penalty — up to ~900%.
  • Larger originals see smaller penalties, sometimes wins.

Operational read:

  • Compressing small files is worse than not compressing.
  • Large files benefit more reliably.
  • Route by original size, not by category label.

Fig 3. Correlation between compression ratio and time reduction (cold cache)

Fig 3. Cold cache — Pearson r per tier

Per-tier correlation

TierrRead
Large0.78Strongest positive correlation — more compression, more time saved
Medium0.69Next strongest; more scatter
Small0.74Tight cluster
Thumbnail0.55Weakest — high variance

Summary of Run 1

Akamai → S3 beats CloudFront → Imgproxy across every tier on total load time. The time-reduction panel in Fig 2 says it best: every bar sits in the negative.

Hypothesis for Run 2: the CDN cache hadn’t been built yet. Let’s run again with the cache warmed.


Experiment 2 — warm cache

Purpose

Does Imgproxy compression + warm CDN cache actually beat the origin path?

Fig 4. Second-run compression dashboard — warm-cache results

Fig 4. Second-run dashboard — warm-cache results

Differences from Run 1

  1. Regression count (orange in top-middle) dropped substantially.
  2. Large still produces bigger-than-original files — parameters need tightening, or Akamai’s built-in compression on large images is just very good (see Appendix).
  3. Time reduction (bottom-left): Medium, Small, and Thumbnail flipped from negative to positive — they now genuinely save time. Large stayed negative.

Regression-case scatter (bottom-right)

  • Small files (< 1000 KB) still show the biggest swings — some penalties up to 700%.
  • Large files (> 3000 KB) regress less — usually under 50%.

Conclusion:

  • > 3 MB files: route through Imgproxy.
  • < 1 MB files: serve original via Akamai → S3.

Fig 5. Correlation between compression ratio and time reduction (warm cache)

Fig 5. Warm cache — Pearson r per tier

Correlation analysis

Pearson r decays as we move to smaller tiers (0.77 → 0.47). All tiers remain positive — higher compression usually delivers larger time savings — but the relationship grows noisier the smaller the file.

  • Large is the most predictable (highest r).
  • Thumbnail underperforms Akamai’s pipeline by enough margin that the win, when it happens, isn’t worth the variance.

Negative regions on these charts mean:

  • Negative ratio: the file grew.
  • Negative time reduction: load time got worse.

Operational recommendations

  • When you need predictable performance gains, prefer Large.
  • Thumbnail’s high compression doesn’t translate to a reliable speedup over Akamai.
  • Large parameters still need tuning to eliminate the “compressed but bigger” cases.

Summary

  1. Not every file should be compressed. Files under 1 MB are faster through Akamai → S3 directly.
  2. Cold cache: Imgproxy loses outright. Every tier sat at −113% to −154%.
  3. Warm cache: only Medium / Small / Thumbnail benefit. Large still grows files.
  4. Compression ratio correlates with time savings (r 0.77 → 0.47). Large is most predictable; Thumbnail noisiest.
  5. Action rule: in the Imgproxy gateway, add if original_size > 3 MB → compress; else → origin. This was the smallest change with the largest effect on the long tail of regressions.

The bigger lesson for any image pipeline I touch from here on: measure round-trip time, not file-size deltas. Compression is a means, not the goal.


Appendix

1. Akamai settings

We had Akamai’s automatic compression already running on large images — almost certainly part of why the Large tier in Imgproxy can’t beat it head-to-head. For full reproducibility, the relevant Akamai configuration screens:

Akamai image transformation settings — 1

Akamai settings — 1

Akamai image transformation settings — 2

Akamai settings — 2

Akamai image transformation settings — 3

Akamai settings — 3

Akamai image transformation settings — 4

Akamai settings — 4


This post is also available on Medium (cross-posted, link forthcoming).