
Я не оптимизировал код — я перестал заставлять свою базу данных выполнять неподходящую работу. Как только я понял разницу между SQL и NoSQL, задержки снизились, а продакшен перестал давать сбой.
Раньше я думал, что выбор между SQL и NoSQL — это дело предпочтений.
Вы понимаете, о чем речь: SQL — старая, но надежная, NoSQL — современная и масштабируемая, и у всех есть свое мнение по этому поводу.
Потом продакшен научил меня смирению.
Мой бэкенд не выходил из строя. Было хуже: он медленно деградировал — тихо и незаметно. Задержки росли, дашборды становились все «ажиотажнее», и каждая «незначительная» функциональность ощущалась как дополнительная нагрузка на систему. Я делал то, что делает большинство из нас поначалу: настраивал запросы, добавлял индексы, увеличивал мощности серверов, накидывал кэширование.
Некоторые улучшения помогали. Но ни одно из них не было по-настоящему изящным.
Настоящий прорыв случился, когда я перестал спрашивать:
«Какая база данных лучше?»
и начал задавать единственный по-настоящему важный вопрос:
«Какую форму имеют мои данные и паттерны доступа к ним?»
Вот тогда я наконец понял разницу между SQL и NoSQL. И как только это произошло, решения стали очевидны. Мой бэкенд не просто «заработал». Он стал легче, быстрее и предсказуемее.
Это история о том моменте озарения — плюс практическая схема, которую можно применить, не превращая свою систему в научно-исследовательский проект.
Ошибка, которую я повторял снова и снова
Я относился к базе данных как к хранилищу.
У меня были данные, мне нужно было их сохранить, и я считал, что производительность сводится в основном к:
- добавлению индексов;
- вертикальному масштабированию;
- кэшированию горячих чтений.
Но проблема была не в хранилище как таковом. Проблема была в использовании его не по назначению.
Я использовал одну модель для всего:
- транзакционных записей;
- сложных фильтров;
- отчетности;
- потоков событий;
- пользовательских сессий;
- логов.
Этот подход «одна база данных на все случаи жизни» работает, но лишь до тех пор, пока трафик не вырастет, а продукт не станет более реальным, чем предположения о нем.
Момент истины: SQL и NoSQL оптимизированы под разные проблемы
Вместо того чтобы сравнивать SQL и NoSQL как соперников, считайте их инструментами, созданными для решения разных типов проблем.
SQL-системы созданы для:
- связей (использование соединений (joins) как первоклассных функций);
- согласованности (обеспечение надежных транзакций);
- ограничений (обеспечение корректности данных);
- структурированных запросов (возможность использования специальных вопросы, сложных фильтров).
Они идеальны там, где важна правильность, а данные по своей природе являются реляционными:
- заказы и платежи;
- инвентарь и движение товаров;
- HR / начисление зарплат;
- бухгалтерские книги, аудит;
- все, где состояние «полузаписанных» данных неприемлемо.
NoSQL-системы созданы для:
- гибких или развивающихся форм;
- высокой пропускной способности записи;
- распределенного масштабирования;
- простых шаблонов запросов;
- дешевого чтения по ключу.
Они идеальны в тех случаях, когда шаблон доступа предсказуем и требуется скорость:
- пользовательские сессии;
- кэши;
- каталоги продуктов с различными атрибутами;
- прием событий;
- хранение логов/телеметрии;
- хронология сообщений (в зависимости от модели).
Проблема не в том, что одна из них в целом быстрее. Проблема в следующем: приложение становится медленным, когда база данных вынуждена выполнять работу, для которой она не была разработана.
Что замедлило работу моего бэкэнда (простыми словами)
У меня было два основных узких места.
1) Я использовал SQL для рабочих нагрузок с интенсивной записью и добавлением.
Я хранил постоянно растущий поток событий в реляционной таблице и задавал ей такие вопросы, как:
- «Покажи мне последние события для пользователя X».
- «Сгруппируй по типу за последние 30 дней».
- «Отфильтруй по нескольким дополнительным полям».
В небольших масштабах это работало нормально.
В больших масштабах это привело к:
- большим индексам;
- замедлению вставки;
- дорогостоящей агрегации;
- высокой нагрузке на ввод-вывод во время пикового трафика.
База данных не была «плохой». Просто от нее требовали работать как хранилище событий + аналитический движок.
2) Я использовал NoSQL как реляционную базу данных.
В другой части системы я хранил документы, которые казались удобными — до тех пор, пока мне не понадобились:
- запросы между сущностями;
- правила уникальности;
- транзакционные обновления по нескольким документам;
- последовательные ограничения.
В тот момент, когда мне понадобились «гарантии бизнес-корректности», я начал реализовывать их в коде приложения.
Это самый быстрый способ создать незаметные ошибки, которые проявятся через три недели.
Структура, которая решила проблему
Вот ментальная модель, которая наконец-то сделала все очевидным:
Шаг 1: Определение «уровня достоверности» данных
Спросите: насколько дорого обходятся неверные данные?
Если «неверные» данные приводят к катастрофическим последствиям (деньги, запасы, разрешения): лучший выбор — SQL. Используйте транзакции и ограничения.
Если «неверные» данные допустимы или поддаются восстановлению (логи, метрики, аналитика, производные представления): NoSQL — подходящий вариант.
Шаг 2: Определение модели доступа (это важнее, чем данные)
Задайте себе вопросы:
- Преобладают ли операции чтения или записи?
- Используется ли точечный поиск (по идентификатору/ключу) или нужны специальные запросы?
- Нужны ли соединения?
- Нужны ли агрегации?
- Нужны ли транзакции между несколькими сущностями?
Затем определите форму.
SQL отлично подходит, когда требуется:
- бизнес-логика с несколькими таблицами;
- ограничения (уникальные, внешние ключи);
- границы транзакций;
- непредсказуемые запросы.
NoSQL отлично подходит, когда нужно:
- быстрое чтение по ключу;
- высокая пропускная способность записи;
- гибкая схема;
- распределение огромных объемов.
Шаг 3: Понимание, что «один размер» редко бывает идеальным размером
Именно здесь проявляется преимущество производительности.
Когда вы разделяете рабочие нагрузки, база данных перестает выполнять неестественную работу.
Именно тогда бэкенд «внезапно» становится быстрее.
Изменение архитектуры, которое привело к ускорению бэкенда
Я не переписывал все. Я не подвергал миграции всю систему.
Я сделал кое-то более простое:
Я перестал заставлять одну базу данных выполнять две разные задачи.
Я разделил систему на:
- Хранилище транзакций (SQL) — источник истины.
- Хранилище обслуживания/скорости (NoSQL) — оптимизировано для конкретных операций чтения/записи.
- Кэш (опционально) — для горячих путей.
- Хранилище аналитики (опционально) — для отчетности.
Вот ключевая идея:
Сохраняйте правильность в SQL. Повышайте скорость с помощью производных данных, оптимизированных для чтения.
Вместо того, чтобы выполнять тяжелые операции чтения из реляционной базы данных при каждом запросе, я создал модель чтения, которая была быстрой по своему дизайну.
Конкретный пример: Система заказов (что изменилось)
До (медленный путь)
- Пользователь открывает «Мои заказы».
- Бэкенд запрашивает
orders+order_items+shipment
- Добавляет фильтры, сортировку, пагинацию.
- Возвращает ответ.
По мере роста таблиц этот запрос становился все тяжелее — даже с индексами — потому что соединения и фильтры в больших масштабах требуют реальных затрат IO и CPU.
После (быстрый путь)
SQL остается источником достоверной информации для:
- заказов;
- платежей;
- переходов между состояниями.
Но для «Моих заказов» я храню заранее сформированный документ в хранилище NoSQL:
{
"userId": "u123",
"orders": [
{
"orderId": "o998",
"status": "SHIPPED",
"total": 450000,
"itemsPreview": ["Bag A", "Wallet B"],
"lastUpdated": "2025-12-31T11:12:00Z"
}
]
}
Теперь запрос выглядит следующим образом:
- один поиск по ключу (быстрый);
- возврат ответа;
- отсутствие сложных путей соединения в условиях горячего трафика
Документ NoSQL является производным. Если он немного устарел на несколько секунд, это не страшно. Источником достоверной информации по-прежнему остается SQL.
Это единственное изменение позволило снять целый класс нагрузки с моей основной базы данных.
Разве это не дублирование?
Да. Но это дублирование полезное.
Дублирование опасно, когда вы его не контролируете. Дублирование эффективно, когда вы делаете это намеренно.
Современная модель выглядит так:
- SQL: правильная и последовательная;
- NoSQL: быстрая и адаптированная для запросов;
- События/CDC: синхронизация.
Вы не дублируете «истину». Вы дублируете «представления».
А представления можно оптимизировать.
Что я сделал, чтобы обеспечить их синхронизацию (без лишних сложностей)
Есть много способов, от простых до продвинутых.
Простой (подходит для многих систем):
- После фиксации транзакции в SQL публикуется событие.
- Рабочий процесс обрабатывает событие и обновляет модель чтения NoSQL.
- Повторные попытки + идемпотентность на стороне потребителя.
Если потребитель на короткое время выходит из строя, модель чтения «зависает». Система по-прежнему работает, потому что источник истины остается неизменным.
Продвинутый (если нужны более надежные гарантии):
- Шаблон outbox, обеспечивающий сохранение сообщений в хранилище данных (запись события в SQL в той же транзакции).
- Потребитель надежно считывает из outbox.
- Предотвращает ситуацию «событие зафиксировано, но не опубликовано».
Необязательно начинать с этого. Но полезно знать о существовании продвинутой версии.
Настоящий выигрыш в производительности был не в NoSQL — он был в предотвращении случайной сложности
Вот неудобная часть.
Мой бэкенд стал быстрее в основном потому, что я перестал заставлять каждый запрос выполнять «детективную работу».
Когда запрос заставляет систему:
- объединять несколько таблиц;
- вычислять состояние;
- агрегировать данные;
- динамически форматировать представления,
приходится повторно нести эти затраты при трафике.
Зрелые системы избегают этого, перемещая эти затраты за пределы критического пути:
- предварительно вычисляют (там, где это имеет смысл);
- формируют данные для запроса;
- принимают конечную согласованность, где это безопасно.
Так достигается эффект «бума»: меньше работы на каждый запрос.
Краткая шпаргалка-памятка
Используйте SQL, когда:
- правильность данных не подлежит обсуждению;
- нужны транзакции;
- нужны ограничения (constraints);
- нужны соединения (joins) и гибкие запросы.
Используйте NoSQL, когда:
- нужен быстрый доступ по ключу;
- у вас огромная пропускная способность записи;
- схема часто меняется;
- запросы предсказуемы;
- создаете модели чтения или производные представления.
Используйте обе базы, когда:
- хотите использовать SQL как источник истины + NoSQL для быстрого обслуживания;
- можете допустить конечную согласованность для чтения;
- требуется защитить SQL от запросов с высокой нагрузкой.
Заключительный урок
Я не добился волшебным образом ускорения бэкенда, «перейдя на NoSQL». Я не решил все проблемы, «оптимизировав SQL».
Я наконец понял нечто более простое: базы данных — не взаимозаменяемые хранилища. Это специализированные инструменты, созданные для конкретных задач.
Как только вы согласуете рабочую нагрузку с подходящим инструментом, производительность повысится сама собой.
Поэтому, если ваш бэкенд постепенно становится все тяжелее, не начинайте с «большего количества индексов» или «более мощных инстансов».
Начните с вопроса:
Не заставляю ли я свою базу данных выполнять задачу, для которой она не предназначена?
Ответьте на этот вопрос честно — и ваша система тоже заработает быстрее.
Читайте также:
- Основные методы оптимизации базы данных SQL
- Основы SQL: разница между GROUP BY и PARTITION BY
- Продвинутые техники SQL
Читайте нас в Telegram, VK и Дзен
Перевод статьи Ade Mawan: I Finally Understood SQL vs NoSQL — and Boom, My Backend Runs Faster





