load balancing и
оптимальная загрузка
кластера
Андрей Свелов
andrew.svetlov@gmail.com
● Пишу на Python с 1999 года
● Python Code Developer
● Соавтор asyncio, aiohttp и ещё дюжины
библиотек
● Работаю в DataRobot
Простейшая схема развертывания
Browser Server
Проблемы:
1. Статика
Проблемы:
1. Статика
2. Безопасность
Reverse proxy
Browser Application
Server
Reverse proxy
aka NGINX, HAProxy etc
Конфигурация NGINX:
http {
server {
location /static {
root /www/static;
}
location / {
proxy_pass http://127.0.0.7:8080;
}
}
}
Балансировка на несколько app servers:
Browser Reverse proxy
Конфигурация NGINX:
server {
upstream backend {
server http://127.0.0.7:8080;
server http://127.0.0.7:8081;
}
location / {
proxy_pass http://backend;
}
}
Round Robin:
Reverse proxy
1
2
3
Ситуация:
1. В DataRobot делаем предсказания на обученых моделях
2. Предсказание занимает в 20-600 милисекунд в среднем.
3. Модель может "весить" гигабайт(ы).
4. Загрузка модели в память требует в 10-30 раз больше времени.
Хочу чтобы запрос шел на сервер с уже загруженной моделью.
Каждому узлу -- по своей модели:
Node 1
Reverse proxy Node 2
Model 1
Model 2
Model 3
Model 4
Особенность:
url = "/v1/<project_id>/<model_id>/predict"
Удобный hash key
URL Hash как решение:
upstream = servers[hash(url) % len(servers)]
Конфигурация NGINX:
server {
upstream backend {
hash $request_uri;
server http://127.0.0.7:8080;
server http://127.0.0.7:8081;
}
location / {
proxy_pass http://backend;
}
}
URL Hash как плохое решение:
upstream = servers[hash(url) % len(servers)]
Если один сервер умирает -- вся система
требует перебалансировки.
Пропадание узла:
4 сервера, 12 объектов 3 сервера, 12 объектов
Consistent hash:
Consistent hash 2:
Конфигурация NGINX:
server {
upstream backend {
consistent_hash $request_uri;
server http://127.0.0.7:8080;
server http://127.0.0.7:8081;
}
location / {
proxy_pass http://backend;
}
}
Sticky sessions:
HTTP cookie based upstream selection
Всё это классно, но нам не годится:
● Нужен лучший контроль над выбором
upstream
● Велосипедим сами
Самодельный контроллер
NGINX
Controller
Node 1
Node 2
Конфигурация NGINX:
server {
location / {
set $upstream "<placeholder>";
rewrite_by_lua '
ctrl = require("controller")
ngx.var.upstream = ctrl.get(ngx.var.uri,
"unix://tmp/ctrl.sock")
';
proxy_pass http://$upstream;
}
}
LUA:
function get(uri, sock_path)
local sock = ngx.socket.connect(sock_path)
local buf = pack("<HB", string.len(uri)+1, 1)
sock:send({buf, uri})
local resp_len_buf = sock:receive(3)
local resp_len, resp_code = unpack("<HB", resp_len_buf)
local upstream = sock:receive(resp_len - 1)
sock:setkeepalive()
return upstream
end
Контроллер:
● asyncio UNIX/TCP сервер
● данные -- в REDIS slave
Управление:
● REST сервер -- конфигурирование
● Sentinel -- наблюдение
Результат:
● Емкость кластера х4
● Утилизация серверов х3 (75%)
Ищем сотрудников в Минский офис
http://datarobot.com
● Python backend
● JavaScript frontend
● DevOps
● Data Science
Вопросы?
Андрей Светлов
andrew.svetlov@gmail.com
asvetlov.blogspot.com

Андрей Светлов-«Делаем своё решение для оптимальной загрузки кластера»