Использовал ли вы индексы в базах данных и как их оптимизировали?

Ответ

Да, активно работал с индексами как в SQL (PostgreSQL), так и в NoSQL (MongoDB) базах данных в контексте Node.js приложений.

Для PostgreSQL с Node.js:

// Создание индексов через миграции Knex
exports.up = function(knex) {
  return knex.schema.table('users', function(table) {
    // Простой индекс
    table.index('email');

    // Составной индекс для частых запросов
    table.index(['status', 'created_at']);

    // Уникальный индекс
    table.unique('username');

    // Частичный индекс (только для активных пользователей)
    knex.raw('CREATE INDEX idx_users_active ON users(id) WHERE status = 'active'');
  });
};

Для MongoDB с Mongoose:

// Определение индексов в схеме
const userSchema = new mongoose.Schema({
  email: { type: String, required: true },
  companyId: { type: mongoose.Schema.Types.ObjectId },
  createdAt: { type: Date, default: Date.now }
});

// Простой индекс
userSchema.index({ email: 1 });

// Составной индекс
userSchema.index({ companyId: 1, createdAt: -1 });

// TTL индекс для автоматического удаления старых записей
userSchema.index({ createdAt: 1 }, { expireAfterSeconds: 2592000 }); // 30 дней

Как я анализировал и оптимизировал индексы:

  1. Использовал EXPLAIN в PostgreSQL:

    EXPLAIN ANALYZE 
    SELECT * FROM orders 
    WHERE user_id = 123 AND status = 'completed' 
    ORDER BY created_at DESC;
  2. Мониторинг через pg_stat_user_indexes: Отслеживал, какие индексы реально используются

  3. Практические правила, которые применял:

    • Индексировал поля в WHERE, JOIN, ORDER BY — особенно в часто выполняемых запросах
    • Использовал составные индексы для запросов с несколькими условиями, учитывая порядок полей
    • Избегал избыточных индексов — удалял дублирующиеся или редко используемые
    • Для текстового поиска использовал GIN/GIST индексы в PostgreSQL или text index в MongoDB

Пример проблемы и решения: На одном проекте запрос поиска заказов по диапазону дат выполнялся 2+ секунды. Добавил индекс (user_id, created_at) и время уменьшилось до 50ms, так как PostgreSQL смог использовать индекс только для чтения (index-only scan).

Ответ 18+ 🔞

А, ну это про индексы, да? Ёпта, тема такая, что если не разбираться — можно на ровном месте такую производительность угробить, что потом волосы на жопе дыбом встанут. Я с этим делом в обеих вселенных работал — и в реляционной с PostgreSQL, и в документной с MongoDB, когда на Node.js всё это крутилось.

Вот как в PostgreSQL через Knex миграции это обычно выглядит:

// Создание индексов через миграции Knex
exports.up = function(knex) {
  return knex.schema.table('users', function(table) {
    // Простой индекс
    table.index('email');

    // Составной индекс для частых запросов
    table.index(['status', 'created_at']);

    // Уникальный индекс
    table.unique('username');

    // Частичный индекс (только для активных пользователей)
    knex.raw('CREATE INDEX idx_users_active ON users(id) WHERE status = 'active'');
  });
};

А в MongoDB с Mongoose вообще красота — прямо в схеме прописываешь:

// Определение индексов в схеме
const userSchema = new mongoose.Schema({
  email: { type: String, required: true },
  companyId: { type: mongoose.Schema.Types.ObjectId },
  createdAt: { type: Date, default: Date.now }
});

// Простой индекс
userSchema.index({ email: 1 });

// Составной индекс
userSchema.index({ companyId: 1, createdAt: -1 });

// TTL индекс для автоматического удаления старых записей
userSchema.index({ createdAt: 1 }, { expireAfterSeconds: 2592000 }); // 30 дней

А теперь, блядь, самое интересное — как я эту всю магию анализировал и оптимизировал. Тут без подхода, как без рук — наткнёшься на проблему и будешь гадать, почему запрос тормозит, как будто на дворе 2002-й год.

  1. EXPLAIN в PostgreSQL — это святое, ебать мои старые костыли. Без него вообще никуда:

    EXPLAIN ANALYZE 
    SELECT * FROM orders 
    WHERE user_id = 123 AND status = 'completed' 
    ORDER BY created_at DESC;
  2. Мониторинг через pg_stat_user_indexes — чтобы понять, какие индексы реально пашут, а какие просто мусором лежат и память жрут. Удивление пиздец иногда наступает, когда видишь, что половина индексов ни разу не использовалась.

  3. Практические правила, которые в голове отложились:

    • Индексируй то, что в WHERE, JOIN, ORDER BY часто мелькает — особенно в запросах, которые вызываются каждые пять секунд. Иначе будет тебе хиросима.
    • Составные индексы — твои лучшие друзья для запросов с кучей условий, но тут главное порядок полей не перепутать, а то будет манда с ушами вместо оптимизации.
    • Избыточные индексы режь без жалости — зачем тебе три индекса, которые делают одно и то же? Это же просто место на диске и память впустую.
    • Для текстового поиска — либо GIN/GIST в PostgreSQL, либо text index в MongoDB, иначе поиск будет работать, как хуй с горы катится.

Был у меня один случай, просто пиздец. Запрос поиска заказов по диапазону дат выполнялся больше двух секунд — представляешь? Пользователи уже начинали бздеть от негодования. Добавил составной индекс (user_id, created_at), и, ёпта, время упало до 50 миллисекунд. Оказалось, PostgreSQL смог использовать индекс только для чтения, и всё полетело как по маслу. Вот так иногда одно правильное решение спасает от волнения ебать всего отдела.