Tier 0 scalability: PgBouncer, Redis split, DB split, workers
T0.1: Separate redis-auth service (64mb, noeviction) for auth state
T0.2: Bump data Redis from 256mb to 1gb
T0.3: Per-app DATABASE_URL via PgBouncer to per-domain databases
T0.4: PgBouncer service (transaction mode, pool=20, max_conn=300);
session.py pools reduced to 3+5 with timeout and recycle
T0.5: Hypercorn --workers 2 --keep-alive 75 on all 6 entrypoints
Deploy requires running split-databases.sh first to create per-domain
databases from the existing appdb.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -128,9 +128,9 @@ for target_db in db_account db_blog db_market db_cart db_events db_federation; d
|
|||||||
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
|
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
|
||||||
);
|
);
|
||||||
DELETE FROM alembic_version;
|
DELETE FROM alembic_version;
|
||||||
INSERT INTO alembic_version (version_num) VALUES ('v2t0p8q9r0');
|
INSERT INTO alembic_version (version_num) VALUES ('w3u1q9r0s1');
|
||||||
SQL
|
SQL
|
||||||
echo " $target_db stamped at v2t0p8q9r0"
|
echo " $target_db stamped at w3u1q9r0s1"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -23,4 +23,4 @@ fi
|
|||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||||
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000}
|
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000} --workers ${WORKERS:-2} --keep-alive 75
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ fi
|
|||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||||
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000}
|
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000} --workers ${WORKERS:-2} --keep-alive 75
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ fi
|
|||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||||
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000}
|
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000} --workers ${WORKERS:-2} --keep-alive 75
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ x-app-common: &app-common
|
|||||||
- /root/rose-ash/_config/app-config.yaml:/app/config/app-config.yaml:ro
|
- /root/rose-ash/_config/app-config.yaml:/app/config/app-config.yaml:ro
|
||||||
|
|
||||||
x-app-env: &app-env
|
x-app-env: &app-env
|
||||||
DATABASE_URL: postgresql+asyncpg://postgres:change-me@db:5432/appdb
|
DATABASE_URL_ACCOUNT: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_account
|
||||||
DATABASE_URL_ACCOUNT: postgresql+asyncpg://postgres:change-me@db:5432/appdb
|
DATABASE_URL_FEDERATION: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_federation
|
||||||
DATABASE_URL_FEDERATION: postgresql+asyncpg://postgres:change-me@db:5432/appdb
|
|
||||||
ALEMBIC_DATABASE_URL: postgresql+psycopg://postgres:change-me@db:5432/appdb
|
|
||||||
SMTP_HOST: ${SMTP_HOST}
|
SMTP_HOST: ${SMTP_HOST}
|
||||||
SMTP_PORT: ${SMTP_PORT}
|
SMTP_PORT: ${SMTP_PORT}
|
||||||
MAIL_FROM: ${MAIL_FROM}
|
MAIL_FROM: ${MAIL_FROM}
|
||||||
@@ -25,7 +23,7 @@ x-app-env: &app-env
|
|||||||
GHOST_CONTENT_API_KEY: ${GHOST_CONTENT_API_KEY}
|
GHOST_CONTENT_API_KEY: ${GHOST_CONTENT_API_KEY}
|
||||||
GHOST_WEBHOOK_SECRET: ${GHOST_WEBHOOK_SECRET}
|
GHOST_WEBHOOK_SECRET: ${GHOST_WEBHOOK_SECRET}
|
||||||
GHOST_ADMIN_API_KEY: ${GHOST_ADMIN_API_KEY}
|
GHOST_ADMIN_API_KEY: ${GHOST_ADMIN_API_KEY}
|
||||||
REDIS_AUTH_URL: redis://redis:6379/15
|
REDIS_AUTH_URL: redis://redis-auth:6379/0
|
||||||
SECRET_KEY: ${SECRET_KEY}
|
SECRET_KEY: ${SECRET_KEY}
|
||||||
SUMUP_API_KEY: ${SUMUP_API_KEY}
|
SUMUP_API_KEY: ${SUMUP_API_KEY}
|
||||||
APP_URL_BLOG: https://blog.rose-ash.com
|
APP_URL_BLOG: https://blog.rose-ash.com
|
||||||
@@ -58,6 +56,8 @@ services:
|
|||||||
dockerfile: blog/Dockerfile
|
dockerfile: blog/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
|
DATABASE_URL: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_blog
|
||||||
|
ALEMBIC_DATABASE_URL: postgresql+psycopg://postgres:change-me@db:5432/db_blog
|
||||||
REDIS_URL: redis://redis:6379/0
|
REDIS_URL: redis://redis:6379/0
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
@@ -74,6 +74,7 @@ services:
|
|||||||
- /root/rose-ash/_snapshot:/app/_snapshot
|
- /root/rose-ash/_snapshot:/app/_snapshot
|
||||||
environment:
|
environment:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
|
DATABASE_URL: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_market
|
||||||
REDIS_URL: redis://redis:6379/1
|
REDIS_URL: redis://redis:6379/1
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
@@ -86,6 +87,7 @@ services:
|
|||||||
dockerfile: cart/Dockerfile
|
dockerfile: cart/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
|
DATABASE_URL: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_cart
|
||||||
REDIS_URL: redis://redis:6379/2
|
REDIS_URL: redis://redis:6379/2
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
@@ -98,6 +100,7 @@ services:
|
|||||||
dockerfile: events/Dockerfile
|
dockerfile: events/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
|
DATABASE_URL: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_events
|
||||||
REDIS_URL: redis://redis:6379/3
|
REDIS_URL: redis://redis:6379/3
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
@@ -110,6 +113,7 @@ services:
|
|||||||
dockerfile: federation/Dockerfile
|
dockerfile: federation/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
|
DATABASE_URL: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_federation
|
||||||
REDIS_URL: redis://redis:6379/4
|
REDIS_URL: redis://redis:6379/4
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
@@ -122,6 +126,7 @@ services:
|
|||||||
dockerfile: account/Dockerfile
|
dockerfile: account/Dockerfile
|
||||||
environment:
|
environment:
|
||||||
<<: *app-env
|
<<: *app-env
|
||||||
|
DATABASE_URL: postgresql+asyncpg://postgres:change-me@pgbouncer:6432/db_account
|
||||||
REDIS_URL: redis://redis:6379/5
|
REDIS_URL: redis://redis:6379/5
|
||||||
DATABASE_HOST: db
|
DATABASE_HOST: db
|
||||||
DATABASE_PORT: "5432"
|
DATABASE_PORT: "5432"
|
||||||
@@ -145,6 +150,25 @@ services:
|
|||||||
constraints:
|
constraints:
|
||||||
- node.labels.gpu != true
|
- node.labels.gpu != true
|
||||||
|
|
||||||
|
pgbouncer:
|
||||||
|
image: bitnami/pgbouncer:latest
|
||||||
|
environment:
|
||||||
|
POSTGRESQL_HOST: db
|
||||||
|
POSTGRESQL_PORT: "5432"
|
||||||
|
POSTGRESQL_USERNAME: ${POSTGRES_USER:-postgres}
|
||||||
|
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD:-change-me}
|
||||||
|
POSTGRESQL_DATABASE: "*"
|
||||||
|
PGBOUNCER_POOL_MODE: transaction
|
||||||
|
PGBOUNCER_DEFAULT_POOL_SIZE: "20"
|
||||||
|
PGBOUNCER_MAX_CLIENT_CONN: "300"
|
||||||
|
PGBOUNCER_MIN_POOL_SIZE: "5"
|
||||||
|
networks:
|
||||||
|
appnet:
|
||||||
|
deploy:
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- node.labels.gpu != true
|
||||||
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer
|
image: adminer
|
||||||
networks:
|
networks:
|
||||||
@@ -164,16 +188,32 @@ services:
|
|||||||
appnet:
|
appnet:
|
||||||
command:
|
command:
|
||||||
redis-server
|
redis-server
|
||||||
--maxmemory 256mb
|
--maxmemory 1gb
|
||||||
--maxmemory-policy allkeys-lru
|
--maxmemory-policy allkeys-lru
|
||||||
deploy:
|
deploy:
|
||||||
placement:
|
placement:
|
||||||
constraints:
|
constraints:
|
||||||
- node.labels.gpu != true
|
- node.labels.gpu != true
|
||||||
|
|
||||||
|
redis-auth:
|
||||||
|
image: redis:7-alpine
|
||||||
|
volumes:
|
||||||
|
- redis_auth_data:/data
|
||||||
|
networks:
|
||||||
|
appnet:
|
||||||
|
command:
|
||||||
|
redis-server
|
||||||
|
--maxmemory 64mb
|
||||||
|
--maxmemory-policy noeviction
|
||||||
|
deploy:
|
||||||
|
placement:
|
||||||
|
constraints:
|
||||||
|
- node.labels.gpu != true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db_data_1:
|
db_data_1:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
redis_auth_data:
|
||||||
networks:
|
networks:
|
||||||
appnet:
|
appnet:
|
||||||
driver: overlay
|
driver: overlay
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ fi
|
|||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||||
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000}
|
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000} --workers ${WORKERS:-2} --keep-alive 75
|
||||||
|
|||||||
@@ -29,4 +29,4 @@ fi
|
|||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||||
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000}
|
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000} --workers ${WORKERS:-2} --keep-alive 75
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ fi
|
|||||||
|
|
||||||
# Start the app
|
# Start the app
|
||||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||||
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000}
|
PYTHONUNBUFFERED=1 exec hypercorn "${APP_MODULE:-app:app}" --bind 0.0.0.0:${PORT:-8000} --workers ${WORKERS:-2} --keep-alive 75
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ _engine = create_async_engine(
|
|||||||
future=True,
|
future=True,
|
||||||
echo=False,
|
echo=False,
|
||||||
pool_pre_ping=True,
|
pool_pre_ping=True,
|
||||||
pool_size=5,
|
pool_size=int(os.getenv("DB_POOL_SIZE", "3")),
|
||||||
max_overflow=10,
|
max_overflow=int(os.getenv("DB_MAX_OVERFLOW", "5")),
|
||||||
|
pool_timeout=10,
|
||||||
|
pool_recycle=1800,
|
||||||
)
|
)
|
||||||
|
|
||||||
_Session = async_sessionmaker(
|
_Session = async_sessionmaker(
|
||||||
@@ -57,7 +59,8 @@ _account_engine = (
|
|||||||
else create_async_engine(
|
else create_async_engine(
|
||||||
DATABASE_URL_ACCOUNT,
|
DATABASE_URL_ACCOUNT,
|
||||||
future=True, echo=False, pool_pre_ping=True,
|
future=True, echo=False, pool_pre_ping=True,
|
||||||
pool_size=3, max_overflow=5,
|
pool_size=2, max_overflow=3,
|
||||||
|
pool_timeout=10, pool_recycle=1800,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_AccountSession = async_sessionmaker(
|
_AccountSession = async_sessionmaker(
|
||||||
@@ -71,7 +74,8 @@ _federation_engine = (
|
|||||||
else create_async_engine(
|
else create_async_engine(
|
||||||
DATABASE_URL_FEDERATION,
|
DATABASE_URL_FEDERATION,
|
||||||
future=True, echo=False, pool_pre_ping=True,
|
future=True, echo=False, pool_pre_ping=True,
|
||||||
pool_size=3, max_overflow=5,
|
pool_size=2, max_overflow=3,
|
||||||
|
pool_timeout=10, pool_recycle=1800,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_FederationSession = async_sessionmaker(
|
_FederationSession = async_sessionmaker(
|
||||||
|
|||||||
Reference in New Issue
Block a user