Migration — migrate-mongo bilan schema o'zgarishlarini boshqarish

~20 daq · MongoDB · Amaliy va Professional

Muammodan boshlaylik

Yangi talab: users dagi ism maydoni ism va familiya ga bo'linsin. Mongoose schema ni o'zgartirdingiz, deploy qildingiz:

  • Yangi userlar — to'g'ri
  • 200 000 ta eski user da hali ism: "Lola Karimova" va familiya yo'q — sahifalarda "Lola Karimova undefined"
  • Hamkasbingizning lokal bazasida ham, staging da ham eski ma'lumotlar
  • Kimdir mongosh da qo'lda updateMany yozdi — lekin faqat production da. Staging da esa unutildi

"MongoDB schemaless — migratsiya kerak emas" — bu afsona. Schema kodda bor; ma'lumotlar unga mos bo'lishi kerak.


Migration nima?

Migration — ma'lumotlar yoki baza tuzilmasini o'zgartiradigan, versiyalangan va takrorlanadigan skript.

  • Har bir o'zgarish — alohida fayl, git da
  • Qaysi migratsiyalar bajarilgani bazada saqlanadi
  • Har bir muhitda (lokal, staging, production) bir xil tartibda bajariladi
  • Iloji bo'lsa — orqaga qaytarish (down)

Hayotiy o'xshatma

Uy ta'mirining jurnali: "1-mart — elektr simlari almashtirildi", "15-mart — devorlar bo'yaldi". Yangi usta kelsa — jurnalga qarab, nima qilingan va nima qilinmaganini biladi. Xuddi shu ta'mirni boshqa xonadonda takrorlash kerak bo'lsa — jurnal bo'yicha qadamma-qadam bajaradi.


migrate-mongo

npm install -D migrate-mongo
npx migrate-mongo init
// migrate-mongo-config.js
const config = {
  mongodb: {
    url: process.env.MONGODB_URI,
  },
  migrationsDir: 'migrations',
  changelogCollectionName: 'changelog',
  migrationFileExtension: '.js',
  useFileHash: false,
  moduleSystem: 'esm',
}

export default config

Migratsiya yaratish

npx migrate-mongo create ism-familiyaga-bolish
# migrations/20261108120000-ism-familiyaga-bolish.js
// migrations/20261108120000-ism-familiyaga-bolish.js
export const up = async (db) => {
  await db.collection('users').updateMany(
    { familiya: { $exists: false }, ism: { $type: 'string' } },
    [
      { $set: {
          _qismlar: { $split: [{ $trim: { input: '$ism' } }, ' '] },
      } },
      { $set: {
          ism: { $arrayElemAt: ['$_qismlar', 0] },
          familiya: {
            $reduce: {
              input: { $slice: ['$_qismlar', 1, { $size: '$_qismlar' }] },
              initialValue: '',
              in: { $trim: { input: { $concat: ['$value', ' ', '$this'] } } },
            },
          },
      } },
      { $unset: '_qismlar' },
    ]
  )
}

export const down = async (db) => {
  await db.collection('users').updateMany(
    { familiya: { $exists: true } },
    [
      { $set: { ism: { $trim: { input: { $concat: ['$ism', ' ', '$familiya'] } } } } },
      { $unset: 'familiya' },
    ]
  )
}

Update ichidagi aggregation pipeline (massiv) — bitta so'rovda har bir documentning o'z qiymatlaridan foydalanish. 200 000 ta documentni JavaScript ga yuklash shart emas.

Bajarish

npx migrate-mongo status     # qaysilari bajarilgan, qaysilari kutmoqda
npx migrate-mongo up         # kutayotganlarning hammasini bajarish
npx migrate-mongo down       # oxirgisini qaytarish
┌─────────────────────────────────────────────┬──────────────────────────┐
│ Filename                                    │ Applied At               │
├─────────────────────────────────────────────┼──────────────────────────┤
│ 20261020093000-mahsulot-indexlari.js        │ 2026-10-20T09:31:12.000Z │
│ 20261108120000-ism-familiyaga-bolish.js     │ PENDING                  │
└─────────────────────────────────────────────┴──────────────────────────┘

changelog collection ida qaysi fayllar qachon bajarilgani saqlanadi.


Index migratsiyasi

Production da autoIndex: false (38-dars) — index lar migratsiya orqali:

export const up = async (db) => {
  await db.collection('buyurtmalar').createIndex(
    { holat: 1, createdAt: -1 },
    { name: 'holat_sana' }
  )
  await db.collection('users').createIndex(
    { email: 1 },
    { unique: true, partialFilterExpression: { deletedAt: null }, name: 'email_unique' }
  )
}

export const down = async (db) => {
  await db.collection('buyurtmalar').dropIndex('holat_sana')
  await db.collection('users').dropIndex('email_unique')
}

Deploy ga qo'shish

{
  "scripts": {
    "migrate": "migrate-mongo up",
    "migrate:status": "migrate-mongo status"
  }
}

CI/CD da (Express kursi, 67-dars) — yangi kodni ishga tushirishdan oldin:

npm ci && npm run migrate && pm2 reload dokon-api

Xavfsiz migratsiya qoidalari

[!WARNING] Deploy vaqtida bir muddat eski kod va yangi ma'lumotlar (yoki aksincha) birga ishlaydi. Migratsiyalarni orqaga mos qiling: avval yangi maydon qo'shiladi va kod ikkala shaklni ham tushunadi; eski maydon esa keyingi deploy da, hamma narsa yangi shaklga o'tgandan keyin olib tashlanadi.

  1. Idempotent — ikki marta bajarilsa ham zarar bo'lmasin: { familiya: { $exists: false } } filtri shu uchun
  2. Bo'laklab — millionlab document uchun bitta ulkan updateMany emas, partiyalar bilan (masalan, 10 000 tadan)
  3. Avval zaxira — katta o'zgarishdan oldin mongodump (34-dars)
  4. Staging da sinash — production nusxasiga yaqin ma'lumotlarda
  5. Kod bilan bir commit da — schema o'zgarishi va uning migratsiyasi birga ko'rib chiqiladi

Bo'laklab yangilash

export const up = async (db) => {
  const col = db.collection('buyurtmalar')
  let jami = 0

  while (true) {
    const idlar = await col.find({ valyuta: { $exists: false } }, { projection: { _id: 1 } })
      .limit(10000).map((d) => d._id).toArray()
    if (idlar.length === 0) break

    const natija = await col.updateMany({ _id: { $in: idlar } }, { $set: { valyuta: 'UZS' } })
    jami += natija.modifiedCount
    console.log(`Yangilandi: ${jami}`)
  }
}

Mongoose modellari bilan emas

Migratsiyada Mongoose model larini import qilmang — to'g'ridan-to'g'ri db.collection() bilan ishlang. Sabab: model kelajakda o'zgaradi (maydon o'chiriladi, validatsiya qo'shiladi), va eski migratsiya yangi model bilan buziladi. Migratsiya — o'z yozilgan paytidagi holatni ifodalashi kerak.

[!TIP] Kichik o'zgarishlar uchun migratsiya o'rniga "dangasa" (lazy) yondashuv ham bor: kod eski va yangi shaklni o'qiydi, document saqlanganda yangi shaklga o'tkaziladi. Bu juda katta collection larda qulay, lekin eski shakl uzoq vaqt qoladi va kod murakkablashadi.


Tez-tez uchraydigan xatolar

Production da qo'lda o'zgartirish

mongosh dagi updateMany — hech qayerda yozilmagan, staging va lokal bazalar farqlanib qoladi.

Migratsiya faylini bajarilgandan keyin tahrirlash

U allaqachon changelog da "bajarildi" deb belgilangan — o'zgarishlar boshqa muhitlarda ishlamaydi. Yangi migratsiya yarating.

down ni yozmaslik yoki sinamaslik

Muammo chiqsa — orqaga qaytish yo'li yo'q. Har doim qaytarib bo'lmasa ham (ma'lumot o'chirilgan bo'lsa), buni faylda aniq yozing.


Xulosa

  • MongoDB da ham migratsiya kerak — schema kodda, ma'lumot unga mos bo'lishi kerak
  • migrate-mongo: create, up, down, status; holat — changelog collection ida
  • Update pipeline — bitta so'rovda document qiymatlaridan foydalanish
  • Index lar — migratsiya orqali (autoIndex: false)
  • Deploy da — kod ishga tushishidan oldin npm run migrate
  • Orqaga mos, idempotent, bo'laklab; avval zaxira; Mongoose model larini import qilmang

Keyingi dars: 60-dars — Real loyiha — to'liq schema loyihalash, Mongoose bilan yozish