fix: load PaddleOCR model in background thread
Some checks failed
Deploy to Coolify / deploy (push) Has been cancelled

The import and model loading can take minutes and was blocking
the startup event, causing health checks to timeout. Now loads
in a background thread — health endpoint returns 200 immediately
with status 'loading' until model is ready.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-13 10:21:59 +01:00
parent b36712247b
commit 5ee3cc0104

View File

@@ -3,6 +3,7 @@
import io import io
import logging import logging
import os import os
import threading
import numpy as np import numpy as np
from fastapi import FastAPI, File, Header, HTTPException, UploadFile from fastapi import FastAPI, File, Header, HTTPException, UploadFile
@@ -15,17 +16,18 @@ app = FastAPI(title="PaddleOCR Service")
_engine = None _engine = None
_ready = False _ready = False
_loading = False
API_KEY = os.environ.get("PADDLEOCR_API_KEY", "") API_KEY = os.environ.get("PADDLEOCR_API_KEY", "")
def get_engine(): def _load_model():
global _engine """Load PaddleOCR model in background thread."""
if _engine is None: global _engine, _ready
try:
logger.info("Importing paddleocr...") logger.info("Importing paddleocr...")
from paddleocr import PaddleOCR from paddleocr import PaddleOCR
logger.info("Import done. Loading PaddleOCR model...") logger.info("Import done. Loading PaddleOCR model...")
# PaddleOCR >= 3.x: lang="en" + PP-OCRv5; older: lang="latin"
try: try:
_engine = PaddleOCR( _engine = PaddleOCR(
lang="en", lang="en",
@@ -42,27 +44,29 @@ def get_engine():
show_log=False, show_log=False,
) )
logger.info("Using PP-OCRv4 fallback (latin)") logger.info("Using PP-OCRv4 fallback (latin)")
logger.info("PaddleOCR model loaded successfully") _ready = True
return _engine logger.info("PaddleOCR model loaded successfully — ready to serve")
except Exception as e:
logger.error(f"Failed to load PaddleOCR model: {e}")
@app.on_event("startup") @app.on_event("startup")
def startup_load_model(): def startup_load_model():
"""Pre-load model at startup so health check passes.""" """Start model loading in background so health check passes immediately."""
global _ready global _loading
try: _loading = True
get_engine() thread = threading.Thread(target=_load_model, daemon=True)
_ready = True thread.start()
logger.info("PaddleOCR ready to serve requests") logger.info("Model loading started in background thread")
except Exception as e:
logger.error(f"Failed to load PaddleOCR model: {e}")
@app.get("/health") @app.get("/health")
def health(): def health():
if _ready: if _ready:
return {"status": "ok", "model": "PP-OCRv5-latin"} return {"status": "ok", "model": "PP-OCRv5-latin"}
if _loading:
return {"status": "loading"} return {"status": "loading"}
return {"status": "error"}
@app.post("/ocr") @app.post("/ocr")
@@ -80,8 +84,7 @@ async def ocr(
img = Image.open(io.BytesIO(img_bytes)).convert("RGB") img = Image.open(io.BytesIO(img_bytes)).convert("RGB")
img_np = np.array(img) img_np = np.array(img)
engine = get_engine() result = _engine.ocr(img_np)
result = engine.ocr(img_np)
words = [] words = []
for line in result[0] or []: for line in result[0] or []: