Skip to content

Commit 0c582df

Browse files
committed
doc: add description
1 parent 6423492 commit 0c582df

File tree

3 files changed

+141
-47
lines changed

3 files changed

+141
-47
lines changed

README.md

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,103 @@
11
# Pixel-Battle backend
22
[![CI](https://github.com/emptybutton/Pixel-battle-backend/actions/workflows/ci.yml/badge.svg)](https://github.com/emptybutton/Pixel-battle-backend/actions?query=workflow%3ACI)
33
[![CD](https://github.com/emptybutton/Pixel-battle-backend/actions/workflows/cd.yaml/badge.svg)](https://github.com/emptybutton/Pixel-battle-backend/actions/workflows/cd.yaml)
4+
[![GitHub Release](https://img.shields.io/github/v/release/emptybutton/Pixel-battle-backend?style=flat&logo=github&labelColor=%23282e33&color=%237c73ff)](https://github.com/emptybutton/Pixel-battle-backend/releases)
5+
[![Lines](https://img.shields.io/endpoint?url=https%3A%2F%2Fghloc.vercel.app%2Fapi%2Femptybutton%2FPixel-battle-backend%2Fbadge%3Ffilter%3D.py&logo=python&label=lines&color=blue)](https://github.com/search?q=repo%3Aemptybutton%2FPixel-battle-backend+language%3APython+&type=code)
46
[![codecov](https://codecov.io/gh/emptybutton/Pixel-battle-backend/graph/badge.svg?token=VJ5A2WS1Q7)](https://codecov.io/gh/emptybutton/Pixel-battle-backend)
57

6-
Simple backend for pixel battle.
8+
Бэкенд-приложение, разрабатываемое с расчётом на нагрузки выше, чем у VK Pixel-Battle и Reddit r/place.
79

10+
## Предметная область
11+
- В игре есть холст размером 1000х1000 пикселей
12+
- Пользователи могут перекрашивать любой пиксель на холсте раз в минуту
13+
- Новые пользователи могут начать редактировать холст только через минуту после присоединения к игре (это сделано для предотвращения обхода ограничения на редактирование раз в минуту)
14+
- Пользователи не могут редактировать холст, когда пиксель-батл не активен
15+
- Конфигурированием времени проведения пиксель-батла занимаются админы
16+
- Админ может запланировать или изменить время проведения пиксель-батла, если у него есть админский ключ, соответствующий админскому ключу самого пиксель-батла
17+
- Холст разбит на 100 чанков — областей размером 100х100 пикселей
18+
- Каждый чанк характеризуется своим номером — ужатой минимальной позиции в своей области. Как пример, минимальная позиция чанка `1, 0` — это `100, 0`, чанка `5, 6` — это `500, 600`
19+
20+
## Сценарии
21+
**Редактирования холста**:
22+
1. пользователь регестрируется в системе
23+
2. ожидает одну минуту
24+
3. перекрашивает пиксель
25+
4. `повторяет шаги 2 и 3 до окончания пребывания в игре`
26+
27+
**Просмотр холста**:
28+
1. пользователь собирается просматривать области холста, расположенные в рамках определённых чанков
29+
2. клиент пользователя начинает отслеживать изменения этих чанков
30+
3. через некоторое время просматривает их устаревшие представления вместе с актуализирующими изменениями
31+
4. применяет актуализирущие изменения
32+
5. применяет накопленные отслеженные изменения
33+
6. отображает представления
34+
7. по мере поступления новых изменений применяет их
35+
8. `повторяется шаг 7`
36+
9. пользователь прекращает просмотр областей холста, расположенных в рамках определённых чанков
37+
10. чанки больше не отображаются и не отслеживаются
38+
39+
**Планирование пиксель батла**:
40+
1. админ получает админский ключ вне системы.
41+
2. планирует проведение пиксель-батла в рамках определённого временного промежутка
42+
43+
## Реализация
44+
![System design](https://raw.githubusercontent.com/emptybutton/Pixel-battle-backend/refs/heads/main/assets/system-design/image.png)
45+
46+
> [!IMPORTANT]
47+
> Все сервисы — это один сервис, разворачиваемый как несколько сервисов для точечного масштабирования.
48+
49+
В системе два Redis-кластера:
50+
1. Кластер холста
51+
2. Кластер метаданных холста
52+
53+
### Кластер холста
54+
Хранит данные состояния чанков.
55+
56+
Каждый шард хранит данные только одного чанка (максимум 100 шардов). Если бы шард хранил данные разных чанков, сбой мог бы привести к неконсистентному состоянию чанков, данные которого хранит шард.
57+
58+
Данные шарда:
59+
- два варианта изображения чанка
60+
- поток изменений чанка. Каждое событие — это данные отдельного пикселя, закодированные в 5 байт, где первые два — позиция, остальные три — RGB цвет (позиция хранится относительно минимальной позиции чанка, поэтому максимальное значения позиции это не `999, 999`, а `99, 99`)
61+
- смещение потока изменений чанка, определяющее какие события были применены к хранимому изображению, а какие нет. Используются именно ручное хранение смещений, вместо consumer groups, из-за того, что в системе необходимо читать события, которые не нужно после этого комитить. При этом чтение может быть конкуретным
62+
- разные распределённые локи, в рамках которых изменяются вышеперечисленные данные
63+
64+
Изображение чанка представляется в таких вариантах:
65+
1. `png` картинка, не требующая каких-либо дополнительных преобразований для операций чтения
66+
2. сырые пиксельные данные, не закодированные в какой-либо формат, использующиеся библеотекой `Pillow` в качестве данных при редактировании изображений
67+
68+
Вобщем, если не брать в расчёт время ввода-вывода, то с этим разделением операции чтения выполняются в \~1000 раз быстрее, а операции рефреша на 10%\~30% быстрее.
69+
70+
71+
### Кластер метаданных холста
72+
Хранит данные, не относящиеся к конкретным чанкам:
73+
- Состояние пиксель-батла (временной интервал)
74+
-Оркестрирующая очередь задач (номера чанков для рефреша)
75+
- Распределённые локи оркестратора рефреша
76+
77+
Этот кластер имеет очень маленький обьем данных и низкую постоянную нагрузку, поэтому он не шардирован, но реплицирован, но не столько, что бы не потерять данные, столько что бы имелись замены в случае падения мастера.
78+
79+
### Поток данных при изменении пикселя
80+
1. запись нового состояния пикселя происходит в `chunk_writing_service`, где он добавляется в очередь изменений чанка, к которому относится
81+
2. посредством вебсокетов, `chunk_streaming_service` посылает новое состояние пикселя всем слушающим клиетам того чанка, к которому относится пиксель
82+
3. копиться микробатч в очереди, перед его записью в представления
83+
4. до тех пор, пока пиксель в микробатче, операции `chunk_reading_service`-а читают его (и все остальные незафиксированные состояния пикселей) из очереди как актуализирующую дельту основного изображения
84+
5. `chunk_refresh_worker` приступает к рефрешу и применяет микробатч к изображениям и фиксируют его смещением
85+
86+
### Оркестрация рефреша чанков
87+
- `chunk_refresh_worker` пулит очередь задач из кластера метаданных холста и рефрешит тот чанк, команду которого он вытащил.
88+
- `chunk_refresh_orchestrator` пушит очередь задач, таким образом, что команды хранятся зациклированно.
89+
90+
Зациклированно значит, что если существуют комманды `А`, `Б`, `С`, то после пулинга `A`, будет спулет `Б`, потом спулет `C`, а после него опять спулет `A` и по кругу.
91+
92+
> [!CAUTION]
93+
> В случае одновременной работы нескольких оркестраторов присутвует риск того, что команд в очереди будет больше 100 и что рефреш будет происходить немного чаще, тем самым уменьшая микробатчи некоторых чанков и увеличивая избыточное потребление ресурсов.
94+
>
95+
> Несмотря на это, можно убрать риск уменьшения времени хранения микробатчей, храня расписания оркестрации (пуша), но пока это не реализовано.
96+
97+
При сбое всех воркеров рефреш можно запустить вручную через `admin_cli`.
98+
99+
### Данные пользователей
100+
Всего на одного пользователя необходимо сохранять только время, когда он обретёт право на перекрашивание пикселей, поэтому эти данные не хранятся на сервере, а их хранит сам клиент пользователя в качестве JWT, через http-only куку.
101+
102+
### В действительности
103+
Это приложение уже развёрнуто, но из-за того, что оно не испытывает нагрузку, под которую проектировалось, оно работает не в виде 8+ сервисов и 2 кластеров на множество нод, а в виде одной ноды с 1 ядром и 1 GB RAM. Оно развёрнуто как единый сервис в рамках одного процесса с тремя процессами Redis-сервера, образующими один кластер (минимально необходимое количество для создания кластера), который заменяет оба запланированных кластера.

assets/system-design/image.png

-6.25 KB
Loading

0 commit comments

Comments
 (0)