Praktik terbaik untuk menggunakan transaksi dan mengonfigurasi Read/Write Concern di ApsaraDB for MongoDB.
Latar Belakang
MongoDB 4.0 memperkenalkan transaksi standalone (transaksi replica set) untuk operasi pada satu atau beberapa koleksi dalam satu replica set. MongoDB 4.2 menambahkan transaksi terdistribusi (sharded transactions) untuk operasi lintas beberapa koleksi dan shard.
Di MongoDB, operasi dokumen tunggal selalu bersifat atomik. Aplikasi dapat menggunakan dokumen embedded dan array untuk memodelkan relasi dalam satu dokumen, berbeda dengan database relasional yang memerlukan koleksi ternormalisasi dan join. Dengan pemodelan data yang tepat, atomisitas dokumen tunggal menghilangkan kebutuhan akan transaksi terdistribusi dalam sebagian besar kasus.
Namun, aplikasi di bidang keuangan atau akuntansi mungkin tetap memerlukan transaksi terdistribusi. MongoDB 4.2 dan versi setelahnya mendukung kemampuan ini secara penuh.
Transaksi
Informasi dasar
API transaksi MongoDB mirip dengan yang ada di database relasional, sehingga kurva pembelajarannya minimal.
Contoh berikut menunjukkan transaksi lengkap dengan API startTransaction/abortTransaction/commitTransaction, serta pengaturan session dan readConcern/writeConcern.
// Buat koleksi.
db.getSiblingDB("mydb1").foo.insertOne(
{abc: 0},
{ writeConcern: { w: "majority", wtimeout: 2000 } }
)
db.getSiblingDB("mydb2").bar.insertOne(
{xyz: 0},
{ writeConcern: { w: "majority", wtimeout: 2000 } }
)
// Mulai sesi.
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );
coll1 = session.getDatabase("mydb1").foo;
coll2 = session.getDatabase("mydb2").bar;
// Mulai transaksi.
session.startTransaction( { readConcern: { level: "local" }, writeConcern: { w: "majority" } } );
// Lakukan operasi dalam transaksi.
try {
coll1.insertOne( { abc: 1 } );
coll2.insertOne( { xyz: 999 } );
} catch (error) {
// Batalkan transaksi jika terjadi error.
session.abortTransaction();
throw error;
}
// Commit transaksi.
session.commitTransaction();
session.endSession();
Perilaku utama transaksi:
-
Setiap transaksi harus dikaitkan dengan sebuah session. Satu session hanya mendukung satu transaksi aktif dalam satu waktu. Mengakhiri session akan melakukan rollback terhadap transaksi aktif apa pun.
-
Transaksi terdistribusi dapat mencakup beberapa dokumen, koleksi, dan database.
-
Transaksi dapat membaca tulisan sendiri yang belum di-commit, tetapi tulisan tersebut tidak terlihat oleh operasi di luar transaksi.
-
Tulisan tidak direplikasi ke node secondary hingga transaksi di-commit. Setelah commit, tulisan tersebut secara otomatis diterapkan ke semua node secondary.
-
Transaksi mengunci dokumen yang dimodifikasi, memblokir operasi lain hingga selesai. Jika kunci tidak dapat diperoleh dalam waktu 5 milidetik (default), transaksi dibatalkan karena konflik tulis. Timeout ini dikendalikan oleh parameter maxTransactionLockRequestTimeoutMillis.
-
Transaksi secara otomatis mencoba ulang pada error sementara seperti gangguan jaringan temporary. Percobaan ulang ini transparan bagi client.
-
Transaksi yang berjalan lebih dari 60 detik akan dibatalkan paksa. Timeout ini dikendalikan oleh parameter transactionLifetimeLimitSeconds.
Batasan
-
Anda tidak dapat membuat koleksi atau indeks baru dalam transaksi terdistribusi.
-
Transaksi tidak dapat menulis ke koleksi capped.
-
Transaksi tidak dapat menggunakan read concern snapshot untuk membaca dari koleksi capped. Batasan ini berlaku untuk MongoDB 5.0 dan versi setelahnya.
-
Dalam transaksi, Anda tidak dapat membaca dari atau menulis ke koleksi di database
config/admin/local. -
Transaksi tidak dapat menulis ke koleksi sistem, seperti koleksi dengan format
system.*. -
Transaksi tidak mendukung
explain. -
Anda tidak dapat menggunakan
getMoredi dalam transaksi untuk membaca cursor yang dibuat di luar transaksi. Demikian pula, Anda tidak dapat menggunakangetMoredi luar transaksi untuk membaca cursor yang dibuat di dalamnya. -
Operasi pertama dalam transaksi tidak boleh berupa perintah seperti killCursors atau
hello. -
Anda tidak dapat menjalankan perintah non-CRUD di dalam transaksi. Contohnya termasuk listCollections, listIndexes, createUser, getParameter, dan
count. -
Untuk transaksi terdistribusi, Anda tidak dapat mengatur parameter writeConcernMajorityJournalDefault pada shard ke false.
-
Transaksi terdistribusi tidak mendukung shard dengan arbiters.
Praktik terbaik
Utamakan transaksi standalone daripada transaksi terdistribusi
Transaksi terdistribusi memiliki performa lebih rendah dibandingkan transaksi standalone karena logikanya lebih kompleks. Di MongoDB, model data denormalisasi (dokumen embedded dan array) tetap menjadi pilihan terbaik. Dengan pemodelan data yang tepat, transaksi standalone memenuhi sebagian besar kebutuhan transaksional.
Hindari transaksi jangka panjang
MongoDB secara otomatis membatalkan transaksi terdistribusi yang berjalan lebih dari 60 detik. Pecah transaksi besar menjadi bagian-bagian kecil dan pastikan kueri menggunakan indeks yang tepat agar eksekusi cepat.
Hindari memodifikasi terlalu banyak dokumen dalam satu transaksi
Meskipun tidak ada batas keras pada jumlah dokumen yang dapat dibaca dalam transaksi, memodifikasi banyak dokumen meningkatkan beban sinkronisasi primary-secondary, berpotensi menyebabkan replikasi lag. Batasi modifikasi hingga 1.000 dokumen per transaksi. Untuk batch yang lebih besar, pecah menjadi beberapa transaksi.
Hindari transaksi besar yang melebihi 16 MB
Di MongoDB 4.0, transaksi menggunakan satu entri oplog yang dibatasi hingga 16 MB. Operasi update menyimpan perubahan inkremental; insert menyimpan dokumen lengkap. Jika total oplog melebihi 16 MB, transaksi dibatalkan dan di-rollback.
MongoDB 4.2 dan versi setelahnya dapat membagi tulisan transaksi ke beberapa entri oplog, menghilangkan batas 16 MB per entri tunggal. Namun, tetap batasi ukuran transaksi dalam 16 MB untuk menghindari masalah lain.
Tangani rollback transaksi di sisi client
Saat transaksi dibatalkan, transaksi tersebut mengembalikan exception dan melakukan rollback. Aplikasi Anda harus menangkap exception ini dan mencoba ulang pada error sementara (switchover primary/secondary, kegagalan node). Meskipun driver MongoDB secara otomatis mencoba ulang commit melalui Retryable Writes, aplikasi tetap harus menangani error yang tidak dapat dicoba ulang seperti TransactionTooLarge, TransactionTooOld, dan TransactionExceededLifetimeLimitSeconds.
Hindari melakukan operasi DDL dalam transaksi
Operasi DDL seperti createIndex atau dropDatabase diblokir oleh transaksi aktif pada database atau koleksi yang sama. Operasi DDL yang diblokir mencegah transaksi baru mendapatkan kunci, sehingga menyebabkan transaksi tersebut dibatalkan.
MongoDB 4.4 melonggarkan batasan ini melalui parameter shouldMultiDocTxnCreateCollectionAndIndexes. Anda dapat menjalankan createCollection atau createIndex dalam transaksi terdistribusi, dengan batasan berikut:
-
Koleksi hanya dapat dibuat secara implisit.
-
Koleksi target tidak boleh sudah ada.
-
Koleksi target harus kosong.
Oleh karena itu, hindari melakukan operasi DDL dalam transaksi.
Lakukan rollback transaksi yang belum di-commit atau error segera
Modifikasi transaksi yang belum di-commit disimpan di cache WiredTiger. Beberapa transaksi concurrent yang belum di-commit atau error dapat memberi tekanan pada cache dan menyebabkan masalah lebih lanjut. Lakukan rollback transaksi yang tidak diperlukan sesegera mungkin untuk melepaskan sumber daya.
Tingkatkan timeout kunci jika transaksi sering rollback
Secara default, operasi transaksi akan rollback jika tidak dapat memperoleh kunci dalam waktu 5 milidetik. Kunci dilepaskan saat commit atau rollback. Jika timeout kunci menyebabkan rollback yang sering, tingkatkan parameter maxTransactionLockRequestTimeoutMillis.
Jika peningkatan timeout tidak membantu, periksa operasi yang menahan kunci dalam waktu lama, seperti operasi DDL atau kueri yang tidak dioptimalkan, lalu optimalkan operasi tersebut.
Hindari modifikasi konkuren pada dokumen yang sama di dalam dan di luar transaksi
Jika tulisan eksternal memodifikasi dokumen yang juga dimodifikasi oleh transaksi yang sedang berjalan, transaksi tersebut akan rollback karena konflik tulis. Sebaliknya, jika transaksi sudah memegang kunci pada dokumen, tulisan eksternal ke dokumen tersebut akan menunggu hingga transaksi selesai.
Pada konflik tulis, tulisan eksternal tidak gagal. MongoDB mencoba ulang secara internal, menambahkan counter writeConflicts hingga berhasil. Client melihat operasi tersebut berhasil meski lebih lambat.
Jumlah kecil konflik tulis berdampak minimal. Konflik tulis yang sering menurunkan performa. Gunakan log audit atau slow query log untuk mendeteksi konflik tulis berlebihan.
Risiko bug kernel
Transaksi jangka panjang atau berukuran besar memberikan beban signifikan pada cache WiredTiger. Sejak awal transaksi tertua yang belum di-commit, WiredTiger harus mempertahankan data dan status untuk semua tulisan berikutnya. Transaksi aktif berbagi snapshot yang sama, sehingga tulisan baru menumpuk di cache sepanjang masa transaksi dan tidak dapat di-evict hingga transaksi di-commit atau di-rollback. Cache yang kelebihan beban (penggunaan dan dirty usage melebihi ambang batas) menyebabkan database tersendat, latensi meningkat, CPU penuh, dan potensi deadlock. Isu terkait: SERVER-50365 dan SERVER-51281.
ApsaraDB for MongoDB merekomendasikan upgrade ke MongoDB 5.0 atau versi setelahnya untuk aplikasi yang banyak menggunakan transaksi.
Read Concern
Informasi dasar
Read Concern mengontrol konsistensi dan isolasi dengan level berikut:
-
"local": Default untuk baca dari node primary atau secondary. Membaca dari node lokal dan mungkin mengembalikan data yang nantinya di-rollback. -
"available": Default untuk baca dari secondary dalam kluster sharded. Mungkin mengembalikan data yang nantinya di-rollback. Tidak memeriksa versi shard dan mungkin mengembalikan dokumen orphaned. Memberikan latensi akses terendah. -
"majority": Membaca data yang telah diakui oleh mayoritas anggota replica set. Data ini tidak akan di-rollback. -
"linearizable": Level paling ketat. Menunggu semua tulisan sebelumnya diakui oleh mayoritas node. Performa terendah; hanya tersedia di primary. -
"snapshot": Membaca dari snapshot data yang diakui oleh mayoritas node. Dapat dikaitkan dengan titik waktu tertentu menggunakan atClusterTime.
Catatan penggunaan:
-
Data terbaru pada satu node mongod belum tentu merepresentasikan data terkini dalam replica set, terlepas dari level Read Concern.
-
Level Read Concern yang berbeda dapat ditentukan per operasi. MongoDB 4.4+ juga mendukung default di sisi server, yang dapat ditimpa oleh pengaturan tingkat operasi.
-
Read Concern diabaikan saat membaca dari database
local. Semua data di databaselocaldapat dibaca terlepas dari levelnya. -
Transaksi terdistribusi hanya mendukung tiga level Read Concern:
"local","majority", dan"snapshot". -
Session konsisten kausal harus menggunakan level Read Concern
"majority".
Praktik terbaik
Untuk transaksi terdistribusi, atur Read Concern pada tingkat transaksi, bukan pada operasi individual
Tentukan Read Concern pada tingkat transaksi. Pengaturan tingkat transaksi akan menimpa pengaturan atau default Read Concern lainnya.
Read Concern berlaku untuk setiap kueri database, baik baca dokumen tunggal, baca multi-dokumen, maupun bagian dari transaksi.
Gunakan level Read Concern "majority" untuk kasus penggunaan umum
Atur Read Concern ke "majority" untuk isolasi dan konsistensi. Ini memastikan aplikasi hanya membaca data yang telah direplikasi ke mayoritas node, mencegah rollback saat pemilihan primary.
Untuk skenario 'Read Your Own Writes', baca dari primary dan gunakan level Read Concern "local" atau "linearizable"
Baca dari node primary dengan Read Concern "local" atau "linearizable". Jika Write Concern adalah "majority", Anda juga dapat menggunakan Read Concern "majority".
MongoDB 3.6+ juga mendukung session konsisten kausal untuk skenario ini.
Untuk skenario yang memerlukan konsistensi terkuat, gunakan Read Concern "linearizable" dengan timeout maxTimeMS
Level "linearizable" memastikan node tersebut masih primary dan data yang dikembalikan tidak akan di-rollback. Namun, ini berdampak signifikan pada latensi. Gunakan dengan timeout maxTimeMS untuk mencegah blocking tak terbatas jika mayoritas node tidak tersedia.
Write Concern
Informasi dasar
Write Concern menggunakan format berikut. Referensi lengkap: Write Concern.
{ w: <value>, j: <boolean>, wtimeout: <number> }
-
Write Concern mengontrol jaminan persistensi data pada level berikut:
-
{w: 0}: Tanpa acknowledgment. Tidak mengonfirmasi penyelesaian; kehilangan data mungkin terjadi.
-
{w: 1}: Mengakui tulisan di memori. Default sebelum MongoDB 5.0. Kehilangan data masih mungkin terjadi karena data belum dipersist ke disk.
-
{j: true}: Acknowledgment jurnal. Mengonfirmasi tulisan telah di-flush ke write-ahead log (WAL) pada penyimpanan persisten. Tulisan tidak akan hilang.
-
{ w: "majority" }: Acknowledgment mayoritas. Default untuk MongoDB 5.0+. Menunggu tulisan direplikasi ke mayoritas node. Data tidak akan di-rollback.
-
Acknowledgment replica: Menunggu replikasi ke jumlah node tertentu sebelum mengakui.
-
Acknowledgment kustom: Anda dapat menggunakan parameter settings.getLastErrorModes untuk menentukan metode acknowledgment kustom lain menggunakan tag.
-
Catatan penggunaan:
-
Write Concern dapat ditentukan untuk setiap operasi tulis atau transaksi. Jika diabaikan, default akan digunakan.
CatatanDi MongoDB 5.0+, default global Write Concern untuk replica set standar beranggotakan tiga berubah dari
{w:1}menjadi{w:"majority"}. Hal ini dapat menyebabkan degradasi performa setelah upgrade. -
Node hidden, node delayed, dan node voting lain dengan priority 0 dalam replica set semuanya dapat dihitung untuk acknowledgment
"majority". -
Level Write Concern yang berbeda dapat ditentukan per operasi. MongoDB 4.4+ juga mendukung default di sisi server, yang dapat ditimpa oleh pengaturan tingkat operasi.
-
Saat Anda menulis ke database
local, Write Concern yang ditentukan diabaikan. -
Session konsisten kausal harus menggunakan Write Concern
"majority".
Praktik terbaik
Untuk transaksi terdistribusi, atur Write Concern pada tingkat transaksi, bukan pada operasi individual
Mengatur Write Concern untuk operasi tulis individual dalam transaksi akan mengembalikan error.
Gunakan Write Concern "majority" untuk kasus penggunaan umum
Write Concern "majority" memastikan mayoritas node replica set mengakui tulisan, mencegah kehilangan data atau rollback bahkan saat terjadi kegagalan node atau switchover tak terduga.
Untuk skenario intensif tulis, pertimbangkan penggunaan {w:1} dan pantau replikasi lag node secondary
{w:1} memberikan performa tulis lebih baik untuk skenario yang intensif tulis. Namun, pantau replikasi lag node secondary. Lag berlebihan dapat menyebabkan primary masuk ke status ROLLBACK. Jika lag melebihi periode retensi oplog, secondary masuk ke status RECOVERING yang memerlukan intervensi manual.
Saat melakukan pemuatan data batch atau migrasi DTS pada instans ApsaraDB for MongoDB yang menjalankan versi sebelum 5.0, gunakan Write Concern "majority" jika terjadi lag berlebihan.
Atur Write Concern yang paling sesuai untuk operasi berbeda
Write Concern dapat diatur per operasi. Misalnya, gunakan transaksi dengan Write Concern tertentu untuk data keuangan guna memastikan atomisitas, "majority" untuk data pemain inti guna mencegah rollback, dan default atau {w:1} untuk data log.
MongoDB menyediakan fleksibilitas ini agar aplikasi dapat memilih pengaturan yang sesuai berdasarkan kebutuhannya.