Dalam beberapa tahun terakhir, banyak
hal baru telah muncul di
instagram.com . Banyak. Misalnya - alat mendongeng, filter, alat kreatif, pemberitahuan, pesan langsung. Namun, seiring pertumbuhan proyek, semua ini memberikan satu efek samping yang menyedihkan, yaitu kinerja instagram.com mulai menurun. Selama setahun terakhir, tim pengembangan Instagram telah melakukan upaya berkelanjutan untuk memperbaikinya. Hal ini menyebabkan fakta bahwa total waktu pemuatan umpan Instagram (halaman umpan) menurun hampir 50%.

Hari ini kami menerbitkan terjemahan dari materi pertama dari serangkaian artikel yang ditujukan untuk kisah bagaimana percepatan instagram.com.
Tentang mengoptimalkan kinerja proyek web
Peningkatan kinerja selama setahun terakhir (umpan Instagram, metrik Display Done, ms.)Salah satu pendekatan paling penting untuk meningkatkan kinerja aplikasi web adalah memprioritaskan pemuatan dan pemrosesan sumber daya dengan benar dan mengurangi waktu henti peramban selama pemuatan halaman. Dalam kasus kami, banyak dari optimasi ini telah terbukti lebih efektif daripada mengurangi ukuran kode. Sebagai aturan, kami tidak memiliki keluhan tentang ukuran kode. Itu cukup kompak. Dimensinya mulai mengganggu kami hanya setelah banyak perbaikan kecil dilakukan untuk proyek (kami juga berencana untuk menceritakan tentang optimasi ukuran kode). Perbaikan semacam itu, di samping itu, memiliki dampak yang lebih kecil pada proses pengembangan proyek. Mereka membutuhkan lebih sedikit perubahan kode dan lebih sedikit refactoring. Sebagai hasilnya, kami awalnya memusatkan upaya kami tepat pada bidang ini, dimulai dengan sumber daya prapembuatan.
Sebuah cerita tentang gambar prapembuat, kode JavaScript dan bahan-bahan yang dibutuhkan untuk menyelesaikan kueri, serta di mana harus berhati-hati
Prinsip umum dari optimasi kami adalah untuk memberi tahu browser secepat mungkin tentang sumber daya apa yang diperlukan untuk memuat halaman. Kami, sebagai pengembang proyek, dalam banyak kasus tahu sebelumnya apa sebenarnya yang dibutuhkan untuk ini. Tetapi browser tidak dapat mengetahuinya sampai bagian tertentu dari materi halaman dimuat dan diproses. Sumber daya yang dimaksud, sebagian besar, termasuk yang dimuat secara dinamis menggunakan JavaScript (misalnya, skrip lain, gambar, bahan yang diperlukan untuk menjalankan permintaan XHR). Faktanya adalah bahwa browser tidak dapat mendeteksi sumber daya dependen ini hingga mem-parsing dan mengeksekusi beberapa kode JavaScript.
Alih-alih menunggu hingga peramban itu sendiri menemukan sumber daya ini, kami dapat memberikannya petunjuk, berikut ini yang dapat segera mulai mengunduhnya. Kami melakukan ini menggunakan atribut HTML
preload
. Itu terlihat seperti ini:
<link rel="preload" href="my-js-file.js" as="script" type="text/javascript" />
Kami menggunakan petunjuk serupa untuk dua jenis sumber daya pada jalur pemuatan halaman penting. Ini adalah kode JavaScript yang dimuat secara dinamis dan materi yang dimuat secara dinamis dari GraphQL XHR-request untuk data. Skrip yang dimuat secara dinamis adalah skrip yang dimuat menggunakan konstruk dari form
import('...')
untuk rute klien tertentu. Kami memelihara daftar korespondensi dari titik masuk server dan skrip rute klien. Akibatnya, ketika kami, di server, menerima permintaan untuk memuat halaman, kami tahu tentang skrip untuk rute klien mana yang perlu Anda unduh. Sebagai hasilnya, kita dapat, saat membuat kode HTML halaman, menambahkan petunjuk yang sesuai.
Misalnya, ketika bekerja dengan titik masuk
FeedPage
kita tahu bahwa router klien pada akhirnya akan menyelesaikan permintaan untuk mengunduh
FeedPageContainer.js
. Sebagai hasilnya, kita dapat menambahkan konstruksi berikut ke kode halaman:
<link rel="preload" href="/static/FeedPageContainer.js" as="script" type="text/javascript" />
Demikian pula, jika kita tahu bahwa permintaan GraphQL direncanakan akan dieksekusi untuk titik masuk dari halaman tertentu, ini berarti bahwa kita perlu memuat bahan untuk mempercepat pelaksanaan permintaan ini. Ini sangat penting karena fakta bahwa eksekusi permintaan GraphQL seperti itu kadang-kadang membutuhkan banyak waktu, dan halaman tidak dapat merender sampai hasil kueri dikembalikan. Karena itu, kita perlu membuat server sedini mungkin terlibat dalam pembentukan tanggapan terhadap permintaan tersebut.
<link rel="preload" href="/graphql/query?id=12345" as="fetch" type="application/json" />
Perubahan fitur pemuatan halaman terutama terlihat pada koneksi yang lambat. Dengan mensimulasikan koneksi 3G cepat (bagan air terjun pertama di bawah ini, yang menggambarkan situasi ketika preloading sumber daya tidak digunakan), kita dapat melihat bahwa memuat
FeedPageContainer.js
dan mengeksekusi permintaan GraphQL yang terkait dengannya mulai hanya setelah memuat
Consumer.js
. Namun, dalam kasus di mana preloading digunakan, memuat skrip
FeedPageContainer.js
dan mengeksekusi permintaan GraphQL dapat dimulai segera setelah halaman HTML tersedia. Ini, sebagai tambahan, mengurangi waktu yang diperlukan untuk mengunduh skrip minor apa pun yang menggunakan mekanisme pemuatan malas. Di sini,
FeedSidebarContainer.js
dan
ActivityFeedBox.js
(yang bergantung pada
FeedPageContainer.js
) mulai memuat segera setelah memproses
Consumer.js
.
Preload tidak digunakanPreload digunakanManfaat memprioritaskan preload
Selain menggunakan atribut
preload
untuk mulai memuat sumber daya lebih cepat, menggunakan mekanisme ini memiliki keunggulan lain. Ini terdiri dalam meningkatkan prioritas jaringan pemuatan skrip asinkron. Ini menjadi penting ketika menggunakan skrip yang dimuat secara tidak sinkron di jalur kritis untuk memuat halaman, karena secara default mereka memuat dengan prioritas rendah. Akibatnya, prioritas permintaan dan gambar XHR yang terkait dengan area halaman yang terlihat oleh pengguna akan lebih tinggi daripada materi di luar area tampilan. Tetapi ini dapat menyebabkan situasi di mana skrip kritis yang diperlukan untuk membuat halaman diblokir atau dipaksa untuk berbagi bandwidth dengan sumber daya lainnya. Jika Anda tertarik,
inilah akun terperinci dari prioritas sumber daya Chrome. Penggunaan mekanisme preload secara bijaksana (kami akan berbicara lebih banyak tentang ini di bawah) memberi pengembang tingkat kontrol tertentu atas bagaimana browser memprioritaskan proses awal memuat halaman. Ini terutama berlaku untuk kasus-kasus ketika pengembang tahu sumber daya apa yang penting untuk tampilan halaman yang benar.
Preload Prioritas Masalah
Masalah preloading sumber daya justru terletak pada fakta bahwa hal itu memberikan pengembang tambahan pengaruh untuk mempengaruhi prioritas pemuatan sumber daya. Ini berarti bahwa pengembang memiliki lebih banyak tanggung jawab untuk penentuan prioritas yang tepat. Misalnya, ketika menguji situs di wilayah di mana kecepatan jaringan seluler dan WiFi sangat rendah, dan di mana persentase besar dari hilangnya paket diamati, kami memperhatikan bahwa permintaan dieksekusi selama pemrosesan
<link rel="preload" as="script">
mendapatkan prioritas yang lebih tinggi daripada permintaan yang dieksekusi saat memproses
<script />
bundel JavaScript yang digunakan dalam jalur render halaman kritis. Ini mengarah ke peningkatan waktu buka halaman keseluruhan.
Sumber masalah ini adalah bagaimana kami menempatkan tag preload di halaman kami. Yaitu, kami menambahkan petunjuk preload hanya untuk bundel, yang merupakan bagian dari halaman saat ini, yang akan kami muat secara asinkron dengan router klien.
<link rel="preload" href="SomeConsumerRoute.js" as="script" /> <link rel="preload" href="..." as="script" /> ... <script src="Common.js" type="text/javascript"></script> <script src="Consumer.js" type="text/javascript"></script>
Misalnya, pada halaman logout, kami memuat
SomeConsumerRoute.js
ke
Common.js
dan
Consumer.js
, dan karena sumber daya preload dimuat dengan prioritas yang lebih tinggi, tetapi mereka tidak diuraikan, ini memblokir
Common.js
dan parsing
Consumer.js
. Tim pengembangan Penghemat Data Chrome menemukan masalah pramuat yang serupa dan
menjelaskan solusi mereka untuk masalah ini. Dalam kasus mereka, diputuskan untuk selalu menempatkan konstruksi untuk preloading sumber daya asinkron setelah
<script />
sumber daya yang menggunakan sumber daya asinkron ini. Kami memutuskan untuk memuat sebelumnya semua skrip dan menempatkan konstruksi yang sesuai dalam kode sesuai urutan yang dibutuhkan. Ini memberi kita peluang untuk mulai melakukan preloading semua sumber skrip halaman secepat mungkin. Ini termasuk tag untuk skrip yang memuat secara sinkron yang tidak dapat ditambahkan ke HTML sampai data server tertentu ditempatkan pada halaman. Ini memungkinkan kami untuk mengontrol urutan pemuatan skrip.
Ini adalah markup yang memuat semua bundel JavaScript.
<link rel="preload" href="Common.js" as="script" /> <link rel="preload" href="Consumer.js" as="script" /> <link rel="preload" href="SomeConsumerRoute.js" as="script" /> ... <script src="Common.js" type="text/javascript"></script> <script src="Consumer.js" type="text/javascript"></script> <script src="SomeConsumerRoute.js" type="text/javascript" async></script>
Preload gambar
Salah satu area kerja utama instagram.com adalah Feed. Ini adalah halaman gambar dan video yang mendukung pengguliran tanpa akhir. Kami mengisi halaman ini seperti ini. Pertama, unduh set publikasi awal, dan kemudian, saat pengguna menggulir halaman, muat set materi tambahan. Namun, kami tidak ingin pengguna menunggu materi baru dimuat setiap kali ia mencapai bagian bawah kaset. Akibatnya, agar nyaman untuk bekerja dengan halaman ini, kami mengunggah set materi baru sebelum pengguna mencapai akhir rekaman.
Dalam praktiknya, ini, karena beberapa alasan, bukanlah tugas yang mudah:
- Kita perlu mengunduh materi yang tidak terlihat oleh pengguna, sehingga mereka tidak mengambil sumber daya jaringan dan prosesor dari materi yang dia lihat.
- Kami tidak ingin mengirimkan data yang tidak perlu melalui jaringan, berusaha terlalu keras untuk memuat publikasi yang mungkin tidak dilihat pengguna. Tetapi, di sisi lain, jika kita tidak memuat dalam jumlah yang cukup bahan, ini akan sering berarti risiko bahwa pengguna akan "mengalami" akhir dari rekaman itu.
- Proyek instagram.com dirancang untuk bekerja di berbagai perangkat dan di layar dengan berbagai ukuran. Hasilnya, kami menampilkan gambar dalam rekaman menggunakan atribut
srcset
dari <img>
. Atribut ini memungkinkan browser, mengingat ukuran layar, untuk memutuskan resolusi gambar mana yang digunakan. Ini berarti tidak mudah bagi kami untuk menentukan resolusi gambar yang perlu diunduh terlebih dahulu. Selain itu, ada risiko preloading gambar yang tidak akan digunakan browser.
Pendekatan yang kami gunakan untuk menyelesaikan masalah ini adalah dengan membuat abstraksi dari tugas prioritisasi, yang bertanggung jawab untuk mengantri tugas-tugas yang tidak sinkron (dalam hal ini, ini adalah tugas untuk preloading set publikasi berikutnya untuk output dalam rekaman). Tugas serupa pada awalnya antri dengan
idle
prioritas (di sini
requestIdleCallback
digunakan). Ini berarti bahwa pelaksanaan tugas semacam itu tidak akan dimulai selama peramban sibuk dengan pekerjaan penting lainnya. Namun, jika pengguna menggulir halaman cukup dekat ke tempat di mana kumpulan publikasi yang diunduh saat ini berakhir, prioritas tugas ini dari bahan preloading berubah menjadi
high
. Ini dilakukan dengan membatalkan callback siaga, setelah itu proses preloading segera dimulai.
Di awal dan di tengah rekaman, tugas preload data memiliki prioritas idle, dan pada akhir rekaman, prioritas tinggiSetelah pengunduhan data JSON selesai untuk kumpulan publikasi berikutnya, kami mengantre tugas latar yang berulang untuk preloading gambar dari kumpulan ini. Preloading gambar dilakukan dalam urutan publikasi yang ditampilkan dalam umpan daripada secara paralel. Ini memungkinkan kami untuk memprioritaskan tugas pemuatan data dan menampilkan gambar untuk publikasi yang paling dekat dengan tempat pada halaman yang dilihat pengguna. Untuk mengunduh gambar dengan ukuran yang benar, kami menggunakan komponen media tersembunyi, yang parameternya sesuai dengan parameter rekaman saat ini. Di dalam komponen ini ada elemen
<img>
yang menggunakan atribut
srcset
, yang sama yang digunakan untuk menampilkan publikasi nyata dalam umpan. Ini berarti bahwa kami dapat menyediakan browser dengan kemampuan untuk membuat keputusan tentang gambar mana yang akan dimuat. Akibatnya, browser akan menggunakan logika yang sama saat menampilkan gambar yang digunakan saat melakukan prapembuatan. Ini juga berarti bahwa kami, menggunakan komponen media yang serupa, dapat memuat foto untuk area lain di situs. Seperti halaman profil pengguna.
Efek keseluruhan dari perbaikan di atas menghasilkan pengurangan 25% dalam waktu yang diperlukan untuk mengunggah foto. Kita berbicara tentang lamanya waktu antara saat kode publikasi ditambahkan ke DOM dan saat gambar dari publikasi dimuat dan ditampilkan. Selain itu, ini menyebabkan pengurangan 56% dalam waktu yang pengguna, setelah mencapai akhir umpan, dihabiskan menunggu untuk mengunduh materi baru.
Pembaca yang budiman! Apakah Anda menggunakan mekanisme preloading data untuk mengoptimalkan proyek web Anda?
