Akselerasi instagram.com. Bagian 2

Hari ini kami menyampaikan kepada Anda terjemahan dari materi kedua dari seri yang didedikasikan untuk optimasi instagram.com. Di sini kita akan fokus pada peningkatan mekanisme untuk eksekusi awal permintaan GraphQL dan pada peningkatan efisiensi pengiriman data HTML ke klien.



→ Baca dengan napas tertahan, bagian pertama

Pengiriman data yang diprakarsai server ke klien menggunakan teknologi unduhan HTML progresif


Pada bagian pertama, kami berbicara tentang bagaimana, menggunakan mekanisme preloading, untuk mulai mengeksekusi query pada tahap awal pemrosesan halaman. Itu - bahkan sebelum skrip yang memulai permintaan tersebut dimuat. Mengingat hal ini, dapat dicatat bahwa pelaksanaan permintaan ini pada tahap prapembuatan materi masih berarti bahwa pelaksanaannya tidak dimulai sebelum rendering halaman HTML pada klien. Dan ini, pada gilirannya, berarti bahwa permintaan tidak dapat dimulai sebelum klien mengirim permintaan ke server dan server menanggapi permintaan ini (di sini Anda juga perlu menambahkan waktu yang diperlukan server untuk menghasilkan respons HTML kepada klien). Pada gambar berikut, Anda dapat melihat bahwa awal permintaan GraphQL bisa sangat tertunda. Dan ini - mengingat bahwa kami mulai melakukan permintaan tersebut menggunakan kode yang terletak di tag HTML <head> , dan ini adalah salah satu tugas pertama yang kami selesaikan dengan bantuan alat preloading data.


Eksekusi awal permintaan dimulai dengan penundaan yang nyata

Secara teori, awal permintaan GraphQL seperti itu idealnya akan melihat saat ketika permintaan untuk memuat halaman yang sesuai dikirim ke server. Tetapi bagaimana membuat browser mulai mengunduh sesuatu bahkan sebelum ia menerima setidaknya beberapa kode HTML dari server? Jawabannya adalah mengirim sumber daya ke browser atas inisiatif server. Tampaknya untuk menerapkan mekanisme seperti itu, Anda akan memerlukan sesuatu seperti HTTP / 2 Server Push. Namun, pada kenyataannya, ada teknologi yang sangat lama (yang sering dilupakan) yang memungkinkan Anda untuk menerapkan skema interaksi yang serupa antara klien dan server. Teknologi ini dibedakan oleh dukungan browser universal, untuk implementasinya Anda tidak perlu mempelajari kompleksitas infrastruktur yang tipikal untuk mengimplementasikan HTTP / 2 Server Push. Facebook telah menggunakan teknologi ini sejak 2010 (baca tentang BigPipe ), dan di situs lain seperti Ebay, Facebook juga menemukan aplikasi dalam berbagai bentuk. Tetapi tampaknya pengembang JavaScript aplikasi satu halaman pada dasarnya mengabaikan teknologi ini atau tidak menggunakannya. Ini tentang memuat HTML secara progresif. Teknologi ini dikenal dengan berbagai nama: "flush awal", "pembilasan kepala", "HTML progresif". Ini bekerja berkat kombinasi dua mekanisme:

  • Yang pertama adalah penyandian transfer HTTP chunked.
  • Yang kedua adalah rendering progresif HTML di browser.

Mekanisme pengkodean transfer yang terpotong muncul di HTTP / 1.1. Ini memungkinkan Anda untuk membagi respons HTTP menjadi banyak bagian kecil yang dikirim ke browser dalam mode streaming. Browser "mengencangkan" bagian-bagian ini ketika mereka tiba, membentuk kode respons lengkap dari mereka. Meskipun pendekatan ini memberikan perubahan signifikan dalam cara halaman dibentuk di server, sebagian besar bahasa dan kerangka kerja memiliki kemampuan untuk memberikan jawaban yang sama, dipecah menjadi beberapa bagian. Frontend web Instagram menggunakan Django, jadi kami menggunakan objek StreamingHttpResponse . Alasan mengapa penggunaan mekanisme semacam itu dapat bermanfaat adalah karena memungkinkan Anda untuk mengirim konten HTML halaman ke browser dalam mode streaming karena masing-masing bagian halaman siap, daripada menunggu kode halaman penuh siap. Ini berarti bahwa kami dapat menyiram judul halaman browser hampir secara instan setelah menerima permintaan (oleh karena itu istilah "flush awal"). Persiapan header tidak memerlukan sumber daya server yang besar. Ini memungkinkan browser untuk mulai memuat skrip dan gaya bahkan ketika server sedang sibuk menghasilkan data dinamis untuk sisa halaman. Mari kita lihat apa efek teknik ini. Ini adalah tampilan halaman yang normal.


Teknologi flush awal tidak digunakan: pemuatan sumber daya tidak dimulai sampai halaman HTML terisi penuh

Tetapi apa yang terjadi jika server, setelah menerima permintaan, segera meneruskan judul halaman ke browser.


Teknologi flush awal digunakan: sumber daya mulai memuat segera setelah tag HTML dibuang ke browser

Selain itu, kita dapat menggunakan mekanisme pengiriman pesan HTTP di bagian-bagian untuk mengirim data ke klien saat mereka siap. Dalam hal aplikasi yang diberikan di server, data ini dapat disajikan dalam bentuk kode HTML. Tetapi jika kita berbicara tentang aplikasi satu halaman seperti instagram.com, server juga dapat mengirimkan sesuatu seperti data JSON ke klien. Untuk melihat bagaimana ini bekerja, mari kita lihat contoh paling sederhana untuk memulai aplikasi satu halaman.

Pertama, markup HTML asli dikirim ke browser yang berisi kode JavaScript yang diperlukan untuk membuat halaman. Setelah parsing dan mengeksekusi skrip ini, permintaan XHR akan dieksekusi, memuat data sumber yang diperlukan untuk membuat halaman.


Proses memuat halaman dalam situasi di mana browser secara mandiri meminta dari server semua yang diperlukan

Proses ini melibatkan beberapa situasi di mana klien mengirim permintaan ke server dan menunggu respons darinya. Akibatnya, ada periode ketika server dan klien tidak aktif. Alih-alih menunggu server menunggu permintaan API dari klien, akan lebih efisien jika server mulai bekerja menyiapkan respons API segera setelah kode HTML dihasilkan. Setelah jawaban siap, server dapat, atas inisiatifnya sendiri, meracuni klien. Ini berarti bahwa pada saat klien telah menyiapkan segala yang diperlukan untuk memvisualisasikan data yang sebelumnya dimuat setelah permintaan API selesai, data ini kemungkinan besar sudah siap. Klien tidak harus memenuhi permintaan terpisah ke server dan menunggu jawaban darinya.

Langkah pertama dalam mengimplementasikan skema interaksi klien-server tersebut adalah membuat cache JSON yang dirancang untuk menyimpan respons server. Kami mengembangkan bagian sistem ini menggunakan blok skrip kecil yang tertanam dalam kode HTML halaman. Itu memainkan peran cache dan berisi informasi tentang permintaan yang akan ditambahkan ke cache oleh server (ini, dalam bentuk yang disederhanakan, ditunjukkan di bawah).

 <script type="text/javascript">  //      API,       ,  //     ,       ,    //        window.__data = {    '/my/api/path': {        waiting: [],    }  };  window.__dataLoaded = function(path, data) {    const cacheEntry = window.__data[path];    if (cacheEntry) {      cacheEntry.data = data;      for (var i = 0;i < cacheEntry.waiting.length; ++i) {        cacheEntry.waiting[i].resolve(cacheEntry.data);      }      cacheEntry.waiting = [];    }  }; </script> 

Setelah mengatur ulang kode HTML ke browser, server dapat secara independen menjalankan permintaan API. Setelah menerima jawaban untuk permintaan ini, server akan membuang data JSON ke halaman dalam bentuk tag skrip yang berisi data ini. Ketika browser menerima dan mem-parsing bagian yang sama dari kode HTML halaman, ini akan mengarah pada fakta bahwa data akan jatuh ke dalam cache JSON. Yang paling penting di sini adalah browser akan menampilkan halaman secara progresif - karena menerima fragmen dari respon (yaitu, blok skrip yang telah selesai akan dieksekusi ketika mereka tiba di browser). Ini berarti sangat mungkin untuk secara bersamaan menghasilkan sejumlah besar data di server dan meletakkan blok skrip ke halaman segera setelah data yang sesuai siap. Script ini akan segera dieksekusi pada klien. Ini adalah dasar dari sistem BigPipe yang digunakan oleh Facebook. Di sana, banyak pager independen dimuat secara paralel di server dan dikirim ke klien saat tersedia.

 <script type="text/javascript">  window.__dataLoaded('/my/api/path', {    // JSON- API,      ,     //    JSON-...  }); </script> 

Ketika skrip klien siap untuk meminta data yang dibutuhkan, skrip itu, alih-alih menjalankan permintaan XHR, terlebih dulu memeriksa cache JSON. Jika cache sudah memiliki hasil query, skrip segera menerima apa yang dibutuhkan. Jika permintaan sedang berlangsung, skrip sedang menunggu hasilnya.

 function queryAPI(path) {  const cacheEntry = window.__data[path];  if (!cacheEntry) {    //   XHR-  API    return fetch(path);  } else if (cacheEntry.data) {    //          return Promise.resolve(cacheEntry.data);  } else {    //       ,    //            //       const waiting = {};    cacheEntry.waiting.push(waiting);    return new Promise((resolve) => {      waiting.resolve = resolve;    });  } } 

Semua ini mengarah pada fakta bahwa proses memuat halaman menjadi sama seperti pada diagram berikut.


Proses memuat halaman dalam situasi di mana browser terlibat aktif dalam menyiapkan data untuk klien

Jika Anda membandingkan ini dengan cara termudah memuat halaman, ternyata server dan klien sekarang dapat melakukan lebih banyak tugas secara paralel. Ini mengurangi waktu henti selama server dan klien saling menunggu.

Optimalisasi ini memiliki efek yang sangat positif pada sistem kami. Jadi, di browser desktop, pemuatan halaman mulai menyelesaikan 14% lebih cepat dari sebelumnya. Dan di peramban seluler (karena penundaan yang lebih lama di jaringan seluler) halaman mulai memuat 23% lebih cepat.

Pembaca yang budiman! Apakah Anda berencana untuk menggunakan metodologi ini untuk mengoptimalkan pembentukan halaman web yang dibahas di sini dalam proyek Anda?


Source: https://habr.com/ru/post/id468413/


All Articles