Redis Streaming sebagai Struktur Data Bersih

Struktur data Redis 5 yang baru, yang disebut stream, telah menarik minat masyarakat. Entah bagaimana, saya akan berbicara dengan mereka yang menggunakan aliran dalam produksi dan menulis tentang itu. Tapi sekarang saya ingin mempertimbangkan topik yang sedikit berbeda. Bagi saya mulai terlihat bahwa banyak orang menganggap stream sebagai semacam alat surealis untuk menyelesaikan tugas yang sangat sulit. Memang, struktur data ini * juga * menyediakan perpesanan, tetapi akan menjadi penyederhanaan yang luar biasa untuk mengasumsikan bahwa fungsi Redis Streams hanya dibatasi oleh ini.

Streaming adalah template hebat dan "model mental" yang dapat digunakan dengan sukses besar dalam desain sistem, tetapi pada kenyataannya stream, seperti kebanyakan struktur data Redis, adalah struktur yang lebih umum dan dapat digunakan untuk banyak tugas lainnya. Dalam artikel ini, kami akan menyajikan stream sebagai struktur data murni, sepenuhnya mengabaikan operasi pemblokiran, grup penerima, dan semua fungsi pengiriman pesan lainnya.

Streaming - Ini adalah CSV tentang Steroid


Jika Anda ingin merekam sejumlah elemen data terstruktur dan berpikir bahwa database akan berlebih di sini, Anda cukup membuka file dalam mode append only dan menulis setiap baris sebagai CSV (Comma Separated Value):

 (open data.csv in append only) time=1553096724033,cpu_temp=23.4,load=2.3 time=1553096725029,cpu_temp=23.2,load=2.1 

Itu terlihat sederhana. Orang telah melakukan ini sejak lama dan masih melakukannya: ini adalah templat yang andal, jika Anda tahu apa apa. Tapi apa yang akan setara dalam memori? Dalam memori, pemrosesan data yang jauh lebih maju menjadi mungkin, dan banyak pembatasan file CSV dihapus secara otomatis, seperti:

  1. Sulit (tidak efisien) untuk memenuhi berbagai permintaan.
  2. Terlalu banyak informasi yang berlebihan: setiap catatan memiliki waktu yang hampir bersamaan, dan bidangnya digandakan. Pada saat yang sama, menghapus data akan membuat format kurang fleksibel jika saya ingin beralih ke kumpulan bidang yang berbeda.
  3. Offset elemen hanyalah offset byte dalam file: jika kita mengubah struktur file, offset akan menjadi salah, sehingga tidak ada konsep nyata dari pengidentifikasi utama. Entri pada dasarnya tidak dapat disajikan secara jelas.
  4. Tanpa kemampuan untuk mengumpulkan sampah dan tanpa menulis ulang log, Anda tidak dapat menghapus entri, tetapi hanya menandainya sebagai tidak valid. Menulis ulang log biasanya menyebalkan karena beberapa alasan, disarankan untuk menghindarinya.

Pada saat yang sama, log CSV seperti itu bagus dengan caranya sendiri: tidak ada struktur tetap, bidang dapat berubah, itu sepele untuk menghasilkannya dan cukup kompak. Gagasan dengan aliran Redis adalah untuk melestarikan kebajikan, tetapi untuk mengatasi keterbatasan. Hasilnya adalah struktur data hybrid yang sangat mirip dengan set Redis diurutkan: mereka * terlihat seperti * struktur data dasar, tetapi menggunakan beberapa representasi internal untuk mendapatkan efek ini.

Pengantar utas (Anda dapat melewati jika Anda sudah terbiasa dengan dasar-dasar)


Redis stream direpresentasikan sebagai node makro terkompresi-delta yang dihubungkan oleh pohon basis. Akibatnya, Anda dapat dengan cepat mencari catatan acak, mendapatkan rentang, menghapus elemen lama, dll. Pada saat yang sama, antarmuka untuk pemrogram sangat mirip dengan file CSV:

 > XADD mystream * cpu-temp 23.4 load 2.3 "1553097561402-0" > XADD mystream * cpu-temp 23.2 load 2.1 "1553097568315-0" 

Seperti yang Anda lihat dari contoh, perintah XADD secara otomatis menghasilkan dan mengembalikan pengidentifikasi catatan, yang meningkat secara monoton dan terdiri dari dua bagian: <time> - <counter>. Waktu dalam milidetik, dan penghitung bertambah untuk catatan dengan waktu yang sama.

Jadi, abstraksi baru pertama untuk gagasan file CSV dalam mode append only adalah menggunakan asterisk sebagai argumen ID untuk XADD: ini adalah cara kami mendapatkan pengenal catatan dari server secara gratis. Pengidentifikasi ini berguna tidak hanya untuk menunjukkan elemen tertentu dalam aliran, itu juga terkait dengan waktu catatan ditambahkan ke aliran. Bahkan, dengan XRANGE, Anda bisa menjalankan kueri rentang atau mengambil elemen individual:

 > XRANGE mystream 1553097561402-0 1553097561402-0 1) 1) "1553097561402-0" 2) 1) "cpu-temp" 2) "23.4" 3) "load" 4) "2.3" 

Dalam hal ini, saya menggunakan ID yang sama untuk memulai dan mengakhiri rentang untuk mengidentifikasi satu item. Namun, saya dapat menggunakan rentang dan argumen COUNT apa pun untuk membatasi jumlah hasil. Demikian juga, tidak perlu menentukan pengidentifikasi lengkap untuk rentang, saya hanya bisa menggunakan waktu unix untuk mendapatkan elemen dalam rentang waktu tertentu:

 > XRANGE mystream 1553097560000 1553097570000 1) 1) "1553097561402-0" 2) 1) "cpu-temp" 2) "23.4" 3) "load" 4) "2.3" 2) 1) "1553097568315-0" 2) 1) "cpu-temp" 2) "23.2" 3) "load" 4) "2.1" 

Saat ini, tidak perlu menunjukkan kepada Anda fitur API lainnya, ada dokumentasi untuk ini. Untuk saat ini, mari kita fokus pada pola penggunaan ini: XADD untuk menambahkan, XRANGE (dan juga XREAD) untuk mengekstraksi rentang (tergantung pada apa yang ingin Anda lakukan), dan mari kita lihat mengapa aliran sangat kuat untuk menyebutnya struktur data.

Jika Anda ingin tahu lebih banyak tentang stream dan API, pastikan untuk membaca tutorialnya .

Pemain tenis


Beberapa hari yang lalu, seorang teman saya yang mulai mempelajari Redis dan saya mensimulasikan aplikasi untuk melacak lapangan tenis, pemain, dan pertandingan lokal. Cara memodelkan pemain sudah jelas, pemain adalah objek kecil, jadi kita hanya perlu hash dengan kunci seperti player:<id> . Maka Anda akan segera menyadari bahwa Anda membutuhkan cara untuk melacak permainan di klub tenis tertentu. Jika player:1 dan player:2 bermain di antara mereka sendiri dan player:1 menang, kami dapat mengirim catatan berikut ke stream:

 > XADD club:1234.matches * player-a 1 player-b 2 winner 1 "1553254144387-0" 

Operasi sederhana seperti itu memberi kita:

  1. Pengidentifikasi kecocokan unik: ID di arus.
  2. Tidak perlu membuat objek untuk identifikasi kecocokan.
  3. Permintaan rentang gratis untuk pertandingan paging atau menonton pertandingan untuk tanggal dan waktu tertentu.

Sebelum aliran muncul, kami harus membuat set yang diurutkan berdasarkan waktu: elemen-elemen dari set yang diurutkan akan menjadi pengidentifikasi yang cocok, yang disimpan dalam kunci yang berbeda sebagai nilai hash. Ini tidak hanya lebih banyak bekerja, tetapi juga lebih banyak memori. Jauh lebih banyak memori (lihat di bawah).

Sekarang tujuan kami adalah untuk menunjukkan bahwa aliran Redis adalah semacam diurutkan diatur dalam mode append only , dengan kunci berdasarkan waktu, di mana setiap elemen adalah hash kecil. Dan dalam kesederhanaannya, ini adalah revolusi nyata dalam konteks pemodelan.

Memori


Kasus penggunaan di atas bukan hanya pola pemrograman yang lebih kohesif. Konsumsi memori dalam utas sangat berbeda dari pendekatan lama dengan set + hash yang diurutkan untuk setiap objek sehingga beberapa hal sekarang mulai berfungsi yang sebelumnya tidak mungkin diimplementasikan sama sekali.

Berikut ini adalah statistik jumlah memori untuk menyimpan sejuta pertandingan dalam konfigurasi yang disajikan sebelumnya:

   +  = 220  (242 RSS)  = 16,8  (18.11 RSS) 

Perbedaannya lebih dari urutan besarnya (yaitu, 13 kali). Ini berarti bisa bekerja dengan tugas-tugas yang sebelumnya terlalu mahal untuk dilakukan dalam memori. Sekarang mereka cukup layak. Keajaibannya adalah memperkenalkan aliran Redis: node makro dapat berisi beberapa elemen yang sangat kompak dikodekan dalam struktur data yang disebut listpack. Struktur ini akan menangani, misalnya, pengkodean bilangan bulat dalam bentuk biner, bahkan jika mereka semantik string. Selain itu, kami menerapkan kompresi delta dan mengompres bidang yang sama. Namun, tetap dimungkinkan untuk mencari berdasarkan ID atau waktu, karena node makro tersebut ditautkan dalam struktur dasar, yang juga dirancang dengan optimasi memori. Bersama-sama, ini menjelaskan penggunaan memori yang ekonomis, tetapi bagian yang menarik adalah bahwa secara semantik pengguna tidak melihat detail implementasi yang membuat utas jadi efisien.

Sekarang mari kita hitung. Jika saya dapat menyimpan 1 juta catatan dalam memori sekitar 18 MB, maka saya dapat menyimpan 10 juta dalam 180 MB dan 100 juta dalam 1,8 GB. Hanya dengan memori 18 GB, saya dapat memiliki 1 miliar item.

Seri waktu


Penting untuk dicatat bahwa contoh di atas dengan pertandingan tenis secara semantik * sangat berbeda * dari menggunakan aliran Redis untuk deret waktu. Ya, secara logis kami masih mendaftarkan beberapa jenis acara, tetapi ada perbedaan mendasar. Dalam kasus pertama, kami mencatat dan membuat catatan untuk merender objek. Dan dalam deret waktu, kita cukup mengukur sesuatu yang terjadi di luar yang sebenarnya tidak mewakili objek. Anda dapat mengatakan bahwa perbedaan ini sepele, tetapi tidak. Penting untuk memahami gagasan bahwa utas Redis dapat digunakan untuk membuat objek kecil dengan urutan yang sama dan untuk menetapkan pengidentifikasi ke objek tersebut.

Tetapi bahkan cara paling sederhana untuk menggunakan seri waktu jelas merupakan terobosan besar, karena sebelum munculnya utas, Redis praktis tidak berdaya untuk melakukan apa pun di sini. Karakteristik memori dan fleksibilitas aliran, serta kemampuan untuk membatasi aliran yang dibatasi (lihat parameter XADD) adalah alat yang sangat penting di tangan pengembang.

Kesimpulan


Streaming fleksibel dan menawarkan banyak kasus penggunaan, tetapi saya ingin menulis artikel yang sangat singkat untuk menunjukkan dengan jelas contoh dan konsumsi memori. Mungkin bagi banyak pembaca penggunaan aliran ini jelas. Namun, percakapan dengan pengembang dalam beberapa bulan terakhir telah meninggalkan saya dengan kesan bahwa banyak yang memiliki hubungan yang kuat antara stream dan streaming data, seolah-olah struktur data hanya baik di sana. Ini tidak benar. :-)

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


All Articles