Багато застосунків починають із SQL-версії на PostgreSQL чи MySQL. Вони добре працюють кілька років — поки не стикаються зі шкала́більністю, жорсткою схемою або потребою зберігати напівструктуровані дані. Через це дедалі більше команд розглядають MongoDB як альтернативу або доповнення до існуючої SQL-інфраструктури.
MongoDB пропонує документно-орієнтований, схема‑гнучкий підхід, який краще підходить для швидко змінних моделей даних. На відміну від SQL‑баз з таблицями й рядками, MongoDB зберігає документи у форматі JSON-like у колекціях — кожен запис може мати свою структуру. Це дає свободу, але вимагає іншого підходу до моделювання, запитів і гарантій цілісності.
Міграція з SQL у MongoDB — це не простий «замінити БД», а вибір найкращого інструменту для конкретного випадку. SQL добре працює з відносинами та транзакціями по нормалізованих таблицях; MongoDB — з різнорідними, ієрархічними або швидко змінними даними в масштабі. Часто в продуктиві використовують обидві системи, кожну для своїх задач.
У цій статті ми пройдемо весь процес міграції: планування й редизайн схеми, трансформацію даних, перепис запитів і тестування. Ви дізнаєтеся, як проаналізувати існуючу SQL‑схему, спроектувати еквівалент у MongoDB, безпечно перенести дані та адаптувати логіку застосунку під документну модель. Наприкінці отримаєте чіткий план для міграції Laravel‑застосунків із збереженням цілісності і надійності.
Ця стаття для розробників і архітекторів, які планують перевести на MongoDB існуючі Laravel або PHP‑проєкти — повністю або частково. Тут є практичні приклади, поширені помилки та стратегії тестування перед запуском у продакшн.
Міграція з SQL у MongoDB передбачає кілька базових технічних відмінностей, що впливають на дизайн і запити. Усвідомлення їх наперед допоможе вирішити, що саме і як переносити.
У SQL дані живуть у таблицях з фіксованими стовпцями та типами — кожен рядок має ту ж структуру. У MongoDB дані зберігаються в колекціях, де кожен документ може мати різні поля. Це дозволяє еволюціонувати схему без ALTER TABLE і простоїв, але потребує валідації й дисципліни для збереження послідовності.
SQL керує відносинами через foreign keys і JOIN‑и. MongoDB пропонує два підходи: embedding — вкладати пов’язані дані в документ, або references — зв’язувати документи між колекціями по ObjectId.
Вибір залежить від патернів доступу. Якщо дані завжди читаються разом (наприклад, замовлення та його позиції), embedding прибирає JOIN і покращує читання. Якщо «багато» може рости необмежено (наприклад, користувач з тисячами замовлень), краще використовувати references.
І MongoDB, і SQL‑СУБД підтримують ACID‑транзакції, але керують конкурентністю інакше. PostgreSQL/MySQL застосовують pessimistic locking, блокуючи рядки чи таблиці. MongoDB орієнтується на optimistic concurrency control, дозволяючи одночасний доступ і виявляючи конфлікти під час коміту.
Жоден підхід не кращий сам по собі — у кожного свої компроміси. Pessimistic знижує паралельність, але запобігає конфліктам; optimistic підвищує паралельність, але іноді вимагає повторних спроб. Дизайн схеми в MongoDB з embedding‑ом часто дозволяє обійтися без багатодокументних транзакцій, бо операції над одним документом атомарні.
SQL — декларативні текстові запити з ключовими словами SELECT, JOIN, WHERE. MongoDB — JSON‑подібні запити й aggregation pipeline, де запити описуються як структуровані об’єкти. Laravel‑овий Eloquent часто ховає ці відмінності, але розуміння виконання важливе для оптимізації й налагодження.
Не все застосунок має обов’язково переїжджати в MongoDB. Рішення ґрунтуйте на патернах доступу:
Кандидати для MongoDB: дані з різними структурами (каталоги товарів з різними атрибутами), ієрархічні або вкладені дані, що завжди читаються разом (замовлення з позиціями), напівструктуровані логи чи відгуки, і дані, що часто змінюють схему.
Підходить для обох: стандартні CRUD‑операції, дані, що вимагають ACID (обидві СУБД підтримують це), складні відносини (можна реалізувати по‑різному).
Багато команд працюють у гібриді: обирають базу під конкретні шаблони доступу.
Перш ніж переносити дані, зрозумійте, що саме переносите. Проаналізуйте таблиці, зв’язки, обмеження й типові запити. Чим краще ви розберетеся в поточній структурі, тим легше спроектувати 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 відрізняється від 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 і перевірити коректність.
Базовий робочий процес:
Далі — практичні приклади кожного кроку.
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‑документи. Часто це означає:
id → _id).Для замовлень: експортуйте 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 для імпорту.
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 повторіть процес у продакшні в часи низького навантаження.
Міграція даних — один із найризикованіших етапів, але з ретельним плануванням і верифікацією її можна виконати безпечно.
Переїзд схеми й даних — лише половина роботи. Друга — адаптація коду: перепис запитів, корекція 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 зв’язки явні й забезпечуються на рівні бази: 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 використовує 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');
}
}
У PostgreSQL/MySQL транзакції звичні й використовуються для групування кількох операцій:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
При помилці всі зміни відкатуються.
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 хвилин зменшить число запусків важких запитів.
Для дуже великих даних 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 використовують як «щось як SQL»: створюють колекції, що точно копіюють таблиці, з безліччю референсів і без embedding‑у. Це марнує переваги документної моделі.
Натомість використовуйте денормалізацію: вбудовуйте пов’язані дані, що читаються разом (наприклад, profile в user), замість окремих колекцій.
$lookup потужний, але повільніший за embedding. Якщо ви часто використовуєте $lookup, перегляньте схему: чи можна вбудувати дані або зберегти сніпшот потрібної інформації в документі?
Без індексів запити повільні. Створюйте індекси на полях у where(), orderBy() і тих, що використовуються в агрегаціях. Перевіряйте через explain(), чи використовується індекс.
Гнучкість MongoDB може призвести до розбіжностей: іноді в одному документі поле називається userId, в іншому — user_id. Використовуйте schema validation, щоб впровадити єдині правила іменування та обов’язкові поля.
Транзакції в MongoDB дорожчі, ніж у SQL. Часте їхнє використання вдарить по продуктивності. Переосмисліть схему, щоб мінімізувати потребу в транзакціях, віддаючи перевагу атомарним операціям над одним документом.
Що робити, якщо міграція зупиняється на півшляху? Завжди майте rollback‑план:
Перед перенесенням у продакшн переконайтеся, що:
Дотримання цих практик допоможе уникнути типових помилок і зробити міграцію плавною.
Міграція з SQL у MongoDB — це не лише переміщення даних, а зміна підходу до моделювання, запитів і консистентності. PostgreSQL підходить для транзакційних навантажень з суворою цілісністю; MongoDB — для гнучких, ієрархічних і швидко змінних моделей у масштабі.
Ми пройшли весь шлях: аналіз ландшафту й SQL‑схеми, проєктування MongoDB‑структури, трансформацію й імпорт даних, перепис запитів, тестування й оптимізацію. Кожен крок вимагає планування й ретельної валідації.
Ключова ідея: моделюйте дані під патерни використання, а не під теоретичну нормалізацію. Вбудовуйте пов’язані дані, використовуйте референси для великих чи незалежних наборів і застосовуйте транзакції тільки там, де без них не обійтися.
Для Laravel‑розробників обидві технології мають місце: PostgreSQL — для строгої транзакційної логіки, MongoDB — для гнучких моделей і горизонтального масштабу. Часто найкраще рішення — гібрид, де кожна БД обслуговує свої задачі.
Рухайтеся поступово: мігруйте по одній колекції або фичі, ретельно валідуйте на staging і уважно моніторьте продакшн. Такий інкрементальний підхід знижує ризики і дає змогу коригувати курс на ходу.
Додатково дивіться офіційну документацію MongoDB по data modeling і migrations, а також документацію по Laravel MongoDB package. MongoDB Atlas також має інструменти для міграцій, які допомагають автоматизувати частину процесу для великих наборів даних.
Пам’ятайте: міграція не обов’язково «все або нічого». Працюйте гібридно — використовуйте кожну базу там, де вона сильніша, і еволюціонуйте стратегію з часом.