Як перейти з 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 також має інструменти для міграцій, які допомагають автоматизувати частину процесу для великих наборів даних.

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

Популярні

Logomark Logotype

Як задокументувати кілька API в Laravel за допомогою Scramble

Ви знали, що в одному додатку Laravel можна реалізувати кілька API? У нашій статті ви дізнаєтеся, як за допомогою Scramble легко документувати різні версії API та налаштувати доступ до документації, щоб зробити її публічною або приватною. Читайте далі, щоб дізнатися більше

Logomark Logotype

Створення CLI-додатка за допомогою Laravel та Docker

Зазирніть у світ Laravel, де потужний CLI-фреймворк відкриває нові можливості для розробки командного інтерфейсу. Дізнайтеся, як створити просту утиліту для перевірки акцій, яка працює з Docker, та які переваги це може принести у вашому проєкті!

Logomark Logotype

4 поширені помилки Vite у Laravel

Використання Vite для створення фронтенд-ресурсів у вашому додатку Laravel може бути захоплюючим, але іноді ви можете стикнутися з певними помилками. У цій статті ми розглянемо чотири поширені помилки, з якими ви можете зіткнутися, а також підкажемо способи їх усунення, щоб ви могли знову зосередитися на розробці вашого додатку