Halo, nama saya Dmitry Karlovsky dan saya ... tidak suka membaca buku, karena ketika Anda membalik halaman, Anda keluar dari cerita yang menarik. Dan perlu sedikit keraguan ketika Anda lupa apa kalimat terakhir dari halaman sebelumnya berakhir, dan Anda harus menggulir ke belakang untuk membacanya kembali. Dan jika itu tidak begitu menakutkan dengan buku-buku fisik, maka dengan penerbitan server istirahat semuanya menjadi lebih sedih - lagi pula, sekarang ada beberapa data di halaman, dan setelah sedetik itu benar-benar berbeda. Mari kita pikirkan bagaimana itu terjadi, siapa yang harus disalahkan dan yang paling penting - apa yang harus dilakukan.

Masalah
Jadi, kita perlu mengeluarkan semua pesan untuk kueri "pagination", dimulai dengan yang terbaru (yang terakhir diubah dari atas ) atau dalam urutan yang rumit. Semuanya baik-baik saja, selama kami memiliki kurang dari seratus pesan ini - kami hanya membuat pilihan dari database dan mengembalikan data:
Permintaan dari pelanggan:
GET /message/text=/
Permintaan basis data:
SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC
Skema respons JSON untuk klien:
Array<{ id : number , text : string }>
Tetapi jumlah pesan terus bertambah dan kami memiliki masalah berikut:
- Kueri basis data menjadi lebih lambat karena lebih banyak data harus diambil.
- Mengirim data melalui jaringan membutuhkan lebih banyak waktu.
- Render data ini pada klien semakin lama.
Mulai dari batas tertentu, penundaan menjadi sangat signifikan sehingga tidak mungkin untuk menggunakan situs kami. Jika, tentu saja, dia belum berbaring, bosan dengan sejumlah besar permintaan paralel paralel.
Solusi paling sederhana, yang mungkin pertama kali terlintas dalam pikiran, dan Anda bisa menemuinya sekarang di pemanggang apa pun - untuk memberikan data tidak semuanya secara massal, tetapi dipecah menjadi beberapa halaman. Yang perlu kita lakukan hanyalah membuang satu parameter tambahan dari klien ke dalam permintaan basis data:
GET /message/text=/page=5/
SELECT FROM Message WHERE text LICENE "" ORDER BY changed DESC SKIP 5 * 10 LIMIT 10
SELECT count(*) FROM Message WHERE text LICENE ""
{ pageItems : Array<{ id : number , text : string }> totalCount : number }
Ya, kami masih harus menghitung ulang semua pesan sehingga klien dapat menggambar daftar halaman atau menghitung ketinggian gulir virtual, tetapi setidaknya kami tidak perlu mendapatkan semua 100500 pesan ini dari database.
Dan semuanya akan baik-baik saja jika kita memiliki semacam forum yang tidak populer untuk waktu yang lama bukan topik yang relevan. Tetapi mereka menulis dan menulis kepada kami, menulis dan menulis, dan sementara pengguna membaca halaman kelima, daftar pesan berubah melebihi pengakuan: yang baru ditambahkan dan yang lama dihapus. Dengan demikian, kami mendapatkan dua jenis masalah dari sudut pandang pengguna:
- Pada halaman berikutnya, pesan mungkin muncul lagi yang sudah ada di sebelumnya.
- Pengguna tidak akan melihat beberapa pesan sama sekali, karena mereka berhasil berpindah dari halaman 6 ke 5 persis di antara transisi pengguna dari 5 ke 6.
Selain itu, kami masih memiliki masalah kinerja. Setiap transisi ke halaman berikutnya mengarah pada fakta bahwa kita perlu melakukan sebanyak dua permintaan pencarian dalam database dengan semakin banyak elemen yang dilewati dari halaman sebelumnya.
Ya, dan implementasi yang kompeten di sisi klien tidak sesederhana itu - Anda harus selalu siap dengan kenyataan bahwa setiap respons server dapat mengembalikan jumlah total pesan yang baru, yang berarti kita perlu menggambar ulang paginator dan mengalihkan ke halaman lain jika yang sekarang tiba-tiba kosong Dan tentu saja Anda tidak dapat jatuh dalam kasus duplikat.
Selain itu, terkadang klien perlu memperbarui hasil pencarian, tetapi pemuatan masih akan menerima data yang mungkin sudah ada dari permintaan sebelumnya.
Seperti yang Anda lihat, pagination memiliki banyak masalah. Apakah benar-benar tidak ada solusi yang lebih baik?
Solusi
Pertama, mari kita perhatikan bahwa ketika bekerja dengan database ada 2 operasi yang pada dasarnya berbeda:
- Cari Operasi yang relatif berat untuk menemukan pointer ke data untuk beberapa permintaan.
- Sampling. Operasi yang relatif sederhana untuk mendapatkan data.
Itu akan ideal:
- Setelah mencari dan di suatu tempat untuk mengingat hasilnya dalam bentuk foto pada titik waktu tertentu.
- Pilih data dengan cepat dalam porsi kecil sesuai kebutuhan.
Di mana menyimpan snapshot? ada 2 opsi:
- Di server. Tapi kemudian kita menyumbatnya dengan banyak sampah dengan hasil pencarian yang perlu dibersihkan dari waktu ke waktu.
- Kepada klien. Tetapi kemudian Anda harus segera mentransfer semua snapshot ke klien.
Mari kita perkirakan ukuran foto itu, yang hanya merupakan daftar pengidentifikasi. Sangat diragukan bahwa pengguna memiliki kesabaran untuk menggulung setidaknya 100 halaman tanpa menggunakan pemfilteran dan penyortiran. Katakanlah kita memiliki 20 elemen per halaman. Setiap pengidentifikasi akan menempati tidak lebih dari 10 byte dalam representasi json. Lipat gandakan dan dapatkan tidak lebih dari 20kb. Dan kemungkinan besar jauh lebih sedikit. Akan masuk akal untuk menetapkan batas keras pada ukuran output di, katakanlah, 1000 elemen.
GET /message/text=/
SELECT id FROM Message WHERE text LICENE "" ORDER BY changed DESC LIMIT 1000
Array<number>
Sekarang klien dapat menggambar setidaknya paginator, setidaknya gulir virtual, meminta data hanya untuk pengidentifikasi yang menarik baginya.
GET /message=49,48,47,46,45,42,41,40,39,37/
SELECT FROM Message WHERE id IN [49,48,47,46,45,42,41,40,39,37]
Array< { id : number , text : string } | { id : number , error : string } >
Apa yang akhirnya kita dapatkan:
- API yang dinormalisasi: cari secara terpisah, pilih data secara terpisah.
- Minimalkan jumlah permintaan pencarian.
- Anda tidak dapat meminta data yang sudah diunduh, atau memperbaruinya di latar belakang.
- Kode yang relatif sederhana dan universal di sisi klien.
Dari kekurangannya, orang hanya dapat mencatat:
- Untuk menunjukkan sesuatu, pengguna harus membuat setidaknya 2 permintaan berturut-turut.
- Penting untuk menangani kasus ketika pengidentifikasi, dan data tentang itu tidak lagi tersedia.