Ответ
Оптимизация запросов к БД — это основа производительности Node.js-приложения. Вот мои основные практики:
1. Использование пула соединений. Это предотвращает накладные расходы на установление нового соединения для каждого запроса. Например, с pg (PostgreSQL):
const { Pool } = require('pg');
const pool = new Pool({
max: 20, // Максимальное число клиентов в пуле
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Использование
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
2. Эффективное кеширование. Для часто запрашиваемых и редко меняющихся данных я использую Redis.
const getCachedUser = async (userId) => {
const cacheKey = `user:${userId}`;
let user = await redisClient.get(cacheKey);
if (!user) {
// Кеш-промах: идем в БД
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
user = rows[0];
// Сохраняем в Redis с TTL (например, 5 минут)
await redisClient.setEx(cacheKey, 300, JSON.stringify(user));
} else {
user = JSON.parse(user);
}
return user;
};
3. Оптимизация самих SQL-запросов:
- Индексы: Анализирую медленные запросы с помощью
EXPLAIN ANALYZEи добавляю индексы на поля в условияхWHERE,JOINиORDER BY. - Селективность: Выбираю только нужные колонки (
SELECT id, nameвместоSELECT *). - Пакетные операции (Batching): Объединяю несколько мелких операций в одну.
// Вместо N запросов в цикле — один запрос с IN const userIds = [1, 2, 3, 4]; const { rows } = await pool.query( 'SELECT * FROM users WHERE id = ANY($1::int[])', [userIds] ); - Prepared Statements: Использую параметризованные запросы (как в примерах выше) для безопасности от SQL-инъекций и возможности повторного использования плана выполнения СУБД.
4. Пагинация. Для больших наборов данных всегда реализую пагинацию на уровне БД с помощью LIMIT и OFFSET (или лучше WHERE id > lastId для keyset-пагинации).
5. Мониторинг. Подключаю логирование медленных запросов и использую инструменты вроде pg_stat_statements для постоянного анализа и выявления узких мест.
Ответ 18+ 🔞
Боже, ну вот смотри, опять про эти ваши базы данных. Как будто без них жизнь кончается! Но ладно, раз уж спросил, держи мои священные знания, собранные кровью и потом в три часа ночи на проде.
1. Пул соединений — это святое, ёпта.
Представь, что твое приложение — это общага, а БД — один единственный чайник на этаже. Если каждый студент будет каждый раз заново его кипятить для одной чашки — электричество кончится, а чайник накроется медным тазом. Пул — это очередь к уже горячему чайнику. Вот смотри, как просто:
const { Pool } = require('pg');
const pool = new Pool({
max: 20, // Больше 20 чуваков к одному чайнику — уже давка и ругань
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
// Используем
const result = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
Без пула каждый запрос — это новый поход на кухню, новый чайник. Овердохуища накладных расходов, приложение будет ползать как черепаха в сапогах.
2. Кеширование — твой лучший друг, если не забывать его кормить.
Зачем каждый раз тащить из дальнего магазина одну и ту же бутылку воды, если можно поставить ящик дома? Для этого — Redis, брат. Для данных, которые меняются реже, чем погода в Питере.
const getCachedUser = async (userId) => {
const cacheKey = `user:${userId}`;
let user = await redisClient.get(cacheKey);
if (!user) {
// Вот он, кеш-промах. Пошел в дальний магазин, бля.
const { rows } = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
user = rows[0];
// Принес, положил в ящик. Но ненадолго, а то протухнет.
await redisClient.setEx(cacheKey, 300, JSON.stringify(user));
} else {
user = JSON.parse(user); // Ага, вот она, водичка под рукой!
}
return user;
};
3. Сами SQL-запросы — тут надо думать головой, а не жопой.
- Индексы: Это как оглавление в толстой книге. Без него чтобы найти одну фразу, придется читать всё, ёб твою мать. Используй
EXPLAIN ANALYZE— это твой рентген для медленных запросов. - Не тащи всё подряд: Зачем тебе
SELECT *, если нужны только имя и фамилия? Это как заказывать весь ресторан, чтобы съесть салат. Бери конкретные колонки:SELECT id, name. - Пакетные операции — мощь: Вместо того чтобы стучаться в дверь к соседу десять раз за разными специями, возьми всё за один раз.
// ПЛОХО: 10 отдельных походов
// ХОРОШО: один поход со списком
const userIds = [1, 2, 3, 4];
const { rows } = await pool.query(
'SELECT * FROM users WHERE id = ANY($1::int[])',
[userIds]
);
- Prepared Statements: Всегда, блядь, всегда используй параметризованные запросы (
$1, $2). Это и от SQL-инъекций спасет (тебя же не назовут потом пидарасом шерстяным?), и БД сможет переиспользовать план запроса, не перестраивая его каждый раз с нуля.
4. Пагинация. Ты что, собрался всю таблицу на клиент вывалить?
Если пользователь пролистает до миллионной записи — твоя БД сдохнет, а приложение ляжет с удивлением пиздец. Всегда режь данные на куски: LIMIT 20 OFFSET 0. А для очень больших таблиц смотри в сторону keyset-пагинации (WHERE id > lastId), она стабильнее.
5. Мониторинг. Без него ты слепой кот в тёмной комнате.
Включи pg_stat_statements в Постгресе. Смотри, какие запросы жрут больше всего времени. Логируй медленные операции. Иначе однажды ночью ты получишь звонок, и на том конце трубки будут спрашивать, какого хуя всё легло. А ты будешь сидеть и бздеть, потому что ни хуя не поймешь.
Короче, суть в чём: база данных — не помойка, куда можно скидывать всё подряд. Относись к ней с уважением, оптимизируй запросы, кешируй где можно, и следи за её здоровьем. Тогда и она тебя не подведёт в самый ответственный момент.