MongoDB

Основные концепции

MongoDB - документо-ориентированная NoSQL база данных, которая хранит данные в формате BSON (Binary JSON). Основные отличия от реляционных БД:

  • Гибкая схема данных (schema-less)
  • Горизонтальное масштабирование из коробки
  • Встроенная поддержка репликации и шардинга

Терминология:

  • Database - база данных (аналог схемы в SQL)
  • Collection - коллекция документов (аналог таблицы)
  • Document - документ в формате BSON (аналог строки)
  • Field - поле документа (аналог колонки)
  • _id - уникальный идентификатор документа (автогенерируется)

CRUD операции

Create (Создание)

// Вставка одного документа
db.users.insertOne({name: "John", age: 30, email: "john@example.com"})

// Вставка нескольких документов
db.users.insertMany([
  {name: "Alice", age: 25},
  {name: "Bob", age: 35}
])

Read (Чтение)

// Найти все документы
db.users.find()

// Найти с условием
db.users.find({age: {$gte: 30}})

// Найти один документ
db.users.findOne({name: "John"})

// Проекция (выбор конкретных полей)
db.users.find({}, {name: 1, email: 1, _id: 0})

Update (Обновление)

// Обновить один документ
db.users.updateOne(
  {name: "John"}, 
  {$set: {age: 31}}
)

// Обновить несколько документов
db.users.updateMany(
  {age: {$lt: 30}}, 
  {$inc: {age: 1}}
)

// Upsert (создать если не существует)
db.users.updateOne(
  {name: "Mike"}, 
  {$set: {age: 28}}, 
  {upsert: true}
)

Delete (Удаление)

// Удалить один документ
db.users.deleteOne({name: "John"})

// Удалить несколько документов
db.users.deleteMany({age: {$lt: 18}})

Операторы запросов

Операторы сравнения

  • $eq - равно
  • $ne - не равно
  • $gt - больше
  • $gte - больше или равно
  • $lt - меньше
  • $lte - меньше или равно
  • $in - содержится в массиве
  • $nin - не содержится в массиве

Логические операторы

// AND (по умолчанию)
db.users.find({age: {$gte: 25}, status: "active"})

// OR
db.users.find({$or: [{age: {$lt: 25}}, {status: "premium"}]})

// NOT
db.users.find({age: {$not: {$gte: 30}}})

Операторы для массивов

// $all - содержит все элементы
db.users.find({skills: {$all: ["Java", "MongoDB"]}})

// $size - размер массива
db.users.find({skills: {$size: 3}})

// $elemMatch - элемент массива соответствует условию
db.users.find({scores: {$elemMatch: {$gte: 80, $lt: 90}}})

Индексы

Индексы - структуры данных для ускорения поиска. MongoDB автоматически создает индекс для поля _id.

// Создание индекса
db.users.createIndex({name: 1})        // по возрастанию
db.users.createIndex({age: -1})        // по убыванию
db.users.createIndex({name: 1, age: -1}) // составной индекс

// Уникальный индекс
db.users.createIndex({email: 1}, {unique: true})

// Текстовый индекс для поиска
db.users.createIndex({name: "text", bio: "text"})

// Просмотр индексов
db.users.getIndexes()

// Удаление индекса
db.users.dropIndex({name: 1})

Aggregation Framework

Aggregation Pipeline - мощный инструмент для обработки данных, работает как конвейер этапов.

Основные этапы (stages):

  • $match - фильтрация документов
  • $group - группировка данных
  • $sort - сортировка
  • $project - преобразование структуры
  • $limit - ограничение количества
  • $skip - пропуск документов
  • $lookup - join с другой коллекцией
// Группировка и подсчет
db.orders.aggregate([
  {$match: {status: "completed"}},
  {$group: {
    _id: "$customerId",
    totalOrders: {$sum: 1},
    totalAmount: {$sum: "$amount"}
  }},
  {$sort: {totalAmount: -1}}
])

// Lookup (join)
db.orders.aggregate([
  {$lookup: {
    from: "customers",
    localField: "customerId",
    foreignField: "_id",
    as: "customer"
  }}
])

Репликация

Replica Set - группа MongoDB серверов для обеспечения высокой доступности. Состоит из:

  • Primary - принимает все записи
  • Secondary - реплики для чтения
  • Arbiter - участвует только в выборах (опционально)
// Инициализация replica set
rs.initiate({
  _id: "myReplicaSet",
  members: [
    {_id: 0, host: "mongo1:27017"},
    {_id: 1, host: "mongo2:27017"},
    {_id: 2, host: "mongo3:27017"}
  ]
})

// Проверка статуса
rs.status()

Шардинг

Шардинг - горизонтальное разделение данных между несколькими серверами. Компоненты:

  • Shard - сервер с частью данных
  • Config Server - хранит метаданные
  • Mongos - роутер запросов
// Включение шардинга для базы
sh.enableSharding("myDatabase")

// Шардинг коллекции
sh.shardCollection("myDatabase.users", {userId: 1})

Транзакции

Транзакции - ACID операции для нескольких документов (доступны с версии 4.0).

// Начало сессии
session = db.getMongo().startSession()

// Транзакция
session.startTransaction()
try {
  session.getDatabase("mydb").users.insertOne({name: "John"})
  session.getDatabase("mydb").orders.insertOne({userId: "john_id"})
  session.commitTransaction()
} catch (error) {
  session.abortTransaction()
}

Spring Data MongoDB

Spring Data MongoDB - интеграция MongoDB с Spring Framework.

Основные аннотации:

  • @Document - маркирует класс как документ
  • @Id - указывает поле ID
  • @Field - кастомное имя поля
  • @Indexed - создает индекс
  • @DBRef - ссылка на другой документ
@Document(collection = "users")
public class User {
    @Id
    private String id;
    
    @Field("full_name")
    private String name;
    
    @Indexed(unique = true)
    private String email;
    
    @DBRef
    private List<Order> orders;
}

Repository интерфейс:

public interface UserRepository extends MongoRepository<User, String> {
    List<User> findByAgeGreaterThan(int age);
    
    @Query("{'name': ?0}")
    User findByCustomName(String name);
    
    @Aggregation(pipeline = {
        "{ '$match': { 'status': 'active' } }",
        "{ '$group': { '_id': '$department', 'count': { '$sum': 1 } } }"
    })
    List<DepartmentStats> getActiveUsersByDepartment();
}

Производительность и оптимизация

Основные рекомендации:

  1. Создавайте индексы для часто используемых полей в запросах
  2. Используйте проекцию для выборки только нужных полей
  3. Денормализуйте данные для избежания лишних join-ов
  4. Мониторьте производительность с помощью explain()
// Анализ производительности запроса
db.users.find({age: {$gte: 30}}).explain("executionStats")

// Профилирование (включение логирования медленных запросов)
db.setProfilingLevel(2, {slowms: 100})

Важные особенности для собеседования

Отличия от реляционных БД:

  • Схема данных: гибкая vs жесткая
  • ACID: поддержка транзакций появилась в версии 4.0
  • Масштабирование: горизонтальное vs вертикальное
  • Запросы: специфичные операторы vs SQL

Когда использовать MongoDB:

  • Быстрая разработка с изменяющимися требованиями
  • Большие объемы данных с необходимостью горизонтального масштабирования
  • Документы с вложенной структурой
  • Кэширование и сессии

Ограничения:

  • Размер документа не более 16MB
  • Максимум 100 уровней вложенности
  • Имена полей не могут содержать точки или начинаться с $
  • Отсутствие foreign key constraints

Команды администрирования

// Информация о базе данных
db.stats()

// Размер коллекции
db.users.stats()

// Восстановление и проверка
db.repairDatabase()

// Backup
mongodump --db myDatabase --out /backup/

// Restore
mongorestore --db myDatabase /backup/myDatabase/

Redis

Основные концепции

Redis (Remote Dictionary Server) — это высокопроизводительная NoSQL база данных типа "ключ-значение" в памяти, которая может использоваться как кеш, брокер сообщений или основное хранилище данных.

Ключевые особенности:

  • Все данные хранятся в оперативной памяти
  • Поддерживает различные структуры данных
  • Атомарные операции
  • Персистентность данных (снимки и журналирование)
  • Встроенная репликация и кластеризация

Структуры данных

Строки (Strings)

Самый простой тип данных — бинарно-безопасные строки до 512 МБ.

SET user:1:name "John"     # Установить значение
GET user:1:name           # Получить значение
INCR counter              # Атомарно увеличить число на 1
EXPIRE session:123 3600   # Установить TTL (время жизни) в секундах

Хеши (Hashes)

Коллекции полей и значений, идеальны для представления объектов.

HSET user:1 name "John" age 30 email "john@example.com"
HGET user:1 name          # Получить одно поле
HGETALL user:1            # Получить все поля
HEXISTS user:1 phone      # Проверить существование поля

Списки (Lists)

Упорядоченные коллекции строк, реализованные как двусвязные списки.

LPUSH queue:tasks "task1" "task2"  # Добавить в начало
RPUSH queue:tasks "task3"          # Добавить в конец
LPOP queue:tasks                   # Извлечь с начала
LRANGE queue:tasks 0 -1            # Получить все элементы

Множества (Sets)

Неупорядоченные коллекции уникальных строк.

SADD tags:post:1 "redis" "database" "nosql"
SMEMBERS tags:post:1      # Получить все элементы
SISMEMBER tags:post:1 "redis"  # Проверить принадлежность
SINTER tags:post:1 tags:post:2  # Пересечение множеств

Отсортированные множества (Sorted Sets)

Множества с весами (scores) для каждого элемента.

ZADD leaderboard 100 "player1" 250 "player2" 180 "player3"
ZRANGE leaderboard 0 -1 WITHSCORES  # Получить по возрастанию
ZREVRANGE leaderboard 0 2           # Топ-3 игрока
ZRANK leaderboard "player1"          # Позиция в рейтинге

Паттерны использования

Кеширование

Redis часто используется для кеширования дорогих операций или результатов запросов к базе данных.

Стратегии кеширования:

  • Cache-Aside — приложение управляет кешем
  • Write-Through — запись одновременно в кеш и БД
  • Write-Behind — асинхронная запись в БД

Сессии пользователей

Хранение данных сессий с автоматическим истечением.

SETEX session:abc123 1800 '{"user_id": 42, "role": "admin"}'

Счетчики и метрики

Атомарные операции для подсчета событий.

INCR page:views:home
HINCRBY stats:daily visits 1

Pub/Sub (Издатель-Подписчик)

Система обмена сообщениями в реальном времени.

PUBLISH news:updates "Breaking news!"
SUBSCRIBE news:updates

Персистентность данных

RDB (Redis Database)

Создает снимки данных через определенные интервалы.

  • Плюсы: компактность, быстрое восстановление
  • Минусы: возможна потеря данных между снимками

AOF (Append Only File)

Записывает каждую операцию изменения в лог-файл.

  • Плюсы: минимальная потеря данных
  • Минусы: больший размер файлов, медленнее восстановление

Настройка персистентности

# В redis.conf
save 900 1     # Сохранить если изменился 1 ключ за 15 минут
appendonly yes # Включить AOF

Репликация и высокая доступность

Master-Slave репликация

Асинхронная репликация данных с главного сервера на реплики.

# На slave-сервере
SLAVEOF 192.168.1.100 6379

Redis Sentinel

Система мониторинга и автоматического переключения при отказе master-сервера.

  • Отслеживает состояние Redis-инстансов
  • Автоматически переключает slave на master
  • Предоставляет service discovery

Redis Cluster

Горизонтальное масштабирование с автоматическим шардингом.

  • Данные распределяются по 16384 слотам
  • Каждый узел отвечает за определенные слоты
  • Встроенная репликация и failover

Производительность и оптимизация

Управление памятью

INFO memory                    # Информация об использовании памяти
FLUSHDB                       # Очистить текущую БД
CONFIG SET maxmemory 256mb    # Ограничить использование памяти

Политики вытеснения (Eviction Policies)

Когда память заканчивается:

  • allkeys-lru — удаляет наименее используемые ключи
  • volatile-lru — удаляет наименее используемые ключи с TTL
  • allkeys-random — удаляет случайные ключи
  • volatile-ttl — удаляет ключи с наименьшим TTL

Пайплайнинг

Отправка нескольких команд за один раз для уменьшения latency.

// Java пример с Jedis
Pipeline pipeline = jedis.pipelined();
pipeline.set("key1", "value1");
pipeline.set("key2", "value2");
pipeline.sync();

Интеграция с Java

Популярные клиенты

  • Jedis — простой и быстрый клиент
  • Lettuce — асинхронный клиент с поддержкой reactive streams
  • Redisson — полнофункциональный клиент с распределенными объектами

Spring Data Redis

@RedisHash("users")
public class User {
    @Id
    private String id;
    private String name;
    // ...
}

@Repository
public interface UserRepository extends CrudRepository<User, String> {
    List<User> findByName(String name);
}

Мониторинг и отладка

Полезные команды

INFO                    # Общая информация о сервере
MONITOR                 # Отслеживание всех команд в реальном времени
SLOWLOG GET 10         # Получить 10 самых медленных операций
CLIENT LIST            # Список подключенных клиентов
MEMORY USAGE key       # Использование памяти конкретным ключом

Ключевые метрики

  • connected_clients — количество подключений
  • used_memory — использованная память
  • hits/misses ratio — эффективность кеширования
  • evicted_keys — количество удаленных ключей
  • keyspace_hits/keyspace_misses — статистика обращений

Безопасность

Базовая защита

# В redis.conf
requirepass mypassword          # Установить пароль
bind 127.0.0.1                 # Привязать к определенному IP
rename-command FLUSHDB ""       # Отключить опасные команды

Шифрование

Redis 6.0+ поддерживает TLS для шифрования соединений и AUTH для аутентификации пользователей.

Распространенные проблемы

Большие ключи

Ключи размером >1MB могут блокировать сервер. Используйте MEMORY USAGE для мониторинга.

Горячие ключи

Часто запрашиваемые ключи могут создавать узкие места. Решение — репликация или шардинг.

Блокирующие операции

Команды вроде KEYS * могут заблокировать сервер. Используйте SCAN для итерации по ключам.

Альтернативы и сравнение

  • Memcached — проще, но менее функционален
  • Hazelcast — Java-ориентированное решение
  • Apache Ignite — более сложная платформа с SQL-поддержкой

Redis остается популярным выбором благодаря простоте, производительности и богатому функционалу для большинства задач кеширования и обмена сообщениями в enterprise-приложениях.

ClickHouse

Основные концепции

ClickHouse — это колоночная СУБД для онлайн-аналитической обработки данных (OLAP), оптимизированная для быстрого выполнения аналитических запросов на больших объемах данных.

Ключевые особенности:

  • Колоночное хранение — данные хранятся по столбцам, а не по строкам
  • Векторизация — обработка данных блоками для повышения производительности
  • Сжатие данных — эффективное сжатие благодаря колоночному формату
  • Линейная масштабируемость — производительность растет пропорционально добавлению серверов
  • SQL-совместимость — поддерживает большинство SQL-конструкций

Архитектура и движки таблиц

Движок MergeTree

Основной движок для аналитических данных с поддержкой сортировки и индексов.

CREATE TABLE events (
    date Date,
    user_id UInt64,
    event_type String,
    value Float64
) ENGINE = MergeTree()
ORDER BY (date, user_id);

Особенности MergeTree:

  • Данные автоматически сортируются по ключу сортировки
  • Поддерживает первичные и вторичные индексы
  • Фоновое слияние кусков данных для оптимизации хранения
  • Поддержка партиционирования по датам или другим полям

ReplacingMergeTree

Автоматически удаляет дубликаты при слиянии кусков данных.

CREATE TABLE user_profiles (
    user_id UInt64,
    name String,
    updated_at DateTime
) ENGINE = ReplacingMergeTree(updated_at)
ORDER BY user_id;

SummingMergeTree

Автоматически суммирует числовые столбцы при слиянии записей с одинаковыми ключами.

CREATE TABLE metrics (
    date Date,
    metric_name String,
    value UInt64
) ENGINE = SummingMergeTree()
ORDER BY (date, metric_name);

Distributed

Распределяет данные по нескольким серверам кластера.

CREATE TABLE events_distributed AS events
ENGINE = Distributed(cluster_name, database, events, rand());

Типы данных

Числовые типы

  • UInt8, UInt16, UInt32, UInt64 — беззнаковые целые числа
  • Int8, Int16, Int32, Int64 — знаковые целые числа
  • Float32, Float64 — числа с плавающей точкой
  • Decimal(P, S) — точные десятичные числа

Строковые типы

  • String — переменная длина, UTF-8
  • FixedString(N) — фиксированная длина N байт
  • LowCardinality(String) — оптимизация для строк с малым количеством уникальных значений

Дата и время

  • Date — дата в формате YYYY-MM-DD
  • DateTime — дата и время с точностью до секунды
  • DateTime64 — дата и время с подсекундной точностью

Массивы и сложные типы

CREATE TABLE complex_data (
    id UInt64,
    tags Array(String),
    metadata Map(String, String),
    nested Nested(name String, value UInt64)
);

Индексы и оптимизация

Первичный ключ

Определяется параметром ORDER BY и создает разреженный индекс.

-- Данные физически сортируются по (date, user_id)
-- Индекс содержит каждую 8192-ю строку
ORDER BY (date, user_id)

Вторичные индексы

Дополнительные индексы для ускорения запросов по неключевым полям.

ALTER TABLE events ADD INDEX idx_event_type event_type TYPE set(100) GRANULARITY 1;

Материализованные представления

Предвычисленные агрегаты для ускорения аналитических запросов.

CREATE MATERIALIZED VIEW daily_stats
ENGINE = SummingMergeTree()
ORDER BY (date, event_type)
AS SELECT
    toDate(timestamp) as date,
    event_type,
    count() as events_count
FROM events
GROUP BY date, event_type;

Производительность запросов

Проекции

Альтернативные представления данных той же таблицы с другой сортировкой.

ALTER TABLE events ADD PROJECTION proj_by_event_type (
    SELECT user_id, event_type, value
    ORDER BY (event_type, user_id)
);

Sampling

Выборочный анализ данных для быстрого получения приблизительных результатов.

CREATE TABLE events_sampled (
    date Date,
    user_id UInt64,
    event_type String
) ENGINE = MergeTree()
ORDER BY (date, user_id)
SAMPLE BY intHash32(user_id);

-- Запрос с выборкой 10% данных
SELECT event_type, count() * 10 as estimated_count
FROM events_sampled SAMPLE 0.1
GROUP BY event_type;

Партиционирование

Разделение данных на части для ускорения запросов и управления данными.

CREATE TABLE events_partitioned (
    date Date,
    user_id UInt64,
    event_type String
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (date, user_id);

Функции и операторы

Агрегатные функции

SELECT
    count() as total_events,
    uniq(user_id) as unique_users,
    avg(value) as avg_value,
    quantile(0.95)(response_time) as p95_response_time,
    topK(5)(event_type) as top_events
FROM events;

Функции для работы с датами

SELECT
    toStartOfDay(timestamp) as day,
    toStartOfHour(timestamp) as hour,
    toYYYYMM(date) as year_month,
    dateDiff('day', start_date, end_date) as days_diff
FROM events;

Функции для работы с массивами

SELECT
    arrayJoin(tags) as tag,  -- Развернуть массив в строки
    has(tags, 'premium') as is_premium,
    length(tags) as tags_count
FROM user_data;

Интеграция с Java

JDBC драйвер

// Подключение к ClickHouse
String url = "jdbc:clickhouse://localhost:8123/default";
Connection conn = DriverManager.getConnection(url);

// Batch-вставка для высокой производительности
PreparedStatement stmt = conn.prepareStatement(
    "INSERT INTO events (date, user_id, event_type, value) VALUES (?, ?, ?, ?)"
);
for (Event event : events) {
    stmt.setDate(1, event.getDate());
    stmt.setLong(2, event.getUserId());
    stmt.setString(3, event.getType());
    stmt.setDouble(4, event.getValue());
    stmt.addBatch();
}
stmt.executeBatch();

HTTP интерфейс

// Отправка данных через HTTP POST
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("http://localhost:8123/"))
    .header("Content-Type", "text/plain")
    .POST(HttpRequest.BodyPublishers.ofString(
        "INSERT INTO events FORMAT CSV\n" + csvData
    ))
    .build();

Репликация и кластеризация

Репликация

Синхронная репликация данных между серверами с использованием ZooKeeper.

CREATE TABLE events_replicated (
    date Date,
    user_id UInt64,
    event_type String
) ENGINE = ReplicatedMergeTree('/clickhouse/tables/events', 'replica1')
ORDER BY (date, user_id);

Шардирование

Горизонтальное разделение данных между узлами кластера.

<!-- config.xml -->
<remote_servers>
    <analytics_cluster>
        <shard>
            <replica>
                <host>clickhouse1.example.com</host>
                <port>9000</port>
            </replica>
        </shard>
        <shard>
            <replica>
                <host>clickhouse2.example.com</host>
                <port>9000</port>
            </replica>
        </shard>
    </analytics_cluster>
</remote_servers>

Мониторинг и производительность

Системные таблицы

-- Активные запросы
SELECT query, elapsed, memory_usage
FROM system.processes
WHERE query != '';

-- Статистика по таблицам
SELECT table, rows, bytes_on_disk
FROM system.parts
WHERE active = 1;

-- Медленные запросы
SELECT query, query_duration_ms, memory_usage
FROM system.query_log
WHERE query_duration_ms > 1000
ORDER BY query_duration_ms DESC;

Профилирование запросов

-- Включить профилирование
SET send_logs_level = 'trace';
SET log_queries = 1;

-- Анализ плана выполнения
EXPLAIN PIPELINE SELECT count() FROM events WHERE date >= '2023-01-01';

Особенности разработки

Асинхронные вставки

Группировка небольших вставок для повышения производительности.

SET async_insert = 1;
SET wait_for_async_insert = 1;
INSERT INTO events VALUES (today(), 123, 'click', 1.5);

Оптимизация памяти

-- Настройка размера блока для запросов
SET max_block_size = 65536;
SET max_memory_usage = 10000000000;  -- 10GB

-- Использование внешней сортировки при нехватке памяти
SET max_bytes_before_external_group_by = 1000000000;

Работа с большими данными

-- Потоковая обработка для больших результатов
SELECT * FROM huge_table
FORMAT TSVRaw
SETTINGS max_result_rows = 0, max_result_bytes = 0;

Сравнение с другими СУБД

ClickHouse vs PostgreSQL

  • ClickHouse: колоночное хранение, OLAP, быстрые агрегации
  • PostgreSQL: строковое хранение, OLTP, ACID-транзакции

ClickHouse vs Apache Spark

  • ClickHouse: SQL-база данных, интерактивные запросы
  • Apache Spark: вычислительный движок, batch-обработка

ClickHouse vs Elasticsearch

  • ClickHouse: структурированные данные, SQL-запросы
  • Elasticsearch: полнотекстовый поиск, неструкт. данные

Практические рекомендации

Дизайн схемы

  • Используйте правильный порядок колонок в ORDER BY (от наиболее к наименее селективным)
  • Применяйте LowCardinality для строк с малым количеством уникальных значений
  • Избегайте nullable полей без необходимости

Оптимизация запросов

  • Используйте PREWHERE вместо WHERE для фильтрации по колонкам не из ORDER BY
  • Применяйте LIMIT для ограничения результатов
  • Используйте материализованные представления для часто выполняемых агрегаций

Управление данными

  • Регулярно выполняйте OPTIMIZE TABLE для принудительного слияния кусков
  • Используйте TTL для автоматического удаления старых данных
  • Мониторьте количество кусков данных (parts) в таблицах

ClickHouse — это мощная система для аналитики больших данных, которая требует понимания её архитектуры и особенностей для эффективного использования в enterprise-проектах.

MinIO S3

Что такое MinIO

MinIO — это высокопроизводительное объектное хранилище, совместимое с Amazon S3 API. Основные преимущества:

  • Self-hosted решение — можно развернуть на собственной инфраструктуре
  • S3-совместимость — используется тот же API, что и у Amazon S3
  • Высокая производительность — оптимизировано для больших объемов данных
  • Distributed storage — поддержка кластерной архитектуры
  • Erasure coding — технология защиты данных от потерь

Архитектура и основные концепции

Bucket (Корзина)

Контейнер для хранения объектов. Аналог папки верхнего уровня в файловой системе.

Object (Объект)

Файл + метаданные. Каждый объект имеет уникальный ключ (key) внутри bucket.

Key (Ключ)

Уникальный идентификатор объекта внутри bucket. Может содержать "/" для имитации иерархии папок.

Metadata (Метаданные)

Дополнительная информация об объекте (Content-Type, размер, пользовательские заголовки).

Настройка Java клиента

Зависимости Maven

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.7</version>
</dependency>

Создание клиента

MinioClient client = MinioClient.builder()
    .endpoint("http://localhost:9000")  // URL MinIO сервера
    .credentials("minioadmin", "minioadmin")  // Access key и Secret key
    .build();

Объяснение параметров:

  • endpoint — URL сервера MinIO (по умолчанию порт 9000)
  • credentials — пара ключей для аутентификации
  • Для production используйте HTTPS и сильные пароли

Операции с Bucket

Создание bucket

client.makeBucket(MakeBucketArgs.builder()
    .bucket("my-bucket")
    .region("us-east-1")  // Регион (опционально)
    .build());

Проверка существования bucket

boolean exists = client.bucketExists(BucketExistsArgs.builder()
    .bucket("my-bucket")
    .build());

Список всех bucket

List<Bucket> buckets = client.listBuckets();
for (Bucket bucket : buckets) {
    System.out.println(bucket.name() + " - " + bucket.creationDate());
}

Удаление bucket

client.removeBucket(RemoveBucketArgs.builder()
    .bucket("my-bucket")
    .build());

Важно: Bucket можно удалить только если он пустой.

Операции с объектами

Загрузка файла

client.uploadObject(UploadObjectArgs.builder()
    .bucket("my-bucket")
    .object("path/to/file.txt")  // Ключ объекта
    .filename("/local/path/file.txt")  // Локальный путь
    .contentType("text/plain")  // MIME-тип
    .build());

Загрузка из InputStream

InputStream stream = new FileInputStream("file.txt");
client.putObject(PutObjectArgs.builder()
    .bucket("my-bucket")
    .object("file.txt")
    .stream(stream, stream.available(), -1)
    .contentType("text/plain")
    .build());

Параметры stream():

  • stream — поток данных
  • objectSize — размер объекта (-1 если неизвестен)
  • partSize — размер части для multipart upload (-1 для автоопределения)

Скачивание файла

client.downloadObject(DownloadObjectArgs.builder()
    .bucket("my-bucket")
    .object("file.txt")
    .filename("/local/download/file.txt")
    .build());

Получение объекта как InputStream

try (InputStream stream = client.getObject(GetObjectArgs.builder()
    .bucket("my-bucket")
    .object("file.txt")
    .build())) {
    // Работа с потоком
    byte[] data = stream.readAllBytes();
}

Получение информации об объекте

StatObjectResponse stat = client.statObject(StatObjectArgs.builder()
    .bucket("my-bucket")
    .object("file.txt")
    .build());

System.out.println("Size: " + stat.size());
System.out.println("ETag: " + stat.etag());
System.out.println("Content-Type: " + stat.contentType());

ETag — уникальный идентификатор версии объекта, аналог MD5 хеша.

Удаление объекта

client.removeObject(RemoveObjectArgs.builder()
    .bucket("my-bucket")
    .object("file.txt")
    .build());

Массовое удаление

List<DeleteObject> objects = Arrays.asList(
    new DeleteObject("file1.txt"),
    new DeleteObject("file2.txt")
);

Iterable<Result<DeleteError>> results = client.removeObjects(
    RemoveObjectsArgs.builder()
        .bucket("my-bucket")
        .objects(objects)
        .build());

// Проверка ошибок
for (Result<DeleteError> result : results) {
    DeleteError error = result.get();
    if (error != null) {
        System.err.println("Error: " + error.message());
    }
}

Работа со списками объектов

Список объектов в bucket

Iterable<Result<Item>> results = client.listObjects(
    ListObjectsArgs.builder()
        .bucket("my-bucket")
        .prefix("folder/")  // Фильтр по префиксу
        .recursive(true)    // Рекурсивный обход
        .maxKeys(1000)      // Максимум объектов за запрос
        .build());

for (Result<Item> result : results) {
    Item item = result.get();
    System.out.println(item.objectName() + " - " + item.size());
}

Параметры:

  • prefix — фильтрация по началу ключа (имитация папок)
  • recursive — true для рекурсивного обхода всех "подпапок"
  • maxKeys — ограничение количества результатов

Пагинация при больших списках

String marker = null;
do {
    ListObjectsArgs args = ListObjectsArgs.builder()
        .bucket("my-bucket")
        .maxKeys(100)
        .marker(marker)  // Начать с этого ключа
        .build();
    
    Iterable<Result<Item>> results = client.listObjects(args);
    for (Result<Item> result : results) {
        Item item = result.get();
        marker = item.objectName();  // Сохраняем для следующей итерации
    }
} while (marker != null);

Presigned URLs

Presigned URL — временная ссылка для доступа к объекту без аутентификации.

Создание ссылки для скачивания

String url = client.getPresignedObjectUrl(
    GetPresignedObjectUrlArgs.builder()
        .method(Method.GET)
        .bucket("my-bucket")
        .object("file.txt")
        .expiry(60 * 60)  // Время жизни в секундах (1 час)
        .build());

Создание ссылки для загрузки

String uploadUrl = client.getPresignedObjectUrl(
    GetPresignedObjectUrlArgs.builder()
        .method(Method.PUT)
        .bucket("my-bucket")
        .object("new-file.txt")
        .expiry(60 * 10)  // 10 минут
        .build());

Применение: Позволяет клиентам напрямую загружать/скачивать файлы, минуя ваш сервер.

Метаданные и заголовки

Установка метаданных при загрузке

Map<String, String> userMetadata = new HashMap<>();
userMetadata.put("author", "john-doe");
userMetadata.put("department", "engineering");

client.putObject(PutObjectArgs.builder()
    .bucket("my-bucket")
    .object("document.pdf")
    .stream(inputStream, size, -1)
    .contentType("application/pdf")
    .userMetadata(userMetadata)  // Пользовательские метаданные
    .build());

Установка заголовков

Map<String, String> headers = new HashMap<>();
headers.put("Cache-Control", "max-age=3600");
headers.put("Content-Disposition", "attachment; filename=document.pdf");

client.putObject(PutObjectArgs.builder()
    .bucket("my-bucket")
    .object("document.pdf")
    .stream(inputStream, size, -1)
    .headers(headers)
    .build());

Разница:

  • userMetadata — произвольные метаданные с префиксом "x-amz-meta-"
  • headers — стандартные HTTP заголовки

Копирование объектов

Простое копирование

client.copyObject(CopyObjectArgs.builder()
    .bucket("destination-bucket")
    .object("new-file.txt")
    .source(CopySource.builder()
        .bucket("source-bucket")
        .object("original-file.txt")
        .build())
    .build());

Копирование с изменением метаданных

Map<String, String> metadata = new HashMap<>();
metadata.put("copied-at", "2025-01-15");

client.copyObject(CopyObjectArgs.builder()
    .bucket("destination-bucket")
    .object("new-file.txt")
    .source(CopySource.builder()
        .bucket("source-bucket")
        .object("original-file.txt")
        .build())
    .metadataDirective(Directive.REPLACE)  // Заменить метаданные
    .userMetadata(metadata)
    .build());

Multipart Upload

Multipart Upload — загрузка больших файлов частями. Преимущества:

  • Возможность параллельной загрузки частей
  • Восстановление после сбоев
  • Загрузка файлов до 5TB

Инициация multipart upload

CreateMultipartUploadResponse response = client.createMultipartUpload(
    CreateMultipartUploadArgs.builder()
        .bucket("my-bucket")
        .object("large-file.zip")
        .build());

String uploadId = response.result().uploadId();

Загрузка части

UploadPartResponse partResponse = client.uploadPart(
    UploadPartArgs.builder()
        .bucket("my-bucket")
        .object("large-file.zip")
        .uploadId(uploadId)
        .partNumber(1)  // Номер части (начинается с 1)
        .data(partInputStream, partSize, -1)
        .build());

String etag = partResponse.etag();  // Сохранить для завершения

Завершение multipart upload

Part[] parts = {new Part(1, etag1), new Part(2, etag2)};

client.completeMultipartUpload(
    CompleteMultipartUploadArgs.builder()
        .bucket("my-bucket")
        .object("large-file.zip")
        .uploadId(uploadId)
        .parts(parts)
        .build());

Отмена multipart upload

client.abortMultipartUpload(
    AbortMultipartUploadArgs.builder()
        .bucket("my-bucket")
        .object("large-file.zip")
        .uploadId(uploadId)
        .build());

Политики безопасности (Bucket Policy)

Bucket Policy — JSON-документ, определяющий разрешения доступа.

Установка публичной политики чтения

String policy = """
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {"AWS": "*"},
            "Action": "s3:GetObject",  
            "Resource": "arn:aws:s3:::my-bucket/*"
        }
    ]
}
""";

client.setBucketPolicy(SetBucketPolicyArgs.builder()
    .bucket("my-bucket")
    .config(policy)
    .build());

Получение текущей политики

String currentPolicy = client.getBucketPolicy(
    GetBucketPolicyArgs.builder()
        .bucket("my-bucket")
        .build());

Элементы политики:

  • Effect — Allow/Deny
  • Principal — кто получает разрешение (* = все)
  • Action — какие действия разрешены
  • Resource — на какие ресурсы распространяется

Уведомления (Event Notifications)

Event Notifications — уведомления о событиях в bucket (создание, удаление объектов).

Настройка уведомлений

NotificationConfiguration config = new NotificationConfiguration();

QueueConfiguration queueConfig = new QueueConfiguration();
queueConfig.setQueue("arn:minio:sqs::primary:webhook");
queueConfig.addEvent(EventType.OBJECT_CREATED_PUT);
queueConfig.addFilterRule("prefix", "documents/");
queueConfig.addFilterRule("suffix", ".pdf");

config.setQueueConfigurations(Arrays.asList(queueConfig));

client.setBucketNotification(SetBucketNotificationArgs.builder()
    .bucket("my-bucket")
    .config(config)
    .build());

Типы событий:

  • OBJECT_CREATED_PUT — создание объекта через PUT
  • OBJECT_CREATED_POST — создание через POST
  • OBJECT_REMOVED_DELETE — удаление объекта

Обработка ошибок

Основные исключения

try {
    client.statObject(StatObjectArgs.builder()
        .bucket("my-bucket")
        .object("nonexistent.txt")
        .build());
} catch (ErrorResponseException e) {
    if (e.errorResponse().code().equals("NoSuchKey")) {
        System.out.println("Объект не найден");
    }
} catch (InsufficientDataException e) {
    System.out.println("Недостаточно данных");
} catch (InternalException e) {
    System.out.println("Внутренняя ошибка");
} catch (InvalidKeyException | InvalidResponseException e) {
    System.out.println("Проблемы с форматом");
} catch (IOException e) {
    System.out.println("Ошибка сети");
} catch (NoSuchAlgorithmException | XmlParserException e) {
    System.out.println("Проблемы с парсингом/криптографией");
}

Проверка доступности сервиса

public boolean isMinIOHealthy() {
    try {
        client.listBuckets();
        return true;
    } catch (Exception e) {
        return false;
    }
}

Конфигурация для Production

Connection Pool

OkHttpClient httpClient = new OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(60, TimeUnit.SECONDS)
    .readTimeout(60, TimeUnit.SECONDS)
    .connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES))
    .build();

MinioClient client = MinioClient.builder()
    .endpoint("https://minio.company.com")
    .credentials(accessKey, secretKey)
    .httpClient(httpClient)
    .build();

SSL/TLS конфигурация

MinioClient client = MinioClient.builder()
    .endpoint("https://minio.company.com", 9000, true)  // true = HTTPS
    .credentials(accessKey, secretKey)
    .build();

Retry механизм

public <T> T executeWithRetry(Supplier<T> operation, int maxRetries) {
    Exception lastException = null;
    
    for (int i = 0; i < maxRetries; i++) {
        try {
            return operation.get();
        } catch (Exception e) {
            lastException = e;
            if (i < maxRetries - 1) {
                try {
                    Thread.sleep(1000 * (i + 1)); // Exponential backoff
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }
    throw new RuntimeException("Operation failed after retries", lastException);
}

Spring Boot интеграция

Configuration класс

@Configuration
public class MinioConfig {
    
    @Value("${minio.endpoint}")
    private String endpoint;
    
    @Value("${minio.access-key}")
    private String accessKey;
    
    @Value("${minio.secret-key}")
    private String secretKey;
    
    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
            .endpoint(endpoint)
            .credentials(accessKey, secretKey)
            .build();
    }
}

Service класс

@Service
public class FileStorageService {
    
    private final MinioClient minioClient;
    private final String bucketName = "app-files";
    
    public FileStorageService(MinioClient minioClient) {
        this.minioClient = minioClient;
        initializeBucket();
    }
    
    @PostConstruct
    private void initializeBucket() {
        try {
            if (!minioClient.bucketExists(BucketExistsArgs.builder()
                .bucket(bucketName).build())) {
                minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName).build());
            }
        } catch (Exception e) {
            throw new RuntimeException("Не удалось создать bucket", e);
        }
    }
    
    public String uploadFile(String fileName, InputStream inputStream, 
                           String contentType) throws Exception {
        String objectName = UUID.randomUUID() + "_" + fileName;
        
        minioClient.putObject(PutObjectArgs.builder()
            .bucket(bucketName)
            .object(objectName)
            .stream(inputStream, inputStream.available(), -1)
            .contentType(contentType)
            .build());
            
        return objectName;
    }
}

Мониторинг и метрики

Основные метрики для отслеживания

  • Request latency — время отклика операций
  • Error rate — процент неуспешных запросов
  • Throughput — количество операций в секунду
  • Storage usage — использование дискового пространства
  • Connection pool usage — использование пула соединений

Логирование операций

@Slf4j
@Component
public class MinioOperationLogger {
    
    public <T> T logOperation(String operation, Supplier<T> supplier) {
        long startTime = System.currentTimeMillis();
        try {
            T result = supplier.get();
            long duration = System.currentTimeMillis() - startTime;
            log.info("MinIO operation {} completed in {}ms", operation, duration);
            return result;
        } catch (Exception e) {
            long duration = System.currentTimeMillis() - startTime;
            log.error("MinIO operation {} failed after {}ms: {}", 
                     operation, duration, e.getMessage());
            throw e;
        }
    }
}

Важные особенности для собеседования

Eventual Consistency

MinIO обеспечивает строгую консистентность для операций записи, в отличие от Amazon S3 (eventual consistency).

Erasure Coding

Технология защиты данных, разбивающая объект на части и добавляющая избыточность. Позволяет восстановить данные при потере части дисков.

Distributed Mode

В кластерном режиме MinIO требует минимум 4 диска. Рекомендуется четное количество узлов.

Performance Tips

  • Используйте connection pooling
  • Реализуйте retry логику
  • Для больших файлов используйте multipart upload
  • Мониторьте размеры bucket (влияет на производительность list операций)

Security Best Practices

  • Всегда используйте HTTPS в production
  • Регулярно ротируйте access keys
  • Используйте принцип минимальных привилегий в bucket policies
  • Не храните credentials в коде — используйте переменные окружения или секреты

Эта шпаргалка покрывает основные концепции и практические примеры работы с MinIO S3, которые важны для Senior Java Backend разработчика.

Cassandra

Коротко: Cassandra — распределённая (данные хранятся на многих узлах), отказоустойчивая (переживает падения узлов), горизонтально масштабируемая колонночная СУБД. Работает по принципу tunable consistency (настраиваемый уровень согласованности). Данные проектируем от запросов, без JOIN и агрегатов на стороне БД — используем денормализацию.


Оглавление


1) Архитектура и базовые сущности

  • КластерDC (Data Center, датацентр) → RackНода (узел) → Keyspace (логический «база данных») → Таблица.
  • Кольцо (ring): все ноды равноправны (masterless). Данные распределяются по токенам (диапазонам хэш‑пространства).
  • Partitioner — функция, которая превращает ключ партиции в токен. По умолчанию: Murmur3Partitioner.
  • vnodes (виртуальные ноды) — каждая физическая нода держит много небольших диапазонов токенов (обычно num_tokens: 256) для равномерного баланса.
  • Replication Factor (RF) — сколько копий (реплик) каждой партиции хранится в кластере/в DC.
  • Consistency Level (CL) — сколько реплик должны подтвердить операцию чтения/записи (см. §3).
  • SSTable — неизменяемый («immutable») файл на диске с данными таблицы (формат LSM‑дерева).
  • Memtable — память, куда пишут новые данные до сброса («flush») на диск в SSTable.
  • Commit Log — журнал предзаписи (WAL), куда попадает каждая запись перед фиксацией в memtable для восстановления после сбоев.
  • LSM‑дерево (Log‑Structured Merge‑Tree) — подход хранения: быстрые последовательные записи, периодические слияния файлов (compaction).
  • Gossip — протокол обмена метаданными между нодами (кто жив, где находится, версия и т. п.).
  • Seed nodes — стартовые адреса для госсип‑обмена при запуске ноды (не «мастера», просто точки входа).

2) Модель данных и CQL

Ключи

PRIMARY KEY ((partition_key), clustering1, clustering2, ...)

  • Partition key — распределяет данные по нодам; минимальная единица чтения/хранения — партиция.
  • Clustering columns — сортируют строки внутри партиции (по умолчанию ASC; можно задать DESC).

Правила моделирования

  • Начинаем с списка запросов: каждую выборку нужно уметь выполнить по ключам (без фильтров по неключевым колонкам).
  • Одна таблица = один/несколько близких запросов. Денормализация — норма.
  • Избегаем гига‑партиций: разумно держать размер партиции — десятки МБ, а число строк — сотни тысяч, но не миллионы. Для временных рядов делаем бакеты (см. ниже).
  • Нет JOIN/AGG как в SQL‑СУБД → считаем/агрегируем вне Cassandra или поддерживаем отдельные материализованные представления (с оговорками).

Пример: события пользователя с бакетами по дню

CREATE TABLE app.events_by_user_day (
  user_id     uuid,
  day_bucket  date,        -- YYYY-MM-DD
  event_ts    timestamp,
  event_type  text,
  payload     text,
  PRIMARY KEY ((user_id, day_bucket), event_ts)
) WITH CLUSTERING ORDER BY (event_ts DESC);

3) Консистентность (CL) и репликация (RF)

  • RF (Replication Factor) — сколько копий партиции хранится в кластере (или в каждом DC при NetworkTopologyStrategy).

  • CL (Consistency Level) — сколько реплик должны участвовать в операции:

    • ONE, TWO, THREE — подтверждение от 1/2/3 реплик.
    • QUORUM — кворм всех реплик (больше половины).
    • LOCAL_QUORUM — кворм в пределах текущего DC.
    • ALL — все реплики.
    • LOCAL_ONE — одна реплика в текущем DC.
  • Правило сильного чтения: если CLread + CLwrite > RF (в рамках одного DC), то чтения увидят последнюю успешную запись (пример: RF=3, запись LOCAL_QUORUM, чтение LOCAL_QUORUM).

  • Типовой прод‑выбор: RF=3 в DC и LOCAL_QUORUM для чтений/записей.


4) Запись/чтение: путь данных, TTL и tombstones

Запись: координатор (любой узел, принявший запрос) → Commit Log (диск) → Memtable (RAM) → периодический flush → новые SSTable.
Чтение: Bloom‑фильтр (проверка «может быть есть/точно нет») → индекс/summary → чтение нужных SSTable + memtable → слияние результата.

  • TTL (Time To Live) — время жизни строки/ячейки. По истечении создаётся tombstone (маркёр удаления).
  • Tombstones накапливаются до тех пор, пока компакция их не вычистит после периода gc_grace_seconds (по умолчанию 10 дней).
  • Избыток tombstones повышает латентность и риск OOM при широких сканах.
  • Read Repair — ленивое исправление расхождений при чтении.
  • Hinted Handoff — «подсказки» для временно недоступной реплики, чтобы потом доставить пропущенные записи.

5) Компакция и компрессия (STCS/LCS/TWCS)

Compaction — слияние SSTable для удаления дубликатов и tombstones.

  • STCS (Size‑Tiered Compaction Strategy) — по умолчанию, сгруппированные по размеру файлы сливаются; универсально, но может держать больше дубликатов.
  • LCS (Leveled Compaction Strategy) — уровни фиксированного размера; идеально для запросов по узким диапазонам (меньше дубликатов, предсказуемое чтение), но дороже по IO/CPU.
  • TWCS (Time‑Window Compaction Strategy) — для временных рядов с TTL/Retention; файлы группируются по окнам времени (дни/часы).
ALTER TABLE app.events_by_user_day
  WITH compaction = {
    'class': 'TimeWindowCompactionStrategy',
    'compaction_window_unit': 'DAYS',
    'compaction_window_size': '1'
  };

Compression — обычно LZ4Compressor снижает IO на чтениях/записях за счёт CPU.


6) Индексы, Materialized Views, поиск

  • Secondary Index (встроенный вторичный индекс) — годится, когда:
    1. уже сузили партицию по ключу, и
    2. индексируемая колонка низкой кардинальности.
      Для глобальных запросов по высокой кардинальности — не подходит.
  • Materialized Views (MV) — автоматическое поддержание альтернативного ключа чтения. Плюсы — удобство; минусы — накладные расходы и ограничения согласованности/отказоустойчивости. Использовать осознанно.
  • Полнотекстовый/сложный поиск — выносить в Elasticsearch/OpenSearch или спец‑хранилища.

7) Проектирование «от запросов» (паттерны/антипаттерны)

  1. Составьте список конкретных SELECT: фильтры, сортировки, пределы, объёмы, частоты.
  2. Под каждый важный запрос — своя таблица/MV.
  3. Избегайте «горячих» партиций — вводите бакетирование (например, (user_id, day_bucket)), либо хэш‑соль.
  4. Пагинация — за счёт clustering‑порядка и LIMIT/paging (не «OFFSET»).
  5. Агрегации/счётчики — либо counters (см. рецепт), либо вне БД.

Антипаттерны:

  • ALLOW FILTERING в проде.
  • LOGGED BATCH на десятки/сотни разных партиций (это не транзакция между партициями).
  • Гига‑партиции (например, без бакетов по времени).
  • Массовые UPDATE/DELETE → шторм tombstones.

8) Эксплуатация: nodetool, бэкапы, repair

Частые команды:

nodetool status           # состояние кластера (UN/UJ/DN/...)
nodetool ring             # распределение токенов
nodetool info             # инфо по ноде
nodetool tpstats          # thread pools
nodetool cfstats          # статистика таблиц
nodetool compactionstats  # компакции
nodetool flush [ks] [tbl] # форс-flush
nodetool repair [ks] [tbl] --full|--incremental  # антиэнтропия
nodetool cleanup          # после уменьшения RF/перебалансировки
nodetool decommission     # корректно удалить ноду
nodetool removenode <id>  # убрать мёртвую ноду
nodetool rebuild          # подтянуть данные из DC
nodetool snapshot [ks]    # снапшот (бэкап)

Бэкапы:

  • Снапшоты (жёсткие ссылки на SSTable) + incremental_backups: true для дельт.
  • Восстановление: остановить ноду → вернуть файлы → nodetool refresh.
  • Массовая загрузка: sstableloader.

Repair:

  • Делайте инкрементальный repair регулярно (дни/недели) + периодический --full. Иначе риск «воскрешения» удалённых данных после gc_grace_seconds.

9) Конфигурация и топология (DC/Rack/Snitch)

Файл cassandra.yaml (важное):

  • cluster_name, partitioner (обычно Murmur3Partitioner), num_tokens (примерно 256).
  • Сети: listen_address, rpc_address, seed_provider (несколько seed на DC).
  • Snitch — определяет DC/Rack‑привязку: обычно GossipingPropertyFileSnitch + файл cassandra-rackdc.properties (dc=..., rack=...).
  • IO/память: concurrent_reads/writes, memtable_flush_writers, commitlog_segment_size_in_mb.
  • Диски: разнести Commit Log и Data на разные устройства. Для Data — несколько каталогов под разные диски (JBOD).
  • Выбор replication strategy на keyspace: NetworkTopologyStrategy для прод (задаём RF по каждому DC).

10) Производительность и наблюдаемость

Что мониторить:

  • Латентности p50/p95/p99 (медиана/95‑й/99‑й перцентиль) по чтению/записи.
  • Таймауты: ReadTimeout, WriteTimeout, Unavailable.
  • Кол‑во tombstones на запрос (предупреждения в логах, в cqlsh при TRACING ON).
  • Очереди компакции, бэкт‑прешер на flush/compaction.
  • Heap/GC паузы JVM, hit‑rate page cache.
  • Давность и длительность repair.

Грубые ориентиры по ресурсам:

  • Память (RAM): heap 8–16 ГБ (слишком большие heap ведут к длинным GC‑паузам), остальное — под page cache.
  • Диски: NVMe/SSD дают наибольшую пользу. Коммит‑лог — отдельный быстрый диск.
  • CPU: больше ядер помогает при компакции/потоках чтения.

11) Клиенты/драйверы (Java пример)

Рекомендации:

  • Включайте Token‑Aware routing (запросы летят на владельца партиции).
  • Балансировка DC‑local (не ходим меж DC без нужды).
  • Настраивайте paging (по умолчанию ~5k строк/страница) и таймауты.
  • Помечайте запросы как idempotent (идемпотентные), чтобы драйвер мог безопасно ретраить.

Пример (Java Driver 4.x):

CqlSession session = CqlSession.builder()
    .withLocalDatacenter("dc1")           // DC для LOCAL_*
    .build();

// Пейджинг:
SimpleStatement st = SimpleStatement.builder(
        "SELECT * FROM app.events_by_user_day WHERE user_id=? AND day_bucket=?")
    .addPositionalValues(userId, day)
    .setPageSize(5000)
    .setIdempotence(true)
    .build();

ResultSet rs = session.execute(st);

12) Безопасность (аутентификация, шифрование)

  • Аутентификация: включить authenticator: PasswordAuthenticator и authorizer: CassandraAuthorizer, создать роли/пользователей, выдать минимально необходимые права.

  • Шифрование трафика:

    • Клиентское: client_encryption_options (TLS между драйвером и нодой).
    • Межузловое: server_encryption_options (TLS между нодами).
  • Секреты (пароли, ключи) — хранить вне конфигов (Vault и т. п.).


13) Частые рецепты (CQL‑шпаргалка)

Keyspace с RF по DC:

CREATE KEYSPACE app
WITH REPLICATION = {
  'class': 'NetworkTopologyStrategy',
  'dc1': '3',
  'dc2': '2'
} AND DURABLE_WRITES = true;

Таблица заказов по клиенту (последние N):

CREATE TABLE app.orders_by_customer (
  customer_id uuid,
  created_at  timestamp,
  order_id    uuid,
  status      text,
  amount      decimal,
  PRIMARY KEY ((customer_id), created_at, order_id)
) WITH CLUSTERING ORDER BY (created_at DESC);

SELECT * FROM app.orders_by_customer
WHERE customer_id = ?
LIMIT 50;

Лёгкие транзакции (LWT, Paxos) — уникальный ник:

INSERT INTO app.unique_usernames (username, user_id)
VALUES ('sergey', 123)
IF NOT EXISTS;    -- вернёт применилось/нет

BATCH — только для одной партиции (оптимизация сети):

BEGIN BATCH
  INSERT INTO app.orders_by_customer (...) VALUES (...);
  INSERT INTO app.orders_by_customer (...) VALUES (...);
APPLY BATCH;   -- те же (customer_id), иначе не используйте LOGGED BATCH

Counters (счётчики):

CREATE TABLE app.likes_counter (
  post_id uuid PRIMARY KEY,
  likes   counter
);

UPDATE app.likes_counter SET likes = likes + 1 WHERE post_id = ?;

TTL и автоснос старых данных:

ALTER TABLE app.events_by_user_day
  WITH default_time_to_live = 86400; -- сутки

-- либо построчный TTL:
INSERT INTO app.events_by_user_day (...) VALUES (...) USING TTL 3600;

Материализация альтернативного ключа чтения:

CREATE TABLE app.orders_by_status_day (
  status      text,
  day_bucket  date,
  created_at  timestamp,
  order_id    uuid,
  customer_id uuid,
  amount      decimal,
  PRIMARY KEY ((status, day_bucket), created_at, order_id)
) WITH CLUSTERING ORDER BY (created_at DESC);

14) Словарь терминов и аббревиатур

  • CQL (Cassandra Query Language) — язык запросов Cassandra, похож на SQL, но без JOIN/AGG.
  • RF (Replication Factor) — число копий каждой партиции.
  • CL (Consistency Level) — сколько реплик должны подтвердить запрос (например, LOCAL_QUORUM).
  • DC (Data Center) — логическая группа нод (обычно физически разнесённых).
  • Rack — подгруппа нод внутри DC для отказоустойчивости.
  • Partition key — ключ, определяющий распределение данных по нодам и границы партиции.
  • Clustering columns — колонки, задающие порядок строк в партиции.
  • SSTable — неизменяемый файловый сегмент данных таблицы на диске.
  • Memtable — в памяти структура для новых/изменённых данных до записи в SSTable.
  • Commit Log (WAL) — журнал предзаписи для устойчивости к сбоям.
  • LSM‑дерево — структура хранения на основе последовательных записей и периодических слияний.
  • Compaction — процесс слияния SSTable с удалением дублей и tombstones.
  • STCS/LCS/TWCS — стратегии компакции: Size‑Tiered / Leveled / Time‑Window.
  • Bloom‑фильтр — вероятностная структура, быстро отвечающая «точно нет / может быть есть».
  • TTL (Time To Live) — «срок жизни» данных; по истечении возникает tombstone (маркёр удаления).
  • Tombstone — специальная запись‑«надгробие», означающая удаление/истечение TTL до окончательной чистки компакцией.
  • gc_grace_seconds — период, в течение которого tombstones хранятся для корректной репликации/repair, чтобы не «воскресить» удалённые данные.
  • Repair (антиэнтропия) — выравнивание расхождений между репликами (по Merkle‑деревьям).
  • Read Repair — исправление расхождений при чтении данных.
  • Hinted Handoff — временное хранение «подсказок» для упавшей реплики с последующей доставкой.
  • Gossip — протокол обмена состоянием между нодами.
  • Snitch — компонент, сообщающий Cassandra о топологии DC/Rack для правильной репликации/маршрутизации.
  • Seed nodes — адреса для начального госсип‑обмена при запуске ноды.
  • vnodes (virtual nodes) — множественные диапазоны токенов на одной ноде для балансировки.
  • Murmur3Partitioner — стандартный хэш‑партиционер.
  • Materialized View (MV) — таблица, автоматически поддерживаемая из исходной с альтернативным ключом.
  • Secondary Index — встроенный индекс по неключевой колонке; применим в узких случаях.
  • Idempotent (идемпотентный) запрос — повтор безопасен, результат не меняется.
  • Paging — постраничная выборка: сервер отдаёт данные частями (страницами).
  • Per‑centile p50/p95/p99 — 50/95/99‑й перцентили латентности (медиана и «хвосты»).
  • Backoff — стратегия задержек между повторами (ретраями).
  • LOGGED/UNLOGGED BATCH — батч с/без журнала; LOGGED над многими партициями — антипаттерн.

Мини‑чек‑лист для продакшена

  • NetworkTopologyStrategy, RF≥3 в каждом DC, запросы LOCAL_QUORUM
  • Регулярный incremental repair + периодический --full
  • Раздельные диски для Commit Log и Data
  • Модель данных покрывает все запросы без ALLOW FILTERING
  • Контроль размеров партиций; time‑series — через бакеты + default_time_to_live
  • Драйверы: token‑aware, DC‑local, paging, таймауты, ретраи только для idempotent

Виды хранилищ

Классификация хранилищ данных

По типу доступа к данным

  • Файловые системы - доступ через файловые операции
  • Блочные хранилища - доступ к блокам данных напрямую
  • Объектные хранилища - доступ к объектам через HTTP API

По расположению

  • Локальные - на том же сервере что и приложение
  • Сетевые - доступ через сеть (NAS, SAN)
  • Облачные - управляемые облачными провайдерами

Реляционные базы данных (RDBMS)

Характеристики

  • ACID-совместимость - Atomicity, Consistency, Isolation, Durability
  • SQL-запросы - стандартизированный язык запросов
  • Строгая схема - структура данных определена заранее
  • Нормализация - устранение избыточности данных

Популярные RDBMS

  • PostgreSQL - расширяемая, поддержка JSON, массивов
  • MySQL - высокая производительность, простота
  • Oracle - корпоративные функции, высокая надежность
  • SQL Server - интеграция с Microsoft ecosystem

Применение

  • Транзакционные системы (банковские операции)
  • ERP, CRM системы
  • Системы с сложными связями между данными
// Пример работы с PostgreSQL через JPA
@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private BigDecimal amount;
    
    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
}

NoSQL базы данных

Document-based (Документные)

MongoDB

  • Хранение в формате BSON (Binary JSON)
  • Гибкая схема - документы могут иметь разную структуру
  • Горизонтальное масштабирование через шардирование
  • Индексирование по любым полям
// MongoDB с Spring Data
@Document(collection = "products")
public class Product {
    @Id
    private String id;
    private String name;
    private Map<String, Object> attributes; // Гибкая структура
}

@Repository
public interface ProductRepository extends MongoRepository<Product, String> {
    List<Product> findByAttributesContaining(String key, Object value);
}

CouchDB

  • RESTful HTTP API
  • Multi-Version Concurrency Control (MVCC)
  • Репликация master-master
  • MapReduce для агрегации данных

Key-Value (Ключ-значение)

Redis

  • In-memory хранилище с персистентностью
  • Структуры данных: strings, lists, sets, sorted sets, hashes
  • Pub/Sub messaging
  • Lua скриптинг для атомарных операций
// Redis с Spring Boot
@Service
public class CacheService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public void cacheUser(String userId, User user) {
        redisTemplate.opsForValue().set("user:" + userId, user, Duration.ofHours(1));
    }
    
    public User getUser(String userId) {
        return (User) redisTemplate.opsForValue().get("user:" + userId);
    }
}

Amazon DynamoDB

  • Управляемая NoSQL от AWS
  • Автоматическое масштабирование
  • Eventual consistency по умолчанию
  • Global Secondary Indexes

Column-Family (Колоночные)

Apache Cassandra

  • Децентрализованная архитектура (нет single point of failure)
  • Eventual consistency
  • CQL (Cassandra Query Language) похожий на SQL
  • Tunable consistency - можно настроить уровень согласованности
// Cassandra с Spring Data
@Table("sensor_data")
public class SensorData {
    @PrimaryKey
    private SensorDataKey key;
    
    @Column("temperature")
    private Double temperature;
    
    @Column("humidity")
    private Double humidity;
}

@PrimaryKeyClass
public class SensorDataKey {
    @PrimaryKeyColumn(name = "sensor_id", type = PrimaryKeyType.PARTITIONED)
    private String sensorId;
    
    @PrimaryKeyColumn(name = "timestamp", type = PrimaryKeyType.CLUSTERED)
    private Date timestamp;
}

HBase

  • Построена поверх Hadoop HDFS
  • Строго консистентная
  • Автоматическое шардирование
  • Оптимизирована для чтения/записи больших объемов данных

Graph (Графовые)

Neo4j

  • Native graph storage
  • Cypher Query Language
  • ACID транзакции
  • Эффективный поиск связей
// Neo4j с Spring Data
@Node
public class Person {
    @Id @GeneratedValue
    private Long id;
    private String name;
    
    @Relationship(type = "FRIEND_OF")
    private Set<Person> friends;
}

@Query("MATCH (p:Person)-[:FRIEND_OF*2]-(friendOfFriend) WHERE p.name = $name RETURN friendOfFriend")
List<Person> findFriendsOfFriends(@Param("name") String name);

Файловые хранилища

Локальные файловые системы

  • ext4 - стандартная FS для Linux
  • NTFS - Windows файловая система
  • APFS - Apple File System

Сетевые файловые системы

  • NFS - Network File System для Unix-систем
  • SMB/CIFS - Windows сетевые папки
  • GlusterFS - распределенная файловая система

Объектные хранилища

Amazon S3

  • Практически неограниченная емкость
  • REST API для доступа
  • Различные классы хранения (Standard, IA, Glacier)
  • Версионирование объектов
// S3 с AWS SDK
@Service
public class S3Service {
    private final AmazonS3 s3Client;
    
    public void uploadFile(String bucketName, String key, File file) {
        s3Client.putObject(new PutObjectRequest(bucketName, key, file));
    }
    
    public S3Object downloadFile(String bucketName, String key) {
        return s3Client.getObject(bucketName, key);
    }
}

MinIO

  • S3-совместимое объектное хранилище
  • Можно развернуть on-premise
  • Высокая производительность
  • Kubernetes native

Специализированные хранилища

Time Series (Временные ряды)

InfluxDB

  • Оптимизирована для метрик и событий с временными метками
  • Встроенная агрегация и даунсэмплинг
  • Retention policies для автоматического удаления старых данных
  • Flux query language
// InfluxDB клиент
@Service
public class MetricsService {
    private final InfluxDBClient client;
    
    public void writeMetric(String measurement, Map<String, String> tags, 
                          Map<String, Object> fields) {
        Point point = Point.measurement(measurement)
                          .addTags(tags)
                          .addFields(fields)
                          .time(Instant.now(), WritePrecision.MS);
        
        client.getWriteApiBlocking().writePoint(point);
    }
}

Apache Druid

  • Аналитическая OLAP база данных
  • Колоночное хранение
  • Реальное время + batch обработка
  • Высокая производительность агрегации

Search Engines (Поисковые системы)

Elasticsearch

  • Построена на Apache Lucene
  • RESTful API
  • Полнотекстовый поиск
  • Агрегации и аналитика
// Elasticsearch с Spring Data
@Document(indexName = "products")
public class Product {
    @Id
    private String id;
    
    @Field(type = FieldType.Text, analyzer = "standard")
    private String title;
    
    @Field(type = FieldType.Nested)
    private List<Category> categories;
}

@Repository
public interface ProductSearchRepository extends ElasticsearchRepository<Product, String> {
    
    @Query("{\"bool\": {\"must\": [{\"match\": {\"title\": \"?0\"}}]}}")
    List<Product> findByTitleContaining(String title);
}

Apache Solr

  • Также основана на Lucene
  • Мощные возможности фасетирования
  • Встроенная административная панель
  • Хорошая производительность

Кэширование

In-Memory кэши

Redis (уже описан выше)

  • Персистентность через RDB снапшоты или AOF лог
  • Кластеризация для высокой доступности
  • Множество структур данных

Memcached

  • Простой key-value кэш
  • Только строковые ключи и значения
  • Распределенный кэш через consistent hashing
  • Меньше функций, но выше производительность
// Memcached с Spring Boot
@Service
public class MemcachedService {
    private final MemcachedClient client;
    
    public void set(String key, Object value, int expiration) {
        client.set(key, expiration, value);
    }
    
    public Object get(String key) {
        return client.get(key);
    }
}

Embedded кэши

Caffeine

  • Высокопроизводительный Java кэш
  • Размер-основанные и время-основанные eviction политики
  • Асинхронная загрузка
  • Статистика использования
// Caffeine кэш
@Configuration
public class CacheConfig {
    
    @Bean
    public Cache<String, User> userCache() {
        return Caffeine.newBuilder()
                .maximumSize(10000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .recordStats()
                .build();
    }
}

Ehcache

  • Многоуровневое кэширование (heap, off-heap, disk)
  • Интеграция с Spring Cache
  • Cluster поддержка
  • JMX мониторинг

Выбор хранилища

Факторы выбора

  1. Консистентность данных

    • Строгая консистентность → RDBMS
    • Eventual consistency → NoSQL
  2. Масштабируемость

    • Вертикальная → RDBMS
    • Горизонтальная → NoSQL
  3. Структура данных

    • Структурированные → RDBMS
    • Полуструктурированные → Document DB
    • Неструктурированные → Object Storage
  4. Паттерны доступа

    • Сложные запросы → RDBMS/Graph DB
    • Простые операции → Key-Value
    • Полнотекстовый поиск → Search Engine
  5. Производительность

    • Низкая латентность → In-Memory (Redis)
    • Высокая пропускная способность → Column-Family

Типичные архитектурные решения

Polyglot Persistence - использование разных типов хранилищ для разных задач:

  • PostgreSQL для транзакционных данных
  • Redis для кэширования
  • Elasticsearch для поиска
  • S3 для файлов
  • InfluxDB для метрик

CQRS (Command Query Responsibility Segregation)

  • Отдельные хранилища для записи и чтения
  • Например: PostgreSQL для команд, Elasticsearch для запросов

Event Sourcing

  • Хранение событий вместо текущего состояния
  • Часто с Apache Kafka + Event Store