Penulis materi, terjemahan yang kami
terbitkan , adalah salah satu pendiri proyek
Webiny - CMS tanpa server berdasarkan React, GraphQL, dan Node.js. Dia mengatakan bahwa mendukung platform cloud tanpa server multi-tenant adalah bisnis yang memiliki tugas khusus. Banyak artikel telah ditulis di mana teknologi standar untuk mengoptimalkan proyek web dibahas. Diantaranya adalah rendering server, penggunaan teknologi pengembangan aplikasi web canggih, berbagai cara untuk meningkatkan pembangunan aplikasi, dan banyak lagi. Artikel ini, di satu sisi, mirip dengan yang lain, dan di sisi lain, berbeda dari mereka. Faktanya adalah itu didedikasikan untuk mengoptimalkan proyek yang berjalan di lingkungan tanpa server.

Persiapan
Untuk melakukan pengukuran yang akan membantu mengidentifikasi masalah proyek, kami akan menggunakan
webpagetest.org . Dengan bantuan sumber daya ini, kami akan memenuhi permintaan dan mengumpulkan informasi tentang waktu pelaksanaan berbagai operasi. Ini akan memungkinkan kami untuk lebih memahami apa yang dilihat dan dirasakan pengguna saat bekerja dengan proyek.
Kami sangat tertarik dengan indikator "Tampilan pertama", yaitu, berapa lama waktu yang diperlukan untuk memuat situs dari pengguna yang mengunjunginya untuk pertama kalinya. Ini adalah indikator yang sangat penting. Faktanya adalah bahwa cache browser mampu menyembunyikan banyak kemacetan proyek web.
Indikator yang mencerminkan fitur pemuatan situs - identifikasi masalah
Lihatlah tabel berikut.
Analisis indikator lama dan baru dari suatu proyek webDi sini, indikator paling penting dapat dikenali sebagai “Waktu untuk Memulai Render” - waktu sebelum dimulainya rendering. Jika Anda melihat dengan cermat pada indikator ini, Anda dapat melihat bahwa hanya untuk mulai merender halaman, dalam versi lama proyek, butuh hampir 2 detik. Alasan untuk ini terletak pada esensi Aplikasi Satu Halaman (SPA). Untuk menampilkan halaman aplikasi seperti itu di layar, Anda harus memuat bundel JS yang tebal (tahap pemuatan halaman ini ditandai pada gambar berikut sebagai 1). Maka bundel ini perlu diproses di utas utama (2). Dan hanya setelah itu, sesuatu dapat muncul di jendela browser.
(1) Unduh bundel JS. (2) Menunggu pemrosesan bundel di utas utamaNamun, ini hanya sebagian dari gambar. Setelah utas utama memproses bundel JS, ia membuat beberapa permintaan ke Gateway API. Pada tahap pemrosesan halaman ini, pengguna melihat indikator pemuatan berputar. Pemandangan itu bukan yang paling menyenangkan. Namun, pengguna belum melihat konten halaman apa pun. Berikut adalah storyboard dari proses pemuatan halaman.
Pemuatan halamanSemua ini menunjukkan bahwa pengguna yang mengunjungi situs semacam itu merasakan sensasi yang tidak menyenangkan dari bekerja dengannya. Yaitu, dia dipaksa untuk melihat halaman kosong selama 2 detik, dan kemudian satu detik lagi - pada indikator unduhan. Yang kedua ini ditambahkan ke waktu persiapan halaman karena fakta bahwa setelah memuat dan memproses permintaan API JS-bundle dieksekusi. Pertanyaan ini diperlukan untuk memuat data dan, sebagai hasilnya, menampilkan halaman yang sudah selesai.
Pemuatan halamanJika proyek di-host pada VPS biasa, maka waktu yang dibutuhkan untuk menyelesaikan permintaan API ini sebagian besar dapat diprediksi. Namun, proyek yang berjalan di lingkungan tanpa server dipengaruhi oleh masalah "cold start" yang terkenal buruk. Dalam kasus platform cloud Webiny, situasinya bahkan lebih buruk. Fitur AWS Lambda adalah bagian dari VPC (Virtual Private Cloud). Ini berarti bahwa untuk setiap instance baru dari fungsi tersebut, Anda perlu menginisialisasi ENI (Antarmuka Jaringan Elastis, antarmuka jaringan elastis). Ini secara signifikan meningkatkan waktu mulai dingin fungsi.
Berikut adalah beberapa jadwal untuk memuat fitur AWS Lambda di dalam VPC dan di luar VPC.
Analisis beban fungsi AWS Lambda di dalam VPC dan di luar VPC (gambar diambil dari sini )Dari sini kita dapat menyimpulkan bahwa dalam kasus ketika fungsi diluncurkan di dalam VPC, ini memberikan peningkatan 10 kali lipat pada waktu mulai dingin.
Selain itu, di sini satu faktor lagi harus diperhitungkan - keterlambatan pengiriman data jaringan. Durasi mereka sudah termasuk pada waktu yang diperlukan untuk menjalankan permintaan API. Permintaan dimulai oleh browser. Oleh karena itu, ternyata pada saat API merespons permintaan ini, waktu yang diperlukan untuk mendapatkan permintaan dari browser ke API, dan waktu yang diperlukan untuk mendapatkan tanggapan dari API ke browser ditambahkan. Penundaan ini terjadi selama setiap permintaan.
Tugas optimasi
Berdasarkan analisis di atas, kami merumuskan beberapa tugas yang perlu kami selesaikan untuk mengoptimalkan proyek. Inilah mereka:
- Meningkatkan kecepatan mengeksekusi permintaan API atau mengurangi jumlah permintaan API yang memblokir rendering.
- Mengurangi ukuran bundel JS atau mengonversi bundel ini ke sumber daya yang tidak diperlukan untuk output halaman.
- Membuka kunci utas.
Pendekatan Masalah
Berikut adalah beberapa pendekatan untuk menyelesaikan masalah yang kami pertimbangkan:
- Optimasi kode dengan maksud untuk mempercepat pelaksanaannya. Pendekatan ini membutuhkan banyak usaha, ia memiliki biaya tinggi. Manfaat yang dapat diperoleh sebagai hasil dari optimasi tersebut diragukan.
- Tingkatkan jumlah RAM yang tersedia untuk fitur AWS Lambda. Mudah dilakukan, biaya solusi semacam itu ada di antara menengah dan tinggi. Hanya efek positif kecil yang dapat diharapkan dari penerapan solusi ini.
- Penggunaan beberapa cara lain untuk menyelesaikan masalah. Benar, pada saat itu kami belum tahu apa metode ini.
Pada akhirnya, kami memilih item ketiga dalam daftar ini. Kami beralasan seperti ini: “Bagaimana jika kami benar-benar tidak memerlukan panggilan API? Bagaimana jika kita dapat melakukannya tanpa bundel JS sama sekali? Ini akan memungkinkan kami untuk menyelesaikan semua masalah proyek. "
Gagasan pertama yang kami temukan menarik adalah membuat snapshot HTML dari halaman yang diberikan dan membagikan snapshot dengan pengguna.
Usaha yang gagal
Webiny Cloud adalah infrastruktur tanpa server berbasis AWS Lambda yang mendukung situs Webiny. Sistem kami dapat mendeteksi bot. Ketika ternyata permintaan itu diselesaikan oleh bot, permintaan ini dialihkan ke instance
Puppeteer , yang menjadikan halaman menggunakan Chrome tanpa antarmuka pengguna. Kode HTML yang sudah jadi dari halaman dikirim ke bot. Ini dilakukan terutama karena alasan SEO, karena fakta bahwa banyak bot tidak tahu cara menjalankan JavaScript. Kami memutuskan untuk menggunakan pendekatan yang sama untuk menyiapkan halaman yang ditujukan untuk pengguna biasa.
Pendekatan ini bekerja dengan baik di lingkungan yang tidak memiliki dukungan JavaScript. Namun, jika Anda mencoba untuk memberikan halaman yang diberikan sebelumnya kepada klien yang browsernya mendukung JS, halaman ditampilkan, tetapi kemudian, setelah mengunduh file JS, komponen Bereaksi tidak tahu di mana harus memasangnya. Ini menghasilkan sejumlah pesan kesalahan di konsol. Akibatnya, keputusan seperti itu tidak cocok untuk kita.
Memperkenalkan SSR
Sisi kuat dari Server Side Rendering (SSR) adalah bahwa semua permintaan API dieksekusi dalam jaringan lokal. Karena mereka diproses oleh sistem atau fungsi tertentu yang berjalan di dalam VPC, penundaan yang terjadi saat menjalankan permintaan dari browser ke backend sumber daya tidak seperti biasanya. Meskipun dalam skenario ini, masalah "mulai dingin" tetap.
Keuntungan tambahan menggunakan SSR adalah bahwa kami memberikan klien versi HTML halaman tersebut, ketika bekerja dengan itu, setelah memuat file JS, komponen Bereaksi tidak memiliki masalah dengan pemasangan.
Dan akhirnya, kita tidak membutuhkan bundel JS yang sangat besar. Selain itu, kita dapat melakukannya tanpa panggilan API untuk menampilkan halaman. Bundel dapat dimuat secara tidak sinkron dan ini tidak akan memblokir utas.
Secara umum, kita dapat mengatakan bahwa rendering server, tampaknya, seharusnya telah menyelesaikan sebagian besar masalah kita.
Ini adalah bagaimana analisis situs terlihat setelah menerapkan rendering sisi server.
Metrik situs setelah menerapkan rendering serverSekarang permintaan API tidak dieksekusi, dan halaman dapat dilihat sebelum bundel JS besar dimuat. Tetapi jika Anda melihat dekat pada permintaan pertama, Anda dapat melihat bahwa dibutuhkan hampir 2 detik untuk mendapatkan dokumen dari server. Mari kita bicarakan.
Masalah dengan TTFB
Di sini kita membahas metrik TTFB (Time To First Byte, time to byte pertama). Berikut ini rincian permintaan pertama.
Detail Permintaan PertamaUntuk memproses permintaan pertama ini, kita perlu melakukan hal berikut: meluncurkan server Node.js, melakukan rendering server, membuat permintaan API dan mengeksekusi kode JS, dan kemudian mengembalikan hasil akhir ke klien. Masalahnya di sini adalah bahwa semua ini, rata-rata, membutuhkan waktu 1-2 detik.
Server kami, yang melakukan rendering server, perlu melakukan semua pekerjaan ini, dan hanya setelah itu ia akan dapat mengirimkan byte pertama dari respons ke klien. Ini mengarah pada fakta bahwa browser memiliki waktu yang sangat lama untuk menunggu dimulainya respons terhadap permintaan. Sebagai hasilnya, ternyata sekarang untuk output halaman yang Anda butuhkan untuk menghasilkan jumlah pekerjaan yang hampir sama seperti sebelumnya. Satu-satunya perbedaan adalah bahwa pekerjaan ini dilakukan bukan pada sisi klien, tetapi pada server, dalam proses rendering server.
Di sini Anda mungkin memiliki pertanyaan tentang kata "server". Kami telah berbicara tentang sistem tanpa server selama ini. Dari mana "server" ini berasal? Kami, tentu saja, mencoba membuat rendering server dalam fungsi AWS Lambda. Tetapi ternyata ini adalah proses yang sangat memakan sumber daya (khususnya, itu perlu untuk meningkatkan jumlah memori sangat banyak untuk mendapatkan lebih banyak sumber daya prosesor). Selain itu, masalah "mulai dingin", yang telah kami sebutkan, juga ditambahkan di sini. Akibatnya, maka solusi ideal adalah menggunakan server Node.js yang akan memuat materi situs dan terlibat dalam rendering sisi server dari mereka.
Mari kita kembali ke konsekuensi menggunakan rendering sisi server. Lihatlah storyboard berikut. Mudah untuk melihat bahwa itu tidak jauh berbeda dari apa yang diperoleh dalam studi proyek, yang diberikan pada klien.
Pemuatan halaman saat menggunakan rendering sisi serverPengguna dipaksa untuk melihat halaman kosong selama 2,5 detik. Ini menyedihkan.
Walaupun melihat hasil-hasil ini, orang mungkin berpikir bahwa kita sama sekali tidak mencapai apa-apa, ini sebenarnya tidak begitu. Kami memiliki snapshot HTML dari halaman yang berisi semua yang kami butuhkan. Tembakan ini siap bekerja dengan Bereaksi. Pada saat yang sama, selama pemrosesan halaman pada klien, tidak perlu melakukan permintaan API apa pun. Semua data yang diperlukan sudah tertanam dalam HTML.
Satu-satunya masalah adalah pembuatan snapshot HTML ini memakan waktu terlalu lama. Pada titik ini, kami dapat menginvestasikan lebih banyak waktu untuk mengoptimalkan rendering server, atau cukup men-cache hasil-hasilnya dan memberi klien snapshot halaman dari sesuatu seperti cache Redis. Kami melakukan hal itu.
Caching hasil rendering server
Setelah pengguna mengunjungi situs Webiny, pertama-tama kami memeriksa cache Redis yang terpusat untuk melihat apakah ada snapshot HTML halaman tersebut. Jika demikian, kami memberikan pengguna sebuah halaman dari cache. Rata-rata, ini menurunkan TTFB menjadi 200-400 ms. Itu setelah pengenalan cache bahwa kami mulai melihat peningkatan yang signifikan dalam kinerja proyek.
Pemuatan halaman saat menggunakan rendering sisi server dan cacheBahkan pengguna yang mengunjungi situs untuk pertama kalinya, melihat konten halaman dalam waktu kurang dari satu detik.
Mari kita lihat bagaimana tampilan diagram air terjun.
Metrik situs setelah menerapkan rendering dan caching sisi serverGaris merah menunjukkan cap waktu 800 ms. Di sinilah konten halaman dimuat sepenuhnya. Selain itu, di sini Anda dapat melihat bahwa bundel JS dimuat sekitar 1,3 detik. Tetapi ini tidak mempengaruhi waktu yang dibutuhkan pengguna untuk melihat halaman. Pada saat yang sama, Anda tidak perlu melakukan panggilan API dan memuat utas utama untuk menampilkan halaman.
Perhatikan fakta bahwa indikator sementara tentang memuat bundel JS, menjalankan permintaan API, dan melakukan operasi di utas utama masih memainkan peran penting dalam mempersiapkan halaman untuk bekerja. Investasi waktu dan sumber daya ini diperlukan agar halaman menjadi "interaktif". Tapi ini tidak memainkan peran apa pun, pertama, untuk bot mesin pencari, dan kedua, untuk menciptakan perasaan "pemuatan halaman cepat" di antara pengguna.
Misalkan halaman adalah "dinamis". Misalnya, ini menampilkan tautan di header untuk mengakses akun pengguna jika pengguna yang melihat halaman tersebut masuk. Setelah rendering sisi server, halaman tujuan umum akan dikirim ke browser. Yaitu - yang ditampilkan kepada pengguna yang tidak masuk. Judul halaman ini akan berubah, yang mencerminkan fakta bahwa pengguna masuk, hanya setelah bundel JS dimuat dan panggilan API dilakukan. Di sini kita berurusan dengan indikator
TTI (Waktu Ke Interaktif, waktu untuk interaktivitas pertama).
Setelah beberapa minggu, kami menemukan bahwa server proxy kami tidak menutup koneksi dengan klien jika diperlukan, jika rendering server diluncurkan sebagai proses latar belakang. Koreksi satu baris kode menyebabkan fakta bahwa indikator TTFB berkurang hingga level 50-90 ms. Akibatnya, situs tersebut sekarang mulai ditampilkan di browser setelah sekitar 600 ms.
Namun, kami menghadapi masalah lain ...
Masalah pembatalan cache
"Dalam ilmu komputer, hanya ada dua hal kompleks: pembatalan cache dan penamaan entitas."
Phil CarletonPembatalan cache, memang, tugas yang sangat sulit. Bagaimana cara mengatasinya? Pertama, Anda sering dapat memperbarui cache dengan mengatur waktu penyimpanan yang sangat singkat untuk objek yang di-cache (TTL, Time To Live, Seumur Hidup). Ini terkadang menyebabkan halaman dimuat lebih lambat dari biasanya. Kedua, Anda dapat membuat mekanisme pembatalan cache berdasarkan peristiwa tertentu.
Dalam kasus kami, masalah ini diselesaikan menggunakan TTL yang sangat kecil 30 detik. Tetapi kami juga menyadari kemungkinan menyediakan data usang dari cache kepada klien. Pada saat klien menerima data seperti itu, cache sedang diperbarui di latar belakang. Berkat ini, kami menyingkirkan masalah, seperti penundaan dan "mulai dingin", yang khas untuk fungsi AWS Lambda.
Begini cara kerjanya. Seorang pengguna mengunjungi situs web Webiny. Kami sedang memeriksa cache HTML. Jika ada tangkapan layar halaman, kami berikan kepada pengguna. Usia gambar bahkan bisa beberapa hari. Dengan mengirimkan snapshot lama ini kepada pengguna dalam beberapa ratus milidetik, kami secara bersamaan meluncurkan tugas untuk membuat snapshot baru dan memperbarui cache. Biasanya diperlukan beberapa detik untuk menyelesaikan tugas ini, karena kami menciptakan mekanisme berkat itu kami selalu memiliki sejumlah fungsi AWS Lambda yang sudah berjalan dan siap untuk bekerja. Oleh karena itu, kita tidak harus, selama pembuatan gambar baru, menghabiskan waktu di awal dingin fungsi.
Akibatnya, kami selalu mengembalikan halaman dari cache ke klien, dan ketika usia data yang di-cache mencapai 30 detik, konten cache diperbarui.
Caching jelas merupakan area di mana kita masih bisa meningkatkan sesuatu. Misalnya, kami mempertimbangkan kemungkinan memperbarui cache secara otomatis ketika pengguna menerbitkan halaman. Namun, mekanisme pembaruan cache seperti itu juga tidak ideal.
Misalnya, beranda beranda sumber daya menampilkan tiga posting blog terbaru. Jika cache diperbarui ketika halaman baru diterbitkan, maka, dari sudut pandang teknis, hanya cache untuk halaman baru ini yang akan dihasilkan setelah publikasi. Tembolok untuk halaman rumah akan kedaluwarsa.
Kami masih mencari cara untuk meningkatkan sistem caching proyek kami. Namun sejauh ini, fokusnya adalah memilah masalah kinerja yang ada. Kami percaya bahwa kami telah melakukan pekerjaan yang cukup baik dalam hal menyelesaikan masalah ini.
Ringkasan
Pada awalnya, kami menggunakan rendering sisi klien. Kemudian, secara rata-rata, pengguna dapat melihat halaman dalam 3,3 detik. Sekarang, angka ini telah turun menjadi sekitar 600 ms. Penting juga bahwa kita sekarang membuang indikator pengunduhan.
Untuk mencapai hasil ini, kami diizinkan, terutama, menggunakan rendering server. Tetapi tanpa sistem caching yang baik, ternyata perhitungannya hanya berpindah dari klien ke server. Dan ini mengarah pada fakta bahwa waktu yang diperlukan bagi pengguna untuk melihat halaman tidak banyak berubah.
Penggunaan rendering server memiliki kualitas positif lain, tidak disebutkan sebelumnya. Kita berbicara tentang fakta bahwa membuatnya lebih mudah untuk melihat halaman pada perangkat seluler yang lemah. Kecepatan menyiapkan halaman untuk dilihat pada perangkat semacam itu tergantung pada kemampuan sederhana dari prosesor mereka. Server rendering memungkinkan Anda untuk menghapus sebagian dari beban dari mereka. Perlu dicatat bahwa kami tidak melakukan studi khusus tentang masalah ini, tetapi sistem yang kami miliki harus membantu meningkatkan tampilan situs di ponsel dan tablet.
Secara umum, kita dapat mengatakan bahwa implementasi rendering server bukanlah tugas yang mudah. Dan fakta bahwa kami menggunakan lingkungan tanpa server hanya mempersulit tugas ini. Solusi untuk masalah kami memerlukan perubahan kode, infrastruktur tambahan. Kami perlu membuat mekanisme caching yang dirancang dengan baik. Tetapi sebagai gantinya, kami mendapat banyak hal baik. Yang paling penting adalah halaman-halaman situs kami sekarang memuat dan bersiap-siap untuk bekerja lebih cepat dari sebelumnya. Kami percaya pengguna kami akan menyukainya.
Pembaca yang budiman! Apakah Anda menggunakan teknologi caching dan rendering server untuk mengoptimalkan proyek Anda?
