Skip to content

Commit f4d943c

Browse files
author
hzy@singhand.com
committed
Feat: Support production deployment mode by using gunicorn gevent.
1 parent 0770865 commit f4d943c

File tree

7 files changed

+197
-44
lines changed

7 files changed

+197
-44
lines changed

api/db/db_models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ class Meta:
417417

418418

419419
@DB.connection_context()
420+
@DB.lock("init_database_tables", 60)
420421
def init_database_tables(alter_fields=[]):
421422
members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
422423
table_objs = []
@@ -428,7 +429,7 @@ def init_database_tables(alter_fields=[]):
428429
if not obj.table_exists():
429430
logging.debug(f"start create table {obj.__name__}")
430431
try:
431-
obj.create_table()
432+
obj.create_table(safe=True)
432433
logging.debug(f"create table success: {obj.__name__}")
433434
except Exception as e:
434435
logging.exception(e)

api/wsgi.py

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,26 @@
1414
# limitations under the License.
1515
#
1616

17+
# Gevent monkey patching - must be done before importing other modules
18+
import os
19+
if os.environ.get('GUNICORN_WORKER_CLASS') == 'gevent':
20+
from gevent import monkey
21+
monkey.patch_all()
22+
23+
# Import gevent for greenlet spawning
24+
import gevent
25+
from gevent import spawn
26+
USE_GEVENT = True
27+
else:
28+
USE_GEVENT = False
29+
1730
from api.utils.log_utils import initRootLogger
1831
from plugin import GlobalPluginManager
1932

2033
# Initialize logging first
2134
initRootLogger("ragflow_server")
2235

2336
import logging
24-
import os
2537
import signal
2638
import threading
2739
import uuid
@@ -40,9 +52,15 @@
4052
from rag.settings import print_rag_settings
4153
from rag.utils.redis_conn import RedisDistributedLock
4254

43-
# Global stop event and executor for background tasks
44-
stop_event = threading.Event()
45-
background_executor = None
55+
# Global variables for background tasks
56+
if USE_GEVENT:
57+
stop_event = None
58+
background_executor = None
59+
background_greenlet = None
60+
else:
61+
stop_event = threading.Event()
62+
background_executor = None
63+
background_greenlet = None
4664

4765
RAGFLOW_DEBUGPY_LISTEN = int(os.environ.get('RAGFLOW_DEBUGPY_LISTEN', "0"))
4866

@@ -52,24 +70,50 @@ def update_progress():
5270
lock_value = str(uuid.uuid4())
5371
redis_lock = RedisDistributedLock("update_progress", lock_value=lock_value, timeout=60)
5472
logging.info(f"update_progress lock_value: {lock_value}")
55-
while not stop_event.is_set():
56-
try:
57-
if redis_lock.acquire():
58-
DocumentService.update_progress()
73+
74+
if USE_GEVENT:
75+
# Use gevent sleep and loop for greenlet compatibility
76+
while True:
77+
try:
78+
if redis_lock.acquire():
79+
DocumentService.update_progress()
80+
redis_lock.release()
81+
gevent.sleep(6) # Use gevent.sleep instead of stop_event.wait
82+
except Exception:
83+
logging.exception("update_progress exception")
84+
redis_lock.release()
85+
break
86+
else:
87+
# Traditional threading approach
88+
while not stop_event.is_set():
89+
try:
90+
if redis_lock.acquire():
91+
DocumentService.update_progress()
92+
redis_lock.release()
93+
stop_event.wait(6)
94+
except Exception:
95+
logging.exception("update_progress exception")
96+
finally:
5997
redis_lock.release()
60-
stop_event.wait(6)
61-
except Exception:
62-
logging.exception("update_progress exception")
63-
finally:
64-
redis_lock.release()
6598

6699

67100
def signal_handler(sig, frame):
68101
"""Handle shutdown signals gracefully"""
69-
logging.info("Received interrupt signal, shutting down...")
70-
stop_event.set()
71-
if background_executor:
72-
background_executor.shutdown(wait=False)
102+
logging.info("Received shutdown signal, stopping background tasks...")
103+
104+
if USE_GEVENT:
105+
# Kill the background greenlet
106+
global background_greenlet
107+
if background_greenlet and not background_greenlet.dead:
108+
background_greenlet.kill()
109+
else:
110+
# Traditional threading approach
111+
stop_event.set()
112+
if hasattr(background_executor, 'shutdown'):
113+
background_executor.shutdown(wait=False)
114+
115+
logging.info("Background tasks stopped")
116+
exit(0)
73117

74118

75119
def initialize_ragflow():
@@ -113,8 +157,16 @@ def initialize_ragflow():
113157
signal.signal(signal.SIGTERM, signal_handler)
114158

115159
# Start background progress update task
116-
background_executor = ThreadPoolExecutor(max_workers=1)
117-
background_executor.submit(update_progress)
160+
if USE_GEVENT:
161+
# Use gevent spawn for greenlet-based execution
162+
global background_greenlet
163+
background_greenlet = spawn(update_progress)
164+
logging.info("Started document progress update task in gevent mode")
165+
else:
166+
# Use thread pool for traditional threading
167+
background_executor = ThreadPoolExecutor(max_workers=1)
168+
background_executor.submit(update_progress)
169+
logging.info("Started document progress update task in threading mode")
118170

119171
logging.info("RAGFlow WSGI application initialized successfully in production mode")
120172

conf/gunicorn.conf.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
# Gunicorn configuration file for RAGFlow production deployment
22
import multiprocessing
33
import os
4+
from api import settings
5+
from rag.utils.infinity_conn import InfinityConnection
6+
from graphrag import search as kg_search
7+
from rag.nlp import search
48

59
# Server socket
610
bind = f"{os.environ.get('RAGFLOW_HOST_IP', '0.0.0.0')}:{os.environ.get('RAGFLOW_HOST_PORT', '9380')}"
711
backlog = 2048
812

913
# Worker processes
1014
workers = int(os.environ.get('GUNICORN_WORKERS', min(multiprocessing.cpu_count() * 2 + 1, 8)))
11-
worker_class = 'sync'
15+
worker_class = 'gevent'
16+
17+
# Gevent-specific settings
1218
worker_connections = 1000
13-
timeout = 120
14-
keepalive = 2
15-
max_requests = 1000
16-
max_requests_jitter = 100
19+
timeout = 300
20+
keepalive = 10
21+
max_requests = 2000
22+
max_requests_jitter = 200
1723

18-
# Restart workers after this many requests, to help prevent memory leaks
19-
preload_app = True
24+
preload_app = False
2025

2126
# Logging
2227
accesslog = '-'
@@ -64,6 +69,10 @@ def pre_fork(server, worker):
6469
def post_fork(server, worker):
6570
"""Called just after a worker has been forked."""
6671
server.log.info("RAGFlow worker spawned (pid: %s)", worker.pid)
72+
if os.environ.get("DOC_ENGINE") == "infinity":
73+
settings.docStoreConn = InfinityConnection()
74+
settings.retrievaler = search.Dealer(settings.docStoreConn)
75+
settings.kg_retrievaler = kg_search.KGSearch(settings.docStoreConn)
6776

6877
def worker_abort(worker):
6978
"""Called when a worker received the SIGABRT signal."""

docker/.env

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,6 @@ REGISTER_ENABLED=1
181181
# COMPOSE_PROFILES=infinity,sandbox
182182
# - For OpenSearch:
183183
# COMPOSE_PROFILES=opensearch,sandbox
184+
185+
# Gunicorn setting
186+
GUNICORN_WORKERS=4

docker/entrypoint.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,12 @@ if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then
190190
RAGFLOW_HOST=${RAGFLOW_HOST_IP:-0.0.0.0}
191191
RAGFLOW_PORT=${RAGFLOW_HOST_PORT:-9380}
192192
GUNICORN_WORKERS=${GUNICORN_WORKERS:-4}
193-
GUNICORN_TIMEOUT=${GUNICORN_WORKERS:120}
193+
GUNICORN_TIMEOUT=${GUNICORN_TIMEOUT:-120}
194194

195-
echo "Gunicorn config: Workers=${GUNICORN_WORKERS}, Host=${RAGFLOW_HOST}, Port=${RAGFLOW_PORT}"
195+
# Set environment variable for gevent worker class
196+
export GUNICORN_WORKER_CLASS=gevent
197+
198+
echo "Gunicorn config: Workers=${GUNICORN_WORKERS}, Host=${RAGFLOW_HOST}, Port=${RAGFLOW_PORT}, Worker Class=gevent"
196199

197200
# Check if gunicorn config file exists and use it, otherwise use command line options
198201
if [[ -f "/ragflow/conf/gunicorn.conf.py" ]]; then
@@ -202,7 +205,7 @@ if [[ "${ENABLE_WEBSERVER}" -eq 1 ]]; then
202205
echo "Using Gunicorn command line configuration..."
203206
# Start gunicorn with our WSGI application
204207
exec gunicorn --workers ${GUNICORN_WORKERS} \
205-
--worker-class sync \
208+
--worker-class gevent \
206209
--worker-connections 1000 \
207210
--max-requests 1000 \
208211
--max-requests-jitter 100 \

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ dependencies = [
127127
"opensearch-py==2.7.1",
128128
"pluginlib==0.9.4",
129129
"gunicorn>=21.2.0,<22.0.0",
130+
"gevent>=23.9.0,<24.0.0",
130131
]
131132

132133
[project.optional-dependencies]

0 commit comments

Comments
 (0)