Что такое шардирование базы данных и какие задачи оно решает?

Ответ

Шардирование (горизонтальное партиционирование) — это метод распределения строк одной таблицы по нескольким базам данных (шардам) на разных серверах.

Основные цели:

  1. Масштабирование записи и чтения: Нагрузка распределяется между несколькими узлами.
  2. Увеличение производительности: Каждый шард содержит подмножество данных, что уменьшает размер индексов и ускоряет запросы.
  3. Повышение доступности: Отказ одного шарда не останавливает всю систему.

Типичные стратегии шардирования:

  • По диапазону (Range): Данные делятся по диапазону ключа (например, user_id от 1 до 1M на шард A, от 1M+ на шард B).
  • По хешу (Hash): Ключ шардирования хешируется, результат определяет номер шарда. Обеспечивает равномерное распределение.
  • По списку (List): Явное указание, какие значения ключа попадают на какой шард (например, пользователи из region='EU' на шард 1).

Пример реализации шардирования по хешу на уровне приложения:

public class ShardManager {
    private List<String> shardUrls = List.of(
        "jdbc:mysql://shard01.db/app",
        "jdbc:mysql://shard02.db/app",
        "jdbc:mysql://shard03.db/app"
    );

    // Определяем шард для конкретного userId
    public String getShardUrlForUser(long userId) {
        int shardIndex = Math.abs(Long.hashCode(userId)) % shardUrls.size();
        return shardUrls.get(shardIndex);
    }

    public Connection getConnectionForUser(long userId) throws SQLException {
        String url = getShardUrlForUser(userId);
        return DriverManager.getConnection(url, "user", "pass");
    }
}
// Все данные для пользователя с userId=123 всегда будут на одном и том же шарде.

Недостатки и сложности:

  • Решардинг: Перераспределение данных при добавлении/удалении шардов — сложная операция.
  • Распределенные JOIN: Выполнение JOIN между таблицами на разных шардах неэффективно или невозможно.
  • Распределенные транзакции: Требуют сложных протоколов (2PC, Saga).
  • Сложность запросов: Запросы без ключа шардирования (shard key) должны выполняться на всех шардах (fan-out).

Ответ 18+ 🔞

Да ты посмотри, какая красота — шардирование, блядь! Это ж когда одну здоровенную таблицу, сука, как говном кирпичным, раскидывают по разным базам на разных серверах. Не монолит, а этакий конструктор лего, ёпта!

Зачем это всё, спросишь? Да похуй, я всё равно расскажу:

  1. Чтоб не сдохнуть под нагрузкой. Записи и чтения распределяются — и уже не один бедолага-сервер пыхтит, а несколько.
  2. Чтоб быстрее было. В каждом шарде данных — овердохуища поменьше, индексы компактнее, вот и летает всё.
  3. Чтоб живучим быть. Один шард накрылся медным тазом — остальные-то живы, система в целом держится.

А раскидывать-то как? Есть проверенные способы, блядь:

  • По диапазону (Range): Всё просто, как три копейки. User_id от 1 до ляма — на шард A, от ляма и дальше — на шард B. Правда, может получиться, что один шард лопнет, а другие пустые будут.
  • По хешу (Hash): Берёшь ключ, сука, пропускаешь через хеш-функцию — и готов номер шарда. Распределение равномерное, красота.
  • По списку (List): Тупое, но надёжное. Прописал, что пользователи из региона 'EU' — на шард 1, и всё, никаких неожиданностей.

Вот, смотри, как это в коде может выглядеть, если по хешу делать:

public class ShardManager {
    private List<String> shardUrls = List.of(
        "jdbc:mysql://shard01.db/app",
        "jdbc:mysql://shard02.db/app",
        "jdbc:mysql://shard03.db/app"
    );

    // Определяем шард для конкретного userId
    public String getShardUrlForUser(long userId) {
        int shardIndex = Math.abs(Long.hashCode(userId)) % shardUrls.size();
        return shardUrls.get(shardIndex);
    }

    public Connection getConnectionForUser(long userId) throws SQLException {
        String url = getShardUrlForUser(userId);
        return DriverManager.getConnection(url, "user", "pass");
    }
}
// Все данные для пользователя с userId=123 всегда будут на одном и том же шарде.

Но не всё так гладко, ёпта! Подвохи есть, и ещё какие:

  • Решардинг. Решил добавить новый шард — всё, пиздец, надо половину данных перетаскивать. Операция та ещё, волнение ебать.
  • Распределённые JOIN'ы. Хочешь соединить таблицы с разных шардов? Да хуй там, а не JOIN. Или неэффективно, или вообще никак.
  • Транзакции. Чтобы атомарно обновить данные на двух шардах — это целая сага, блядь, с 2PC и прочими танцами с бубном.
  • Запросы без ключа. Забыл указать user_id в запросе? Ну всё, приехали. Придётся этот запрос, сука, на ВСЕ шарды гонять и потом результаты собирать. Терпения ноль ебать.

Вот такая, блядь, хитрая жопа эта архитектура. Мощно, масштабируемо, но и головной боли — овердохуища.