Files
Kecalek_python/scaling.md
2026-03-11 16:54:14 +01:00

7.0 KiB
Raw Blame History

Škálování serveru — plán kapacitního růstu

Cílový hardware

  • CPU: Intel Xeon E5-2630v4 (10 cores / 20 threads, 2.2 GHz)
  • RAM: 256 GB REG ECC
  • Disk: 500 GB SSD (boot/OS/DB) + 4 TB HDD (soubory)
  • Síť: 1 Gbit

Odhadovaná kapacita po optimalizaci: 10 00020 000 uživatelů, 20005000 zpráv/s


Krok 1: Okamžité změny (hotovo v kódu)

1a. Thread pool — server.py

THREAD_POOL_SIZE=40

Nastavuje ThreadPoolExecutor(max_workers=40) jako default executor pro asyncio.to_thread(). S 20 HW thready a DB latencí ~25ms je 40 workerů optimální (2x HW threads — workery čekají na I/O).

1b. DB pool — .env

DB_POOL_SIZE=30

30 simultánních MySQL spojení. S 40 thread workers a ~2ms query je 30 pool konexí dostatek.

1c. Chybějící DB indexy — schema.sql

Přidány 5 nových indexů pro nejčastější dotazy:

Index Tabulka Dotaz který zrychlí
idx_cm_user (user_id) conversation_members list_user_conversationskritický, bez něj full table scan
idx_inv_user (user_id) group_invitations get_pending_invitations
idx_messages_deleted (conversation_id, deleted_at) messages get_deleted_messages_since
idx_messages_pinned (conversation_id, pinned_at) messages get_pinned_messages
idx_reads_user (user_id) message_reads get_unread_counts

SQL migrace pro existující databázi:

ALTER TABLE conversation_members ADD INDEX idx_cm_user (user_id);
ALTER TABLE group_invitations ADD INDEX idx_inv_user (user_id);
ALTER TABLE messages ADD INDEX idx_messages_deleted (conversation_id, deleted_at);
ALTER TABLE messages ADD INDEX idx_messages_pinned (conversation_id, pinned_at);
ALTER TABLE message_reads ADD INDEX idx_reads_user (user_id);

1d. Upload adresář na HDD

UPLOAD_DIR=/mnt/hdd/encrypted_chat/uploads

Šifrované soubory a avatary na 4TB HDD — SSD zůstane pro OS a MySQL data.

mkdir -p /mnt/hdd/encrypted_chat/uploads
chmod 700 /mnt/hdd/encrypted_chat/uploads

Krok 2: MySQL tuning pro 256 GB RAM

/etc/mysql/mysql.conf.d/tuning.cnf (nebo ekvivalent v Dockeru)

[mysqld]
# === Buffer Pool — hlavní cache pro data + indexy ===
# 96 GB = ~37% RAM (MySQL + app na stejném stroji)
innodb_buffer_pool_size = 96G
innodb_buffer_pool_instances = 16

# === Redo Log — větší = méně I/O, rychlejší zápisy ===
innodb_redo_log_capacity = 4G

# === Flush strategie ===
# 2 = flush do OS cache každou sekundu (ne každý commit)
# Ztráta max 1s dat při pádu OS, ale 10x rychlejší zápisy
innodb_flush_log_at_trx_commit = 2
# O_DIRECT = bypass OS page cache (InnoDB má vlastní)
innodb_flush_method = O_DIRECT

# === I/O kapacita (SSD) ===
innodb_io_capacity = 2000
innodb_io_capacity_max = 4000

# === Connections ===
max_connections = 200

# === Sort/Join buffery ===
sort_buffer_size = 4M
join_buffer_size = 4M
read_buffer_size = 2M
read_rnd_buffer_size = 2M

# === Temporary tables ===
tmp_table_size = 256M
max_heap_table_size = 256M

# === Query cache (MySQL 8.0+ nemá, pro 5.7) ===
# query_cache_type = 0

# === Thread cache ===
thread_cache_size = 64

# === Binary logging (pro budoucí repliky) ===
# server-id = 1
# log_bin = /var/log/mysql/mysql-bin
# binlog_expire_logs_seconds = 604800
# max_binlog_size = 256M

Pokud MySQL běží v Dockeru:

# docker-compose.yml
services:
  mysql:
    image: mysql:8.0
    volumes:
      - /var/lib/mysql:/var/lib/mysql          # data na SSD
      - ./tuning.cnf:/etc/mysql/conf.d/tuning.cnf
    deploy:
      resources:
        limits:
          memory: 128G    # limitovat aby zbylo pro app
    environment:
      MYSQL_DATABASE: encrypted_chat

Po aplikaci restartovat MySQL a ověřit:

SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
SHOW ENGINE INNODB STATUS\G

Krok 3: Doporučená .env pro produkci

# Server
SERVER_HOST=0.0.0.0
SERVER_PORT=9999

# MySQL
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=sifrator
MYSQL_PASSWORD=<silne-heslo>
MYSQL_DATABASE=encrypted_chat
DB_POOL_SIZE=30

# Performance
THREAD_POOL_SIZE=40

# Storage
UPLOAD_DIR=/mnt/hdd/encrypted_chat/uploads

# TLS (zapnout pro produkci)
TLS_ENABLED=true
TLS_CERT_FILE=/etc/letsencrypt/live/chat.example.com/fullchain.pem
TLS_KEY_FILE=/etc/letsencrypt/live/chat.example.com/privkey.pem

# Logging
LOG_LEVEL=INFO

Krok 4: Monitoring (doporučeno)

Jednoduché metriky bez externích nástrojů

Přidat do serveru periodické logování:

# V _periodic_cleanup() (každých 10 min):
async with _clients_lock:
    total_connections = sum(len(v) for v in connected_clients.values())
    unique_users = len(connected_clients)
logger.info("[STATS] users=%d connections=%d", unique_users, total_connections)

S externími nástroji (volitelně)

  • htop — CPU / RAM využití procesu
  • mysqladmin status — queries/s, slow queries, connections
  • Prometheus + Grafana — dlouhodobé trendy (přidat až při potřebě)

Budoucí škálování

Fáze A: Separace MySQL (15K+ uživatelů)

MySQL na separátní stroj (nebo managed DB). App server + Redis na jednom, DB na druhém.

[Server: App + Redis]  ──TCP──▶  [Server: MySQL]
      │
      └──▶  [HDD/S3: soubory]

Fáze B: Horizontální škálování (50K+ uživatelů)

Více app serverů za load balancerem + Redis Pub/Sub pro cross-server notifikace.

           ┌─── App server 1 ───┐
Client ──▶ │  connected_clients  │──┐
           └─────────────────────┘  │
                                    ├──▶ Redis Pub/Sub ──▶ MySQL
           ┌─── App server 2 ───┐  │
Client ──▶ │  connected_clients  │──┘
           └─────────────────────┘
                    ▲
              Load Balancer (HAProxy / nginx stream)
              (sticky sessions by user_id)

Hlavní změna: _notify_users() posílá do Redis místo lokálního connected_clients pokud uživatel není na tomto serveru.

Fáze C: DB škálování (100K+ uživatelů)

  • Read replicas pro SELECT dotazy
  • Partitioning tabulky messages podle měsíce
  • Sharding podle conversation_id

Přehled — co je hotovo

Krok Stav Popis
asyncio.to_thread() pro DB Hotovo 131 DB volání offloadováno do thread poolu
ThreadPoolExecutor(40) Hotovo Konfigurovatelný přes THREAD_POOL_SIZE
DB indexy (5 nových) Hotovo Schema + SQL migrace připraveny
UPLOAD_DIR na HDD Konfigurace Nastavit v .env
MySQL tuning Konfigurace Aplikovat tuning.cnf
TLS certifikát TODO Let's Encrypt nebo vlastní CA
Monitoring Volitelné Periodické logování stats