Plugin memcached: NoSQL di MySQL



Halo Nama saya Maxim Matyukhin, saya seorang programmer PHP di Badoo . Dalam pekerjaan kami, kami secara aktif menggunakan MySQL. Tetapi kadang-kadang kita kurang kinerjanya, jadi kami terus mencari cara untuk mempercepat pekerjaannya.

Pada 2010, Yoshinori Matsunobu memperkenalkan plugin NoSQL MySQL yang disebut HandlerSocket. Diklaim bahwa plugin ini memungkinkan Anda untuk melakukan lebih dari 750.000 permintaan per detik. Kami menjadi ingin tahu, dan hampir segera kami mulai menggunakan solusi ini. Kami sangat menyukai hasilnya sehingga kami mulai membuat presentasi dan menulis artikel yang mempromosikan HandlerSocket.

Rupanya, kami adalah salah satu dari sedikit pengguna plugin ini - karena MySQL 5.7 berhenti bekerja. Namun dalam versi ini, muncul lagi plugin Oracle - plugin memoached InnoDB, yang menjanjikan fungsionalitas serupa.

Terlepas dari kenyataan bahwa plugin memcached muncul kembali di MySQL 5.6 pada 2013, tidak ada begitu banyak artikel tentang itu dan sebagian besar mereka mengulangi dokumentasi: label sederhana dibuat dan permintaan dibuat melalui klien memcached.

Kami memiliki pengalaman luas dengan Memcached dan terbiasa dengan kemudahan berinteraksi dengannya. Dari plugin memoached InnoDB kami mengharapkan kesederhanaan yang sama. Tetapi pada kenyataannya, ternyata jika pola untuk menggunakan plug-in setidaknya sedikit berbeda dari yang dijelaskan dalam dokumentasi dan artikel, banyak nuansa dan batasan muncul, yang pasti layak dipertimbangkan jika Anda akan menggunakan plug-in.

MySQL HandlerSocket


Dalam artikel ini, kita akan membandingkan plugin memcached baru dengan HandlerSocket yang lama. Karena itu, saya ingat itu yang terakhir.

Setelah menginstal plugin HandlerSocket, MySQL mulai mendengarkan dua port tambahan:

  1. Port pertama menerima permintaan klien untuk membaca data.
  2. Port kedua menerima permintaan klien untuk merekam data.

Klien harus membuat koneksi TCP reguler pada salah satu port ini (tidak ada otentikasi yang didukung), dan setelah itu perlu untuk mengirim perintah "indeks terbuka" (perintah khusus yang digunakan klien untuk menginformasikan tabel mana dari indeks mana bidang yang akan kita kunjungi baca (atau tulis)).

Jika perintah "indeks terbuka" bekerja dengan sukses, maka Anda dapat mengirim perintah GET atau INSERT / UPDATE / DELETE tergantung pada port tempat koneksi dibuat.

HandlerSocket diizinkan untuk melakukan tidak hanya GET pada kunci utama, tetapi juga sampel sederhana dari indeks yang tidak unik, sampel jangkauan, multiget yang didukung, dan LIMIT. Pada saat yang sama, dimungkinkan untuk bekerja dengan tabel baik dari SQL biasa, maupun melalui plugin. Ini, misalnya, memungkinkan untuk membuat beberapa perubahan dalam transaksi melalui SQL, dan kemudian membaca data ini melalui HandlerSocket.

Penting bahwa HandlerSocket menangani semua koneksi dengan kumpulan benang terbatas melalui epoll, sehingga mudah untuk mendukung puluhan ribu koneksi, sedangkan di MySQL sendiri utas dibuat untuk setiap koneksi dan jumlah mereka sangat terbatas.

Pada saat yang sama, itu masih merupakan server MySQL biasa - sebuah teknologi yang akrab bagi kita. Kami tahu cara mereplikasi dan memonitornya. Memonitor HandlerSocket sulit karena tidak memberikan metrik khusus; namun, beberapa metrik standar MySQL dan InnoDB berguna.

Tentu saja ada ketidaknyamanan, khususnya, plugin ini tidak mendukung bekerja dengan tipe timestamp. Yah, protokol HandlerSocket lebih sulit dibaca dan karenanya lebih sulit untuk di-debug.

Baca lebih lanjut tentang HandlerSocket di sini . Anda juga dapat menonton salah satu presentasi kami .

Plugin InnoDB memcached


Apa yang ditawarkan plugin memcached baru kepada kami?

Sesuai namanya, idenya adalah menggunakan klien memcached untuk bekerja dengan MySQL dan untuk menerima dan menyimpan data melalui perintah memcached.

Anda dapat membaca tentang kelebihan utama plugin di sini .

Kami paling tertarik pada yang berikut:

  1. Konsumsi CPU rendah.
  2. Data disimpan di InnoDB, yang memberikan jaminan tertentu.
  3. Anda dapat bekerja dengan data baik melalui Memcached dan melalui SQL; mereka dapat direplikasi menggunakan alat bawaan MySQL.

Anda dapat menambahkan nilai plus ke daftar ini sebagai:

  1. Koneksi cepat dan murah. Koneksi MySQL biasa diproses oleh satu utas, dan jumlah utas terbatas, dan dalam plugin memcached, satu utas memproses semua koneksi dalam loop peristiwa.
  2. Kemampuan untuk meminta beberapa kunci dengan satu permintaan GET.
  3. Jika dibandingkan dengan MySQL HandlerSocket, maka dalam plugin memcached Anda tidak perlu menggunakan perintah "Open Table" dan semua operasi baca dan tulis terjadi pada port yang sama.


Rincian lebih lanjut tentang plugin dapat ditemukan di dokumentasi resmi. Bagi kami, halaman yang paling berguna adalah:

  1. InnoDB memcached Architecture .
  2. InnoDB memcached Internal Plugin .

Setelah menginstal plugin, MySQL mulai menerima koneksi pada port 11211 (port memcached standar). Database khusus (skema) innodb_memcache juga muncul, di mana Anda akan mengkonfigurasi akses ke tabel Anda.

Contoh sederhana


Misalkan Anda sudah memiliki tabel yang ingin Anda kerjakan melalui protokol memcached:

CREATE TABLE `auth` (  `email` varchar(96) NOT NULL,  `password` varchar(64) NOT NULL,  `type` varchar(32) NOT NULL DEFAULT '',  PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

dan Anda ingin menerima dan memodifikasi data pada kunci utama.

Anda harus terlebih dahulu menjelaskan korespondensi antara kunci memcached dan tabel SQL di tabel innodb_memcache.containers. Tabel ini terlihat seperti ini (Saya menghapus deskripsi penyandian untuk membuatnya lebih mudah dibaca):

 CREATE TABLE `containers` ( `name` varchar(50) NOT NULL, `db_schema` varchar(250) NOT NULL, `db_table` varchar(250) NOT NULL, `key_columns` varchar(250) NOT NULL, `value_columns` varchar(250) DEFAULT NULL, `flags` varchar(250) NOT NULL DEFAULT '0', `cas_column` varchar(250) DEFAULT NULL, `expire_time_column` varchar(250) DEFAULT NULL, `unique_idx_name_on_key` varchar(250) NOT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT 

Bidang yang paling penting:

  • nama - awalan kunci Memcached Anda;
  • db_schema - nama basis (sirkuit);
  • db_table adalah meja Anda;
  • key_columns - nama bidang dalam tabel yang akan kami cari (biasanya ini adalah kunci utama Anda);
  • value_columns - daftar bidang dari tabel yang akan tersedia untuk plugin memcached;
  • unique_idx_name_on_key adalah indeks yang digunakan untuk mencari (walaupun Anda telah menentukan key_columns, mereka dapat berada dalam indeks yang berbeda dan Anda perlu menentukan indeks secara eksplisit).

Kolom yang tersisa tidak terlalu penting untuk memulai.

Tambahkan deskripsi tabel kami ke innodb_memcache.containers:

 INSERT INTO innodb_memcache.containers SET   name='auth',   db_schema='test',   db_table='auth',   key_columns='email',   value_columns='password|type',   flags='0',   cas_column='0',   expire_time_column='0',   unique_idx_name_on_key='PRIMARY'; 

Dalam contoh ini, name = 'auth' adalah awalan dari kunci memcached kami. Dalam dokumentasi itu sering disebut table_id, dan nanti dalam artikel saya akan menggunakan istilah ini.

Sekarang TELNET terhubung ke plugin memcached dan cobalah untuk menyimpan dan mendapatkan data:

 [21:26:22] maxm@localhost: ~> telnet memchached-mysql.dev 11211 Trying 127.0.0.1... Connected to memchached-mysql.dev. Escape character is '^]'. get @@auth.max@example.com END set @@auth.max@example.com 0 0 10 1234567|89 STORED get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END 

Pertama kami mengirim permintaan GET, itu tidak mengembalikan apa pun kepada kami. Kemudian kami menyimpan data dengan permintaan SET, setelah itu kami mendapatkannya kembali dengan GET.

GET mengembalikan baris berikut: 1234567 | 89. Ini adalah nilai dari kolom "kata sandi" dan "ketik", dipisahkan oleh simbol "|". Bidang dikembalikan dalam urutan seperti yang dijelaskan di innodb_memcache.containers.value_columns.

Mungkin Anda sekarang bertanya-tanya: "Apa yang akan terjadi jika simbol" | "ditemukan di" kata sandi "?" Saya akan membicarakan ini di bawah.

Melalui SQL, data ini juga tersedia:

 MySQL [(none)]> select * from auth where email='max@example.com'; +-----------------+----------+------+ | email      | password | type | +-----------------+----------+------+ | max@example.com | 1234567  | 89 | +-----------------+----------+------+ 1 row in set (0.00 sec) 

Table_id default


Ada juga mode operasi seperti itu:

 get @@auth VALUE @@auth 0 21 test/auth END get max@example.com VALUE max@example.com 0 10 1234567|99 END set ivan@example.com 0 0 10 qwerty|xxx STORED get ivan@example.com VALUE ivan@example.com 0 10 qwerty|xxx END 

Dalam contoh ini, dengan get @@ auth, kami membuat table_id auth sebagai awalan default untuk koneksi ini. Setelah itu, semua kueri berikutnya dapat dilakukan tanpa menentukan table_id.

Sejauh ini, semuanya sederhana dan logis. Tetapi jika Anda mulai mengerti, maka ada banyak nuansa. Saya akan memberi tahu Anda apa yang kami temukan.

Nuansa


Caching tabel innodb_memcache.containers


Plugin memcached membaca tabel innodb_memcache.containers sekali saat startup. Lebih lanjut, jika table_id yang tidak dikenal tiba melalui protokol Memcached, plugin mencarinya di tabel. Oleh karena itu, Anda dapat dengan mudah menambahkan kunci baru (table_id), tetapi jika Anda ingin mengubah pengaturan table_id yang ada, Anda harus memulai kembali plugin memcached:

 mysql> UNINSTALL PLUGIN daemon_memcached; mysql> INSTALL PLUGIN daemon_memcached soname "libmemcached.so"; 

Di antara dua permintaan ini, antarmuka Memcached tidak akan berfungsi. Karena itu, seringkali lebih mudah untuk membuat table_id baru daripada mengubah yang sudah ada dan me-restart plugin.

Merupakan kejutan bagi kami bahwa nuansa penting dari operasi plug-in dijelaskan pada Adaptasi Aplikasi memcached untuk halaman Plugin InnoDB memcached , yang bukan tempat yang sangat logis untuk informasi tersebut.

Tandai, cas_column, expire_time_column


Bidang-bidang ini diperlukan untuk mensimulasikan beberapa fitur Memcached. Dokumentasi untuk mereka tidak konsisten. Sebagian besar contoh di dalamnya menggambarkan bekerja dengan tabel di mana bidang ini. Mungkin ada kekhawatiran bahwa Anda perlu menambahkannya ke tabel Anda (dan ini setidaknya tiga bidang INT). Tapi tidak. Jika Anda tidak memiliki bidang tersebut di tabel dan Anda tidak akan menggunakan fungsionalitas Memcached seperti CAS, kedaluwarsa atau bendera, maka Anda tidak perlu menambahkan bidang ini ke tabel.

Saat mengkonfigurasi tabel di innodb_memcache.containers, Anda harus memasukkan '0' di bidang ini, buat persis garis dengan nol:

 INSERT INTO innodb_memcache.containers SET   name='auth',   db_schema='test',   db_table='auth',   key_columns='email',   value_columns='password|type',   flags='0',   cas_column='0',   expire_time_column='0',   unique_idx_name_on_key='PRIMARY'; 

Sangat menjengkelkan karena cas_column dan expire_time_column memiliki nilai default NULL, dan jika Anda menjalankan INSERT INTO innodb_memcache.containers tanpa menentukan nilai '0' untuk bidang ini, NULL akan disimpan di dalamnya dan awalan memcache ini tidak akan berfungsi.

Tipe data


Dari dokumentasi itu tidak terlalu jelas tipe data apa yang dapat digunakan saat bekerja dengan plugin. Di beberapa tempat dikatakan bahwa plugin hanya dapat bekerja dengan bidang teks (CHAR, VARCHAR, BLOB). Di sini: Menyesuaikan Skema MySQL yang Ada untuk Plugin InnoDB memcached menawarkan untuk menyimpan angka dalam bidang string, dan jika Anda kemudian perlu bekerja dengan bidang angka ini dari SQL, lalu buat VIEW di mana bidang VARCHAR dengan angka akan dikonversi ke bidang INTEGER :

 CREATE VIEW numbers AS SELECT c1 KEY, CAST(c2 AS UNSIGNED INTEGER) val FROM demo_test WHERE c2 BETWEEN '0' and '9999999999'; 

Namun, di beberapa tempat dalam dokumentasi masih tertulis bahwa Anda dapat bekerja dengan angka. Sejauh ini, kami hanya memiliki pengalaman produksi nyata dengan bidang teks, tetapi hasil percobaan menunjukkan bahwa plugin juga berfungsi dengan angka:

 CREATE TABLE `numbers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `counter` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB INSERT INTO innodb_memcache.containers SET name='numbers', db_schema='test', db_table='numbers', key_columns='id', value_columns='counter', flags='0', cas_column='0',expire_time_column='0',unique_idx_name_on_key='PRIMARY'; 

Setelah itu, melalui protokol Memcached:

 get @@numbers.1 END set @@numbers.1 0 0 2 12 STORED get @@numbers.1 VALUE @@numbers.1 0 2 12 END 

Kami melihat bahwa plugin memcached dapat mengembalikan semua tipe data. Tapi dia mengembalikan mereka dalam bentuk di mana mereka berada di InnoDB, jadi, misalnya, dalam kasus timestamp / datetime / float / desimal / JSON, string biner dikembalikan. Tetapi bilangan bulat dikembalikan seperti yang kita lihat melalui SQL.

Multiget


Protokol memcached memungkinkan Anda untuk meminta beberapa kunci dengan satu permintaan:

 get @@numbers.2 @@numbers.1 VALUE @@numbers.2 0 2 12 VALUE @@numbers.1 0 2 13 END 

Fakta bahwa multiget berfungsi sudah bagus. Tetapi ia bekerja dalam kerangka satu table_id:

 get @@auth.ivan@example.com @@numbers.2 VALUE @@auth.ivan@example.com 0 10 qwerty|xxx END 

Poin ini dijelaskan dalam dokumentasi di sini: https://dev.mysql.com/doc/refman/8.0/en/innodb-memcached-multiple-get-range-query.html . Ternyata dalam multiget Anda dapat menentukan table_id hanya untuk kunci pertama, jika semua kunci lain diambil dari table_id default (contoh dari dokumentasi):

 get @@aaa.AA BB VALUE @@aaa.AA 8 12 HELLO, HELLO VALUE BB 10 16 GOODBYE, GOODBYE END 

Dalam contoh ini, kunci kedua diambil dari table_id default. Kita bisa menentukan lebih banyak kunci dari table_id default, dan untuk kunci pertama kita tentukan table_id terpisah, dan ini hanya mungkin dalam kasus kunci pertama.

Kami dapat mengatakan bahwa multiget berfungsi dalam kerangka satu tabel, karena Anda tidak ingin mengandalkan logika seperti itu dalam kode produksi: tidak jelas, mudah untuk melupakannya, membuat kesalahan.

Jika dibandingkan dengan HandlerSocket, maka di sana juga, multiget bekerja di tabel yang sama. Tetapi pembatasan ini tampak alami: klien membuka indeks dalam tabel dan meminta satu atau beberapa nilai darinya. Tetapi ketika bekerja dengan plugin memcached multiget pada beberapa tombol dengan awalan yang berbeda, ini adalah praktik biasa. Dan Anda mengharapkan hal yang sama dari plugin memcached MySQL. Tapi tidak :(

INCR, DEL


Saya sudah memberikan contoh permintaan GET / SET. Pertanyaan INCR dan DEL memiliki fitur. Itu terletak pada kenyataan bahwa mereka hanya bekerja ketika menggunakan table_id default:

 DELETE @@numbers.1 ERROR get @@numbers VALUE @@numbers 0 24 test/numbers END delete 1 DELETED 

Keterbatasan protokol memcached


Memcached memiliki protokol teks, yang memberikan beberapa batasan. Misalnya, kunci memcached tidak boleh mengandung karakter spasi putih (spasi, umpan baris). Jika Anda melihat kembali deskripsi tabel dari contoh kita:

 CREATE TABLE `auth` ( `email` varchar(96) NOT NULL, `password` varchar(64) NOT NULL, `type` varchar(32) NOT NULL DEFAULT '', PRIMARY KEY (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

ini berarti bahwa di bidang "email" tidak boleh ada karakter seperti itu.

Selain itu, kunci memcached harus kurang dari 250 byte (byte, bukan karakter). Jika Anda mengirim lebih banyak, Anda mendapatkan kesalahan:

 "CLIENT_ERROR bad command line format" 

Selain itu, seseorang harus memperhitungkan fakta bahwa plugin memcached menambahkan sintaksnya sendiri ke protokol memcached. Misalnya, ia menggunakan karakter "|" sebagai pemisah bidang dalam respons. Anda perlu memastikan bahwa simbol ini tidak digunakan di meja Anda. Pemisah dapat dikonfigurasi, tetapi pengaturan akan berlaku untuk semua tabel di seluruh server MySQL.

Pembatas bidang value_columns


Jika Anda perlu mengembalikan beberapa kolom melalui protokol memcached, seperti pada contoh pertama kami:

 get @@auth.max@example.com VALUE @@auth.max@example.com 0 10 1234567|89 END 

maka nilai kolom dipisahkan oleh pemisah standar "|". Timbul pertanyaan: "Apa yang akan terjadi jika, misalnya, karakter" | "berada di bidang pertama dalam barisan?" Plugin memcached dalam hal ini akan mengembalikan string apa adanya, seperti ini: 1234 | 567 | 89. Dalam kasus umum, tidak mungkin untuk memahami di mana ada bidang.

Karena itu, penting untuk memilih pemisah yang tepat segera. Dan karena itu akan digunakan untuk semua kunci dari semua tabel, itu harus menjadi simbol universal yang tidak akan ditemukan di bidang apa pun yang dengannya Anda akan bekerja melalui protokol memcached.

Ringkasan


Ini bukan untuk mengatakan bahwa plugin memcached itu buruk. Tetapi orang mendapat kesan bahwa itu ditulis untuk skema kerja tertentu: server MySQL dengan satu tabel yang dapat diakses menggunakan protokol memcached, dan table_id ini dibuat default. Klien membuat koneksi persisten dengan plugin Memcached dan membuat permintaan ke table_id default. Mungkin, dalam skema seperti itu, semuanya akan bekerja dengan sempurna. Jika Anda menjauh darinya, Anda menemukan berbagai ketidaknyamanan.

Anda mungkin berharap melihat beberapa laporan kinerja plugin. Tetapi kami belum memutuskan untuk menggunakannya di tempat-tempat yang sangat padat. Kami menggunakannya hanya dalam beberapa sistem yang tidak terlalu banyak dan ada yang bekerja pada kecepatan yang sama dengan HandlerSocket, tetapi kami tidak membuat tolok ukur yang jujur. Namun demikian, plugin ini menyediakan antarmuka sedemikian rupa sehingga programmer dapat dengan mudah melakukan kesalahan - Anda perlu mengingat banyak nuansa. Karena itu, kami belum siap untuk menggunakan plugin ini secara massal.

Kami membuat beberapa permintaan fitur di pelacak bug MySQL:

https://bugs.mysql.com/bug.php?id=95091
https://bugs.mysql.com/bug.php?id=95092
https://bugs.mysql.com/bug.php?id=95093
https://bugs.mysql.com/bug.php?id=95094

Mari berharap tim pengembangan plugin memcached akan meningkatkan produknya.

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


All Articles