Saat sebuah instans ApsaraDB for MongoDB berisi sejumlah besar koleksi, performanya menurun dan terjadi pengecualian. Mesin penyimpanan WiredTiger membuat file disk terpisah untuk setiap koleksi dan setiap indeks, serta setiap resource yang dibuka menggunakan handle data (dhandle) terkait di memori. Dengan ribuan koleksi, volume dhandle yang terbuka menyebabkan kontensi lock yang memperlambat semua operasi database.
Jumlah koleksi yang besar tidak selalu menimbulkan masalah. Dampaknya bergantung pada model bisnis dan workload Anda. Misalnya, dua instans dengan spesifikasi yang sama, 10.000 koleksi, dan 100.000 dokumen dapat berperilaku sangat berbeda:
Perangkat lunak akuntansi: Sebagian besar koleksi menyimpan data dingin dan jarang diakses. Dampak terhadap performa minimal.
Sistem multi-tenant: Koleksi diisolasi per tenant dan sebagian besar aktif digunakan. Kontensi lock sangat parah.
Cara kerjanya
Mesin penyimpanan WiredTiger membuat file disk terpisah untuk setiap koleksi dan setiap indeks. Setiap resource yang dibuka menggunakan struktur data unik bernama dhandle, yang melacak informasi checkpoint, referensi sesi, pointer ke struktur Pohon-B di memori, serta statistik data.
Saat jumlah koleksi meningkat, lebih banyak file sistem operasi dibuka dan lebih banyak dhandle terakumulasi di memori. Jumlah dhandle yang besar di memori menyebabkan kontensi lock, yang menurunkan performa instans.
Potensi masalah
Kueri lambat dan latensi tinggi akibat handle lock atau schema lock
Error kehabisan memori (OOM) selama inisialisasi sinkronisasi saat menambahkan node
Waktu restart instans yang lebih lama
Sinkronisasi data yang lebih lambat
Operasi backup dan restore yang lebih lambat
Tingkat kegagalan backup fisik yang lebih tinggi
Waktu pemulihan instans dari kegagalan yang lebih lama
Mendiagnosis kueri lambat
Saat kontensi lock akibat terlalu banyak koleksi menyebabkan kueri lambat, log kueri lambat berisi entri seperti berikut:
2024-03-07T15:59:16.856+0800 I COMMAND [conn4175155] command db.collections command: count { count: "xxxxxx", query: { A: 1, B: 1 },
$readPreference: { mode: "secondaryPreferred" }, $db: "db" } planSummary: COLLSCAN keysExamined:0 keysExaminedBySizeInBytes:0
docsExamined:1 docsExaminedBySizeInBytes:208 numYields:1 queryHash:916BD9E3 planCacheKey:916BD9E3 reslen:185
locks:{ ReplicationStateTransition: { acquireCount: { w: 2 } }, Global: { acquireCount: { r: 2 } }, Database: { acquireCount: { r: 2 } },
Collection: { acquireCount: { r: 2 } }, Mutex: { acquireCount: { r: 1 } } } storage:{ data: { bytesRead: 304, timeReadingMicros: 4 },
timeWaitingMicros: { handleLock: 40, schemaLock: 134101710 } } protocol:op_query 134268msDalam contoh ini, operasi count pada koleksi berisi satu dokumen memakan waktu 134.268 ms. Bidang kuncinya adalah timeWaitingMicros: { handleLock: 40, schemaLock: 134101710 }, yang menunjukkan bahwa permintaan baca menghabiskan hampir seluruh waktunya menunggu untuk mendapatkan handle lock dan schema lock di lapisan penyimpanan—bukan melakukan pekerjaan aktual.
Tabel berikut menjelaskan bidang-bidang utama dalam entri log kueri lambat:
| Bidang | Deskripsi |
|---|---|
planSummary | Rencana kueri yang digunakan. COLLSCAN menunjukkan pemindaian penuh koleksi tanpa indeks; IXSCAN menunjukkan pemindaian indeks. |
keysExamined | Jumlah kunci indeks yang dipindai. |
docsExamined | Jumlah dokumen yang dipindai. Nilai tinggi relatif terhadap jumlah hasil yang dikembalikan mengindikasikan indeks yang hilang atau suboptimal. |
numYields | Jumlah kali operasi melepas lock-nya agar operasi lain dapat dilanjutkan. |
timeWaitingMicros.handleLock | Waktu yang dihabiskan menunggu untuk mendapatkan handle lock, dalam mikrodetik. |
timeWaitingMicros.schemaLock | Waktu yang dihabiskan menunggu untuk mendapatkan schema lock, dalam mikrodetik. Nilai tinggi merupakan indikator kuat adanya terlalu banyak dhandle yang terbuka. |
protocol:op_query <N>ms | Durasi total operasi dalam milidetik. |
Gunakan perintah berikut untuk mendiagnosis jumlah koleksi dan indeks sebelum mengambil tindakan:
// Menghitung jumlah koleksi dalam sebuah database
db.getSiblingDB(<dbName>).getCollectionNames().length
// Menampilkan statistik database (jumlah koleksi, jumlah indeks, jumlah dokumen, ukuran total)
db.getSiblingDB(<dbName>).stats()
// Menampilkan statistik untuk koleksi tertentu
db.getSiblingDB(<dbName>).<collectionName>.stats()Metode optimasi
Pilih metode yang paling sesuai dengan situasi Anda. Mulailah dengan opsi yang paling tidak mengganggu terlebih dahulu.
Hapus koleksi yang tidak diperlukan
Identifikasi koleksi yang sudah kedaluwarsa atau tidak lagi diperlukan, lalu hapus menggunakan dropCollection. Untuk informasi lebih lanjut, lihat dropCollection().
Pastikan cadangan penuh tersedia sebelum menghapus koleksi apa pun.
Hapus indeks yang tidak diperlukan
Setiap indeks membuat file disk terpisah di WiredTiger dan menambahkan dhandle terkait. Mengurangi jumlah indeks secara langsung mengurangi tekanan dhandle.
Gunakan tahap agregasi $indexStats untuk mengidentifikasi indeks yang jarang digunakan. Jalankan perintah berikut sebelum melakukan perubahan (memerlukan izin yang sesuai):
// Menampilkan statistik akses untuk semua indeks dalam sebuah koleksi
db.getSiblingDB(<dbName>).<collectionName>.aggregate({"$indexStats":{}})Contoh output:
{
"name" : "item_1_quantity_1",
"key" : { "item" : 1, "quantity" : 1 },
"host" : "examplehost.local:27018",
"accesses" : {
"ops" : NumberLong(1),
"since" : ISODate("2020-02-10T21:11:23.059Z")
}
}Tabel berikut menjelaskan bidang-bidang utama dalam output:
| Bidang | Deskripsi |
|---|---|
name | Nama indeks |
key | Detail kunci indeks |
accesses.ops | Jumlah operasi yang menggunakan indeks (jumlah hit). Nilai ini direset saat instans direstart atau indeks dibangun ulang. |
accesses.since | Timestamp saat pengumpulan statistik dimulai |
Terapkan aturan berikut saat memutuskan indeks mana yang harus dihapus:
Indeks tidak valid: Hapus indeks pada bidang yang tidak diakses oleh kueri apa pun.
Redundansi awalan indeks: Jika indeks
{a:1}dan{a:1,b:1}keduanya ada, maka{a:1}bersifat redundan—indeks{a:1,b:1}mencakup semua kueri yang akan menggunakannya.Urutan kueri ekuivalen: Jika indeks
{a:1,b:1}dan{b:1,a:1}keduanya ada, hapus yang memiliki jumlah hit lebih sedikit. Untuk pencocokan ekuivalen, urutan bidang tidak memengaruhi hasil.Aturan ESR untuk kueri rentang: Bangun indeks gabungan dalam urutan Equality, Sort, Range. Untuk informasi lebih lanjut, lihat Aturan ESR (Equality, Sort, Range).
Indeks dengan hit rendah: Indeks semacam ini sering kali diduplikasi oleh indeks dengan hit lebih tinggi. Evaluasi terhadap semua pola kueri sebelum menghapusnya.
Jika instans Anda menjalankan MongoDB 4.4 atau versi lebih baru, gunakan db.collection.hideIndex() untuk menyembunyikan indeks sebelum menghapusnya. Pantau instans selama periode tertentu untuk memastikan tidak ada kueri yang bergantung pada indeks tersebut sebelum menghapusnya secara permanen. Untuk informasi lebih lanjut, lihat db.collection.hideIndex().
Contoh: mengoptimalkan indeks dalam koleksi players
Koleksi players menyimpan data pemain dengan aturan bisnis berikut: setiap 20 coins secara otomatis dikonversi menjadi satu star.
// Struktur dokumen koleksi players
{
"_id": "ObjectId(123)",
"first_name": "John",
"last_name": "Doe",
"coins": 11,
"stars": 2
}Koleksi ini saat ini memiliki indeks berikut:
_id(default){ last_name: 1 }{ last_name: 1, first_name: 1 }{ coins: -1 }{ stars: -1 }
Dengan menerapkan aturan di atas:
Hapus `{ coins: -1 }`: Tidak ada kueri yang mengakses bidang
coinssecara langsung.Hapus `{ last_name: 1 }`: Indeks ini merupakan awalan dari
{ last_name: 1, first_name: 1 }sehingga bersifat redundan.Pertahankan `{ stars: -1 }`: Meskipun
$indexStatsmenunjukkan jumlah hit rendah, leaderboard akhir putaran memerlukan pengurutan pemain berdasarkanstarsdalam urutan menurun. Menghapusnya akan memaksa pemindaian penuh koleksi.
Setelah optimasi, koleksi mempertahankan tiga indeks: _id, { last_name: 1, first_name: 1 }, dan { stars: -1 }. Hal ini mengurangi konsumsi penyimpanan dan meningkatkan performa penulisan.
Untuk pertanyaan lebih lanjut mengenai optimasi indeks, ajukan tiket.
Integrasikan data dari beberapa koleksi
Saat koleksi bertambah seiring waktu karena pola partisi berbasis waktu, menggabungkannya ke dalam satu koleksi menghilangkan masalah akumulasi tersebut.
Sebelum optimasi — database temperatures menyimpan pembacaan harian dalam koleksi terpisah:
// temperatures.march-09-2020
{ "_id": 1, "timestamp": "2020-03-09T010:00:00Z", "temperature": 29 }
{ "_id": 2, "timestamp": "2020-03-09T010:30:00Z", "temperature": 30 }
// ... total 25 pembacaan (sensor berjalan pukul 10:00–22:00, setiap 30 menit)
{ "_id": 25, "timestamp": "2020-03-09T022:00:00Z", "temperature": 26 }
// temperatures.march-10-2020
{ "_id": 1, "timestamp": "2020-03-10T010:00:00Z", "temperature": 30 }
// ...Setiap hari baru membuat koleksi baru dan indeks _id baru. Kueri lintas hari memerlukan $lookup, yang performanya lebih buruk daripada kueri pada satu koleksi.
Setelah optimasi — satu koleksi temperatures.readings menyimpan semua data, satu dokumen per hari. Indeks default _id mendukung kueri berbasis tanggal tanpa perlu indeks tambahan untuk pembacaan setiap hari:
// temperatures.readings
{
"_id": ISODate("2020-03-09"),
"readings": [
{ "timestamp": "2020-03-09T010:00:00Z", "temperature": 29 },
{ "timestamp": "2020-03-09T010:30:00Z", "temperature": 30 },
// ...
{ "timestamp": "2020-03-09T022:00:00Z", "temperature": 26 }
]
}
{
"_id": ISODate("2020-03-10"),
"readings": [
{ "timestamp": "2020-03-10T010:00:00Z", "temperature": 30 },
{ "timestamp": "2020-03-10T010:30:00Z", "temperature": 32 },
// ...
{ "timestamp": "2020-03-10T022:00:00Z", "temperature": 28 }
]
}Hal ini menghilangkan masalah pertumbuhan koleksi tak terbatas dan menghapus kebutuhan untuk membuat indeks setiap hari.
Untuk workload deret waktu, pertimbangkan penggunaan koleksi deret waktu (MongoDB 5.0 dan versi lebih baru). Untuk informasi lebih lanjut, lihat Deret Waktu.
Pisahkan instans
Jika jumlah total koleksi dalam instans mandiri ApsaraDB for MongoDB tidak dapat dikurangi, bagi data tersebut ke beberapa instans.
| Skenario | Solusi pemisahan | Langkah utama |
|---|---|---|
| Koleksi tersebar di beberapa database | Jika beberapa aplikasi atau layanan berbagi instans yang sama dan database mereka tidak saling terkait erat, migrasikan beberapa database ke instans ApsaraDB for MongoDB baru menggunakan Data Transmission Service (DTS). Untuk informasi lebih lanjut, lihat Migrasi data dari instans set replika ApsaraDB for MongoDB ke instans set replika atau kluster sharded ApsaraDB for MongoDB. | Pilih source databases yang diperlukan saat membuat tugas DTS. Pertahankan atau ubah nama koleksi sesuai kebutuhan. Jalankan dropDatabase pada instans sumber setelah migrasi selesai. |
| Semua koleksi berada dalam satu database | Tentukan apakah koleksi dapat dipisahkan berdasarkan dimensi seperti wilayah, kota, atau prioritas. Gunakan DTS untuk memigrasikan subset koleksi ke instans ApsaraDB for MongoDB terpisah. | Pilih source database yang diperlukan saat membuat tugas DTS. Jalankan perintah drop untuk menghapus koleksi yang telah dimigrasikan dari sumber. Kueri agregasi lintas instans memerlukan logika aplikasi tambahan. |
Perbarui logika bisnis dan konfigurasi koneksi aplikasi Anda sebelum menyelesaikan migrasi.
Contoh: memisahkan platform multi-tenant
Platform manajemen multi-tenant menggunakan satu koleksi per tenant. Saat jumlah tenant melebihi 100.000, ukuran database mencapai terabyte dan kueri menjadi lambat.
Aplikasi membagi tenant berdasarkan wilayah: Tiongkok Utara, Tiongkok Timur Laut, Tiongkok Timur, Tiongkok Tengah, Tiongkok Selatan, Tiongkok Barat Daya, dan Tiongkok Barat Laut. DTS memigrasikan tenant ke instans ApsaraDB for MongoDB terpisah yang ditempatkan di wilayah yang sesuai. Data juga disinkronkan ke gudang data untuk agregasi dan analisis lintas wilayah.
Setelah pemisahan:
Setiap instans hanya menyimpan sebagian kecil dari jumlah koleksi awal, sehingga spesifikasi instans dapat dikurangi.
Permintaan dilayani oleh instans terdekat, mengurangi latensi hingga milidetik.
Kompleksitas operasi dan pemeliharaan per instans berkurang secara signifikan.
Migrasi ke kluster sharded menggunakan tag shard
Jika semua koleksi harus tetap berada dalam satu instans logis dan tidak dapat dikurangi, migrasikan ke instans kluster sharded dan gunakan tag shard untuk mengikat setiap koleksi ke shard tertentu. Hal ini mendistribusikan beban dhandle ke beberapa shard tanpa memerlukan perubahan aplikasi—hanya string koneksi yang berubah.
Sebagai contoh, 100.000 koleksi aktif yang dimigrasikan ke kluster 10-shard menghasilkan sekitar 10.000 koleksi per shard.
Untuk informasi lebih lanjut, lihat sh.addShardTag() dan sh.addTagRange().
Langkah-langkah
Beli instans kluster sharded. Untuk informasi lebih lanjut, lihat Buat instans kluster sharded.
Hubungkan ke node mongos dari instans kluster sharded. Untuk informasi lebih lanjut, lihat Hubungkan ke instans kluster sharded ApsaraDB for MongoDB menggunakan mongo shell.
Tambahkan tag shard ke setiap shard:
Akun harus memiliki izin yang diperlukan untuk menjalankan perintah ini. Data Management (DMS) tidak mendukung
sh.addShardTag—gunakan mongo shell atau mongosh sebagai gantinya.sh.addShardTag("d-xxxxxxxxx1", "shard_tag1") sh.addShardTag("d-xxxxxxxxx2", "shard_tag2")Pra-konfigurasikan rentang tag untuk mengikat setiap koleksi ke satu shard. Gunakan
[MinKey, MaxKey]untuk memastikan semua data dalam koleksi tetap berada di satu shard:use <dbName> sh.enableSharding("<dbName>") sh.addTagRange("<dbName>.test", {"_id": MinKey}, {"_id": MaxKey}, "shard_tag1") sh.addTagRange("<dbName>.test1", {"_id": MinKey}, {"_id": MaxKey}, "shard_tag2")Ganti
_iddengan kunci shard aktual Anda. Semua kueri harus menyertakan bidang kunci shard.Shard setiap koleksi:
sh.shardCollection("<dbName>.test", {"_id": 1}) sh.shardCollection("<dbName>.test1", {"_id": 1})Jalankan
sh.status()untuk memastikan aturan tag berlaku.
Migrasikan data ke instans kluster sharded. Untuk informasi lebih lanjut, lihat Migrasi data dari instans set replika ApsaraDB for MongoDB ke instans set replika atau kluster sharded ApsaraDB for MongoDB.
Karena Anda telah pra-konfigurasi koleksi shard pada langkah 5, metadata koleksi sudah ada di target. Atur parameter Processing Mode of Conflicting Tables menjadi Ignore Errors and Proceed dalam tugas DTS.
Setelah konsistensi data diverifikasi, alihkan aplikasi Anda ke instans kluster sharded.
Untuk menambahkan shard di kemudian hari, ulangi langkah 3 untuk menetapkan tag ke shard baru.
Jika koleksi baru dibuat setelah migrasi, ulangi langkah 4 dan 5 untuk setiap koleksi baru. Jika tidak, koleksi hanya akan ada di shard utama, yang menyebabkan jumlah koleksi di shard tersebut meningkat. Dalam kasus ini, instans Anda akan selalu dalam kondisi tersendat atau mengalami pengecualian.
Migrasi ke kluster sharded menggunakan zona
Zona bekerja sama seperti tag shard, tetapi menggunakan perintah yang lebih baru: sh.addShardToZone() dan sh.updateZoneKeyRange(). Untuk informasi lebih lanjut, lihat Kelola Zona Shard, sh.addShardToZone(), dan sh.updateZoneKeyRange().
Langkah-langkah
Beli instans kluster sharded. Untuk informasi lebih lanjut, lihat Buat instans kluster sharded.
Hubungkan ke node mongos dari instans kluster sharded. Untuk informasi lebih lanjut, lihat Hubungkan ke instans kluster sharded ApsaraDB for MongoDB menggunakan mongo shell.
Tetapkan zona ke setiap shard:
Akun harus memiliki izin yang diperlukan. Data Management (DMS) tidak mendukung
sh.addShardToZone—gunakan mongo shell atau mongosh sebagai gantinya.sh.addShardToZone("d-xxxxxxxxx1", "ZoneA") sh.addShardToZone("d-xxxxxxxxx2", "ZoneB")Pra-konfigurasikan rentang kunci zona untuk mengikat setiap koleksi ke satu zona:
use <dbName> sh.enableSharding("<dbName>") sh.updateZoneKeyRange("<dbName>.test", { "_id": MinKey }, { "_id": MaxKey }, "ZoneA") sh.updateZoneKeyRange("<dbName>.test1", { "_id": MinKey }, { "_id": MaxKey }, "ZoneB")Ganti
_iddengan kunci shard aktual Anda.Shard setiap koleksi:
sh.shardCollection("<dbName>.test", { _id: "hashed" }) sh.shardCollection("<dbName>.test1", { _id: "hashed" })Jalankan
sh.status()untuk memastikan aturan zona berlaku.
Migrasikan data ke instans kluster sharded. Untuk informasi lebih lanjut, lihat Migrasi data dari instans set replika ApsaraDB for MongoDB ke instans set replika atau kluster sharded ApsaraDB for MongoDB.
Atur parameter Processing Mode of Conflicting Tables menjadi Ignore Errors and Proceed dalam tugas DTS.
Setelah konsistensi data diverifikasi, alihkan aplikasi Anda ke instans kluster sharded.
Untuk menambahkan shard di kemudian hari, ulangi langkah 3 untuk menetapkan zona ke shard baru.
Jika koleksi baru dibuat setelah migrasi, ulangi langkah 4 dan 5 untuk setiap koleksi baru. Jika tidak, koleksi hanya akan ada di shard utama, yang menyebabkan jumlah koleksi di shard tersebut meningkat. Dalam kasus ini, instans Anda akan selalu dalam kondisi tersendat atau mengalami pengecualian.
Risiko
Hindari menjalankan dropDatabase pada database dengan banyak koleksi
Saat dropDatabase dijalankan, WiredTiger secara asinkron menghapus metadata dan file fisik semua koleksi dalam database tersebut. Hal ini dapat memengaruhi replikasi primary-to-secondary, menyebabkan lag replikasi terus meningkat. Mekanisme flow control MongoDB diaktifkan selama proses ini, dan semua operasi tulis dengan {writeConcern: majority} mungkin terpengaruh.
Untuk menghindari masalah ini, gunakan salah satu pendekatan berikut:
Hapus koleksi satu per satu dengan jeda antar penghapusan, lalu jalankan
dropDatabasesetelah semua koleksi dihapus.Gunakan DTS atau alat migrasi lain untuk memigrasikan database dan koleksi yang ingin dipertahankan ke instans baru, lalu nonaktifkan instans asli setelah migrasi selesai.
Konfigurasikan peringatan lag replikasi pada instans Anda. Jika masalah ini terjadi, ajukan tiket untuk dukungan teknis.ajukan tiketajukan tiket
Ringkasan
Jumlah total koleksi dalam instans set replika tidak boleh melebihi 10.000. Jika jumlah total indeks dalam satu koleksi melebihi 15, kurangi jumlah tersebut.
Jika bisnis Anda memerlukan banyak koleksi—seperti isolasi multi-tenant berbasis koleksi—gunakan instans kluster sharded untuk mendistribusikan beban.
Jika database Anda memiliki jumlah koleksi yang besar dan Anda memerlukan bantuan, ajukan tiket.