Berhenti Melakukan Pemampatan JSON dalam Urutan Salah: minify vs gzip vs brotli, Diukur
Inti pati: Pengecilan memotong 30-50% daripada JSON mentah, tetapi gzip menghapuskan kebanyakan keuntungan itu. Nombor yang diukur merentasi bentuk sebenar, serta empat kes di mana pengecilan menjadikan keadaan lebih teruk.

Kesimpulan utama: pisahkan pemampatan pengangkutan daripada pemangkasan struktur dahulu. Jika gzip atau brotli belum diaktifkan, betulkan konfigurasi pelayan — gzip -6 sahaja sudah mencapai 94.5% dan meninggalkan minify bernilai hanya 0.9 mata. 46.3% penuh minify hanya terpakai apabila bil bait direkodkan secara setempat: badan permintaan, 6 MB Lambda, 1 MiB Firestore, atau JSON yang ditulis ke cakera dan log.
Lencana "menjimatkan 35%" pada pengecil kegemaran anda adalah jujur - ia mengukur bait mentah. API anda berjalan di atas gzip atau brotli, dan kedua-duanya memakan kerumitan berlebihan yang sama. Gandakan dua nombor itu dan anda mendapat jawapan yang salah.
1. Pisahkan "pemampatan" kepada dua perkara berbeza
Apa yang orang panggil pemampatan JSON sebenarnya dua operasi tidak berkaitan, dan mencampurkan keduanya ialah cara setiap kesimpulan salah bermula.
| minify (buang ruang putih) | gzip / brotli (pemampatan pengangkutan) | |
|---|---|---|
| Sifat | Transformasi dokumen | Transformasi pengangkutan |
| Output | Teks JSON yang masih sah dan masih boleh dibaca | Perduaan; tidak boleh digunakan sehingga dinyahkod |
| Di mana keuntungan berlaku | Cakera, memori, log, had saiz | Hanya di wayar dan dalam cache |
| Selepas penyahkodan | Bait kekal lebih kecil | Bait kembali kepada saiz asal |
| Siapa pemiliknya | Skrip binaan anda, argumen pensirian anda | Nginx, CDN, rangka kerja pelayan |
Dua baris terakhir yang penting. Had platform diukur selepas penyahkodan: had muatan 6 MB AWS Lambda, had 1 MiB setiap dokumen Firestore. Mengaktifkan gzip tidak membeli satu bait pun di sana, kerana kedua-duanya mengira hasil yang dinyahkod.
2. minify membuang tepat satu perkara: ruang putih struktur
RFC 8259 membenarkan ruang putih (space, \t, \n, \r) di sekeliling mana-mana aksara struktur. Tiada apa-apa lagi dalam dokumen JSON yang pilihan: nama kunci ialah data, nombor mengekalkan setiap digit, dan kandungan string tidak boleh disentuh bait demi bait.
Itu menjadikan keseluruhan operasi satu baris:
const minified = JSON.stringify(JSON.parse(pretty));
// RFC 8259 allows whitespace only around structural characters,
// so spaces inside a string are data, not formatting
JSON.stringify(JSON.parse('{"note": "two spaces"}'));
// {"note":"two spaces"} - both inner spaces are preserved
Patut disebut tiga operasi yang dikelaskan sebagai "pemampatan" tetapi sebenarnya mengubah dokumen: menggugurkan kunci yang nilainya null, membundarkan float, dan menyusun semula kunci. Itu mengubah dokumen dan bukannya memformat semula, jadi mana-mana alat yang mengiklankan "pemampatan mendalam" memerlukan restu pengguna anda sebelum anda mengaktifkannya.
Kerana minify hanya membuang ruang putih, ganjarannya hampir sepenuhnya bergantung pada berapa banyak fail anda terdiri daripada inden. Jangkakan itu muncul berulang kali dalam jadual di bawah.
3. Pengukuran: empat dokumen, tiga tahap gzip, serta brotli
Persekitaran: Node v22.13.1, zlib.gzipSync pada tahap 1 / 6 / 9, brotliCompressSync pada kualiti lalainya (11). Empat sampel sengaja meliputi bentuk yang benar-benar anda hantar: 50 pesanan bersarang (struktur dalam, kunci berulang), konfigurasi 6 tahap (kunci panjang, nilai pendek), tatasusunan log 200 baris (string panjang, hampir tiada inden), dan media-manifest.json sebenar daripada laman ini (bentuk bercampur, string penuh URL).
Langkah satu: bait mentah.
| Sampel | pretty (2 ruang) | pretty (4 ruang) | selepas minify | minify menjimatkan |
|---|---|---|---|---|
| 50 pesanan bersarang | 29,551 B | 40,761 B | 15,881 B | 46.3% |
| konfigurasi 6 tahap | 25,450 B | 38,012 B | 11,679 B | 54.1% |
| tatasusunan log 200 baris | 14,582 B | 14,982 B | 13,981 B | 4.1% |
| media-manifest.json | 25,149 B | 28,114 B | 21,222 B | 15.6% |
Di sinilah lencana "menjimatkan 35%" itu datang, dan ia tidak berbohong. Masalah muncul pada langkah dua.
Langkah dua: mampatkan setiap varian, kemudian tanya berapa banyak minify masih menambah.
| Sampel | di atas gzip -1 | di atas gzip -6 | di atas gzip -9 | di atas brotli |
|---|---|---|---|---|
| 50 pesanan bersarang | 21.0% | 17.0% | 8.9% | 10.6% |
| konfigurasi 6 tahap | 74.2% | 59.1% | 58.0% | 56.4% |
| tatasusunan log 200 baris | -0.7% | -0.4% | 0.6% | -0.3% |
| media-manifest.json | 1.5% | 1.0% | 1.0% | 1.6% |
Dokumen pesanan bersarang yang sama yang kehilangan 46.3% bait mentahnya hanya mengekalkan 17.0% daripada itu sebaik gzip -6 terlibat. Tatasusunan log terus menjadi negatif: minify-kemudian-mampat sedikit lebih besar daripada memampatkan asal pretty. Negatif bukan bug, ia betul-betul yang anda patut jangkakan, dan bahagian seterusnya menjelaskan mengapa.
Langkah tiga: jumlah pengurangan berbanding dokumen pretty asal - nombor yang sebenarnya bergerak di wayar.
| Sampel | gzip -6 sahaja | minify + gzip -6 | minify + brotli |
|---|---|---|---|
| 50 pesanan bersarang | 94.5% | 95.4% | 96.9% |
| konfigurasi 6 tahap | 96.0% | 98.4% | 99.0% |
| tatasusunan log 200 baris | 89.9% | 89.9% | 95.0% |
| media-manifest.json | 83.5% | 83.6% | 86.1% |
4. Mengapa gzip memakan kredit minify
gzip ialah LZ77 diikuti pengekodan Huffman. Gerakan teras LZ77 ialah "lihat ke belakang melalui tetingkap 32 KB, cari jujukan berulang, gantikannya dengan penunjuk (jarak, panjang)." Inden ialah sasaran paling mudah untuk gerakan itu: setiap tahap bersarang mengeluarkan rentetan ruang yang sama, dan berpuluh-puluh baris bersebelahan mengulang kombinasi yang sama. Huffman kemudian memberikan kod pendek kepada simbol frekuensi tinggi, jadi corak seperti \n ditambah lapan ruang - yang mungkin berulang seribu kali dalam satu fail - akhirnya menelan dua atau tiga bit.
Jadi bait yang dipadamkan minify ialah tepat bait yang pemampat akan runtuhkan hampir percuma. Melakukannya sendiri bermakna mengulang kerja yang pemampat sudah mahir, dan 46% anda mengecut kepada 17%. Itulah keseluruhan cerita di sebalik jadual di atas.
Keputusan negatif tatasusunan log patut penjelasan sendiri. Sampel itu hampir keseluruhannya string panjang (baris log, URL, base64), yang bermaksud sedikit ulangan dan entropi tinggi - sedikit kerumitan berlebihan untuk pemampat buang pada mulanya. Baris baharu dalam versi pretty kebetulan memberikan sempadan stabil kepada pemadan. Buangnya dan LZ77 kehilangan sauh tersebut: dalam sampel ini, memampatkan asal pretty pada gzip -1 menghasilkan 1,508 bait, manakala minify-kemudian-mampat menghasilkan 1,518 bait. Sepuluh bait lebih buruk.
Disusun mengikut magnitud, tuasnya berbaris seperti ini:
| Tuas | Penjimatan vs dokumen pretty asal (pesanan bersarang) | Kos |
|---|---|---|
| minify | 46.3% (bait mentah sahaja) | Kebolehbacaan hilang; perlukan langkah binaan |
| gzip -6 | 94.5% | Sedikit CPU pelayan; konfigurasi hampir sifar |
| minify + gzip -6 | 95.4% | Kedua-dua di atas, ditambah |
| minify + brotli | 96.9% | Perlukan HTTPS dan modul brotli; pra-mampat aset statik |
Lompatan daripada 0 ke 94.5% ialah kerja gzip. Lompatan daripada 94.5% ke 95.4% - sembilan persepuluh mata - ialah kerja minify. Jadual ini menetapkan di mana usaha anda patut diletakkan.
5. Tahap pemampatan ialah pemboleh ubah tersembunyi
Mana-mana hujah yang menganggap satu tahap gzip tetap berdiri di atas pasir, kerana tahap itu secara langsung mengubah nilai marginal minify:
| Tetapan pengangkutan | Bait tambahan yang masih dijimatkan minify (pesanan bersarang) |
|---|---|
gzip -1 (lalai gzip_comp_level Nginx) |
21.0% |
| gzip -6 (lalai dalam kebanyakan pustaka standard) | 17.0% |
| gzip -9 | 8.9% |
| brotli (kualiti lalai) | 10.6% |
Semakin lemah pemampatan pengangkutan anda, semakin banyak minify berbaloi. Itu memberikan anda peraturan praktikal: pada pemasangan Nginx standard di mana gzip_comp_level masih 1, minify memberikan anda 21%. Sebaik anda melaraskan kepada 9 atau beralih ke brotli, ia turun kepada 9-11% dan anda biasanya boleh melangkaunya.
Sebelum anda berhujah sama ada cara, ukur tahap mana yang sebenarnya anda gunakan:
# Fetch the same response with three Accept-Encoding values and compare what landed
for enc in identity gzip br; do
size=$(curl -s -o /dev/null -w '%{size_download}' \
-H "Accept-Encoding: $enc" https://api.example.com/v1/orders)
echo "$enc -> $size bytes"
done
Tiga nombor menjawab tiga soalan sekali gus: adakah pemampatan diaktifkan, pada tahap apa, dan berapa banyak ruang yang tinggal untuk minify. Jika identity dan gzip pulang hampir sama, pemampatan tidak aktif sama sekali - dan perkara yang perlu dibetulkan ialah Nginx, bukan JSON anda:
gzip on;
gzip_comp_level 6;
gzip_min_length 256; # skip tiny responses, they only grow
gzip_types application/json application/javascript text/css;
6. Bila minify benar-benar berbaloi
Satu ujian menentukan: adakah bait yang anda jimatkan akan direkodkan di suatu tempat, atau adakah anda akan mencecah had yang mengira bait mentah? Jika ya, 46% penuh terpakai. Jika jawapannya "ia hanya wujud di wayar," maka 17% ialah nilai sebenarnya.
Pertama, badan permintaan HTTP. Kebanyakan pelayan tidak menyahmampat badan permintaan, dan gzip dikonfigurasikan hampir eksklusif untuk respons. JSON yang anda POST biasanya tiada perlindungan lapisan pengangkutan sama sekali, jadi penjimatan minify datang pada harga penuh. Perkara sama berlaku untuk callback webhook, telemetri MQTT, dan bingkai WebSocket yang tidak pernah merundingkan permessage-deflate.
Kedua, saluran dengan siling bait keras - dan setiap satunya mengira bait dinyahkod:
| Saluran | Had | Boleh gzip membantu? |
|---|---|---|
| Muatan segerak AWS Lambda | 6 MB | Tidak, diukur selepas penyahkodan |
| Dokumen tunggal Firestore | 1 MiB | Tidak |
| Mesej tunggal Kafka (lalai) | 1 MB | Tidak, pemampatan sebelah broker ialah lapisan berbeza |
| Kuota localStorage | ~5 MB (dikira dalam aksara) | Tidak |
| URL permintaan GET | Bergantung pelayan, 8 KB biasa | Tidak |
Dalam kategori ini minify bukan pengoptimuman, ia ialah perbezaan antara permintaan yang dihantar dan yang gagal. Membenamkan JSON dalam rentetan pertanyaan URL ialah kes paling tajam: URL tidak pernah dimampatkan dan ia mempunyai had panjang, jadi kecilkan dahulu, kemudian tentukan sama ada anda memerlukan base64 sama sekali.
Ketiga, JSON yang ditulis ke cakera atau log. Log dan indeks menyimpan bait literal tanpa lapisan pengangkutan terlibat. Ada satu kaveat yang mengelirukan orang: minify hanya membantu jika anda menulis JSON pretty pada mulanya. Sampel log 200 baris itu hanya menjimatkan 4.1%, kerana barisnya sudah padat dan inden hampir tiada dalam fail. Sebaliknya, jika pencatat log anda memanggil JSON.stringify(obj, null, 2), menggugurkan argumen ketiga ialah wang percuma.
Satu nota terakhir: apabila bait mentah benar-benar ketat, nama kunci biasanya tuas yang lebih besar daripada inden. Pada media-manifest.json, menggantikan 14 kunci uniknya dengan k0 hingga k13 membawa bentuk yang dikecilkan daripada 21,222 B turun ke 18,887 B - 11.0% lagi. Setelah dimampatkan, kelebihan yang tinggal itu mengecut kepada 2.4% di bawah gzip -6 dan 1.3% di bawah brotli. Mekanisme sama seperti semua di atas: inden dan nama kunci kedua-duanya maklumat struktur berulang, dan pemampat menuntut dividen yang sama sama ada cara.
7. Empat kes di mana ia menjadi bumerang
Jangan kecilkan fail yang anda komit. Tukar "qty": 2 kepada 3 dan fail pretty menunjukkan satu baris bersih dalam git diff; fail yang dikecilkan menulis semula keseluruhan baris, dan dengan ratusan medan anda tidak dapat menemui perubahan itu sama sekali. Ini mekanik yang sama yang dibincangkan dalam Cara Menyelesaikan Konflik Cantum JSON - alat diff beroperasi per baris, jadi JSON satu baris mengubah setiap suntingan menjadi "seluruh baris berbeza." Simpan fail pretty dalam repositori dan kecilkan hanya dalam output binaan atau semasa menulis ke storan.
Jangan mampatkan respons kecil. Respons yang mengandungi hanya {"ok": true} ialah 16 bait selepas inden ditambah, dan 36 bait selepas gzip -6 - dua puluh bait lebih besar, kerana kamus dan pengepala gzip sendiri menduduki ruang. Ini bukan teori; inilah sebab gzip_min_length wujud. Lalai Nginx 20 bait terlalu rendah untuk pengeluaran, di mana 256 ialah lantai biasa.
Jangan jangkakan penghuraian lebih pantas. Menskala set pesanan yang sama kepada 4,000 rekod, dokumen pretty ialah 2,374,378 bait berbanding 1,282,358 dikecilkan - 46% lebih kecil - namun masa median JSON.parse hanya bergerak daripada 5.7 ms ke 5.0 ms, kira-kira 12% lebih pantas. Menjadikan saiz separuh tidak menjadikan masa separuh, kerana penghurai menghabiskan kebanyakan bajetnya mengiris string dan menukar nombor; melangkau ruang putih adalah sampingan. Apa yang sebenarnya memacu kos penghuraian ialah bilangan kunci dan kandungan string, yang Menghurai, Memaparkan dan Menstrim Fail JSON Besar huraikan secara terperinci.
Jangan pasang retrofik pada perkhidmatan yang sudah menjalankan brotli atau gzip -9. Ganjaran turun kepada 9-11%, manakala kos - kehilangan kebolehbacaan kekal semasa menyahpepijat - kekal. Semak sebaliknya sama ada gzip_types malah meliputi application/json. Banyak laman mengaktifkan gzip dan hanya memampatkan text/html, meninggalkan JSON tidak dimampatkan sepenuhnya; dalam situasi itu minify bukan langkah satu, membetulkan konfigurasi anda ialah.
8. Jadual keputusan: langkah mana dahulu
| Situasi anda | Lakukan ini dahulu | Di mana minify berada |
|---|---|---|
| Tiada pemampatan pengangkutan sama sekali | Konfigurasikan gzip_types Nginx atau aktifkan pemampatan automatik CDN |
Langkau; sembilan persepuluh keuntungan hilang |
| Menjalankan gzip -1 (lalai Nginx) | Naikkan gzip_comp_level ke 6 |
Kedua, bernilai 21% |
| Sudah pada brotli atau gzip -9 | Audit kunci panjang, tambah penomboran halaman atau pangkas medan | Langkau; hanya 9-11% tinggal |
| Mencecah Lambda 6 MB atau Firestore 1 MiB | Kecilkan terus; 46% penuh dikira | Pertama |
| Badan POST, webhook, MQTT | Kecilkan terus; pengangkutan jarang terpakai di sini | Pertama |
| Fail konfigurasi menuju ke Git | Biarkan pretty | Hanya dalam output binaan |
| Log, telemetri, dump luar talian | Buang argumen null, 2 dalam pencatat log anda |
Itu ialah langkah satu |
Peraturan satu ayat: lihat lejar bait mana yang anda cuba kecilkan - yang direkodkan secara setempat (cakera, kuota, had platform, badan permintaan) atau yang wujud hanya di wayar. Setempat dahulu bermakna minify; wayar sahaja bermakna aktifkan brotli atau gzip dahulu.
Dalam praktik, jalankan gelung curl di atas untuk mengesahkan pemampatan pengangkutan hidup, kemudian hantar sampel melalui alat JSON Minify dan baca angka penjimatannya. Jika nombor itu melebihi 40% manakala curl melaporkan identity dan gzip pada saiz hampir sama, kesesakan anda ialah konfigurasi pelayan, bukan JSON. Jika dua nombor curl sudah hampir, dokumen ini tinggal sedikit untuk diberikan dan anda patut memangkas medan atau menomborkan halaman sebaliknya - subjek Asas Reka Bentuk Kontrak JSON API.
Adakah JSON yang dikecilkan masih JSON sah?
Ya. minify hanya membuang ruang putih yang dibenarkan RFC 8259 di sekeliling aksara struktur dan tidak pernah menyentuh kandungan string, jadi JSON.parse menghasilkan nilai yang sama dengan asal. Satu-satunya korban ialah kebolehbacaan manusia.
brotli atau gzip?
Pada sampel dikecilkan yang sama, brotli menjimatkan 96.9% dan gzip -6 menjimatkan 95.4% - jurang yang sempit. Gunakan gzip -6 untuk respons dinamik untuk memastikan CPU rendah, dan simpan brotli untuk aset statik yang dihantar melalui HTTPS yang boleh anda pra-mampatkan menjadi fail .br.
Saya mengecilkan JSON saya tetapi API tidak menjadi lebih pantas. Mengapa?
Semak respons dengan curl -sI untuk pengepala Content-Encoding: gzip. Banyak pemasangan Nginx menetapkan lalai gzip_types kepada text/html sahaja, jadi JSON tidak pernah diliputi - dan kurang daripada satu perlima daripada 46% anda pernah sampai ke wayar.
Patutkah fail konfigurasi dikomit dalam bentuk dikecilkan?
Tidak. Alat diff membandingkan baris demi baris, jadi JSON satu baris mengubah setiap suntingan menjadi "seluruh baris berubah," dan menyelesaikan konflik menelan kos jauh lebih tinggi daripada bait yang anda jimatkan. Simpan versi pretty dalam repositori dan kecilkan semasa binaan.
Berapa besar respons patut sebelum gzip berbaloi?
Ambang biasa ialah 256 bait hingga 1 KB. Respons sekecil {"ok": true} - 16 bait dengan inden - membesar kepada 36 bait di bawah gzip -6, kerana kamus dan pengepala menelan lebih banyak daripada yang mereka pulihkan. Itulah tepat apa yang dikawal gzip_min_length.