Tehnologija Vodič

Optimizacija MongoDB baze podataka

mongodb

MongoDB je već neko vreme popularan izbor među NoSQL rešenjima upravo zbog svoje skalabilnosti, fleksibilnosti u radu sa dokumentima i dobre podrške. Ipak, kao i svaka ozbiljna baza podataka i MongoDB zahteva odgovarajuće podešavanje i održavanje kako bi pružio optimalne performanse. Ovo naročito dolazi do izražaja kada treba da radi sa velikim brojem zapisa, zahtevnim upitima i kompleksnim backend aplikacijama.

U ovom tekstu ćemo vam dati neke praktične savete za podešavanje, dijagnostiku i optimizaciju MongoDB baze.

Cilj je da kao administrator baze ili backend developer naučite da prepoznate uska grla i unapredite performanse vaše baze.

Pa hajde da krenemo redom.

Radno opterećenje baze

Pre bilo kakve optimizacije, ključno je da razumete profil opterećenja baze. Da li se vaša aplikacija više oslanja na čitanje, upisivanje ili su operacije ravnomerno raspoređene? Da biste identifikovali najčešće izvršavane operacije i potencijalne zastoje u izvršavanju, savet je da koristite ugrađene alate poput mongostat, mongotop, kao i Atlas Profiler (ako koristite MongoDB Atlas).

mongostat --host localhost

mongotop --host localhost

Ukoliko koristite Atlas:

db.system.profile.find({ millis: { $gt: 100 } }).sort({ ts: -1 }).limit(5);

Pored osnovnog uvida, važno je da analizirate i dnevne ili sezonske varijacije u opterećenju kako biste znali kada su pikovi i kako se vaša baza ponaša pod stresom. Ukoliko koristite mikroservisnu arhitekturu, razdvajanje radnog opterećenja po servisima može da pomogne da identifikujete uzroke zagušenja.

Indeksiranje

Ako skladištite različite vrste dokumenata sa logičkim razdvajanjem (npr. po mesecima), možete da koristite tzv. bucket pattern, odnosno podelu velikog seta u više manjih. To će značajno poboljšati performanse:

// Primer: logs_2024_01, logs_2024_02...

db.getSiblingDB("logs_2024_01").events.insertOne({ msg: "example", createdAt: new Date() });

Dobro postavljeni indeksi direktno utiču na brzinu upita. Svaki indeks omogućava brži pristup podacima, dok loše indeksiranje može da ima negativan efekat na optimalno izvršavanje upita.

Prilagodite indekse prema obrascu upita:

db.orders.createIndex({ userId: 1, createdAt: -1 });

Ne preterujte, jer višak indeksa usporava pisanje (insert, update) i troši dodatni prostor:

db.orders.dropIndex("userId_1_createdAt_-1");

Redovno proveravajte koje indekse baza koristi pomoću:

db.orders.find({ userId: "123" }).explain("executionStats");

MongoDB Compass takođe može da prikaže vizuelni plan izvršavanja, što je korisno u analizi složenijih slučajeva.

Takođe, dobra praksa je da vodite računa o redosledu polja u složenim indeksima. Ako često radite sortiranje, razmislite o dodavanju sort polja u indeks:

db.products.createIndex({ category: 1, price: -1 });

Takođe, iskoristite partial indekse:

db.logs.createIndex({ timestamp: 1 }, { partialFilterExpression: { level: "error" } });

Optimizacija upita

Kada je potrebno da prikažete više statistika u istom upitu (npr. proseke, brojke i grupe), možete da koristite agregaciju sa $facet kako biste paralelno obradili više tokova:

db.orders.aggregate([

{

    $facet: {

      totalByStatus: [{ $group: { _id: "$status", count: { $sum: 1 } } }],

      averageByType: [{ $group: { _id: "$type", avgAmount: { $avg: "$amount" } } }]

    }

  }

]);

Ako radite sa velikim količinama privremenih podataka (npr. sesije, tokeni), možete da iskoristite TTL indekse da se ti podaci automatski brišu nakon određenog vremena:

db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 });

Rad sa podacima u MongoDB-u ne mora da znači rad sa celim dokumentima. Izbegavajte find({}) bez filtera:

// Loše

 db.users.find({});

Uvek ograničite broj vraćenih polja putem projekcije:

// Dobro

 db.users.find({ isActive: true }, { name: 1, email: 1, _id: 0 });

Za kompleksne zahteve, koristite agregacione tokove:

db.sales.aggregate([

  { $match: { year: 2024 } },

  { $group: { _id: "$region", total: { $sum: "$amount" } } }

]);

Obavezno proverite da li match deo koristi indeksiran atribut.

Izbegavajte $where:

// Nije preporučljivo

 db.products.find({ $where: "this.price > 100 && this.inStock" });

Zamenite sa:

db.products.find({ price: { $gt: 100 }, inStock: true });

Obratite pažnju i na veličinu dokumenata. MongoDB ima ograničenje od 16MB po dokumentu. Ako skladištite kompleksne JSON strukture, razmislite o normalizaciji, referenciranju ili čak upotrebi GridFS-a ako radite sa binarnim podacima.

Hardverski resursi

MongoDB koristi RAM za držanje tzv. working set-a. Kada vaš dataset više ne staje u RAM, performanse naglo opadaju jer počinje swapovanje sa diska. SSD je preporučena opcija – HDD više nije konkurentan.

Proverite mrežnu propusnost i latenciju, naročito ako koristite distribuirane klastere.

Na Linuxu proverite swappiness:

cat /proc/sys/vm/swappiness

Proverite disk performanse:

iostat -x 1 10

Proverite RAM kroz wiredTiger statistiku:

db.serverStatus().wiredTiger.cache;

Takođe, za maksimalne performanse razmislite o tome da journaling direktorijum bude na odvojenoj SSD particiji.

Replikacija i sharding

Replikacija osigurava dostupnost i otpornost sistema. Podesite read preference prema poslovnim potrebama:

const MongoClient = require('mongodb').MongoClient;

const uri = "mongodb://host1,host2,host3/?readPreference=secondaryPreferred";

MongoClient.connect(uri, function(err, client) {

  const db = client.db('test');

});

Sharding dolazi u obzir kod velikih dataset-ova i zahteva pažljiv odabir shard key-a:

sh.enableSharding("mojaBaza")

sh.shardCollection("mojaBaza.kolekcija", { userId: "hashed" })

Provera distribucije:

db.kolekcija.getShardDistribution()

Vodite računa da shard key bude stabilan. Ne sme da se menja nakon kreiranja dokumenta. Ako planirate globalno distribuiranu infrastrukturu, kombinujte shard key sa geografskim podacima da biste kontrolisali lokaciju podataka.

Monitoring i održavanje

Za dodatni uvid u veličinu dokumenata, broj indeksa i iskorišćenost prostora, koristite collStats i dbStats:

db.collection.stats()

db.stats()

Ako skladištite podatke koji brzo rastu, kao što su logovi, možete koristiti capped kolekcije koje automatski brišu najstarije zapise kada dostignu zadatu veličinu:

db.createCollection("logs", { capped: true, size: 10485760 });

Bez redovnog nadzora, nijedna optimizacija neće dugo trajati. Koristite mongostat, mongotop, currentOp(), ali i sistemske alate poput iostat, htop, vmstat:

db.currentOp({ active: true })

db.serverStatus()

Rebalans:

db.repairDatabase()

db.collection.compact()

Provera replike:

rs.printSlaveReplicationInfo()

Održavajte oplog veličinu na dovoljnom nivou kako biste imali prostora za vremenski pomerene replikacije bez rizika od gubitka podataka.

Write concern i performanse

Prevelike transakcije mogu da budu štetne po performanse jer povećavaju zaključavanja i opterećuju replike. Dobra praksa je da transakcije držite kratkim i sa što manje dokumenata. Takođe, kada se zna da je određeni indeks optimalan za vašu bazu hint() metoda može da pomogne da se forsira njegovo korišćenje:

db.orders.find({ userId: "123" }).hint({ userId: 1 });

Podešavanje writeConcern direktno utiče na performanse i postojanost podataka:

db.collection.insertOne(

  { name: "Test" },

  { writeConcern: { w: "majority", wtimeout: 5000 } }

);

Primer za manje bitne podatke:

db.logs.insertOne(

  { msg: "ping", ts: new Date() },

  { writeConcern: { w: 0 } }

);

Read preference i balansiranje čitanja

Podešavanje read preferenci u shell-u:

db.getMongo().setReadPref("nearest")

URI primer:

mongodb://host1,host2,host3/?readPreference=nearest

U replikovanim okruženjima razmislite o tome da manje zahtevne aplikacije (npr. reporting) usmerite ka sekundarnim čvorovima, dok primarni zadržite za upisne operacije i transakcije.

Kako dijagnostikovati probleme u MongoDB-u?

Ako radite sa starijim, retko korišćenim podacima (npr. arhiviranim porudžbinama ili logovima), preporuka je da ih izdvojite u posebne kolekcije ili baze kako bi glavne ostale brže:

// Na primer, premestiti podatke starije od 2 godine:

db.orders.aggregate([

  { $match: { createdAt: { $lt: ISODate("2022-01-01") } } },

  { $out: "archived_orders" }

]);

Ako se baza usporava, prođite sledeće tačke:

db.currentOp({ "secs_running": { $gt: 3 }, active: true });

db.serverStatus().globalLock;

I/O uska grla:

iostat -xz 1 10

WT cache:

db.serverStatus().wiredTiger.cache;

Pored toga, proverite i serverStatus() za celokupnu sliku stanja servera. Pogledajte sekciju wt_cache i obratite pažnju na broj izbačenih stavki iz keša po minutu.

Zaključak

Bez sumnje, MongoDB nudi ozbiljan potencijal za performanse, ali samo ako se koristi pravilno. Bez preciznog indeksiranja, pažljivog odabira shard ključeva, optimizovanih upita i konstantnog nadzora, čak i manji sistemi mogu da postanu spori i neefikasni.

Uložite neke vreme u razumevanje svojih podataka i upotrebe baze. To je najvažniji korak ka visokoj dostupnosti i performansama koje rastu zajedno sa vašim aplikacijama.

Ako vam treba pomoć pri analizi performansi ili podešavanju složenih deployment-a, MongoDB zajednica i forumi su dobra polazna tačka od koje možete krenuti sa pitanjima.

Ostavi komentar

Vaša adresa neće biti objavljena