MongoDB
Փաստաթղթային տվյալների բազա՝ պահեք տվյալներն այնպես, ինչպես դրանք կարդում է ձեր հավելվածը։
30 վայրկյանում
MongoDB-ն տվյալները պահում է JSON-ի նման փաստաթղթերի (BSON) տեսքով՝ խմբավորված կոլեկցիաներում, աղյուսակների տողերի փոխարեն։ Մեկ փաստաթուղթը կարող է պարունակել ներդրված օբյեկտներ և զանգվածներ, այնպես որ մեկ ընթերցումը հաճախ վերադարձնում է այն ամենը, ինչ պետք է էկրանին։ Այն մասշտաբավորվում է replica set-երով՝ հասանելիության համար, և sharding-ով՝ ծավալի համար։ Ընթացիկ գիծը MongoDB 8.x-ն է՝ սեփական սերվերի վրա կամ որպես Atlas ամպային ծառայություն։
Իմ ամենօրյա stack-ում չէ․ գիտեմ հիմնական գաղափարները և մանրամասները կսովորեմ, երբ նախագիծը դա պահանջի։
Ինչու է սա կարևոր
- Այն կարևոր է, երբ տվյալներն ի բնե փաստաթղթի ձև ունեն՝ փոփոխական ատրիբուտներով ապրանք, ներդրված կարգավորումներով օգտատիրոջ պրոֆիլ, կոնտենտի բլոկների ծառ։ Սա բազմաթիվ աղյուսակների մեջ ստիպողաբար տեղավորելը ավելացնում է join-եր՝ առանց արժեք ավելացնելու։
- Թիմերն այն ընտրում են, երբ սխեման հաճախ է փոխվում պրոդուկտի վաղ փուլերում։ Դաշտեր կարելի է ավելացնել առանց արգելափակող միգրացիայի, իսկ schema validation-ը միևնույն է տվյալները կարգին է պահում։
- Replica set-երը տալիս են ավտոմատ failover հենց տուփից, իսկ sharding-ը շատ մեծ կոլեկցիաները բաշխում է մեքենաների միջև, ինչը շատ ավելի դժվար է հետագայում ավելացնել մեկ ռելյացիոն սերվերին։
- Այն լավ հակադրություն է ռելյացիոն բազաներին, ինչպիսիք են MySQL-ը կամ PostgreSQL-ը․ երկուսն էլ իմանալով՝ ընտրում եք ըստ access pattern-ի, ոչ թե սովորությամբ։ Ուժեղ կապերն ու բազմաղյուսակ հաշվետվությունները դեռևս SQL-ի օգտին են։
Ինչպես է աշխատում
01Գաղափարը՝ փաստաթղթեր տողերի փոխարեն
Պատկերացրեք ռելյացիոն բազան որպես աղյուսակների հավաքածու, որոնք միմյանց հետ join եք անում, իսկ MongoDB-ն՝ որպես թղթապանակների պահարան, որտեղ յուրաքանչյուր թղթապանակ պարունակում է ամեն ինչ մեկ բանի մասին։ Փաստաթուղթը BSON օբյեկտ է (երկուական JSON՝ լրացուցիչ տիպերով, ինչպիսիք են ամսաթվերը, decimal-ները և ObjectId-ն)։ Փաստաթղթերն ապրում են կոլեկցիաներում, իսկ կոլեկցիաները՝ բազայում։ Յուրաքանչյուր փաստաթուղթ ունի եզակի _id։ Մեկ կոլեկցիայի փաստաթղթերը պարտադիր չէ, որ նույն դաշտերն ունենան, բայց գործնականում պետք է ունենան, և դա ապահովում եք JSON Schema validator-ով։ Մեկ փաստաթղթի առավելագույն չափը 16 MB է, և դա ակնարկ է․ փաստաթղթերը սահմանափակ տվյալների համար են, ոչ թե անվերջ ցուցակների։
- BSON՝ երկուական ձևաչափը, որով MongoDB-ն պահում է տվյալները․ այն աջակցում է JSON-ում բացակայող տիպեր, ինչպիսիք են Date-ը և Decimal128-ը։
- Կոլեկցիա՝ փաստաթղթերի խումբ, մոտավորապես աղյուսակի նման։
- ObjectId՝ լռելյայն 12 բայթանոց _id, որը մոտավորապես դասավորված է ըստ ստեղծման ժամանակի։
02Մոդելավորում՝ ներդնել, թե հղել
Դիզայնի հիմնական հարցն այն է, թե կապված տվյալները գնում են ծնող փաստաթղթի ներսում (embed), թե իրենց սեփական կոլեկցիայում՝ id-ով, որը հղում է դրանց (reference)։ Ներդրեք, երբ զավակը պատկանում է ծնողին, կարդացվում է նրա հետ միասին և ունի սահմանափակ չափ՝ պատվերն ու դրա տողերը, օգտատերն ու նրա հասցեները։ Հղեք, երբ զավակը ընդհանուր է, աճում է առանց սահմանի կամ թարմացվում է անկախ՝ հանրաճանաչ գրառման մեկնաբանությունները, բազմաթիվ պատվերներում հղված ապրանքները։ MongoDB-ի սեփական ուղեցույցի հիմնական կանոնն է՝ «տվյալները, որոնց հասանելիությունը միասին է, պետք է միասին պահվեն»։ Նախագծեք հարցումներից դեպի հետ․ նախ թվարկեք էկրաններն ու API կանչերը, ապա ձևավորեք փաստաթղթերն այնպես, որ յուրաքանչյուրը սպասարկվի մեկ ընթերցմամբ։
// One order document: line items embedded, customer referenced
{
_id: ObjectId("665f1c2a9b1e8a0012345678"),
customerId: ObjectId("665f1b009b1e8a0011112222"),
status: "paid",
total: NumberDecimal("59.90"),
items: [
{ sku: "TSHIRT-M", qty: 2, price: NumberDecimal("19.95") },
{ sku: "CAP-01", qty: 1, price: NumberDecimal("20.00") }
],
createdAt: ISODate("2026-11-05T10:00:00Z")
}
03Հարցումներ, ինդեքսներ և ESR կանոնը
Հարցումներ եք անում filter փաստաթղթերով ({ status: "paid" }) և օպերատորներով, ինչպիսիք են $gt-ն, $in-ը և $elemMatch-ը։ Առանց ինդեքսի MongoDB-ն սկանավորում է բոլոր փաստաթղթերը (COLLSCAN)։ Ինդեքսները B-tree-ներ են, ճիշտ ինչպես SQL բազաներում, իսկ կոմպոզիտ ինդեքսները հետևում են ESR կանոնին․ նախ դրեք այն դաշտերը, որոնցով համընկնում եք Equality-ով (հավասարություն), ապա այն դաշտերը, որոնցով Sort եք անում, ապա այն դաշտերը, որոնցով ֆիլտրում եք Range-ով (միջակայք)։ { customerId: 1, createdAt: -1 } ինդեքսը կատարյալ սպասարկում է «այս հաճախորդի վերջին պատվերները» հարցումը։ Միշտ ստուգեք հարցումը explain("executionStats")-ով և համեմատեք totalDocsExamined-ը nReturned-ի հետ․ եթե առաջինը շատ ավելի մեծ է, ինդեքսը սխալ է կամ բացակայում է։
db.orders.createIndex({ customerId: 1, status: 1, createdAt: -1 })
db.orders
.find({ customerId: id, status: "paid", createdAt: { $gte: since } })
.sort({ createdAt: -1 })
.limit(20)
.explain("executionStats")
04Aggregation pipeline-ը
Aggregation-ը MongoDB-ի պատասխանն է GROUP BY-ին և հաշվետվություններին։ Pipeline-ը փուլերի զանգված է, և յուրաքանչյուր փուլ ձևափոխում է փաստաթղթերի հոսքը և փոխանցում հաջորդին՝ հավաքման գծի պես։ $match-ը ֆիլտրում է (դրեք այն առաջինը, որ կարողանա ինդեքս օգտագործել), $group-ը հաշվում է գումարներ, $sort-ը դասավորում է, $project-ը վերաձևավորում է, $lookup-ը join է անում մեկ այլ կոլեկցիա, իսկ $unwind-ը զանգվածը վերածում է մեկ փաստաթղթի յուրաքանչյուր տարրի համար։ Pipeline-ները հզոր են, բայց կարող են ծանրանալ․ միլիոնավոր փաստաթղթերի վրա $group-ը հիշողություն է օգտագործում, իսկ 100 MB-ը գերազանցող փուլերը թափվում են սկավառակ։ $match-ը և $project-ը պահեք վաղ փուլերում, որպեսզի տվյալները փոքրացնեք թանկ փուլերից առաջ։
db.orders.aggregate([
{ $match: { status: "paid", createdAt: { $gte: ISODate("2026-10-01") } } },
{ $unwind: "$items" },
{ $group: {
_id: "$items.sku",
units: { $sum: "$items.qty" },
revenue: { $sum: { $multiply: ["$items.qty", "$items.price"] } }
} },
{ $sort: { revenue: -1 } },
{ $limit: 10 }
])
05Replica set-եր, write concern և տրանզակցիաներ
Replica set-ը նույն տվյալները պահող mongod սերվերների խումբ է․ մեկ primary-ն ընդունում է գրառումները, secondary-ները պատճենում են նրա գործողությունների լոգը (oplog)։ Եթե primary-ն ընկնում է, մնացած անդամները վայրկյանների ընթացքում նորն են ընտրում։ Write concern-ը ցույց է տալիս, թե քանի անդամ պետք է հաստատի գրառումը․ MongoDB 5.0-ից ի վեր լռելյայնը «majority»-ն է, ինչը նշանակում է, որ հաստատված գրառումը վերապրում է failover-ը։ Read concern-ը և read preference-ը կառավարում են, թե ինչ եք կարդում և որտեղից։ Բազմափաստաթղթային ACID տրանզակցիաներ կան (replica set-երում՝ 4.0-ից, sharded կլաստերներում՝ 4.2-ից), բայց դրանք արտադրողականություն են արժենում․ լավ փաստաթղթային մոդելին դրանք հազվադեպ են պետք, քանի որ մեկ փաստաթղթի գրառումն արդեն ատոմար է։
- Պրոդակշնում օգտագործեք առնվազն երեք տվյալներ պահող անդամ՝ բաշխված տարբեր զոնաներում։
- Միացեք replica set-ի ամբողջական connection string-ով, որպեսզի դրայվերները failover-ի ժամանակ գտնեն նոր primary-ին։
- Secondary-ներից կարդալը կարող է վերադարձնել մի փոքր հնացած տվյալներ․ օգտագործեք դա միայն այնտեղ, որտեղ դա ընդունելի է։
06Օգտագործումը հավելվածից
Պաշտոնական դրայվերներ կան Node.js-ի, PHP-ի, Python-ի, Go-ի, Java-ի և այլնի համար։ Node.js-ում շատ թիմեր ավելացնում են Mongoose՝ սխեմաների և վալիդացիայի համար, բայց native դրայվերն էլ լավն է և ավելի բարակ։ Ստեղծեք մեկ client յուրաքանչյուր պրոցեսի համար և վերաօգտագործեք այն․ client-ը պահում է connection pool։ Վալիդացրեք մուտքը եզրին, գումարները պահեք Decimal128-ով (երբեք floating point-ով) և ամսաթվերը պահեք որպես իրական Date արժեքներ UTC-ով։ Միացրեք schema validation-ը կոլեկցիայի մակարդակում, որպեսզի սխալ deploy-ը չկարողանա աղբ գրել։
import { MongoClient } from "mongodb";
const client = new MongoClient(process.env.MONGODB_URI!); // reuse per process
const orders = client.db("shop").collection("orders");
export async function latestOrders(customerId: ObjectId) {
return orders
.find({ customerId }, { projection: { items: 0 } })
.sort({ createdAt: -1 })
.limit(20)
.toArray();
}
Օգտագործեք, երբ
- Բնական փաստաթղթային ձև և փոփոխական ատրիբուտներ ունեցող տվյալներ, ինչպիսիք են կատալոգները, CMS կոնտենտը, օգտատերերի պրոֆիլները կամ IoT ցուցմունքները։
- Ընթերցման մեծ ծանրաբեռնվածությամբ API-ներ, որտեղ մեկ փաստաթուղթը կարող է սպասարկել մեկ էկրան առանց join-երի։
- Ծանրաբեռնվածություններ, որոնք կգերազանցեն մեկ մեքենան և կշահեն ներկառուցված sharding-ից ու ավտոմատ failover-ից։
- Արագ զարգացող պրոդուկտներ, որտեղ սխեման ամեն շաբաթ փոխվում է՝ անվտանգության համար schema validation-ի հետ համատեղ։
Մի՛ օգտագործեք, երբ
- Խիստ ռելյացիոն տվյալներ՝ բազմաթիվ many-to-many կապերով և էնտիտիների միջև ad-hoc հաշվետվություններով․ ռելյացիոն բազան, ինչպիսին PostgreSQL-ն կամ MySQL-ն է, ավելի լավ է համապատասխանում։
- Ֆինանսական մատյաններ կամ workflow-ներ, որոնք գրեթե յուրաքանչյուր գրառման վրա բազմատող տրանզակցիաներ են պահանջում։
- Երբ թիմին պետք է միայն փոքր հավելվածի բազա, և այն արդեն լավ գիտի SQL․ անցումն ավելացնում է ուսուցման ծախս՝ առանց հստակ օգուտի։
Այլընտրանքներ
- PostgreSQL (with JSONB)
Ռելյացիոն միջուկ գումարած ինդեքսավորված JSON սյունակներ․ խառը ծանրաբեռնվածությունների համար հաճախ երկուսի լավագույնը։
- MySQL
Հասուն ռելյացիոն բազա JSON տիպով․ ուժեղ է դասական տրանզակցիոն հավելվածների համար։
- DynamoDB
Ամբողջովին կառավարվող key-value և փաստաթղթային պահոց AWS-ում․ շատ մասշտաբավորվող, բայց պահանջում է access pattern-ների խիստ դիզայն։
- Couchbase / Firestore
Այլ փաստաթղթային պահոցներ՝ սինքրոնիզացիայի, մոբայլի և կառավարվող հոսթինգի հետ կապված տարբեր փոխզիջումներով։
Լավագույն պրակտիկաներ
- Նախագծեք փաստաթղթերը հարցումներից ելնելով․ նախ թվարկեք ընթերցումները, ապա ձևավորեք փաստաթղթերն այնպես, որ յուրաքանչյուրը լինի մեկ ինդեքսավորված lookup։
- Կոմպոզիտ ինդեքսների համար հետևեք ESR կանոնին և ստուգեք յուրաքանչյուր կարևոր հարցում explain("executionStats")-ով։
- Զանգվածները պահեք սահմանափակ․ այն, ինչ կարող է անվերջ աճել (մեկնաբանություններ, իրադարձություններ), պատկանում է իր սեփական կոլեկցիային։
- Միացրեք JSON Schema validation-ը կոլեկցիաներում, գումարները պահեք Decimal128-ով, իսկ ամսաթվերը՝ UTC Date-ով։
- Աշխատեցրեք առնվազն երեք անդամով replica set և կարևոր տվյալների համար պահեք լռելյայն majority write concern-ը։
- Միացրեք authentication-ը և TLS-ը, bind արեք միայն մասնավոր ինտերֆեյսներին և երբեք մի բացեք 27017 պորտը ինտերնետին։
- Մոնիտորինգ արեք դանդաղ հարցումները profiler-ով կամ Atlas Query Insights-ով և պարբերաբար վերանայեք ինդեքսները։
Տարածված սխալներ
- «Schemaless»-ը ընկալել որպես «առանց սխեմայի»․ դաշտերի անհամապատասխան անուններն ու տիպերն ի վերջո կոտրում են հարցումներն ու հաշվետվությունները։
- Մոդելավորել SQL-ի պես՝ բազմաթիվ փոքր կոլեկցիաներով և ամենուր $lookup-ով՝ կորցնելով փաստաթղթերի հիմնական առավելությունը։
- Անսահմանափակ զանգվածներ, որոնք փաստաթղթերը մոտեցնում են 16 MB սահմանին և ստիպում են յուրաքանչյուր թարմացման ժամանակ վերագրել հսկայական փաստաթուղթ։
- Բացակայող ինդեքսներ, որի պատճառով հարցումներն անցնում են collection scan-ի, որոնք development-ում լավ են թվում, իսկ պրոդակշնում փլուզվում են։
- Առանց authentication-ի հրապարակայնորեն բաց ինստանսներ, որոնք բազմաթիվ իրական տվյալների արտահոսքի պատճառ են դարձել։
Խորը վերլուծություն
07WiredTiger՝ ինչպես է իրականում աշխատում պահեստավորումը
WiredTiger-ը MongoDB-ի storage engine-ն է։ Այն օգտագործում է փաստաթղթի մակարդակի concurrency control, այնպես որ երկու գրող միմյանց արգելափակում են միայն այն ժամանակ, երբ դիպչում են նույն փաստաթղթին։ Տվյալներն ու ինդեքսները B-tree-ներ են՝ սեղմված սկավառակի վրա (լռելյայն snappy, ըստ ցանկության zstd)։ WiredTiger-ը RAM-ում պահում է cache՝ լռելյայն մոտավորապես (RAM-ը հանած 1 GB)-ի 50%-ը․ արտադրողականությունը կախված է նրանից, թե արդյոք working set-ը (հաճախ օգտագործվող փաստաթղթերը գումարած ինդեքսները) տեղավորվում է այնտեղ։ Գրառումները նախ գնում են journal, և պարբերաբար checkpoint են արվում տվյալների ֆայլերում, այնպես որ crash-ից հետո վերականգնումը կատարվում է վերջին checkpoint-ից գումարած journal-ը։ Երբ working set-ը չի տեղավորվում, տեսնում եք cache eviction-ի ճնշում, և սկավառակից ընթերցումները գերիշխում են latency-ում․ դա այն պահն է, երբ պետք է RAM ավելացնել, ինդեքսները կրճատել կամ shard անել։
- Հետևեք db.serverStatus().wiredTiger.cache-ին՝ dirty և evicted բայթերի համար։
- Արագ հարցումների համար ինդեքսները պետք է տեղավորվեն RAM-ում․ չափերը ստուգեք db.collection.stats()-ով։
08Sharding և shard key-ի ընտրություն
Sharding-ը կոլեկցիան բաժանում է մի քանի replica set-երի միջև։ Router-ը (mongos) յուրաքանչյուր հարցում ուղարկում է այն shard-երին, որոնց պատկանում են shard key-ի համապատասխան միջակայքերը։ Shard key-ն որոշում է ամեն ինչ․ այն պետք է ունենա բարձր cardinality, հավասարաչափ բաշխի գրառումները և առկա լինի հարցումների մեծ մասում։ Մոնոտոն աճող key-ը, ինչպիսին timestamp-ն է կամ ObjectId-ն, բոլոր insert-ներն ուղարկում է մեկ shard (hot shard)․ hashed sharding-ը շտկում է բաշխումը, բայց range հարցումները ցրում է բոլոր shard-երով։ Կոմպոզիտ key-ը, ինչպիսին { tenantId: 1, createdAt: 1 }-ն է, հաճախ լավ է աշխատում multi-tenant հավելվածների համար։ 5.0-ից ի վեր կարող եք կոլեկցիան reshard անել online, իսկ 6.0-ից balancer-ն աշխատում է տվյալների չափով, ոչ թե chunk-երի քանակով, բայց resharding-ը դեռևս թանկ է, այնպես որ ընտրեք ուշադիր։
sh.enableSharding("shop")
sh.shardCollection("shop.events", { tenantId: 1, createdAt: 1 })
// Queries that include tenantId are targeted to one shard
db.events.find({ tenantId: "t_42", createdAt: { $gte: since } })
09Consistency-ի կարգավորիչներ՝ write concern, read concern, causal session-ներ
MongoDB-ն թույլ է տալիս consistency ընտրել յուրաքանչյուր գործողության համար։ Write concern w: "majority"-ն սպասում է, մինչև անդամների մեծամասնությունը ստանա գրառումը, այնպես որ այն վերապրում է failover-ը․ w: 1-ն ավելի արագ է, բայց գրառումը կարող է rollback արվել, եթե primary-ն ընկնի մինչև ռեպլիկացիան։ Read concern "majority"-ն վերադարձնում է միայն մեծամասնության կողմից հաստատված տվյալներ, "linearizable"-ը տալիս է ամենաուժեղ երաշխիքը մեկ փաստաթղթի ընթերցումների համար՝ ավելի բարձր գնով, իսկ "snapshot"-ն օգտագործվում է տրանզակցիաների ներսում։ Causally consistent session-ները երաշխավորում են, որ client-ը կարդում է իր սեփական գրառումները, նույնիսկ secondary-ներից կարդալիս։ Սխալն այն է, երբ կարգավորումները խառնում եք առանց մտածելու․ w: 1-ով գրելը և secondary-ներից կարդալը կարող է օգտատերերին ցույց տալ տվյալներ, որոնք հետո անհետանում են։
10Change stream-ներ, TTL և time series
Change stream-ները թույլ են տալիս հավելվածին բաժանորդագրվել insert-ներին, update-ներին և delete-ներին կոլեկցիայի, բազայի կամ կլաստերի վրա՝ oplog-ի հիման վրա։ Դրանք վերսկսելի են resume token-ով, ինչը դրանք դարձնում է հուսալի աղբյուր որոնման ինդեքսի կամ cache-ի սինքրոնիզացիայի կամ իրադարձությունների հրապարակման համար (հաճախ outbox պատերնի հետ համատեղ)։ TTL ինդեքսները ավտոմատ ջնջում են փաստաթղթերը որոշակի ժամանակ անց, ինչը օգտակար է session-ների և ժամանակավոր token-ների համար։ Time series կոլեկցիաները չափումները արդյունավետ պահում են bucket-ներում՝ ավտոմատ սեղմումով, ինչը հարմար է մետրիկների և IoT տվյալների համար։ Atlas-ը նաև ավելացնում է Atlas Search (Lucene-ի վրա հիմնված full text) և Vector Search, իսկ Community և Enterprise վերջին թողարկումները սկսել են search-ը և vector search-ը բերել նաև սեփական սերվերի վրա տեղակայումներին։
// Expire sessions 24 hours after createdAt
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 86400 })
// React to new paid orders
const stream = db.orders.watch([{ $match: { "fullDocument.status": "paid" } }],
{ fullDocument: "updateLookup" })
Իրական իրավիճակներ
Ապրանքների կատալոգ՝ շատ տարբեր ատրիբուտներով
Խնդիր
Խանութը վաճառում է հագուստ, էլեկտրոնիկա և կահույք։ Յուրաքանչյուր կատեգորիա ունի իր ատրիբուտները, և ռելյացիոն մոդելը վերածվել է entity-attribute-value աղյուսակի, որը դանդաղ է և դժվար հարցվող։
Ինչպես լուծել
Պահեք յուրաքանչյուր ապրանք որպես մեկ փաստաթուղթ՝ ընդհանուր դաշտերը վերևում (sku, name, price, categoryId), իսկ կատեգորիայի հատուկ ատրիբուտները՝ attrs զանգվածում { k, v } զույգերով։ Ստեղծեք կոմպոզիտ ինդեքս { "attrs.k": 1, "attrs.v": 1 }-ի վրա (attribute pattern), որպեսզի ցանկացած ատրիբուտի ֆիլտր օգտագործի մեկ ինդեքս՝ յուրաքանչյուր դաշտի համար մեկ ինդեքսի փոխարեն։ Ավելացրեք JSON Schema validator ընդհանուր դաշտերի համար և կատեգորիայի կանոնները պարտադրեք հավելվածում։ Գները պահեք Decimal128-ով։ Եթե պետք է faceted full-text որոնում, ապրանքները սինքրոնացրեք որոնման շարժիչի հետ change stream-ի միջոցով, այլ ոչ թե MongoDB-ն ծանրաբեռնեք տեքստային հարցումներով։
Hot shard գործարկումից հետո
Խնդիր
Events կոլեկցիան shard է արվել createdAt-ով։ Թրաֆիկի կտրուկ աճից հետո մեկ shard ստանում է գրեթե բոլոր գրառումները, և նրա CPU-ն 100% է, մինչդեռ մյուսները պարապ են։
Ինչպես լուծել
Timestamp key-ը մոնոտոն է, այնպես որ բոլոր նոր փաստաթղթերը ընկնում են մեկ shard-ի վերջին chunk-ում։ Պլանավորեք reshard դեպի key, որը բաշխում է գրառումները և դեռ թիրախավորում է հարցումները, օրինակ՝ { tenantId: 1, createdAt: 1 }, եթե հարցումները ըստ tenant-ի են, կամ hashed key բարձր cardinality ունեցող դաշտի վրա, եթե ոչ։ Օգտագործեք reshardCollection, որն աշխատում է online, բայց պատճենում է տվյալները, այնպես որ պլանավորեք այն հանգիստ պատուհանում, ստուգեք ազատ սկավառակային տարածքը (պետք է մոտավորապես կրկնակի) և հետևեք ընթացքին currentOp-ով։ Միևնույն ժամանակ insert-ները batch արեք և համոզվեք, որ balancer-ն աշխատում է՝ ճնշումը նվազեցնելու համար։
Պատվերներն անհետանում են failover-ից հետո
Խնդիր
Primary-ի crash-ից հետո մի քանի պատվեր, որոնք հաճախորդները տեսել են հաստատված, բացակայում են։ Հավելվածը արագության համար գրում է w: 1-ով։
Ինչպես լուծել
w: 1-ի դեպքում primary-ն հաստատում է մինչև ռեպլիկացիան․ եթե այն ընկնում է, նոր primary-ն երբեք չի տեսել այդ գրառումները, և հինը վերամիանալիս rollback է անում դրանք։ Կարևոր գրառումները փոխեք w: "majority"-ի (5.0-ից ի վեր լռելյայնը) և ավելացրեք ողջամիտ wtimeout։ Կորած գրառումները վերականգնեք հին primary-ի rollback ֆայլերից։ Համոզվեք, որ հավելվածը միանում է replica set URI-ով և միացված retryable writes-ով (ժամանակակից դրայվերներում լռելյայն), որպեսզի failover-ի ընթացքում գրառումն անվտանգ կրկնվի մեկ անգամ։ Ընդունեք latency-ի փոքր գինը․ ճիշտ պատվերներն ավելի արժեքավոր են, քան միլիվայրկյանները։
Արագ ուղեցույց
- Միանալ mongosh-ով
mongosh "mongodb://your_user@localhost:27017/shop?authSource=admin"- Ստեղծել կոմպոզիտ ինդեքս
db.orders.createIndex({ customerId: 1, createdAt: -1 })- Explain արել հարցումը
db.orders.find({ status: "paid" }).explain("executionStats")- Ինդեքսների ցուցակ և օգտագործում
db.orders.aggregate([{ $indexStats: {} }])- TTL ինդեքս
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })- Replica set-ի կարգավիճակ
rs.status()- Միացնել profiler-ը դանդաղ գործողությունների համար
db.setProfilingLevel(1, { slowms: 100 })- Բազայի backup
mongodump --uri="$MONGODB_URI" --db=shop --out=/backups/2026-11-05- Ավելացնել schema validation
db.runCommand({ collMod: "orders", validator: { $jsonSchema: { required: ["status"] } } })
Հարցեր և պատասխաններ
Q1Ե՞րբ կընտրեիք MongoDB-ն PostgreSQL-ի փոխարեն։
Ես կընտրեի MongoDB-ն, երբ տվյալներն ի բնե հիերարխիկ են, ձևը տարբերվում է գրառումից գրառում, և ընթերցումների մեծ մասը միանգամից մեկ ագրեգատ է բերում, օրինակ՝ ապրանքների կատալոգ, որտեղ յուրաքանչյուր կատեգորիա շատ տարբեր ատրիբուտներ ունի, կամ կոնտենտի ծառ։ Այն նաև օգնում է, երբ սպասում եք հորիզոնական sharding։ Ես կմնայի PostgreSQL-ի վրա, երբ տվյալները ռելյացիոն են, երբ հաշվետվություններին ճկուն join-եր են պետք, կամ երբ էնտիտիների միջև խիստ տրանզակցիաները կենտրոնական են։ PostgreSQL-ի JSONB-ն ծածկում է կիսակառուցվածքային շատ դեպքեր, այնպես որ ազնիվ պատասխանն է՝ ընտրեք MongoDB-ն փաստաթղթային access pattern-ների համար, ոչ թե պարզապես միգրացիաներ չգրելու համար։
Q2Ինչպե՞ս եք որոշում՝ ներդնել, թե հղել։
Ես նայում եմ երեք բանի՝ ինչպես են տվյալները կարդացվում, որքան կարող են աճել և որքան հաճախ են անկախ փոխվում։ Եթե զավակը միշտ կարդացվում է ծնողի հետ, սահմանափակ չափ ունի և պատկանում է միայն այդ ծնողին, ես այն ներդնում եմ, ինչպես պատվերի տողերը։ Եթե այն ընդհանուր է մի քանի ծնողների համար, աճում է առանց սահմանի կամ թարմացվում է ինքնուրույն, ես այն հղում եմ, ինչպես մեկնաբանություններն ու ապրանքները։ Կան նաև հիբրիդ պատերններ՝ ներդնել փոքր ամփոփում (անուն, գին) և հղել ամբողջական գրառումը՝ ընդունելով որոշակի կրկնօրինակում։ Ուղղորդող սկզբունքն այն է, որ միասին հասանելի տվյալները պետք է միասին պահվեն։
Q3MongoDB-ն աջակցո՞ւմ է տրանզակցիաներ։
Այո։ Մեկ փաստաթղթի գրառումը միշտ ատոմար է, ներառյալ ներդրված դաշտերն ու զանգվածները, ինչը լավ մոդելի դեպքում ծածկում է դեպքերի մեծ մասը։ Բազմափաստաթղթային ACID տրանզակցիաները հասանելի են replica set-երում 4.0-ից, իսկ sharded կլաստերներում՝ 4.2-ից։ Դրանք աշխատում են snapshot isolation-ով և commit են արվում write concern-ով։ Գինը լրացուցիչ latency-ն է, տրանզակցիայի ընթացքում պահվող lock-երը և լռելյայն 60 վայրկյանի սահմանը։ Եթե գտնում եք, որ գրեթե յուրաքանչյուր գրառման համար տրանզակցիա է պետք, դա ազդանշան է, որ տվյալները գուցե այլ կերպ մոդելավորելը ավելի լավ է, կամ որ ռելյացիոն բազան ավելի լավ է համապատասխանում։
Q4Ի՞նչ է replica set-ը, և ինչո՞ւ են պետք երեք անդամ։
Replica set-ը նույն տվյալները պահող MongoDB սերվերների խումբ է։ Մեկ primary-ն ընդունում է գրառումները, իսկ secondary-ները ռեպլիկացնում են նրա oplog-ը։ Եթե primary-ն ձախողվում է, մյուսներն ընտրություն են անցկացնում և secondary-ներից մեկին բարձրացնում, սովորաբար վայրկյանների ընթացքում։ Ընտրությանը պետք է քվեարկող անդամների մեծամասնություն, այնպես որ երկու անդամի դեպքում մեկին կորցնելը մեծամասնություն չի թողնում, և set-ը դառնում է միայն ընթերցման։ Երեք անդամը կարող է կորցնել մեկին և դեռ primary ընտրել։ Որպես երրորդ ձայն կարող եք օգտագործել arbiter, բայց այն տվյալներ չի պահում և թուլացնում է majority գրառման երաշխիքները, այնպես որ երեք տվյալներ պահող անդամն ավելի անվտանգ լռելյայն ընտրություն է։
Q5Ինչպե՞ս եք գտնում և ուղղում դանդաղ հարցումը։
Նախ ես այն գտնում եմ․ բազայի profiler-ը կամ slow query log-ը, կամ Atlas Query Insights-ը ցույց են տալիս շեմը գերազանցող հարցումները։ Ապա ես հարցումն աշխատեցնում եմ explain("executionStats")-ով և նայում հաղթող պլանին։ COLLSCAN փուլը կամ totalDocsExamined-ի և nReturned-ի միջև մեծ տարբերությունը նշանակում է բացակայող կամ վատ դասավորված ինդեքս։ Ես նախագծում եմ կոմպոզիտ ինդեքս՝ հետևելով equality, sort, range կանոնին, ստուգում եմ, որ sort-ը ծածկված լինի ինդեքսով, որպեսզի հիշողության մեջ SORT փուլ չլինի, և ավելացնում եմ projection՝ ավելի քիչ տվյալ վերադարձնելու համար։ Վերջում հեռացնում եմ չօգտագործվող ինդեքսները, քանի որ յուրաքանչյուր ինդեքս դանդաղեցնում է գրառումները։
Q6MongoDB-ն schemaless է՞։
Գործնականում՝ ոչ։ Բազան չի պարտադրում ֆիքսված սխեմա, այնպես որ մեկ կոլեկցիայի փաստաթղթերը կարող են տարբերվել, ինչը օգտակար է փոփոխությունների ժամանակ։ Բայց ձեր հավելվածը միշտ սխեմա ունի՝ գրված, թե ոչ։ Լավ թիմերն այն բացահայտ են դարձնում՝ JSON Schema validator կոլեկցիայի վրա, տիպավորված մոդելներ կոդում (օրինակ՝ Mongoose կամ TypeScript տիպեր) և միգրացիոն սկրիպտներ, երբ դաշտի իմաստը փոխվում է։ Ճկուն մասն այն է, թե ինչպես են փոփոխությունները ներդրվում․ կարող եք դաշտ ավելացնել և աստիճանաբար լրացնել այն՝ մեծ աղյուսակի վրա արգելափակող ALTER TABLE աշխատեցնելու փոխարեն։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս կնախագծեիք multi-tenant SaaS MongoDB-ի վրա։
Կա երեք մոդել։ Բազա յուրաքանչյուր tenant-ի համար տալիս է ուժեղ մեկուսացում և հեշտ backup ըստ tenant-ի, բայց հազարավոր բազաները ծանրաբեռնում են սերվերը բազմաթիվ ֆայլերով և կապերով։ Կոլեկցիա յուրաքանչյուր tenant-ի համար ունի նման սահմանափակումներ։ Ընդհանուր կոլեկցիան՝ յուրաքանչյուր փաստաթղթում tenantId դաշտով, լավագույնս է մասշտաբավորվում․ յուրաքանչյուր ինդեքս սկսվում է tenantId-ով, յուրաքանչյուր հարցում պետք է այն ներառի (պարտադրված data-access շերտում, ոչ թե պայմանավորվածությամբ), և երբ shard եք անում, tenantId-ն գլխավորում է shard key-ը, որպեսզի հարցումների մեծ մասը թիրախավորի մեկ shard։ Մեծ tenant-ները կարող են տեղափոխվել առանձին զոնաներ zone sharding-ով։ Փոխզիջումը մեկուսացումն է․ ֆիլտրի մեկ սխալը կարող է տվյալների արտահոսք առաջացնել, այնպես որ ես կավելացնեի թեստեր և repository շերտ, որը մերժում է առանց tenant-ի հարցումները։
S2Որո՞նք են բազմափաստաթղթային տրանզակցիաների փոխզիջումները sharded կլաստերում։
Բաշխված տրանզակցիաներն օգտագործում են two-phase commit shard-երի միջև՝ համակարգված մեկ shard-ի կողմից։ Դրանք տալիս են snapshot isolation և ատոմարություն, բայց յուրաքանչյուր մասնակից shard պահում է lock-եր և հին տարբերակները cache-ում մինչև commit, այնպես որ երկար տրանզակցիաները մեծացնում են cache-ի ճնշումը և latency-ն բոլորի համար։ Դրանք նաև abort են լինում write conflict-ների դեպքում և պետք է կրկնվեն հավելվածի կողմից․ դրայվերներն օգնում են withTransaction helper-ով։ Լռելյայն կյանքի տևողությունը 60 վայրկյան է։ Իմ մոտեցումն է նախագծել այնպես, որ ինվարիանտներն ապրեն մեկ փաստաթղթի ներսում, տրանզակցիաներ օգտագործել միայն հազվադեպ միջփաստաթղթային դեպքերի համար, ինչպիսին գումարի փոխանցումներն են, պահել դրանք կարճ և փոքր, և ապահովել, որ տրանզակցիան դիպչի հնարավորինս քիչ shard-երի՝ shard key-ն ընտրելով այդ ճանապարհը հաշվի առնելով։
S3Ինչպե՞ս եք MongoDB-ն և որոնման ինդեքսը կամ cache-ը համաձայնեցված պահում։
Հավելվածից կրկնակի գրառումները (գրել MongoDB-ում, ապա Elasticsearch-ում) մասնակիորեն ձախողվում են և ժամանակի ընթացքում շեղվում։ Ավելի լավ մոտեցումը change data capture-ն է․ worker-ը կարդում է change stream, յուրաքանչյուր մշակված batch-ից հետո resume token-ը պահում է հուսալի տեղում և ինդեքսը թարմացնում է idempotent կերպով՝ օգտագործելով փաստաթղթի _id-ն։ Եթե worker-ը վերագործարկվում է, այն վերսկսում է token-ից․ եթե այն չափազանց հետ է մնում և oplog-ի պատուհանն անցնում է, պետք է ամբողջական reindex, այնպես որ oplog-ի չափը որոշեք ձեր ամենավատ անջատման համար և ալերտ դրեք lag-ի վրա։ Դոմենային իրադարձություններն այլ ծառայություններին հրապարակելու համար լավ է աշխատում outbox պատերնը․ գրեք իրադարձությունը outbox կոլեկցիայում նույն տրանզակցիայում, ինչ փոփոխությունը, և այնտեղից փոխանցեք։
Բառարան
- Փաստաթուղթ (Document)
- Մեկ գրառում՝ պահված BSON-ով՝ JSON-ի երկուական ձևով՝ լրացուցիչ տիպերով, մինչև 16 MB։
- Կոլեկցիա (Collection)
- Փաստաթղթերի խումբ բազայում, մոտավորապես համեմատելի աղյուսակի հետ։
- Replica set
- Նույն տվյալներով մի քանի սերվեր․ մեկ primary-ն ընդունում է գրառումները, իսկ secondary-ները ստանձնում են նրա ձախողման դեպքում։
- Oplog
- Primary-ի գործողությունների լոգը, որը secondary-ները վերարտադրում են սինքրոն մնալու համար։
- Write concern
- Քանի replica set անդամ պետք է հաստատի գրառումը, մինչև այն համարվի հաջողված։
- Shard key
- Դաշտը կամ դաշտերը, որոնցով կոլեկցիայի տվյալները բաժանվում են shard-երի միջև։
- Aggregation pipeline
- Փուլերի հաջորդականություն, որոնք ֆիլտրում, խմբավորում և վերաձևավորում են փաստաթղթերը՝ SQL GROUP BY-ի և join-երի պես։
- ESR կանոն
- Կոմպոզիտ ինդեքսի դաշտերը դասավորել որպես Equality, ապա Sort, ապա Range՝ ամենաարդյունավետ հարցումների համար։
- Change stream
- Կոլեկցիայի կամ բազայի insert, update և delete իրադարձությունների վերսկսելի հոսք։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։