Kebocoran Memori Sisi Server Nuxt Menggunakan SSR (Server Side Rendering)

Halo, Habr! Artikel ini harus dibaca untuk siapa saja yang bekerja dengan Vue SSR, khususnya dengan Nuxt . Ini tentang kebocoran memori saat menggunakan aksioma .

Latar Belakang


Setengah tahun yang lalu, saya mendapatkan proyek dengan tumpukan VueJS + Nuxt, kekhasannya adalah bahwa server Nod (Nuxt) terus-menerus sekarat di prod dan yang baru naik di tempat mereka. Dari grafik dan log, jelas bahwa operasi proses simpul mencapai 100% dan jatuh dengan kesalahan memori. Pada saat ini, yang baru naik ke tempat proses terbunuh, yang memakan waktu sekitar 30 detik, ini cukup bagi pengguna untuk mendapatkan kesalahan 502. Jelas, di suatu tempat dalam kode ada kebocoran memori yang perlu ditemukan.

Saya ingin langsung menyoroti poin-poin penting, karena hanya membaca sebagian dari artikel ini mungkin tidak menjawab semua pertanyaan Anda:

  1. Relevansi topik
  2. Pencegat aksioma
  3. jalankanInNewContext

1. Relevansi topik


Hal pertama, karena banyak dari kita akan lakukan, saya mulai mencari solusi di internet, pertanyaan saya tampak seperti ini: kebocoran memori NodeJS , kebocoran memori nuxt , kebocoran memori nuxt dalam produksi , dll

Tentu saja, tidak satu pun dari dua puluh masalah pada stackoverflow membantu saya, tetapi saya belajar cara melacak penggunaan memori melalui chrome: // inspect. Untuk kekecewaan saya, saya menemukan bahwa 90% dari semua memori yang karena alasan tertentu tidak dibersihkan adalah beberapa fungsi Vue seperti renderComponent, renderElement, dan lainnya.



1. Axios Interceptors


Kami dengan cepat melewati siksaan saya untuk mencari masalah dan segera melanjutkan ke fakta bahwa aksioma. Pelaku disalahkan atas segalanya (Maaf, Habr, karena menemukan yang bersalah).

Segera buat reservasi bahwa aksioma dibuat seperti ini:

import baseAxios from 'axios';

const axios = baseAxios.create({
  timeout: 10000,
});


export default axios;

Dan melekat pada konteks aplikasi seperti ini:

import axios from './index';

export default function(context) {

  if(!context.axios) {
    context.axios = axios;
  }
}

  • Setelah lama mencari kebocoran, saya menemukan bahwa jika Anda menonaktifkan semua axios.interceptors, maka memori mulai dibersihkan.
  • Apa masalahnya?
  • interseptor adalah proxy yang memotong semua respons atau permintaan dan memungkinkan Anda untuk mengeksekusi kode apa pun dengan jawaban (misalnya, untuk menangani kesalahan) atau menambahkan sesuatu sebelum mengirim permintaan secara global untuk semua permintaan dan di satu tempat, nyaman, bukan? Berikut adalah contoh tampilannya (file 'plugins / axios / interceptor.js')

export default function({ axios }) {

  const interceptor = axios.interceptors.response.use( (response) => {
    return response;
  }, function (error) {
    //-   ,  
    return Promise.reject(error);
  });

}

Dan di sini kesenangan dimulai. Kami menambahkan fungsi menambahkan interseptor melalui plugin di nuxt.config.js

  plugins: [
    { src: '~/plugins/axios/bindContext' },
    { src: '~/plugins/axios/interceptor' },
  ]

dan nuxt secara otomatis untuk setiap permintaan baru melakukan semua fungsi plugin, kemudian melakukan nuxtServerInit dan kemudian semuanya seperti biasa. Artinya, untuk pengguna pertama, kami membuat pencegat di sisi server, di suatu tempat di komponen kami di asyncData atau dalam menjemput kami membuat permintaan, dan pencegat berfungsi sebagaimana mestinya, maka pengguna kedua masuk dan kami membuat pencegat kedua dan kode di dalam fungsi akan bekerja 2 kali!

Untuk pemahaman yang lebih baik dari kata-kata saya, saya akan menggambar penghitung yang menambah setiap kali fungsi dipanggil dan mengetuk indeks 5 kali.



Kita dapat melihat bahwa 15 panggilan telah terjadi, dan ini adalah 1 + 2 + 3 + 4 + 5, saya juga meluangkan waktu untuk membuat pencegat berikutnya untuk memastikan bahwa ada tantangan dari mereka yang diciptakan sebelumnya.

Dari sekolah, kita semua ingat rumus perkembangan aritmatika dengan baik, dan jumlah dari 1 ke n dapat ditulis sebagai n * (n + 1) / 2. Ternyata ketika pengguna ke-1000 masuk, fungsi kita akan dipanggil 1000 kali, dan secara total ini sudah setengah juta panggilan, jadi jika bebannya sedang atau tinggi, maka jangan kaget jika server Anda mogok.

Solusi untuk masalah tersebut


UPD. Solusi # 0 - Komentar menggambarkan solusi yang baik untuk masalah ini.

Solusi # 1 - Jangan gunakan axios.interceptors.

Solusi No. 2 - Semuanya sangat sederhana, Anda perlu membersihkan pencegat untuk diri sendiri, dipandu oleh dokumentasi aksioma

export default function({ axios }) {

  const interceptor = axios.interceptors.response.use( (response) => {
    
    if(process.server) {
      axios.interceptors.response.eject(interceptor);
    }
    
    return response;
  }, function (error) {
    if(process.server) {
      axios.interceptors.response.eject(interceptor);
    }
    
    return Promise.reject(error);
  });

}

Ini perlu dilakukan hanya di sisi server, karena jika tidak di sisi klien, setelah berhasil mengeksekusi permintaan pertama, pencegat ini akan berhenti mengeksekusi. Ada satu lagi nuansa dengan fakta bahwa sementara kita masih di server dan sedang memproses permintaan pengguna berikutnya, tetapi mungkin ada beberapa, tetapi beberapa permintaan, kemudian dengan mengeluarkan pencegat ini, semua permintaan kecuali yang pertama tidak akan melewatinya, dalam hal ini untuk secara mandiri memikirkan saat di mana Anda perlu melakukan eject, cara termudah untuk melakukan ini adalah melalui setTimeout, misalnya, setelah 10 detik, maka kita dapat mengasumsikan bahwa di sisi server kita akan berhasil menyelesaikan semua permintaan untuk pengguna saat ini dan semuanya akan dieksekusi selama waktu ini, ketika pencegat masih akan aktif.

jalankanInNewContext


Ini adalah pilihan yang sangat lucu, karena bug ini tidak dapat diputar secara lokal, tetapi sangat mudah untuk dimainkan di build. Baca tentang ini di sini . Ketika saya bersiap-siap untuk menulis artikel ini, saya membuat proyek nuxt starter-template untuk mereproduksi masalah ini, dan betapa saya terkejut bahwa untuk setiap pengguna biasa - interceptor dieksekusi 1 kali, dan bukan n. Masalahnya adalah, ketika kita menulis npm run dev - opsi ini benar secara default, dan setiap kali kita melakukan fungsi dari plugin di sisi server, konteksnya baru setiap kali (jelas dari nama bendera), dan itu secara otomatis dilakukan dalam build false untuk kinerja yang lebih baik di prod, jadi saya harus menonaktifkan opsi ini di nuxt.config.js


render: {
    bundleRenderer: {
      runInNewContext: false,
    },
  },

Kesimpulan


Bagi saya, masalah ini sangat serius, dan perlu diperhatikan secara khusus. Mungkin masalah ini tidak hanya menyangkut Vue ssr, tetapi juga yang lain, dan tidak hanya aksioma, tetapi juga klien HTTP lain yang memiliki proxy yang mirip dengan pencegat. Jika Anda memiliki pertanyaan, Anda dapat menulis kepada saya di Telegram @alexander_proydenko . Semua kode yang digunakan dalam artikel dapat dilihat di github di sini .

All Articles