Բոլոր դասերը
ՏվյալներՄիջին12 րոպե ընթերցումԱրժե իմանալ

MongoDB

Փաստաթղթային տվյալների բազա՝ պահեք տվյալներն այնպես, ինչպես դրանք կարդում է ձեր հավելվածը։

  • Documents
  • Aggregation
  • Indexes
  • Replica sets
Քայլ 1/5
SQL-ում մեկ պատվերը բաժանվում է տարբեր աղյուսակների տողերի՝ orders, items, customers։ Ցույց տալու համար բազան դրանք նորից JOIN է անում։

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 կանչերը, ապա ձևավորեք փաստաթղթերն այնպես, որ յուրաքանչյուրը սպասարկվի մեկ ընթերցմամբ։

js
// 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-ի հետ․ եթե առաջինը շատ ավելի մեծ է, ինդեքսը սխալ է կամ բացակայում է։

js
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-ը պահեք վաղ փուլերում, որպեսզի տվյալները փոքրացնեք թանկ փուլերից առաջ։

js
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-ը չկարողանա աղբ գրել։

ts
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

    Այլ փաստաթղթային պահոցներ՝ սինքրոնիզացիայի, մոբայլի և կառավարվող հոսթինգի հետ կապված տարբեր փոխզիջումներով։

Լավագույն պրակտիկաներ

  1. Նախագծեք փաստաթղթերը հարցումներից ելնելով․ նախ թվարկեք ընթերցումները, ապա ձևավորեք փաստաթղթերն այնպես, որ յուրաքանչյուրը լինի մեկ ինդեքսավորված lookup։
  2. Կոմպոզիտ ինդեքսների համար հետևեք ESR կանոնին և ստուգեք յուրաքանչյուր կարևոր հարցում explain("executionStats")-ով։
  3. Զանգվածները պահեք սահմանափակ․ այն, ինչ կարող է անվերջ աճել (մեկնաբանություններ, իրադարձություններ), պատկանում է իր սեփական կոլեկցիային։
  4. Միացրեք JSON Schema validation-ը կոլեկցիաներում, գումարները պահեք Decimal128-ով, իսկ ամսաթվերը՝ UTC Date-ով։
  5. Աշխատեցրեք առնվազն երեք անդամով replica set և կարևոր տվյալների համար պահեք լռելյայն majority write concern-ը։
  6. Միացրեք authentication-ը և TLS-ը, bind արեք միայն մասնավոր ինտերֆեյսներին և երբեք մի բացեք 27017 պորտը ինտերնետին։
  7. Մոնիտորինգ արեք դանդաղ հարցումները 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-ը դեռևս թանկ է, այնպես որ ընտրեք ուշադիր։

js
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-ը բերել նաև սեփական սերվերի վրա տեղակայումներին։

js
// 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" })

Իրական իրավիճակներ

01

Ապրանքների կատալոգ՝ շատ տարբեր ատրիբուտներով

Խնդիր

Խանութը վաճառում է հագուստ, էլեկտրոնիկա և կահույք։ Յուրաքանչյուր կատեգորիա ունի իր ատրիբուտները, և ռելյացիոն մոդելը վերածվել է 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-ն ծանրաբեռնեք տեքստային հարցումներով։

02

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-ն աշխատում է՝ ճնշումը նվազեցնելու համար։

03

Պատվերներն անհետանում են 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 իրադարձությունների վերսկսելի հոսք։

Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։

Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։