Архитектура Vitastor
Переходим на тёмную сторону и пишем (П)СХД вместе
Виталий Филиппов
Напомню, что за Vitastor
- vitastor.io
- Меня задолбал Ceph и я его переписал 🤣
- Распределённая блочная SDS (программная СХД)
- 🚀 Быстрая, от ~0.14 мс (Ceph, облака ~1 мс)
- Простая и всегда такой останется
- Симметричная, без SPOF
- QEMU, NBD, K8s, Proxmox, OpenStack, псевдо-NFS
- Для SSD или SSD+HDD
Почти все SDS
- Либо теряют данные
- Либо медленные
- Либо платные
- Либо мёртвые
- Либо всё вместе
Чтобы не сойти с ума 😆
- Ограничимся блочной частью, без ФС и S3
- Сбросим архитектурный груз
- Позаимствуем общие черты: PG, OSD, мониторы
- OSD — демон, работающий с 1 диском
- PG — «шард» кластера
- Монитор — управляющий демон
- Всё I/O через первичный OSD для каждой PG
Порядок реализации
- Прототип:
- Блочный слой, под EC/XOR
- База OSD (1 PG, только XOR)
- Монитор, алгоритм распределения PG
- Драйвер QEMU
- Репликация, EC N+K
- Что-то для монтирования ядром (NBD)
- Снапшоты
- Zero-copy сеть
- Плагины Proxmox, OpenStack и т.п.
- CLI
- Что-то для VMWare (NFS)
Вопросы для решения
- Как сделать быстро
- Алгоритм записи
- Алгоритм синхронизации
- Монитор, алгоритм распределения PG
- Снапшоты
- Zero-copy сеть
- Что-то для VMWare
Производительность (без границ)
Производительность (без границ)
Производительность (без границ)
Производительность (без границ)
Производительность (без границ)
Сравнение (T1Q1)
iops – операций в секунду, больше – лучше
Как не повторить «успех» 🐌
- CPU — узкое место. Не диск, не сеть!
- Простые алгоритмы
- «Си с классами»
- Минимум зависимостей (STL, json11, cpp-btree)
- Никаких boost, folly, grpc, msgpack
Потоки Асинхронщина
- io_uring
io_uring
- Новый интерфейс асинхронного IO в Linux 5.1+
- Универсальный и быстрый
- 🚀 Уже аж 10 млн iops (тут)
- DPDK нинужен!
- Умеет всё, чего не умели epoll и Linux AIO:
- Linux AIO не A без o_direct
- epoll не умеет файлы и диски
- kernel.dk/io_uring.pdf
io_uring
Придумываем алгоритм записи
После включения
Мои данные на месте!
А, чёрт, не совсем
Нужна синхронизация
Нужна синхронизация
Нужна синхронизация
Как?
- Целиком → mdadm
- По контрольным суммам → LizardFS 🦎
- Намерение записи → Linstor
- Журнал операций → Ceph, vSAN, YDB
- А ещё ребаланс
- А ещё распределение места
- А ещё Write Hole
RAID 5 Write Hole
Повреждение парного блока
Метаданные
- Что, если блок запишется, а чексумма нет?
- LizardFS: ха-ха, Lost chunk 🤣
- Нужна атомарность
- Либо WAL, но тогда двойная запись
- Либо CoW/RoW, но и он не бесплатен
- Двойная запись--, метаданные++++, CPU++
- Bluestore → 100 байт ... 16 кб на запись
Итоговая схема
- Все объекты по 128 КБ*
- ID образа + смещение + версия ⇒ адрес
- 256 МБ на 1 ТБ
- Перенаправление при полной записи
- Журналирование при частичной
- Двухфазный коммит против Write Hole
* можно менять
Нюанс
Со сжатием уже не соскочим — придётся делать и CoW, и дефрагментацию 😊
Синхронизация по версиям объектов
Синхронизация по версиям объектов
- При каждой записи версия++
- При подъёме PG сверяем полные списки объектов
PG = 1/100 OSD ~ несколько МБ меты
- Нет нужды сериализовать записи, как в Ceph, vSAN
- Нужно вести историю PG (эпохи)
1. Старые_OSD ∩ Новые_OSD = ∅
Нельзя поднимать PG
2. версия++ ⇒ возможны коллизии
Версия = (Эпоха, Версия)
3. Удаление или неполная запись?
Либо просто запретить неполное удаление
Либо по эпохе записи: если была (1,2,3), то удаление
Монитор
- Хранилище с Raft:
Своё на braft ↗️
Consul
- etcd 👍
- Логика:
Встроить в OSD
Отдельно на C++
- Отдельно на node.js 👍
А что там будет?
- Хранение:
- Конфигурация (пулы, PG, OSD, образы)
- Состояние (OSD, PG, история PG)
- Блокировки (lease)
- Статистика
- Логика:
- (Пере)распределение данных
- Назначение PG первичных OSD
- Агрегация статистики
Хеши vs БД
Храним объекты в БД («MDS-based»)
- Используем консистентные хеши
- Ceph:
- Хеш ID объекта % число PG → номер PG
- CRUSH хеш(Номер PG) → набор OSD
- CRUSH — рандеву-хеш ↗️
- CRUSH не идеален, равномерность 80-90%
- Из-за этого теряется место
Как сделать лучше
- Так же, хеш ID объекта % число PG → номер PG
- PG→OSD просто сохраним в etcd!
-
Оптимизируем через сведение к ЛП:
- Распределённое место → max
- Занятое место на OSD ≤ размера OSD
⇔
- Сумма весов PG → max
- Сумма весов PG по OSDi ≤ размер OSDi
- PGi ≥ 0
Перераспределение
-
- Распределённое место → max
- Перемещение данных → min
- Занятое место на OSD ≤ размера OSD
- Нельзя убрать больше, чем было
- Нельзя убрать или добавить меньше нуля
- ⇔
- Сумма((PG⊕ - PG⊖) * похожесть_PG) → max
- похожесть_PG = 1 + набор - отличия_набора
- Сумма PG по OSDi ≤ размер OSDi
- PG⊖ - PG⊕ ≤ PG_было
- PG⊕ ≥ 0, PG⊖ ≥ 0
Снапшоты с CoW
Легко и откатить, и удалить, но оверхед и
в распределённую архитектуру не вписывается
Снапшоты без CoW
Ceph смог усидеть на 2 стульях
- rbd snap — «назад», «глобальные номера»
⇒ ужасный откат
- rbd clone — «вперёд», object-map
⇒ немножко WA (1024x 😆 🤣)
- Не будь как цеф
Снапшоты в Vitastor
- Всегда «вперёд»
- Образ → данные + родитель → ...
- Снимок == клон
- Битмапы в объектах (всего 4 байта)
- Локальность
- Быстрое чтение, нет WA при записи
- Слияние не мгновенное, но норм
- Можно ещё улучшить
Zero-copy и kernel bypass*
Zero-copy и kernel bypass*
* хрен ты у меня проскочишь
Сеть без копирования данных
Не все zero-copy одинаково полезны
- Zero-copy не бесплатен
- Ядро и так не копирует skb без нужды
- Копировать 4 КБ дешевле, чем ремапить
- Особенно в кэше CPU
- MSG_ZEROCOPY осмыслен от ~16 КБ
DPDK + SPDK
- Сеть (и NVMe) в юзерспейсе
- Модно, молодёжно
- Проблемы:
- ❌ Оптимизирован под ПСП
- ❌ Родного TCP нет
- ❌ 1 процесс — либо IPC
- ❌ Настройка зависит от карт
- ❌ NVMeoF нам бесполезен 🤔
- ❌ Выровненного zero-copy приёма нет
DPDK TCP
- mTCP: студенческая поделка (пинг-понг 16000 мкс)
- ANS: китайские блобы
- Seastar TCP: самопал на 2к строк ↗️
-
F-Stack: тыренный BSD стек
✅
Работает, пинг-понг 26 мкс (40 мкс ядром) ↗️
❌
Не хватает настроек, например, MTU
-
VPP: машинерия с демоном и IPC
✅
Много умеет, настройки есть (vppctl)
❌
Медленно: пинг-понг 39 мкс (40 мкс ядром) ↗️
- TLDK: не проверял 😊
Резюме
DPDK хорош, если вы пишете фаерволл или DPI
Но не хранилку
Zero-copy TCP в ядре Linux
- На отправку — MSG_ZEROCOPY:
- setsockopt SO_ZEROCOPY в 1
- Не освобождаем память до подтверждения
отправки через recvmsg MSG_ERRQUEUE
- ± Юзабельно
- Пока маринуется в ветке
- Ибо ухудшает задержки и иногда жрёт CPU
- На приём — ?
Zero-copy TCP в ядре Linux
- На отправку — MSG_ZEROCOPY
- На приём —
доклад с Netdev 2020:
- MTU = 4096 + заголовки (Ethernet, IP, TCP) = 4096 + 86
- Заголовок отдельно, данные отдельно
- Для этого патчим драйвер сетевой карты 🤣
- Потом mmap на сокете
- И потом через getsockopt материализуем там данные
- Работает только в гугле, где и внедрили 🤣🤣🤣
OpenOnload
- LD_PRELOAD-библиотека для ускорения сокетов
- SolarFlare кем-то куплен, OpenOnload полумёртв
- Карточки есть на ebay 🙃
- Я проверял (SFN5122) 🙃
- Профит: 40 мкс → 32 мкс
- ❌ С io_uring не дружит
- ❌ Работает нестабильно
- Mellanox libvma похожа, но не нужна — RDMA лучше
RDMA
- Remote Direct Memory Access
- Карта пишет в вашу память
- Mellanox, есть пара карт от Intel
- Infiniband 🤨
- iWARP, RoCEv1, RoCEv2 🤟
- PFC+ECN на свиче
Как RoCE работает в Linux?
Из коробки, даже MLNX_OFED не нужен, если ядро 5.9+
Автоматически создаются roce*, LACP, ECMP LAG RDMA устройства
$ apt install ibverbs-utils
...
$ ibv_devinfo
hca_id: rocep5s0f0
transport: InfiniBand (0)
fw_ver: 14.25.1020
node_guid: 248a:0703:002c:47c6
sys_image_guid: 248a:0703:002c:47c6
vendor_id: 0x02c9
vendor_part_id: 4117
hw_ver: 0x0
board_id: DEL2420110034
phys_port_cnt: 1
port: 1
state: PORT_DOWN (1)
max_mtu: 4096 (5)
active_mtu: 4096 (5)
sm_lid: 0
port_lid: 0
port_lmc: 0x00
link_layer: Ethernet
Нюансы программизма
- Пары очередей (QP), типы: RC, UC, UD
- Есть операции передачи, есть — записи в память
- Буфер для приёма должен быть готов заранее,
иначе "RNR retry" и адские тормоза
- Без ODP (On-Demand Paging) неудобно, до ConnectX-4 его нет
- Zero-copy приём всё равно не получается 🤣🤣 [картинка]
- Но профит есть, в Vitastor с 2021
Профит RDMA
|
TCP |
RDMA |
Q=1 randread |
5300 iops (188us) |
6800 iops (147us) |
Q=1 randwrite |
5300 iops (188us) |
7200 iops (138us) |
Q=128 randread |
151000 iops |
263000 iops |
Q=128 randwrite |
53000 iops |
63000 iops |
2 десктопа по 1 NVMe, 10G ConnectX-4
NFS-прокси
- Не полноценная ФС
- Эмулятор ФС для закрытых👎 гипервизоров
- VMWare, HyperV
- Альтернативы — iSCSI, NVMeoF
- Но тогда VMFS и всё в одном образе
- NFS 3.0 — stateless, можно масштабировать
- Свой парсер протокола
Резюме
Вышло интересно, то ли ещё будет 🙃
Пробуйте → vitastor.io →
apt install vitastor 🤟