Як перейти з SQL на MongoDB

1
Перекладено ШІ
Оригінал: Laravel News
Оновлено: 17 грудня, 2025
Плануєте міграцію Laravel‑додатка з реляційної БД (SQL) на MongoDB або хочете краще зрозуміти модель документів? Крок за кроком розглянемо аналіз існуючої схеми, перепроєктування під MongoDB, перетворення й імпорт даних, перепис запитів, тестування та оптимізацію.

Багато застосунків починають із SQL-версії на PostgreSQL чи MySQL. Вони добре працюють кілька років — поки не стикаються зі шкала́більністю, жорсткою схемою або потребою зберігати напівструктуровані дані. Через це дедалі більше команд розглядають MongoDB як альтернативу або доповнення до існуючої SQL-інфраструктури.

MongoDB пропонує документно-орієнтований, схема‑гнучкий підхід, який краще підходить для швидко змінних моделей даних. На відміну від SQL‑баз з таблицями й рядками, MongoDB зберігає документи у форматі JSON-like у колекціях — кожен запис може мати свою структуру. Це дає свободу, але вимагає іншого підходу до моделювання, запитів і гарантій цілісності.

Міграція з SQL у MongoDB — це не простий «замінити БД», а вибір найкращого інструменту для конкретного випадку. SQL добре працює з відносинами та транзакціями по нормалізованих таблицях; MongoDB — з різнорідними, ієрархічними або швидко змінними даними в масштабі. Часто в продуктиві використовують обидві системи, кожну для своїх задач.

У цій статті ми пройдемо весь процес міграції: планування й редизайн схеми, трансформацію даних, перепис запитів і тестування. Ви дізнаєтеся, як проаналізувати існуючу SQL‑схему, спроектувати еквівалент у MongoDB, безпечно перенести дані та адаптувати логіку застосунку під документну модель. Наприкінці отримаєте чіткий план для міграції Laravel‑застосунків із збереженням цілісності і надійності.

Ця стаття для розробників і архітекторів, які планують перевести на MongoDB існуючі Laravel або PHP‑проєкти — повністю або частково. Тут є практичні приклади, поширені помилки та стратегії тестування перед запуском у продакшн.

# Розуміння ландшафту міграції

Міграція з SQL у MongoDB передбачає кілька базових технічних відмінностей, що впливають на дизайн і запити. Усвідомлення їх наперед допоможе вирішити, що саме і як переносити.

# Модель зберігання: Tables vs collections

У SQL дані живуть у таблицях з фіксованими стовпцями та типами — кожен рядок має ту ж структуру. У MongoDB дані зберігаються в колекціях, де кожен документ може мати різні поля. Це дозволяє еволюціонувати схему без ALTER TABLE і простоїв, але потребує валідації й дисципліни для збереження послідовності.

# Відносини: Foreign keys vs embedding and references

SQL керує відносинами через foreign keys і JOIN‑и. MongoDB пропонує два підходи: embedding — вкладати пов’язані дані в документ, або references — зв’язувати документи між колекціями по ObjectId.

Вибір залежить від патернів доступу. Якщо дані завжди читаються разом (наприклад, замовлення та його позиції), embedding прибирає JOIN і покращує читання. Якщо «багато» може рости необмежено (наприклад, користувач з тисячами замовлень), краще використовувати references.

# Контроль конкурентності: Pessimistic vs optimistic locking

І MongoDB, і SQL‑СУБД підтримують ACID‑транзакції, але керують конкурентністю інакше. PostgreSQL/MySQL застосовують pessimistic locking, блокуючи рядки чи таблиці. MongoDB орієнтується на optimistic concurrency control, дозволяючи одночасний доступ і виявляючи конфлікти під час коміту.

Жоден підхід не кращий сам по собі — у кожного свої компроміси. Pessimistic знижує паралельність, але запобігає конфліктам; optimistic підвищує паралельність, але іноді вимагає повторних спроб. Дизайн схеми в MongoDB з embedding‑ом часто дозволяє обійтися без багатодокументних транзакцій, бо операції над одним документом атомарні.

# Мова запитів: SQL vs JSON

SQL — декларативні текстові запити з ключовими словами SELECT, JOIN, WHERE. MongoDB — JSON‑подібні запити й aggregation pipeline, де запити описуються як структуровані об’єкти. Laravel‑овий Eloquent часто ховає ці відмінності, але розуміння виконання важливе для оптимізації й налагодження.

# Що переносити

Не все застосунок має обов’язково переїжджати в MongoDB. Рішення ґрунтуйте на патернах доступу:

Багато команд працюють у гібриді: обирають базу під конкретні шаблони доступу.

# Аналіз і підготовка існуючої SQL‑схеми

Перш ніж переносити дані, зрозумійте, що саме переносите. Проаналізуйте таблиці, зв’язки, обмеження й типові запити. Чим краще ви розберетеся в поточній структурі, тим легше спроектувати MongoDB‑схему.

Почніть із виявлення сутностей — у SQL це таблиці. Як вони пов’язані: one‑to‑one, one‑to‑many, many‑to‑many? Це критично, бо вплине на підхід у MongoDB.

Якщо у вас десятки складних таблиць — не панікуйте. Не потрібно мігрувати все відразу. Почніть із 1–2 колекцій, які найбільше виграють від гнучкої схеми; інше можна залишити в SQL або перенести пізніше.

Приклад: просте e‑commerce з таблицями users, orders, order_items. orders посилаються на users, а order_items — на orders і products. Це нормалізований дизайн, типовий для SQL, де для комбінування даних використовуються JOIN‑и.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(255) UNIQUE NOT NULL
);
 
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id),
    total DECIMAL(10,2),
    status VARCHAR(50),
    created_at TIMESTAMP DEFAULT NOW()
);
 
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INT REFERENCES orders(id),
    product_id INT REFERENCES products(id),
    quantity INT,
    price DECIMAL(10,2)
);

Наступний крок — знайти можливості для денормалізації. У MongoDB часто заохочують денормалізацію, бо вона знижує кількість JOIN‑ів і пришвидшує читання. Запитайте себе: які дані майже завжди запитуються разом? Їх варто вкладати.

У прикладі order_items майже завжди повертаються з їхнім order. Тож у MongoDB їх можна вбудувати прямо в документ замовлення.

Позначайте one‑to‑one й one‑to‑many обережно. One‑to‑one (наприклад, user і profile) зазвичай добре вбудовувати. One‑to‑many — можна або вбудувати, або референтити, залежно від розміру та патернів доступу. Якщо «many» необмежений — референси кращі; якщо малий і предиктований — embedding підходить.

Many‑to‑many складніші: у SQL — join‑таблиця; у MongoDB можна зберігати масиви ідентифікаторів (наприклад, user має масив role IDs).

Експорт схеми теж корисний: дампи, ORM‑інтроспекція або Laravel‑міграції допоможуть отримати повну картину. Це стане довідником під час проектування нової MongoDB‑схеми.

Наприкінці складіть чеклист міграції:

Чеклист допоможе нічого не втратити під час міграції та виявити крайні випадки заздалегідь.

# Проєктування нової MongoDB‑схеми

Дизайн у MongoDB відрізняється від SQL: тут потрібно моделювати дані навколо access patterns — як застосунок читає і записує, а не лише теоретичної структури.

Основний принцип: моделюйте дані під запити, а не під ідеальну нормалізацію. Якщо застосунок часто читає користувача разом з профілем, адресою й налаштуваннями — їх варто вбудувати в один документ. Якщо замовлення часто запитуються окремо від користувача — тримайте їх у різних колекціях із референсом.

Повернемося до прикладу e‑commerce. Ми маємо users, orders, order_items. Можлива мапа:

Users collection

Документ користувача з основним профілем. Якщо одна адреса — її можна вбудувати; кілька адрес — масив вкладених документів.

{
  "_id": ObjectId("652f..."),
  "name": "Alice",
  "email": "alice@example.com",
  "addresses": [
    { "city": "Paris", "country": "France", "type": "home" },
    { "city": "London", "country": "UK", "type": "work" }
  ],
  "created_at": ISODate("2025-01-15T10:00:00Z")
}

Orders collection

Кожне замовлення посилається на user і вбудовує позиції. Це дозволяє отримати повне замовлення одним запитом.

{
  "_id": ObjectId("652e..."),
  "user_id": ObjectId("652f..."),
  "status": "completed",
  "total": 250.00,
  "items": [
    { "product_id": ObjectId("650a..."), "quantity": 2, "price": 50.00 },
    { "product_id": ObjectId("650b..."), "quantity": 1, "price": 150.00 }
  ],
  "created_at": ISODate("2025-03-10T14:30:00Z")
}

Ця структура усуває окрему колекцію order_items, бо позиції завжди повертаються разом із замовленням.

Стратегії мапінгу

Валідація схеми

MongoDB підтримує JSON Schema validation, що дозволяє накладати обмеження на рівні бази. Хоча схема гнучка, валідація гарантує наявність критичних полів і правильні типи.

db.createCollection("orders", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["user_id", "status", "total"],
      properties: {
        user_id: { bsonType: "objectId" },
        status: { enum: ["pending", "completed", "canceled"] },
        total: { bsonType: "decimal" }
      }
    }
  }
});

Це захищає від випадкового вставляння некоректних документів і підвищує узгодженість.

Інтеграція з Laravel

У Laravel MongoDB‑моделі розширюють MongoDB\Laravel\Eloquent\Model. Ви можете визначати зв’язки через embedsMany() або referencesOne().

use MongoDB\Laravel\Eloquent\Model;
 
class Order extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'orders';
    protected $fillable = ['user_id', 'status', 'total', 'items'];
 
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

І приклад моделі User:

use MongoDB\Laravel\Eloquent\Model;
 
class User extends Model
{
    protected $connection = 'mongodb';
    protected $collection = 'users';
    protected $fillable = ['name', 'email', 'addresses'];
 
    public function orders()
    {
        return $this->hasMany(Order::class, 'user_id');
    }
}

Так ви зберігаєте знайомий синтаксис Laravel, але працюєте з документною моделлю MongoDB.

Проєктування схеми — баланс між гнучкістю і структурою. Дайте собі змогу еволюціонувати дані, але встановіть валідацію й правила, щоб уникнути хаосу. Починайте з аналізу access patterns, продумуйте мапінг і застосовуйте schema validation для критичних полів.

# Міграція даних

Після проєктування схеми треба перенести дані: експортувати з SQL, трансформувати під нову структуру, імпортувати в MongoDB і перевірити коректність.

Базовий робочий процес:

  1. Export даних із SQL
  2. Transform дані під MongoDB‑схему
  3. Import в MongoDB
  4. Verify точність і повноту

Далі — практичні приклади кожного кроку.

# Експорт даних із SQL

PostgreSQL має pg_dump і psql, MySQL — mysqldump, SQLite — .dump. Можна експортувати таблиці в CSV, JSON або SQL‑дамп.

Наприклад, експорт таблиці users з PostgreSQL у JSON:

psql -d your_database -c "COPY (SELECT row_to_json(users) FROM users) TO STDOUT" > users.json

Це виводить кожен рядок як JSON‑об’єкт у новому рядку — зручний формат для трансформації.

Реляційні дані (orders + order_items) зазвичай експортують окремо, а потім зливають під час трансформації.

# Трансформація даних

Трансформація — це перетворення SQL‑рядків у MongoDB‑документи. Часто це означає:

Для замовлень: експортуйте orders і order_items, згрупуйте items за order_id і вбудуйте масив у документ замовлення.

Приклад простого PHP‑скрипта для трансформації orders:

$orders = json_decode(file_get_contents('orders.json'), true);
$items = json_decode(file_get_contents('order_items.json'), true);
 
// Group items by order_id
$itemsByOrder = [];
foreach ($items as $item) {
    $itemsByOrder[$item['order_id']][] = [
        'product_id' => new MongoDB\BSON\ObjectId($item['product_id']),
        'quantity' => $item['quantity'],
        'price' => $item['price']
    ];
}
 
// Embed items into orders
$transformed = [];
foreach ($orders as $order) {
    $transformed[] = [
        '_id' => new MongoDB\BSON\ObjectId(),
        'user_id' => new MongoDB\BSON\ObjectId($order['user_id']),
        'status' => $order['status'],
        'total' => (float) $order['total'],
        'items' => $itemsByOrder[$order['id']] ?? [],
        'created_at' => new MongoDB\BSON\UTCDateTime(strtotime($order['created_at']) * 1000\)
    ];
}
 
file_put_contents('orders_transformed.json', json_encode($transformed));

Цей скрипт читає експорт, групує позиції за замовленням і формує JSON для імпорту.

# Імпорт у MongoDB

mongoimport — інструмент для масових імпортів. Для JSON файлів у NDJSON або масивному форматі використовуйте:

mongoimport --uri="mongodb+srv://user:pass@cluster.mongodb.net" --db=laravel_mongo --collection=orders --file=orders_transformed.json --jsonArray

Це імпортує документи в колекцію orders.

Для великих обсягів зручніше використовувати batch insert в Laravel:

use MongoDB\Laravel\Eloquent\Model;
 
$orders = json_decode(file_get_contents('orders_transformed.json'), true);
 
foreach ($orders as $order) {
    Order::create($order);
}

Або швидше — пакетна вставка:

DB::connection('mongodb')->collection('orders')->insert($orders);

# Перевірка міграції

Після імпорту перевірте, чи все перенесено коректно. Перевірте:

Приклад підрахунку користувачів у обох БД:

# PostgreSQL
psql -d your_database -c "SELECT COUNT(*) FROM users;"
 
# MongoDB
mongosh "mongodb+srv://cluster.mongodb.net/laravel_mongo" --eval "db.users.countDocuments()"

Якщо лічильники збігаються — добре. Далі порівняйте конкретні документи:

db.users.findOne({ email: "alice@example.com" })

Порівняйте вивід із відповідним SQL‑рядком, переконайтеся, що вкладені структури, як addresses, правильні.

# Рекомендовані інструменти

# Тестуйте на staging

Ніколи не робіть міграцію напряму в продакшн. Спочатку реплікуйте процес у staging, максимально наближеному до продакшну, щоб упіймати проблеми з індексами, типами чи повільними запитами. Після валідації на staging повторіть процес у продакшні в часи низького навантаження.

Міграція даних — один із найризикованіших етапів, але з ретельним плануванням і верифікацією її можна виконати безпечно.

# Перепис запитів і логіки застосунку

Переїзд схеми й даних — лише половина роботи. Друга — адаптація коду: перепис запитів, корекція Eloquent‑зв’язків і перевірка логіки.

Спочатку знайдіть усі SQL‑запити в проєкті: raw SQL, query builder і Eloquent‑методи. У Laravel більшість взаємодій відбувається через Eloquent чи query builder, тому зміни часто локалізуються в моделях і контролерах.

Розглянемо типові операції й їхній еквівалент у MongoDB.

SELECT

SELECT * FROM users WHERE status = 'active';

У MongoDB:

db.users.find({ status: "active" })

У Laravel синтаксис навіть не змінюється:

// SQL
$users = User::where('status', 'active')->get();
 
// MongoDB (той самий синтаксис)
$users = User::where('status', 'active')->get();

Різниця — під капотом.

JOIN

JOIN‑и найбільше відрізняються. Якщо дані вбудовані — JOIN не потрібен. Якщо ні — використовуйте $lookup в aggregation pipeline:

db.users.aggregate([
  { $match: { status: "active" } },
  {
    $lookup: {
      from: "orders",
      localField: "_id",
      foreignField: "user_id",
      as: "orders"
    }
  },
  { $project: { name: 1, "orders.total": 1 } }
])

У Laravel можна використовувати Eloquent‑зв’язки:

$users = User::where('status', 'active')->with('orders')->get();

Laravel підготує або вбудовані документи, або $lookup залежно від визначених зв’язків.

GROUP BY і агрегації

SELECT user_id, COUNT(*) AS order_count
FROM orders
GROUP BY user_id
ORDER BY order_count DESC;

У MongoDB через aggregation:

db.orders.aggregate([
  { $group: { _id: "$user_id", order_count: { $sum: 1 } } },
  { $sort: { order_count: -1 } }
])

У Laravel:

$results = DB::connection('mongodb')
    ->collection('orders')
    ->raw(function ($collection) {
        return $collection->aggregate([
            ['$group' => ['_id' => '$user_id', 'order_count' => ['$sum' => 1]]],
            ['$sort' => ['order_count' => -1]]
        ]);
    });

Aggregation framework у MongoDB дуже потужний і замінює багато складних SQL‑запитів.

UPDATE і DELETE

У SQL:

UPDATE users SET status = 'inactive' WHERE last_login < '2024-01-01';
DELETE FROM users WHERE status = 'inactive';

У MongoDB:

db.users.updateMany({ last_login: { $lt: new Date("2024-01-01") } }, { $set: { status: "inactive" } })
db.users.deleteMany({ status: "inactive" })

У Laravel синтаксис також схожий:

User::where('last_login', '<', '2024-01-01')->update(['status' => 'inactive']);
User::where('status', 'inactive')->delete();

Перевірка консистентності результатів

Переписуючи запити, обов’язково перевіряйте, що результати збігаються з поведінкою в SQL. Пишіть тести, які порівнюють результати до й після міграції:

public function test_active_users_query()
{
    // Insert test data
    User::create(['name' => 'Alice', 'status' => 'active']);
    User::create(['name' => 'Bob', 'status' => 'inactive']);
 
    // Query
    $users = User::where('status', 'active')->get();
 
    // Assert
    $this->assertCount(1, $users);
    $this->assertEquals('Alice', $users->first()->name);
}

Запустіть такі тести проти обох версій БД, щоб упевнитися в однаковій поведінці.

Крайні випадки

Документуйте ці відмінності й підлаштуйте логіку застосунку.

Перепис запитів — можливо найтриваліша частина міграції, але й та, де можна суттєво оптимізувати й спростити логіку, використовуючи документну модель MongoDB.

# Робота з відносинами і транзакціями

Одна з ключових відмінностей між SQL і MongoDB — підхід до відносин і транзакцій. SQL використовує нормалізовані таблиці й транзакції по кількох таблицях за замовчуванням; MongoDB заохочує embedding і пропонує multi‑document транзакції як опцію.

# SQL‑зв’язки

У SQL зв’язки явні й забезпечуються на рівні бази: foreign keys гарантують referential integrity, а cascading deletes видаляють залежні записи автоматично.

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id) ON DELETE CASCADE,
    total DECIMAL(10,2)
);

Такий підхід робить відносини прозорими, але вимагає JOIN‑ів для читання пов’язаних даних.

# Відносини в MongoDB

MongoDB використовує embedding або referencing.

Embedding — зберігає пов’язані дані всередині батьківського документа, що робить читання швидким, але підходить лише для обмежених наборів даних.

{
  "_id": ObjectId("652e..."),
  "user_id": ObjectId("652f..."),
  "status": "completed",
  "items": [
    { "product_id": ObjectId("650a..."), "quantity": 2, "price": 50.00 }
  ]
}

Referencing — посилання між колекціями через ObjectId; для отримання пов’язаних документів потрібен $lookup або кілька запитів.

{
  "_id": ObjectId("652e..."),
  "user_id": ObjectId("652f..."),
  "product_ids": [ObjectId("650a..."), ObjectId("650b...")]
}

У Laravel визначайте зв’язки через Eloquent‑методи (embedsMany, belongsTo тощо).

// Embedding
class Order extends Model
{
    protected $connection = 'mongodb';
 
    public function items()
    {
        return $this->embedsMany(OrderItem::class);
    }
}
 
// Referencing
class Order extends Model
{
    protected $connection = 'mongodb';
 
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

# Транзакції в SQL

У PostgreSQL/MySQL транзакції звичні й використовуються для групування кількох операцій:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;

При помилці всі зміни відкатуються.

# Транзакції в MongoDB

MongoDB з версії 4.0 підтримує multi‑document ACID‑транзакції. До цього атомарними були лише операції над одним документом. Оновлення одного документу (включно з вкладеними масивами) завжди атомарне; для кількох документів потрібна явна транзакція.

Приклад транзакції в Laravel:

use MongoDB\Laravel\Connection;
 
DB::connection('mongodb')->transaction(function (Connection $connection) {
    $connection->collection('accounts')
        ->where('_id', new MongoDB\BSON\ObjectId($fromId))
        ->decrement('balance', 100);
 
    $connection->collection('accounts')
        ->where('_id', new MongoDB\BSON\ObjectId($toId))
        ->increment('balance', 100);
});

Laravel‑пакет автоматично керує сесією в транзакції. Якщо одна операція провалиться — MongoDB відкотить усі зміни.

# Коли використовувати транзакції

У MongoDB транзакції варто застосовувати рідко — вони додають накладні витрати і залежать від синхронізації replica set. Віддавайте перевагу атомарним операціям над одним документом, коли це можливо.

Наприклад, замість транзакції для оновлення user і profile — вбудуйте profile в user. Тепер оновлення профілю — одна атомарна операція без транзакції:

{
  "_id": ObjectId("652f..."),
  "name": "Alice",
  "profile": {
    "bio": "Developer",
    "avatar": "https://..."
  }
}
User::where('_id', $userId)->update(['profile.bio' => 'Senior Developer']);

Використовуйте транзакції, коли:

# Поширені пастки

Робота з відносинами й транзакціями в MongoDB вимагає іншого мислення: вбудовуйте там, де доречно, використовуйте references для великих або незалежних наборів і застосовуйте транзакції лише там, де це дійсно потрібно.

# Тестування й валідація

Тестування критичне під час будь‑якої міграції. Потрібно переконатися, що дані перенесені коректно, запити повертають ті самі результати, а застосунок поводиться так само. Тут — стратегії валідації на всіх рівнях.

# Точність даних

Почніть з перевірки, що всі дані перейшли без втрат чи пошкоджень. Простий початок — порахувати записи:

# PostgreSQL
psql -d your_database -c "SELECT COUNT(*) FROM users;"
 
# MongoDB
mongosh "mongodb+srv://cluster.mongodb.net/laravel_mongo" --eval "db.users.countDocuments()"

Якщо лічильники збігаються — це хороша ознака. Далі — вибіркова перевірка окремих записів у обох БД.

Для вкладених документів перевірте кількість елементів у масивах (наприклад, п’ять addresses у SQL → п’ять у MongoDB).

# Випадкова вибірка

Перевіряти кожен запис неможливо — використовуйте випадкову вибірку. Наприклад:

// Fetch 100 random users from SQL
$sqlUsers = DB::connection('mysql')
    ->table('users')
    ->inRandomOrder()
    ->limit(100)
    ->get();
 
// Fetch the same users from MongoDB
foreach ($sqlUsers as $sqlUser) {
    $mongoUser = User::where('_id', new MongoDB\BSON\ObjectId($sqlUser->id))->first();
 
    // Compare fields
    $this->assertEquals($sqlUser->name, $mongoUser->name);
    $this->assertEquals($sqlUser->email, $mongoUser->email);
}

Це масштабований підхід, що виявляє крайні випадки.

# Перевірка поведінки застосунку

Крім даних, переконайтеся, що застосунок працює так само. Прогоніть існуючий тест‑пак проти MongoDB‑версії. Laravel‑feature‑тести ідеально підходять — вони симулюють HTTP‑запити й перевіряють контролери, маршрути та представлення.

public function test_active_users_endpoint()
{
    // Seed test data
    User::create(['name' => 'Alice', 'status' => 'active']);
    User::create(['name' => 'Bob', 'status' => 'inactive']);
 
    // Make request
    $response = $this->getJson('/api/users?status=active');
 
    // Assert
    $response->assertStatus(200);
    $response->assertJsonCount(1, 'data');
    $response->assertJsonFragment(['name' => 'Alice']);
}

Запустіть ці тести до й після міграції — якщо проходять, поведінка збережена.

# Автоматизація верифікації

Для великих міграцій пишіть скрипти, що автоматично порівнюють SQL і MongoDB: рахують записи, порівнюють поля для вибірки, шукають відсутні чи зайві документи і перевіряють відносини.

Приклад Artisan‑команди для перевірки кількостей користувачів:

Artisan::command('verify-users', function () {
    $sqlCount = DB::connection('mysql')->table('users')->count();
    $mongoCount = DB::connection('mongodb')->collection('users')->count();
 
    $this->info("SQL users: $sqlCount");
    $this->info("MongoDB users: $mongoCount");
 
    if ($sqlCount === $mongoCount) {
        $this->info('Counts match!');
    } else {
        $this->error('Counts do not match!');
    }
});

Запускайте такі перевірки в процесі деплою, щоб ловити проблеми раніше.

# Перевірка продуктивності

Окрім коректності, потрібно переконатися, що MongoDB працює не гірше за SQL. Порівняйте затримки й пропускну здатність запитів.

У Laravel можна логувати запити та вимірювати час виконання:

DB::connection('mongodb')->enableQueryLog();
 
$users = User::where('status', 'active')->get();
 
$queries = DB::connection('mongodb')->getQueryLog();
dump($queries);

Виявляйте повільні запити та оптимізуйте їх індексами або зміною схеми.

# Моніторинг

Після запуску уважно моніторте MongoDB: використовуйте MongoDB Compass або Atlas Monitoring для відстеження:

Налаштуйте алерти на повільні запити, високий memory usage або збої підключень — так виявите проблеми до того, як вони вплинуть на користувачів.

Тестування й валідація — не одноразова дія, а постійний процес, що гарантує успіх міграції й стабільність застосунку.

# Оптимізація продуктивності

Перехід на MongoDB відкриває можливості для оптимізації: гнучка схема, потужні індекси і горизонтальне масштабування дають інструменти, яких іноді бракує в SQL.

# Індекси

Індекси — ключ до швидких запитів. Без індексів MongoDB робить collection scan, що швидко стає непрактичним при зростанні даних.

Типи індексів:

Створюйте індекси на полях, які часто фільтруються:

Schema::connection('mongodb')->table('users', function ($collection) {
    $collection->index('email'); // Single-field index
});
 
Schema::connection('mongodb')->table('orders', function ($collection) {
    $collection->index(['user_id' => 1, 'created_at' => -1]); // Compound index
});

Тут 1 — ascending, -1 — descending. Використовуйте explain(), щоб перевірити використання індексів; якщо бачите COLLSCAN, запит потребує оптимізації.

# Кешування

Для дорогих запитів, особливо агрегацій, кешування знижує навантаження. У Laravel це просто через Cache:

$stats = Cache::remember('dashboard:stats', now()->addMinutes(5), function () {
    return DB::connection('mongodb')
        ->collection('orders')
        ->raw(function ($collection) {
            return $collection->aggregate([
                ['$group' => ['_id' => null, 'total' => ['$sum' => '$total']]],
            ]);
        });
});

Кеш на 5 хвилин зменшить число запусків важких запитів.

# Sharding

Для дуже великих даних MongoDB підтримує sharding — розподіл колекцій між серверами. Це складна опція і потрібна тільки тоді, коли дані перевищують можливості одного сервера. Для більшості застосунків достатньо добре налаштованого replica set.

Якщо ви починаєте шардінг, виберіть shard key уважно: він повинен рівномірно розподіляти дані, збігатися з патернами запитів і мати високу кардинальність. Наприклад, user_id може бути хорошим shard key для orders, якщо часто шукають замовлення по користувачу.

# Оптимізація запитів

Навіть з індексами деякі запити повільні. Використовуйте aggregation pipeline, щоб виконувати роботу на сервері, а не в PHP.

// Slow
$orders = Order::all();
$activeOrders = $orders->filter(fn($order) => $order->status === 'active');

// Fast
$activeOrders = Order::where('status', 'active')->get();

Для складних обчислень застосовуйте aggregation:

$stats = DB::connection('mongodb')
    ->collection('orders')
    ->raw(function ($collection) {
        return $collection->aggregate([
            ['$match' => ['status' => 'completed']],
            ['$group' => ['_id' => '$user_id', 'total_spent' => ['$sum' => '$total']]],
            ['$sort' => ['total_spent' => -1]],
            ['$limit' => 10]
        ]);
    });

Це дозволяє знайти топ‑10 покупців без витягування всіх замовлень у PHP.

# Налаштування зберігання

Максимальний розмір документа в MongoDB — 16 MB. Якщо документи близькі до цього ліміту, розгляньте:

Тримайте документи компактними і сфокусованими на тому, що реально потрібно застосунку.

Оптимізація — ітеративний процес: почніть з індексів, моніторьте виконання запитів і коригуйте схему на основі реального навантаження.

# Поширені помилки і як їх уникнути

Міграція з SQL у MongoDB складна, і навіть досвідчені розробники роблять помилки. Ось найчастіші з них і як їх уникнути.

# Копіювання реляційного дизайну в MongoDB

Часто MongoDB використовують як «щось як SQL»: створюють колекції, що точно копіюють таблиці, з безліччю референсів і без embedding‑у. Це марнує переваги документної моделі.

Натомість використовуйте денормалізацію: вбудовуйте пов’язані дані, що читаються разом (наприклад, profile в user), замість окремих колекцій.

# Зловживання $lookup для простих відносин

$lookup потужний, але повільніший за embedding. Якщо ви часто використовуєте $lookup, перегляньте схему: чи можна вбудувати дані або зберегти сніпшот потрібної інформації в документі?

# Ігнорування індексів

Без індексів запити повільні. Створюйте індекси на полях у where(), orderBy() і тих, що використовуються в агрегаціях. Перевіряйте через explain(), чи використовується індекс.

# Забуття валідації схеми або консистентності імен полі

Гнучкість MongoDB може призвести до розбіжностей: іноді в одному документі поле називається userId, в іншому — user_id. Використовуйте schema validation, щоб впровадити єдині правила іменування та обов’язкові поля.

# Недооцінка scope транзакцій

Транзакції в MongoDB дорожчі, ніж у SQL. Часте їхнє використання вдарить по продуктивності. Переосмисліть схему, щоб мінімізувати потребу в транзакціях, віддаючи перевагу атомарним операціям над одним документом.

# Відсутність стратегії відкату

Що робити, якщо міграція зупиняється на півшляху? Завжди майте rollback‑план:

# Чеклист перед rollout

Перед перенесенням у продакшн переконайтеся, що:

Дотримання цих практик допоможе уникнути типових помилок і зробити міграцію плавною.

# Висновок

Міграція з SQL у MongoDB — це не лише переміщення даних, а зміна підходу до моделювання, запитів і консистентності. PostgreSQL підходить для транзакційних навантажень з суворою цілісністю; MongoDB — для гнучких, ієрархічних і швидко змінних моделей у масштабі.

Ми пройшли весь шлях: аналіз ландшафту й SQL‑схеми, проєктування MongoDB‑структури, трансформацію й імпорт даних, перепис запитів, тестування й оптимізацію. Кожен крок вимагає планування й ретельної валідації.

Ключова ідея: моделюйте дані під патерни використання, а не під теоретичну нормалізацію. Вбудовуйте пов’язані дані, використовуйте референси для великих чи незалежних наборів і застосовуйте транзакції тільки там, де без них не обійтися.

Для Laravel‑розробників обидві технології мають місце: PostgreSQL — для строгої транзакційної логіки, MongoDB — для гнучких моделей і горизонтального масштабу. Часто найкраще рішення — гібрид, де кожна БД обслуговує свої задачі.

Рухайтеся поступово: мігруйте по одній колекції або фичі, ретельно валідуйте на staging і уважно моніторьте продакшн. Такий інкрементальний підхід знижує ризики і дає змогу коригувати курс на ходу.

Додатково дивіться офіційну документацію MongoDB по data modeling і migrations, а також документацію по Laravel MongoDB package. MongoDB Atlas також має інструменти для міграцій, які допомагають автоматизувати частину процесу для великих наборів даних.

Пам’ятайте: міграція не обов’язково «все або нічого». Працюйте гібридно — використовуйте кожну базу там, де вона сильніша, і еволюціонуйте стратегію з часом.