etcd vs PostgreSQL

Jinhua Luo

March 17, 2023

Technology

Latar Belakang Historis

PostgreSQL

PostgreSQL awalnya dikembangkan pada tahun 1986 di bawah kepemimpinan Profesor Michael Stonebraker di University of California, Berkeley. Selama beberapa dekade pengembangan, PostgreSQL telah menjadi sistem manajemen basis data relasional sumber terbuka terkemuka yang tersedia saat ini. Lisensi yang permisif memungkinkan siapa pun untuk menggunakan, memodifikasi, dan mendistribusikan PostgreSQL secara bebas, baik untuk tujuan pribadi, komersial, atau penelitian akademis.

PostgreSQL menawarkan dukungan yang kuat untuk pemrosesan analitik online (OLAP) dan pemrosesan transaksi online (OLTP), dengan kemampuan kueri SQL yang kuat dan berbagai ekstensi yang memungkinkannya memenuhi hampir semua kebutuhan komersial. Akibatnya, PostgreSQL semakin mendapat perhatian dalam beberapa tahun terakhir. Faktanya, skalabilitas dan kinerja tinggi PostgreSQL memungkinkannya untuk mereplikasi fungsionalitas hampir semua jenis basis data lainnya.

arsitektur postgres Sumber gambar (mengikuti perjanjian lisensi CC 3.0 BY-SA): https://en.wikibooks.org/wiki/PostgreSQL/Architecture

etcd

Bagaimana etcd muncul, dan masalah apa yang dipecahkannya?

Pada tahun 2013, tim startup CoreOS mengembangkan produk bernama Container Linux. Ini adalah sistem operasi sumber terbuka yang ringan yang memprioritaskan otomatisasi dan penyebaran cepat layanan aplikasi. Container Linux mengharuskan aplikasi berjalan dalam kontainer dan menyediakan solusi manajemen kluster, sehingga memudahkan pengguna untuk mengelola layanan seperti pada satu mesin.

Untuk memastikan bahwa layanan pengguna tidak mengalami downtime karena restart node, CoreOS perlu menjalankan beberapa replika. Tetapi bagaimana mereka akan mengoordinasikan antara beberapa replika dan menghindari semua replika menjadi tidak tersedia selama perubahan?

Untuk mengatasi masalah ini, tim CoreOS memerlukan layanan koordinasi yang dapat menyimpan informasi konfigurasi layanan dan menyediakan kemampuan penguncian terdistribusi, dan banyak lagi. Jadi, apa pendekatan mereka? Mereka pertama-tama menganalisis skenario bisnis, titik-titik masalah, dan tujuan inti. Kemudian, mereka memilih solusi yang sesuai dengan tujuan mereka, mengevaluasi apakah akan memilih solusi komunitas sumber terbuka atau mengembangkan alat khusus mereka sendiri. Pendekatan ini adalah metode pemecahan masalah universal yang sering digunakan ketika menghadapi masalah yang menantang, dan tim CoreOS mengikuti prinsip yang sama.

Layanan koordinasi idealnya perlu memenuhi lima tujuan berikut:

  1. Ketersediaan tinggi dengan beberapa replika data
  2. Konsistensi data dengan pemeriksaan versi antara replika
  3. Kapasitas penyimpanan minimal: layanan koordinasi hanya harus menyimpan informasi konfigurasi metadata kritis untuk layanan dan node yang termasuk dalam konfigurasi bidang kontrol, bukan data yang terkait dengan pengguna. Pendekatan ini meminimalkan kebutuhan untuk sharding data untuk penyimpanan dan menghindari desain yang berlebihan.
  4. Fungsi untuk CRUD (buat, baca, perbarui, dan hapus), serta mekanisme untuk mendengarkan perubahan data. Ini harus menyimpan informasi status layanan, dan ketika ada perubahan atau anomali dalam layanan, itu harus cepat mendorong peristiwa perubahan ke bidang kontrol. Ini membantu meningkatkan ketersediaan layanan dan mengurangi overhead kinerja yang tidak perlu untuk layanan koordinasi.
  5. Kemudahan operasional: layanan koordinasi harus mudah dioperasikan, dipelihara, dan diatasi masalahnya. Antarmuka yang mudah digunakan dapat mengurangi risiko kesalahan, menurunkan biaya pemeliharaan, dan meminimalkan downtime.

Dari perspektif Teorema CAP, etcd termasuk dalam sistem CP (Konsistensi & Toleransi Partisi). arsitektur etcd

Sebagai komponen utama dari kluster Kubernetes, kube-apiserver menggunakan etcd sebagai penyimpanan dasarnya.

Di satu sisi, etcd digunakan untuk persistensi dalam membuat objek sumber daya di kluster k8s. Di sisi lain, mekanisme watch data etcd yang menggerakkan seluruh kerja Informer kluster, memungkinkan orkestrasi kontainer yang berkelanjutan.

Oleh karena itu, dari perspektif teknis, alasan inti mengapa Kubernetes menggunakan etcd adalah:

  • etcd ditulis dalam bahasa Go, yang konsisten dengan tumpukan teknologi k8s, memiliki konsumsi sumber daya yang rendah, dan sangat mudah untuk di-deploy.
  • Konsistensi kuat etcd, fitur watch, lease, dan lainnya adalah ketergantungan inti dari k8s.

Secara ringkas, etcd adalah basis data key-value terdistribusi yang dirancang khusus untuk manajemen dan distribusi konfigurasi. Sebagai perangkat lunak cloud-native, etcd menawarkan kegunaan yang siap pakai dan kinerja tinggi, membuatnya lebih unggul daripada basis data tradisional dalam area kebutuhan khusus ini.

Untuk membuat perbandingan objektif antara etcd dan PostgreSQL, yang merupakan dua jenis basis data yang berbeda, penting untuk mengevaluasinya dalam konteks persyaratan yang sama. Oleh karena itu, artikel ini hanya akan membahas perbedaan antara keduanya dalam hal kemampuan mereka untuk memenuhi persyaratan manajemen konfigurasi.

Model Data

Basis data yang berbeda memiliki model data yang berbeda yang mereka sajikan kepada pengguna, dan faktor ini menentukan kesesuaian basis data untuk berbagai skenario.

Key-value vs SQL

Model data key-value adalah model populer dalam NoSQL, yang juga diadopsi oleh etcd. Bagaimana model ini dibandingkan dengan SQL dan apa kelebihannya?

Pertama, mari kita lihat SQL.

Basis data relasional menyimpan data dalam tabel dan menyediakan cara yang efisien, intuitif, dan fleksibel untuk menyimpan dan mengakses informasi terstruktur.

Sebuah tabel, juga dikenal sebagai relasi, terdiri dari kolom yang berisi satu atau lebih kategori data, dan baris, juga dikenal sebagai catatan tabel, yang mencakup sekumpulan data yang mendefinisikan kategori. Aplikasi mengambil data dengan menggunakan kueri yang menggunakan operasi seperti "proyek" untuk mengidentifikasi atribut, "pilih" untuk mengidentifikasi tuple, dan "gabung" untuk menggabungkan relasi. Model relasional untuk mengelola basis data dikembangkan pada tahun 1970 oleh Edgar Codd, seorang ilmuwan komputer di IBM.

basis data relasional

Sumber gambar (mematuhi perjanjian lisensi CC 3.0 BY-SA): https://en.wikipedia.org/wiki/Associative_entity

Catatan dalam tabel tidak memiliki pengidentifikasi unik karena tabel dirancang untuk menampung beberapa baris duplikat. Untuk memungkinkan kueri key-value, indeks unik harus ditambahkan ke bidang yang berfungsi sebagai kunci dalam tabel. Indeks default PostgreSQL adalah btree, yang, mirip dengan etcd, dapat melakukan kueri rentang pada kunci.

Structured query language (SQL) adalah bahasa pemrograman untuk menyimpan dan memproses informasi dalam basis data relasional. Basis data relasional menyimpan informasi dalam bentuk tabel, dengan baris dan kolom mewakili atribut data yang berbeda dan berbagai hubungan antara nilai data. Anda dapat menggunakan pernyataan SQL untuk menyimpan, memperbarui, menghapus, mencari, dan mengambil informasi dari basis data. Anda juga dapat menggunakan SQL untuk memelihara dan mengoptimalkan kinerja basis data.

PostgreSQL telah memperluas SQL dengan banyak ekstensi, menjadikannya bahasa yang lengkap secara Turing. Ini berarti bahwa SQL dapat melakukan operasi kompleks apa pun, memfasilitasi eksekusi logika pemrosesan data sepenuhnya di sisi server.

Sebagai perbandingan, etcd dirancang sebagai alat manajemen konfigurasi, dengan data konfigurasi biasanya direpresentasikan sebagai tabel hash. Inilah mengapa model datanya terstruktur dalam format key-value, secara efektif membuat satu tabel global besar. Operasi CRUD dapat dilakukan pada tabel ini, yang hanya memiliki dua bidang: kunci unik dengan informasi versi, dan nilai yang tidak bertipe. Akibatnya, klien harus mengambil nilai penuh untuk pemrosesan lebih lanjut.

Secara keseluruhan, struktur key-value dari etcd menyederhanakan SQL dan lebih nyaman serta intuitif untuk tugas khusus manajemen konfigurasi.

MVCC (Multi-Version Concurrency Control)

MVCC adalah fitur penting untuk versi data dalam manajemen konfigurasi. Ini memungkinkan:

  • Mengkueri data historis
  • Menentukan usia data dengan membandingkan versi
  • Menonton data, yang memerlukan versi untuk memungkinkan notifikasi inkremental

Baik etcd maupun PostgreSQL memiliki MVCC, tetapi apa perbedaan antara keduanya?

etcd menggunakan penghitung versi 64-bit yang meningkat secara global untuk mengelola sistem MVCC-nya. Tidak perlu khawatir tentang overflow. Penghitung dirancang untuk menangani sejumlah besar pembaruan, bahkan jika terjadi dengan kecepatan jutaan per detik. Setiap kali pasangan key-value dibuat atau diperbarui, itu diberi nomor versi. Ketika pasangan key-value dihapus, sebuah tombstone dibuat dengan nomor versi diatur ulang ke 0. Ini berarti bahwa setiap perubahan menghasilkan versi baru, bukan menimpa yang sebelumnya.

Selain itu, etcd menyimpan semua versi dari pasangan key-value dan membuatnya terlihat oleh pengguna. Data key-value tidak pernah ditimpa, dan versi baru disimpan bersama dengan yang sudah ada. Implementasi MVCC dalam etcd juga menyediakan pemisahan baca-tulis, yang memungkinkan pengguna untuk membaca data tanpa mengunci, membuatnya cocok untuk kasus penggunaan yang intensif membaca.

Implementasi MVCC PostgreSQL berbeda dari etcd karena tidak berfokus pada penyediaan nomor versi yang meningkat, tetapi pada implementasi transaksi dan tingkat isolasi yang berbeda secara transparan kepada pengguna. MVCC adalah mekanisme penguncian optimis yang memungkinkan pembaruan bersamaan. Setiap baris dalam tabel memiliki catatan ID transaksi, dengan xmin mewakili ID transaksi dari pembuatan baris dan xmax mewakili ID transaksi dari pembaruan baris.

  • Transaksi hanya dapat membaca data yang telah di-commit sebelum mereka.
  • Saat memperbarui data, jika terjadi konflik versi, PostgreSQL akan mencoba kembali dengan mekanisme pencocokan untuk menentukan apakah pembaruan harus dilanjutkan.

Untuk melihat contoh, silakan merujuk ke tautan berikut: https://devcenter.heroku.com/articles/postgresql-concurrency

Sayangnya, menggunakan ID transaksi untuk kontrol versi data konfigurasi dalam PostgreSQL tidak mungkin dilakukan karena beberapa alasan:

  • ID transaksi diberikan ke semua baris yang terlibat dalam transaksi yang sama, artinya kontrol versi tidak dapat diterapkan pada tingkat baris.
  • Kueri historis tidak dapat dilakukan, dan hanya versi terbaru dari baris yang dapat diakses.
  • Karena sifatnya sebagai penghitung 32-bit, ID transaksi rentan terhadap overflow dan diatur ulang selama vacuuming.
  • Tidak mungkin untuk mengimplementasikan fungsionalitas watch berdasarkan ID transaksi.

Akibatnya, PostgreSQL memerlukan metode alternatif untuk kontrol versi data konfigurasi karena dukungan bawaan tidak tersedia.

Antarmuka Klien

Desain antarmuka klien adalah aspek kritis ketika menentukan biaya dan konsumsi sumber daya yang terkait dengan penggunaannya. Dengan menganalisis perbedaan antara antarmuka, seseorang dapat membuat pilihan yang tepat ketika memilih opsi yang paling sesuai.

API kv/watch/lease etcd telah terbukti sangat mahir dalam mengelola konfigurasi. Namun, bagaimana seseorang dapat mengimplementasikan API ini dalam PostgreSQL?

Sayangnya, PostgreSQL tidak menyediakan dukungan bawaan untuk API ini, dan enkapsulasi diperlukan untuk mengimplementasikannya. Untuk menganalisis implementasinya, kita akan memeriksa proyek pg_watch_demo yang dikembangkan oleh saya sendiri: pg_watch_demo.

gRPC/HTTP vs TCP

PostgreSQL mengikuti arsitektur multi-proses, di mana setiap proses hanya menangani satu koneksi TCP pada satu waktu. Ini menggunakan protokol khusus untuk memberikan fungsionalitas melalui kueri SQL dan mengikuti model interaksi permintaan-respons (mirip dengan HTTP/1.1, yang hanya menangani satu permintaan pada satu waktu dan memerlukan pipelining untuk memproses beberapa permintaan secara bersamaan). Namun, mengingat konsumsi sumber daya yang tinggi dan efisiensi yang relatif rendah, proxy koneksi pool (seperti pgbouncer) sangat penting untuk meningkatkan kinerja, terutama dalam skenario dengan QPS tinggi.

Di sisi lain, etcd dirancang pada arsitektur multi-coroutine di Golang dan menawarkan dua antarmuka yang ramah pengguna: gRPC dan RESTful. Antarmuka ini mudah diintegrasikan dan efisien dalam hal konsumsi sumber daya. Selain itu, setiap koneksi gRPC dapat menangani beberapa kueri bersamaan, yang memastikan kinerja optimal.

Mendefinisikan Data

etcd

message KeyValue { bytes key = 1; // Nomor revisi saat kunci dibuat int64 create_revision = 2; // Nomor revisi saat kunci terakhir dimodifikasi int64 mod_revision = 3; // Penghitung yang meningkat setiap kali kunci diperbarui. // Penghitung ini diatur ulang ke nol ketika kunci dihapus, dan digunakan sebagai tombstone. int64 version = 4; bytes value = 5; // Objek lease yang digunakan oleh kunci untuk TTL. Jika nilainya 0, maka tidak ada TTL. int64 lease = 6; }

PostgreSQL

PostgreSQL perlu menggunakan tabel untuk mensimulasikan ruang data global etcd:

CREATE TABLE IF NOT EXISTS config ( key text, value text, -- Setara dengan `create_revision` dan `mod_revision` -- Di sini, tipe urutan bilangan bulat besar yang meningkat digunakan untuk mensimulasikan revisi revision bigserial, -- Tombstone tombstone boolean NOT NULL DEFAULT false, -- Indeks komposit, cari berdasarkan kunci terlebih dahulu, lalu berdasarkan revisi primary key(key, revision) );

get

etcd

API get etcd memiliki berbagai parameter:

  • Kueri rentang, misalnya, mengatur key sebagai /abc dan range_end sebagai /abd akan mengambil semua pasangan key-value dengan /abc sebagai awalan.
  • Kueri historis, menentukan revision atau rentang mod_revision.
  • Mengurutkan dan membatasi jumlah hasil yang dikembalikan.
message RangeRequest { ... bytes key = 1; // Kueri rentang bytes range_end = 2; int64 limit = 3; // Kueri historis int64 revision = 4; // Mengurutkan SortOrder sort_order = 5; SortTarget sort_target = 6; bool serializable = 7; bool keys_only = 8; bool count_only = 9; // Kueri historis int64 min_mod_revision = 10; int64 max_mod_revision = 11; int64 min_create_revision = 12; int64 max_create_revision = 13; }

PostgreSQL

PostgreSQL dapat melakukan fungsi get dari etcd melalui SQL, dan bahkan menyediakan fungsionalitas yang lebih kompleks. Karena SQL sendiri adalah bahasa daripada antarmuka parameter tetap, ini sangat serbaguna. Di sini kami menunjukkan contoh sederhana untuk mengambil pasangan key-value terbaru. Karena kunci utama adalah indeks gabungan, ini dapat dicari dengan cepat berdasarkan rentang, menghasilkan pencarian yang cepat.

CREATE FUNCTION get1(kk text) RETURNS table(r bigint, k text, v text, c bigint) AS $$ SELECT revision, key, value, create_time FROM config where key = kk and tombstone = false ORDER BY key, revision desc limit 1 $$ LANGUAGE sql;

put

etcd

message PutRequest { bytes key = 1; bytes value = 2; int64 lease = 3; // apakah akan merespons dengan data pasangan key-value sebelum pembaruan dari permintaan `Put` ini. bool prev_kv = 4; bool ignore_value = 5; bool ignore_lease = 6; }

PostgreSQL

Sama seperti di etcd, PostgreSQL tidak melakukan perubahan di tempat. Sebaliknya, baris baru dimasukkan, dan revisi baru diberikan padanya.

CREATE FUNCTION set(k text, v text) RETURNS bigint AS $$ insert into config(key, value) values(k, v) returning revision; $$ LANGUAGE SQL;

delete

etcd

message DeleteRangeRequest { bytes key = 1; bytes range_end = 2; bool prev_kv = 3; }

PostgreSQL

Mirip dengan etcd, penghapusan di PostgreSQL tidak memodifikasi data di tempat. Sebaliknya, baris baru dimasukkan dengan bidang tombstone diatur ke true untuk menunjukkan bahwa itu adalah tombstone.

CREATE FUNCTION del(k text) RETURNS bigint AS $$ insert into config(key, tombstone) values(k, true) returning revision; $$ LANGUAGE SQL;

watch

etcd

message WatchCreateRequest { bytes key = 1; // Menentukan rentang kunci yang akan di-watch bytes range_end = 2; // Revisi awal untuk watch int64 start_revision = 3; ... } message WatchResponse { ResponseHeader header = 1; ... // Untuk efisiensi, beberapa peristiwa dapat dikembalikan repeated mvccpb.Event events = 11; }

PostgreSQL

PostgreSQL tidak dilengkapi dengan fungsi watch bawaan, dan sebaliknya, memerlukan kombinasi trigger dan channel untuk mencapai fungsionalitas yang serupa. Dengan menggunakan pg_notify, data dapat dikirim ke semua aplikasi yang sedang mendengarkan ke channel tertentu.

-- fungsi trigger untuk mendistribusikan peristiwa put/delete CREATE FUNCTION notify_config_change() RETURNS TRIGGER AS $$ DECLARE data json; channel text; is_channel_exist boolean; BEGIN IF (TG_OP = 'INSERT') THEN -- menggunakan JSON untuk mengkodekan data = row_to_json(NEW); -- Ekstrak nama channel untuk distribusi dari kunci channel = (SELECT SUBSTRING(NEW.key, '/(.*)/')); -- Jika aplikasi sedang menonton channel, kirim peristiwa melalui itu is_channel_exist = NOT pg_try_advisory_lock(9080); IF is_channel_exist THEN PERFORM pg_notify(channel, data::text); ELSE PERFORM pg_advisory_unlock(9080); END IF; END IF; RETURN NULL; -- Hasil diabaikan karena ini adalah trigger AFTER END; $$ LANGUAGE plpgsql; CREATE TRIGGER notify_config_change AFTER INSERT ON config FOR EACH ROW EXECUTE FUNCTION notify_config_change();

Karena fitur watch dienkapsulasi, aplikasi klien juga harus mengimplementasikan logika yang sesuai. Menggunakan Golang sebagai contoh, langkah-langkah berikut harus diambil:

  1. Mulai mendengarkan: Saat mendengarkan dimulai, semua data notify akan di-cache di tingkat PostgreSQL dan channel Golang.
  2. Ambil semua data menggunakan get_all(key_prefix, revision): Fungsi ini membaca semua data yang ada mulai dari revisi yang ditentukan. Untuk setiap kunci, hanya data revisi terbaru yang akan dikembalikan, dengan data yang dihapus secara otomatis dihapus. Jika revisi tidak ditentukan, ini mengembalikan data terbaru untuk semua kunci dengan key_prefix yang diberikan.
  3. Watch untuk data baru, termasuk notifikasi apa pun yang mungkin telah di-cache antara langkah pertama dan kedua, untuk menghindari kehilangan data baru yang mungkin terjadi selama jendela waktu ini. Abaikan revisi apa pun yang telah dibaca pada langkah kedua.
func watch(l *pq.Listener) { for { select { case n := <-l.Notify: if n == nil { log.Println("listener reconnected") log.Printf("get all routes from rev %d including tombstones...\n", latestRev) // Saat menyambung kembali, lanjutkan transmisi berdasarkan revisi sebelum pemutusan koneksi. str := fmt.Sprintf(`select * from get_all_from_rev_with_stale('/routes/', %d)`, latestRev) rows, err := db.Query(str) ... continue } ... // pertahankan status yang mencatat revisi terbaru yang telah diterima updateRoute(cfg) case <-time.After(15 * time.Second): log.Println("Received no events for 15 seconds, checking connection") go func() { // Jika tidak ada peristiwa yang diterima untuk waktu yang lama, periksa kesehatan koneksi if err := l.Ping(); err != nil { log.Println("listener ping error: ", err) } }() } } } log.Println("get all routes...") // Saat menginisialisasi, aplikasi harus mendapatkan semua pasangan key-value saat ini dan kemudian memantau pembaruan secara inkremental melalui watch rows, err := db.Query(`select * from get_all('/routes/')`) ... go watch(listener)

transaksi

etcd

Transaksi etcd adalah kumpulan dari beberapa operasi dengan pemeriksaan kondisi, dan modifikasi yang dilakukan oleh transaksi di-commit secara atomik.

message TxnRequest { // Tentukan kondisi eksekusi transaksi repeated Compare compare = 1; // Operasi yang akan dieksekusi jika kondisi terpenuhi repeated RequestOp success = 2; // Operasi yang akan dieksekusi jika kondisi tidak terpenuhi repeated RequestOp failure = 3; }

PostgreSQL

Perintah DO di PostgreSQL memungkinkan eksekusi perintah apa pun, termasuk prosedur tersimpan. Ini mendukung beberapa bahasa, termasuk bahasa bawaan seperti PL/pgSQL dan Python. Dengan bahasa-bahasa ini, logika kontrol apa pun seperti pengecekan kondisi, loop, dan lainnya dapat diimplementasikan, membuatnya lebih serbaguna daripada etcd.

DO LANGUAGE plpgsql $$ DECLARE n_plugins int; BEGIN SELECT COUNT(1) INTO n_plugins FROM get_all('/plugins/'); IF n_plugins = 0 THEN perform set('/routes/1', 'foobar'); perform set('/upstream/1', 'foobar'); ... ELSE ... END IF; END; $$;

lease

etcd

Di etcd, dimungkinkan untuk membuat objek lease yang harus diperbarui secara berkala oleh aplikasi untuk mencegahnya kedaluwarsa. Setiap pasangan key-value dapat dikaitkan dengan objek lease, dan ketika objek lease kedaluwarsa, semua pasangan key-value yang terkait juga akan kedaluwarsa, secara otomatis menghapusnya.

message LeaseGrantRequest { // TTL dari lease int64 TTL = 1; int64 ID = 2; } // Pembaruan lease message LeaseKeepAliveRequest { int64 ID = 1; } message PutRequest { bytes key = 1; bytes value = 2; // ID lease, digunakan untuk mengimplementasikan TTL int64 lease = 3; ... }

PostgreSQL

  • Di PostgreSQL, lease dapat dipertahankan melalui kunci asing. Saat mengkueri, jika ada objek lease terkait yang telah kedaluwarsa, itu dianggap sebagai tombstone.
  • Permintaan keepalive memperbarui timestamp last_keepalive dalam tabel lease.
CREATE TABLE IF NOT EXISTS config ( key text, value text, ... -- Gunakan kunci asing untuk menentukan objek lease terkait. lease int64 references lease(id), ); CREATE TABLE IF NOT EXISTS lease ( id text, ttl int, last_keepalive timestamp; );

Perbandingan Kinerja

PostgreSQL perlu mensimulasikan berbagai API etcd melalui enkapsulasi. Jadi bagaimana kinerjanya? Berikut adalah hasil dari tes sederhana:https://github.com/kingluo/pg_watch_demo#benchmark.

etcd_vs_postgres

Hasilnya menunjukkan bahwa kinerja baca dan tulis hampir identik, dengan PostgreSQL bahkan mengungguli etcd. Selain itu, latensi dari pembaruan yang terjadi hingga aplikasi menerima peristiwa menentukan efisiensi distribusi pembaruan, dan baik PostgreSQL maupun etcd menunjukkan kinerja yang serupa. Saat diuji pada mesin yang sama untuk klien dan server, latensi watch kurang dari 1 milidetik.

Namun, PostgreSQL memiliki beberapa kekurangan yang perlu disebutkan:

  • Log WAL untuk setiap pembaruan lebih besar, menghasilkan dua kali lipat I/O disk dibandingkan dengan etcd.
  • Ini mengkonsumsi lebih banyak CPU dibandingkan dengan etcd.
  • Notify berdasarkan channel adalah konsep tingkat transaksi. Saat memperbarui jenis sumber daya yang sama, pembaruan dikirim ke channel yang sama, dan permintaan pembaruan bersaing untuk kunci eksklusif, menghasilkan permintaan yang diserialisasi. Dengan kata lain, menggunakan channel untuk mengimplementasikan watch akan memengaruhi paralelisme operasi put.

Ini menunjukkan bahwa untuk mencapai persyaratan yang sama, kita perlu berinvestasi lebih banyak dalam mempelajari dan mengoptimalkan PostgreSQL.

Penyimpanan

Kinerja ditentukan oleh penyimpanan dasar, dan bagaimana data disimpan menentukan kebutuhan basis data untuk memori, disk, dan sumber daya lainnya.

etcd

Diagram arsitektur penyimpanan etcd:

penyimpanan etcd

etcd pertama-tama menulis pembaruan ke log write-ahead (WAL) dan mem-flush-nya ke disk untuk memastikan bahwa pembaruan tidak hilang. Setelah log berhasil ditulis dan dikonfirmasi oleh mayoritas node, hasilnya dapat dikembalikan ke klien. etcd juga secara asinkron memperbarui TreeIndex dan BoltDB.

Untuk menghindari log tumbuh tanpa batas, etcd secara berkala mengambil snapshot dari penyimpanan, dan log sebelum snapshot dapat dihapus.

etcd mengindeks semua kunci dalam memori (TreeIndex), mencatat informasi versi dari setiap kunci, tetapi hanya menyimpan pointer ke BoltDB (revision) untuk nilai.

Nilai yang sesuai dengan kunci disimpan di disk dan dikelola menggunakan BoltDB.

Baik TreeIndex maupun BoltDB menggunakan struktur data btree, yang dikenal efisien dalam pencarian dan pencarian rentang.

Diagram struktur TreeIndex:

treeindex etcd

(Sumber gambar: https://blog.csdn.net/H_L_S/article/details/112691481, dilisensikan di bawah CC 4.0 BY-SA)

Setiap kunci dibagi menjadi generasi yang berbeda, dengan setiap penghapusan menandai akhir dari suatu generasi.

Pointer ke nilai terdiri dari dua bilangan bulat. Bilangan bulat pertama main adalah ID transaksi dari etcd, sedangkan bilangan bulat kedua sub mewakili ID pembaruan dari kunci ini dalam transaksi tersebut.

Boltdb mendukung transaksi dan snapshot, dan menyimpan nilai yang sesuai dengan revisi.

boltdb etcd

(Sumber gambar: https://blog.csdn.net/H_L_S/article/details/112691481, dilisensikan di bawah CC 4.0 BY-SA)

Contoh penulisan data:

Menulis key="key1", revision=(12,1), value="keyvalue5". Perhatikan perubahan pada bagian merah dari treeIndex dan BoltDB:

put etcd

(Sumber gambar: https://blog.csdn.net/H_L_S/article/details/112691481, dilisensikan di bawah CC 4.0 BY-SA)

Menghapus key="key", revision=(13,1) membuat generasi kosong baru dalam treeIndex dan menghasilkan nilai kosong dalam BoltDB dengan key="13_1t".

Di sini, t berarti "tombstone". Ini menyiratkan bahwa Anda tidak dapat membaca tombstone karena pointer dalam treeIndex adalah (13,1), tetapi dalam BoltDB, itu adalah 13_1t, yang tidak dapat dicocokkan.

delete etcd

(Sumber gambar: https://blog.csdn.net/H_L_S/article/details/112691481, dilisensikan di bawah CC 4.0 BY-SA)

Perlu dicatat bahwa etcd menjadwalkan baik baca maupun tulis ke BoltDB menggunakan satu goroutine untuk mengurangi I/O disk acak dan meningkatkan kinerja I/O.

PostgreSQL

Diagram arsitektur penyimpanan PostgreSQL:

penyimpanan postgres

Mirip dengan etcd, PostgreSQL menambahkan pembaruan ke file log terlebih dahulu, dan menunggu log berhasil di-flush ke disk sebelum menganggap transaksi selesai. Sementara itu, pembaruan ditulis ke memori shared_buffer.

Shared_buffer adalah area memori yang dibagi oleh semua tabel dan indeks dalam PostgreSQL, dan berfungsi sebagai pemetaan untuk objek-objek ini.

Dalam PostgreSQL, setiap tabel terdiri dari beberapa halaman, dengan setiap halaman berukuran 8 KB dan berisi beberapa baris.

Selain tabel, indeks (seperti indeks btree) juga terdiri dari halaman tabel dalam format yang sama. Namun, halaman ini bersifat khusus dan saling terhubung untuk membentuk struktur pohon.

PostgreSQL dilengkapi dengan proses checkpointer yang secara berkala mem-flush semua halaman tabel dan indeks yang dimodifikasi ke disk. Sebelum setiap checkpoint, file log dapat dihapus dan didaur ulang untuk mencegah log tumbuh tanpa batas.

Struktur halaman:

halaman postgres

(Sumber gambar: https://en.wikibooks.org/wiki/PostgreSQL/Page_Layout, dilisensikan di bawah CC 3.0 BY-SA)

Struktur indeks btree:

indeks btree postgres

(Sumber gambar: https://en.wikibooks.org/wiki/PostgreSQL/Index_Btree, dilisensikan di bawah CC 3.0 BY-SA)

Untuk meningkatkan kinerja baca, beberapa pernyataan SQL dalam PostgreSQL mempertimbangkan penggunaan bitmap untuk membaca halaman yang tersebar secara berurutan, sehingga meningkatkan kinerja I/O.

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100; QUERY PLAN ------------------------------------------------------------------------------ Bitmap Heap Scan on tenk1 (cost=5.07..229.20 rows=101 width=244) Recheck Cond: (unique1 < 100) -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) Index Cond: (unique1 < 100)

Kesimpulan

PostgreSQL dan etcd sama-sama memprioritaskan kinerja I/O dalam desain penyimpanan mereka. etcd bahkan menempatkan indeks semua kunci dalam memori, dan kedua sistem mengoptimalkan operasi batch untuk pembacaan dan penulisan disk berurutan.

Akibatnya, seperti yang terlihat dalam perbandingan kinerja di atas, PostgreSQL dan etcd menunjukkan kinerja baca dan tulis yang serupa.

Namun, dibandingkan dengan PostgreSQL, etcd membutuhkan kapasitas memori yang lebih besar dan disk yang lebih cepat, seperti yang dijelaskan dalam Panduan perangkat keras untuk mengelola kluster etcd.

Komputasi Terdistribusi

Desentralisasi dan konsistensi data adalah fitur khas dari etcd, dan juga merupakan persyaratan yang diperlukan untuk sistem cloud-native. Namun, bagaimana basis data tradisional dapat memenuhi persyaratan ini?

etcd

Raft adalah protokol terdistribusi populer yang digunakan oleh etcd untuk mendistribusikan pembaruan ke beberapa node, memastikan bahwa data yang di-commit dikonfirmasi oleh mayoritas node.

Raft memiliki peran yang didefinisikan

Tags: