Sebagai mitra strategis MongoDB, Alibaba Cloud merupakan penyedia cloud pertama yang menawarkan MongoDB 4.4, yang tersedia sejak November 2020 setelah rilis resmi pada 30 Juli 2020. Berbeda dengan versi utama sebelumnya, rilis ini merupakan peningkatan komprehensif yang dirancang untuk mengatasi titik nyeri pengguna paling kritis.
Hidden indexes
Memelihara terlalu banyak index menurunkan performa write. Namun, kompleksitas operasional sering kali membuat administrator enggan menghapus index yang berpotensi tidak efisien karena khawatir penghapusan yang salah dapat menyebabkan jitter performa. Membangun ulang index juga merupakan operasi yang mahal.
Untuk mengatasi tantangan ini, ApsaraDB for MongoDB dan MongoDB bersama-sama mengembangkan fitur hidden index sebagai bagian dari kemitraan strategis mereka. Fitur ini memungkinkan Anda menyembunyikan index menggunakan perintah collMod, sehingga query planner tidak dapat menggunakannya. Setelah periode observasi mengonfirmasi bahwa tidak ada dampak negatif terhadap aplikasi Anda, Anda dapat menghapus index tersebut dengan aman.
Contoh:
db.runCommand( {
collMod: 'testcoll',
index: {
keyPattern: 'key_1',
hidden: false
}
} )
Perlu diperhatikan bahwa menyembunyikan index hanya membuatnya tidak terlihat oleh query planner MongoDB. Hal ini tidak mengubah perilaku khusus index tersebut, seperti batasan unique index atau kadaluarsa TTL.
Index tetap diperbarui selama disembunyikan. Jika Anda memerlukan kembali index tersebut, Anda dapat menampilkannya kembali agar segera tersedia.
Refinable shard keys
Dalam kluster sharded MongoDB, shard key yang dirancang dengan baik sangat penting untuk mencapai skalabilitas optimal di bawah workload tertentu. Namun dalam praktiknya, bahkan shard key yang dipilih dengan cermat pun dapat menjadi bermasalah seiring perubahan workload, yang mengakibatkan jumbo chunks (chunk yang melebihi ukuran yang telah ditentukan) atau menyebabkan sebagian besar traffic mengenai satu shard saja.
Pada MongoDB 4.0 dan versi sebelumnya, shard key koleksi dan nilai-nilainya bersifat immutable. Versi 4.2 memungkinkan Anda memodifikasi nilai shard key, tetapi prosesnya melibatkan migrasi data lintas shard berdasarkan transaksi terdistribusi, sehingga menimbulkan overhead performa tinggi tanpa sepenuhnya menyelesaikan masalah jumbo chunks atau query hotspots. Sebagai contoh, pertimbangkan tabel orders dengan shard key {customer_id:1}. Shard key ini mungkin cukup memadai saat bisnis baru dimulai dan setiap pelanggan memiliki sedikit order. Seiring pertumbuhan bisnis, pelanggan besar mungkin mengumpulkan banyak order, sehingga data pelanggan tersebut menjadi query hotspot pada satu shard. Karena order secara inheren terikat pada customer_id, hanya memodifikasi nilai customer_id tidak menyelesaikan masalah akses yang tidak merata.
Dalam skenario seperti ini, MongoDB 4.4 memungkinkan Anda menggunakan perintah refineCollectionShardKey untuk menambahkan satu atau beberapa field sufiks ke shard key yang sudah ada. Hal ini membantu meningkatkan distribusi dokumen di seluruh chunk. Dalam skenario pemrosesan order yang dijelaskan di atas, Anda dapat menggunakan perintah refineCollectionShardKey untuk mengubah shard key menjadi {customer_id:1, order_id:1}, yang mencegah query hotspot pada satu shard.
Perintah refineCollectionShardKey memiliki overhead performa yang sangat rendah karena hanya memodifikasi metadata pada node config server dan tidak memerlukan migrasi data apa pun. Redistribusi data terjadi secara bertahap melalui pemisahan dan migrasi chunk otomatis yang normal. Shard key harus didukung oleh index yang sesuai, sehingga perintah refineCollectionShardKey mengharuskan Anda membuat index untuk shard key baru terlebih dahulu.
Karena tidak semua dokumen mungkin berisi field sufiks baru, MongoDB 4.4 secara implisit mendukung field shard key yang tidak ada. Artinya, dokumen yang baru dimasukkan tidak perlu menyertakan semua field shard key. Namun, praktik ini tidak disarankan karena dapat dengan mudah menyebabkan jumbo chunks.
Compound hashed shard keys
Sebelum versi 4.4, Anda hanya dapat menentukan shard key hashed single-field karena MongoDB tidak mendukung compound hashed indexes. Hal ini sering menyebabkan distribusi data koleksi yang tidak merata di seluruh shard.
MongoDB 4.4 memperkenalkan dukungan untuk compound hashed indexes. Anda kini dapat menentukan satu field hashed dalam compound index. Field ini dapat muncul di posisi mana pun, sebagai awalan atau sufiks, yang pada gilirannya memungkinkan dukungan untuk compound hashed shard keys.
Contoh:
sh.shardCollection(
"examples.compoundHashedCollection",
{ "region_id" : 1, "city_id": 1, "field1" : "hashed" }
)
sh.shardCollection(
"examples.compoundHashedCollection",
{ "_id" : "hashed", "fieldA" : 1}
)
Compound hashed indexes menawarkan beberapa keunggulan. Pertimbangkan dua skenario berikut:
-
Untuk mematuhi persyaratan hukum atau regulasi, Anda menggunakan fitur zone sharding MongoDB untuk mendistribusikan data se-merata mungkin di seluruh shard dalam wilayah geografis tertentu.
-
Shard key koleksi memiliki nilai yang meningkat secara monoton. Misalnya, dengan key
{customer_id:1, order_id:1}, jikacustomer_idselalu meningkat dan aplikasi Anda sering mengakses data pelanggan terbaru, hal ini mengarahkan sebagian besar traffic ke satu shard.
Tanpa dukungan compound hashed shard keys, satu-satunya solusi adalah menghitung hash field yang diperlukan terlebih dahulu, menyimpan hasilnya dalam field terpisah dalam dokumen, lalu menggunakan ranged sharding pada field tersebut.
Pada versi 4.4, Anda dapat menyelesaikan masalah ini hanya dengan menentukan field target sebagai hashed. Untuk skenario kedua, menetapkan shard key menjadi {customer_id:'hashed', order_id:1} sangat menyederhanakan logika aplikasi Anda.
Hedged reads
Waktu respons halaman yang lambat dapat menyebabkan kerugian finansial. Laporan penelitian dari Google menunjukkan bahwa jika halaman membutuhkan waktu lebih dari tiga detik untuk dimuat, tingkat pantulan meningkat hingga 50%. Untuk mengatasi hal ini, MongoDB 4.4 memperkenalkan hedged reads. Dalam kluster sharded, node mongos dapat mengirim permintaan read ke dua anggota replica set dari satu shard dan mengembalikan respons pertama. Hal ini membantu mengurangi latensi P95 (persentil ke-95) dan P99 (persentil ke-99) untuk aplikasi Anda.
Fitur hedged read disediakan sebagai bagian dari parameter Read Preference dan dapat dikonfigurasi per operasi. Saat read preference diatur ke nearest, hedged reads diaktifkan secara default. Fitur ini tidak didukung saat preferensi diatur ke primary. Untuk mode read preference lainnya, Anda harus secara eksplisit mengaktifkan hedged reads dengan mengatur hedgeOptions, seperti yang ditunjukkan di bawah ini:
db.collection.find({ }).readPref(
"secondary", // mode
[ { "datacenter": "B" }, { } ], // tag set
{ enabled: true } // hedge options
)
)
Untuk menggunakan hedged reads, Anda juga harus mengaktifkan dukungan pada node mongos dengan mengatur parameter readHedgingMode ke on.
Contoh:
db.adminCommand( { setParameter: 1, readHedgingMode: "on" } )
Reduced replication latency
MongoDB 4.4 mengurangi latensi replikasi primary/secondary. Di MongoDB, latensi ini dapat berdampak signifikan terhadap operasi read dan write. Dalam skenario tertentu, node secondary harus segera mereplikasi dan menerapkan pembaruan inkremental dari node primary untuk terus memproses read dan write. Oleh karena itu, latensi replikasi yang lebih rendah memberikan konsistensi yang lebih baik.
Streaming replication
Pada versi sebelum 4.4, node secondary harus terus-menerus melakukan polling ke sumber upstream untuk mengambil pembaruan inkremental. Dalam setiap siklus polling, secondary mengirim perintah getMore ke primary untuk membaca Oplog. Jika data tersedia, primary mengembalikan batch hingga 16 MB. Jika tidak, secondary menggunakan opsi awaitData untuk mengurangi overhead getMore yang tidak perlu sekaligus tetap dapat mengambil entri Oplog baru segera setelah muncul. Operasi pull ini ditangani oleh satu thread OplogFetcher, dan setiap pengambilan batch memerlukan waktu round-trip penuh (RTT). Dalam replica set dengan kondisi jaringan buruk, latensi jaringan sangat membatasi performa replikasi.
Pada versi 4.4, entri Oplog inkremental dialirkan secara terus-menerus ke node secondary alih-alih ditarik. Dibandingkan metode polling, hal ini menghemat setidaknya separuh RTT untuk pengambilan Oplog. Streaming replication secara signifikan meningkatkan performa dalam dua skenario berikut:
-
Saat operasi write pengguna menentukan writeConcern
"majority", operasi tersebut harus menunggu acknowledgment dari mayoritas anggota replica set. Dengan mekanisme replikasi baru, performa writemajoritydapat meningkat rata-rata 50%, bahkan di lingkungan jaringan berlatensi tinggi. -
Saat pengguna menerapkan causal consistency untuk menjamin "read your writes", aplikasi bergantung pada replikasi tepat waktu entri Oplog dari primary ke node secondary.
Simultaneous indexing
Sebelum versi 4.4, node primary harus sepenuhnya membangun index sebelum node secondary dapat memulai proses tersebut. Metode pembuatan index pada secondary bervariasi antar versi, dengan dampak berbeda terhadap Oplog.
Bahkan pada versi 4.2, yang menyatukan pembuatan index foreground dan background serta menggunakan fine-grained locking (lock eksklusif hanya dipegang di awal dan akhir pembuatan), overhead CPU dan I/O dari pembuatan index masih dapat menyebabkan latensi replikasi. Operasi tertentu, seperti memodifikasi metadata koleksi dengan perintah collMod, dapat memblokir penerapan Oplog. Dalam skenario terburuk, secondary bisa tertinggal jauh sehingga entri Oplog pada primary ditimpa, memaksa secondary masuk ke status Recovering.
Pada versi 4.4, pembuatan index terjadi secara simultan pada node primary dan secondary. Hal ini secara dramatis mengurangi risiko latensi replikasi akibat hal ini dan memastikan node secondary dapat mengakses data terbaru bahkan selama pembuatan index.
Selain itu, mekanisme pembuatan index baru mensyaratkan mayoritas node voting berhasil menyelesaikan pembuatan sebelum index dapat digunakan. Hal ini juga membantu mengurangi perbedaan performa dalam skenario Pemisahan baca/tulis yang dapat disebabkan oleh ketersediaan index yang berbeda di tiap node.
Mirrored reads
Pola umum yang diamati di ApsaraDB for MongoDB adalah banyak pengguna dengan instans replica set tiga node mengarahkan semua operasi read dan write ke node primary. Salah satu node secondary tetap idle dan tidak menangani traffic read sama sekali. Selama failover sesekali, pengguna mengalami peningkatan latensi aplikasi yang signifikan, yang baru kembali normal setelah beberapa waktu. Hal ini terjadi karena node primary yang baru terpilih belum pernah melayani read sebelumnya, sehingga cache-nya dingin. Node tersebut tidak mengetahui pola akses aplikasi dan belum menyimpan data yang relevan di cache. Akibatnya, operasi read memicu banyak cache miss, memerlukan pembacaan dari disk, dan meningkatkan latensi akses. Masalah ini terutama terasa pada instans dengan memori besar.
Untuk mengatasi masalah ini, MongoDB 4.4 memperkenalkan fitur mirrored read. Node primary dapat mencerminkan sebagian traffic read-nya yang dapat dikonfigurasi ke node secondary untuk membantu memanaskan cache secondary sebelumnya. Ini merupakan aksi non-blocking "fire-and-forget" yang tidak berdampak nyata pada performa node primary, meskipun sedikit meningkatkan beban pada node secondary.
Persentase traffic yang dicerminkan dapat dikonfigurasi secara dinamis melalui parameter mirrorReads. Secara default, 1% traffic dicerminkan.
Contoh:
db.adminCommand( { setParameter: 1, mirrorReads: { samplingRate: 0.10 } } )
Anda juga dapat melihat statistik terkait mirrored reads dengan menggunakan perintah db.serverStatus( { mirroredReads: 1 } ), seperti yang ditunjukkan di bawah ini:
SECONDARY> db.serverStatus( { mirroredReads: 1 } ).mirroredReads
{ "seen" : NumberLong(2), "sent" : NumberLong(0) }
Resumable initial sync
Pada versi sebelum 4.4, jika fluktuasi jaringan menyebabkan koneksi terputus saat node secondary melakukan initial sync, secondary harus memulai ulang seluruh proses dari awal. Untuk dataset besar, hal ini dapat membuang waktu berjam-jam dan berdampak signifikan terhadap operasi.
Pada versi 4.4, MongoDB memungkinkan node secondary melanjutkan proses initial sync dari titik terakhir terputus. Jika koneksi tidak dapat dibangun kembali setelah error transient, sistem memilih sumber sync baru dan memulai initial sync baru. Timeout default untuk upaya retry adalah 24 jam, yang dapat Anda ubah saat startup dengan menggunakan parameter replication.initialSyncTransientErrorRetryPeriodSeconds.
Perlu diperhatikan bahwa untuk gangguan yang disebabkan oleh error non-transient selama initial sync, proses sync penuh tetap harus dimulai ulang dari awal.
Time-based oplog retention
Oplog di MongoDB mencatat semua operasi modifikasi data. Oplog tidak hanya digunakan untuk replikasi, tetapi juga untuk skenario seperti cadangan inkremental, migrasi data, dan langganan data, sehingga menjadi bagian kritis dalam ekosistem data MongoDB.
Oplog diimplementasikan sebagai Capped Collection. Meskipun MongoDB telah mendukung pengubahan ukuran Oplog secara dinamis dengan perintah replSetResizeOplog sejak versi 3.6, retensi berbasis ukuran sering kali tidak secara akurat mencerminkan kebutuhan berbasis waktu dari konsumen downstream. Pertimbangkan skenario berikut:
-
Anda berencana melakukan maintenance pada node secondary dari pukul 02.00 hingga 04.00. Anda perlu memastikan Oplog pada primary upstream tidak dihapus selama periode ini, yang akan memicu resinkronisasi penuh.
-
Komponen langganan data downstream mungkin berhenti karena error tetapi diharapkan pulih dan melanjutkan pengambilan data dalam waktu tiga jam. Anda perlu mencegah hilangnya data inkremental dari sumber upstream.
Sebagian besar skenario aplikasi memerlukan retensi Oplog untuk periode tertentu. Namun, menentukan berapa banyak data Oplog yang akan dihasilkan selama periode tersebut sulit dilakukan.
Pada versi 4.4, MongoDB memungkinkan Anda menentukan periode retensi minimum untuk entri Oplog dengan menggunakan parameter storage.oplogMinRetentionHours. Anda juga dapat mengubah nilai ini secara online dengan menggunakan perintah replSetResizeOplog. Contoh:
// Pertama, tampilkan nilai yang dikonfigurasi saat ini
db.getSiblingDB("admin").serverStatus().oplogTruncation.oplogMinRetentionHours
// Modifikasi
db.adminCommand({
"replSetResizeOplog" : 1,
"minRetentionHours" : 2
})
Union
Untuk kueri multi-koleksi, versi sebelum 4.4 hanya menawarkan $lookup stage, yang mirip dengan left outer join di SQL. Versi 4.4 memperkenalkan $unionWith stage, yang berfungsi seperti union all di SQL. Fitur ini menggabungkan data dari dua atau lebih koleksi menjadi satu set hasil yang kemudian dapat Anda kueri dan filter lebih lanjut. Berbeda dengan $lookup stage, $unionWith stage mendukung koleksi sharded. Anda dapat menggunakan beberapa $unionWith stage dalam aggregation pipeline untuk mengagregasi data dari beberapa koleksi. Sintaksnya adalah sebagai berikut:
{ $unionWith: { coll: "<collection>", pipeline: [ <stage1>, ... ] } }
Anda juga dapat menentukan pipeline dalam stage $unionWith untuk memfilter atau mentransformasi data dari koleksi lain sebelum penggabungan. Hal ini memberikan fleksibilitas tinggi. Misalnya, bayangkan bisnis menyimpan data ordernya dalam koleksi terpisah untuk setiap bulan. Data untuk kuartal kedua tampak seperti ini:
db.orders_april.insertMany([
{ _id:1, item: "A", quantity: 100 },
{ _id:2, item: "B", quantity: 30 },
]);
db.orders_may.insertMany([
{ _id:1, item: "C", quantity: 20 },
{ _id:2, item: "A", quantity: 50 },
]);
db.orders_june.insertMany([
{ _id:1, item: "C", quantity: 100 },
{ _id:2, item: "D", quantity: 10 },
]);
Anggaplah Anda perlu mencantumkan total penjualan untuk setiap produk di kuartal kedua. Sebelum versi 4.4, Anda harus membaca semua data ke dalam aplikasi dan melakukan agregasi di sana, atau mengandalkan data warehouse. Pada versi 4.4, Anda dapat menyelesaikannya dengan satu kueri agregasi:
db.orders_april.aggregate( [
{ $unionWith: "orders_may" },
{ $unionWith: "orders_june" },
{ $group: { _id: "$item", total: { $sum: "$quantity" } } },
{ $sort: { total: -1 }}
] )
Custom aggregation expressions
Sebelum versi 4.4, Anda dapat mengeksekusi JavaScript kustom di server untuk melakukan kueri kompleks dengan menggunakan $where operator dalam perintah find atau dengan menggunakan fitur MapReduce. Namun, fitur-fitur ini tidak terintegrasi dengan aggregation pipeline.
Pada versi 4.4, MongoDB memperkenalkan operator $accumulator dan $function untuk aggregation pipeline. Operator-operator ini menggantikan $where operator dan MapReduce. Operator ini memungkinkan Anda menentukan ekspresi agregasi kustom menggunakan JavaScript sisi server. Hal ini mengkonsolidasikan fungsionalitas kueri kompleks ke dalam aggregation pipeline, yang meningkatkan konsistensi API dan pengalaman pengguna sekaligus memanfaatkan model eksekusi pipeline.
Operator $accumulator mirip dengan MapReduce. Operator ini pertama-tama menentukan state awal menggunakan fungsi init, lalu memperbarui state untuk setiap dokumen input menggunakan fungsi accumulate yang ditentukan. Jika diperlukan, operator ini juga mengeksekusi fungsi merge.
Sebagai contoh, jika Anda menggunakan operator $accumulator pada koleksi sharded, hasil dari shard berbeda harus merged. Jika fungsi finalize ditentukan, fungsi tersebut akan diterapkan setelah semua dokumen input diproses untuk mengubah state menjadi output akhir.
Operator $function dan $where operator memiliki fungsionalitas yang hampir sama. Namun, operator $function lebih kuat karena dapat digunakan dengan operator aggregation pipeline lainnya. Selain itu, Anda dapat menggunakan operator $function dalam perintah find dengan operator $expr. Hal ini setara dengan operator $where. Dalam dokumentasi resminya, MongoDB juga merekomendasikan agar Anda memprioritaskan penggunaan operator $function.
Other usability enhancements
Selain operator $accumulator dan $function, MongoDB 4.4 menambahkan beberapa operator aggregation pipeline baru lainnya. Operator-operator ini mencakup manipulasi string, mendapatkan elemen pertama dan terakhir array, serta mendapatkan ukuran dokumen atau string biner. Lihat tabel di bawah ini untuk detailnya:
|
Operator |
Description |
|
$accumulator |
Mengembalikan hasil dari operator accumulator yang ditentukan pengguna. |
|
$binarySize |
Mengembalikan ukuran string atau data biner yang ditentukan dalam byte. |
|
$bsonSize |
Mengembalikan ukuran dokumen yang dikodekan BSON dalam byte. |
|
$first |
Mengembalikan elemen pertama dalam array. |
|
$function |
Menentukan ekspresi agregasi kustom. |
|
$last |
Mengembalikan elemen terakhir dalam array. |
|
$isNumber |
Mengembalikan |
|
$replaceOne |
Mengganti kemunculan pertama substring yang sesuai dengan pola yang ditentukan. |
|
$replaceAll |
Mengganti semua kemunculan substring yang sesuai dengan pola yang ditentukan. |
Connection monitoring and pooling
Driver untuk MongoDB 4.4 menambahkan kemampuan untuk memantau dan mengonfigurasi perilaku connection pool sisi klien. Anda dapat menggunakan API standar untuk berlangganan event terkait connection pool, seperti koneksi yang dibuka atau ditutup, dan pool yang dihapus. Anda juga dapat menggunakan API untuk mengonfigurasi perilaku connection pool, seperti jumlah maksimum dan minimum koneksi, waktu idle maksimum untuk setiap koneksi, dan timeout untuk thread yang menunggu koneksi yang tersedia. Untuk detail lebih lanjut, lihat dokumentasi resmi MongoDB.
Global read and write concerns
Pada versi sebelum 4.4, jika suatu operasi tidak secara eksplisit menentukan readConcern atau writeConcern, MongoDB menerapkan perilaku default. Misalnya, readConcern default ke local, dan writeConcern default ke {w: 1}. Anda tidak dapat mengubah perilaku default ini. Jika Anda ingin semua operasi insert default ke writeConcern {w: "majority"}, Anda harus secara eksplisit menentukannya dalam kode aplikasi untuk setiap operasi.
Pada versi 4.4, Anda dapat menggunakan perintah setDefaultRWConcern untuk mengonfigurasi readConcern dan writeConcern default global. Contoh:
db.adminCommand({
"setDefaultRWConcern" : 1,
"defaultWriteConcern" : {
"w" : "majority"
},
"defaultReadConcern" : { "level" : "majority" }
})
Anda juga dapat menggunakan perintah getDefaultRWConcern untuk mengambil readConcern dan writeConcern default saat ini.
Selain itu, MongoDB 4.4 mencatat sumber, atau provenance, pengaturan readConcern atau writeConcern untuk suatu operasi dalam slow query log dan diagnostic log. Provenance umum untuk keduanya adalah:
|
Provenance |
Description |
|
clientSupplied |
Ditentukan oleh aplikasi. |
|
customDefault |
Ditentukan oleh pengguna dengan perintah |
|
implicitDefault |
Default server, digunakan saat tidak ada konfigurasi lain yang ditetapkan. |
writeConcern juga memiliki satu provenance tambahan:
|
Provenance |
Description |
|
getLastErrorDefaults |
Diwariskan dari konfigurasi |
New MongoDB Shell (beta)
MongoDB Shell adalah salah satu alat yang paling sering digunakan oleh administrator MongoDB. Versi 4.4 memperkenalkan versi shell baru yang mencakup fitur ramah pengguna seperti syntax highlighting, auto-completion cerdas, dan pesan error yang lebih mudah dibaca. Rilis beta ini memiliki beberapa perintah yang tidak didukung dan ditujukan untuk uji coba dan umpan balik.
function topActors(howMany = 5) {
return db.movies.aggregate([
{ $unwind: '$cast'},
{ $group: {_id: '$cast', movieCount: { $sum: 1 } } },
{ $sort: {movieCount: -1} },
{ $limit: howMany }
])
}
> topActors(3)
[
{ _id: 'Gérard Depardieu', movieCount: 68 },
{ _id: 'Robert De Niro', movieCount: 60 },
{ _id: 'Michael Caine', movieCount: 53 }
]
> db.movies.fnd()
TypeError: db.movies.fnd is not a function
> db.movies.find.help()
db.collection.find(query, projection):
Selects documents in a collection or view.
For more information on usage: https://docs.mongodb.com/manual/reference/method/db.collection.find
> db.movies.find({year: {$gt: 2016}})
Conclusion
Rilis 4.4 terutama merupakan versi maintenance yang membawa banyak peningkatan. Selain fitur-fitur yang dibahas, terdapat banyak optimasi kecil lainnya, seperti peningkatan pada $indexStats, dukungan untuk TCP Fast Open guna mempercepat pembentukan koneksi, dan penghapusan index yang dioptimalkan. Terdapat juga peningkatan besar, seperti format pencatatan terstruktur baru LogV2 dan mekanisme keamanan baru. Untuk detail lebih lanjut, lihat Release Notes resmi.