Commit 7fd76787 authored by Vũ Hoàng Anh's avatar Vũ Hoàng Anh

perf: multi-stage Docker builds, .dockerignore, singleton StarRocks connection

parent 7b429387
Pipeline #3378 failed with stage
# ═══ Git & IDE ═══
.git
.gitignore
.vscode
.idea
*.swp
*.swo
# ═══ Python cache ═══
__pycache__ __pycache__
*.pyc *.pyc
.env *.pyo
*.egg-info
.pytest_cache
.mypy_cache
.ruff_cache
# ═══ Virtual environments ═══
.venv .venv
venv venv
.git env
.gitignore
# ═══ Docker ═══
Dockerfile*
docker-compose*
.dockerignore .dockerignore
logs
data # ═══ Dev & test files ═══
tests/
*.test.py
setup_readonly_user.py
# ═══ Docs & artifacts ═══
*.md
LICENSE
# ═══ OS files ═══
.DS_Store
Thumbs.db
# ═══ Logs ═══
*.log
logs/
# ═══ Local env backup ═══
.env.local
.env.backup
.env.example
# ============================================================ # ============================================================
# DOCKERFILE.DEV - Local Development (Hot Reload + Cache) # DOCKERFILE.DEV — Multi-stage Dev Build (Hot Reload)
# ============================================================ # ============================================================
# Sử dụng Python 3.11 Slim để tối ưu dung lượng # Stage 1: Builder — install dependencies
# Stage 2: Runtime — dev server with reload
# ============================================================
# ──── Stage 1: Builder ────
FROM python:3.11-slim AS builder
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt \
&& pip install --no-cache-dir --user watchdog[watchmedo]
# ──── Stage 2: Runtime ────
FROM python:3.11-slim FROM python:3.11-slim
# Thiết lập thư mục làm việc
WORKDIR /app WORKDIR /app
# Thiết lập biến môi trường RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get purge -y --auto-remove
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONDONTWRITEBYTECODE=1
ENV ENV=development ENV ENV=development
# Copy requirements.txt trước để tận dụng Docker cache # Copy installed packages from builder
COPY requirements.txt . COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# Cài đặt thư viện Python (Docker layer cache)
RUN pip install -r requirements.txt && pip install watchdog[watchmedo]
# Copy toàn bộ source code vào image # Source code mounted via volume in docker-compose (not COPY)
COPY . . COPY . .
# Expose port 5000
EXPOSE 5000 EXPOSE 5000
# Health check (optional)
HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=2 \ HEALTHCHECK --interval=10s --timeout=5s --start-period=5s --retries=2 \
CMD python -c "import requests; requests.get('http://localhost:5000/docs')" || exit 1 CMD curl -f http://localhost:5000/docs || exit 1
CMD ["gunicorn", "--workers", "1", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:5000", "--timeout", "120", "--reload", "server:app"] CMD ["gunicorn", "--workers", "1", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:5000", "--timeout", "120", "--reload", "server:app"]
# ============================================================
# DOCKERFILE.PROD — Multi-stage Production Build
# ============================================================
# Stage 1: Build — install dependencies in isolated layer
# Stage 2: Runtime — minimal image with only what's needed
# ============================================================
FROM python:3.11-slim # ──── Stage 1: Builder ────
FROM python:3.11-slim AS builder
WORKDIR /build
# Install only build-time system deps (if any wheels need compiling)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
# Install to user site-packages (no root pollution)
RUN pip install --no-cache-dir --user -r requirements.txt
# ──── Stage 2: Runtime ────
FROM python:3.11-slim
WORKDIR /app WORKDIR /app
# Minimal runtime deps only
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get purge -y --auto-remove
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1 ENV PYTHONDONTWRITEBYTECODE=1
ENV ENV=development ENV ENV=production
COPY requirements.txt . # Copy only installed packages from builder (no pip cache, no gcc)
RUN pip install -r requirements.txt COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH
# Copy application source
COPY . . COPY . .
EXPOSE 5000 EXPOSE 5000
...@@ -19,4 +48,13 @@ EXPOSE 5000 ...@@ -19,4 +48,13 @@ EXPOSE 5000
ENV WORKERS=1 ENV WORKERS=1
ENV TIMEOUT=60 ENV TIMEOUT=60
CMD gunicorn server:app --workers $WORKERS --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:5000 --timeout $TIMEOUT HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:5000/docs || exit 1
CMD gunicorn server:app \
--workers $WORKERS \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:5000 \
--timeout $TIMEOUT \
--access-logfile - \
--error-logfile -
...@@ -9,7 +9,7 @@ from typing import Optional ...@@ -9,7 +9,7 @@ from typing import Optional
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from common.starrocks_connection import StarRocksConnection from common.starrocks_connection import get_db_connection
from agent.tools.product_mapping import resolve_color, resolve_product_name, SYNONYM_TO_DB from agent.tools.product_mapping import resolve_color, resolve_product_name, SYNONYM_TO_DB
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -21,7 +21,7 @@ TABLE_NAME = "shared_source.magento_product_dimension_with_text_embedding" ...@@ -21,7 +21,7 @@ TABLE_NAME = "shared_source.magento_product_dimension_with_text_embedding"
@router.get("/api/products/overview", summary="Product catalog KPI overview") @router.get("/api/products/overview", summary="Product catalog KPI overview")
async def products_overview(): async def products_overview():
"""Return aggregate KPIs for the product catalog.""" """Return aggregate KPIs for the product catalog."""
db = StarRocksConnection() db = get_db_connection()
try: try:
rows = await db.execute_query_async(f""" rows = await db.execute_query_async(f"""
SELECT SELECT
...@@ -104,7 +104,7 @@ async def products_list( ...@@ -104,7 +104,7 @@ async def products_list(
offset: int = Query(0, ge=0), offset: int = Query(0, ge=0),
): ):
"""Return paginated product listing grouped by internal_ref_code.""" """Return paginated product listing grouped by internal_ref_code."""
db = StarRocksConnection() db = get_db_connection()
# Build WHERE clauses # Build WHERE clauses
clauses = [] clauses = []
...@@ -234,7 +234,7 @@ async def products_colors( ...@@ -234,7 +234,7 @@ async def products_colors(
code: str = Query(..., description="internal_ref_code"), code: str = Query(..., description="internal_ref_code"),
): ):
"""Return all color variants for a given internal_ref_code.""" """Return all color variants for a given internal_ref_code."""
db = StarRocksConnection() db = get_db_connection()
try: try:
variants = await db.execute_query_async( variants = await db.execute_query_async(
f""" f"""
...@@ -266,7 +266,7 @@ async def products_colors( ...@@ -266,7 +266,7 @@ async def products_colors(
@router.get("/api/products/filters", summary="Available filter options") @router.get("/api/products/filters", summary="Available filter options")
async def products_filters(): async def products_filters():
"""Return all available filter values for color, product_line, age, season.""" """Return all available filter values for color, product_line, age, season."""
db = StarRocksConnection() db = get_db_connection()
try: try:
colors = await db.execute_query_async(f""" colors = await db.execute_query_async(f"""
SELECT master_color, COUNT(DISTINCT internal_ref_code) AS cnt SELECT master_color, COUNT(DISTINCT internal_ref_code) AS cnt
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment