yangdx commited on
Commit
29744d8
Β·
1 Parent(s): 8b3b8f9

Add Gunicorn support for production deployment of LightRAG server

Browse files

- Move gunicorn startup an config files to api package
- Create new CLI entry point for Gunicorn mode

gunicorn_config.py β†’ lightrag/api/gunicorn_config.py RENAMED
File without changes
lightrag/api/run_with_gunicorn.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """
3
+ Start LightRAG server with Gunicorn
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import signal
9
+ import pipmaster as pm
10
+ from lightrag.api.utils_api import parse_args, display_splash_screen
11
+ from lightrag.kg.shared_storage import initialize_share_data, finalize_share_data
12
+
13
+
14
+ def check_and_install_dependencies():
15
+ """Check and install required dependencies"""
16
+ required_packages = [
17
+ "gunicorn",
18
+ "tiktoken",
19
+ "psutil",
20
+ # Add other required packages here
21
+ ]
22
+
23
+ for package in required_packages:
24
+ if not pm.is_installed(package):
25
+ print(f"Installing {package}...")
26
+ pm.install(package)
27
+ print(f"{package} installed successfully")
28
+
29
+
30
+ # Signal handler for graceful shutdown
31
+ def signal_handler(sig, frame):
32
+ print("\n\n" + "=" * 80)
33
+ print("RECEIVED TERMINATION SIGNAL")
34
+ print(f"Process ID: {os.getpid()}")
35
+ print("=" * 80 + "\n")
36
+
37
+ # Release shared resources
38
+ finalize_share_data()
39
+
40
+ # Exit with success status
41
+ sys.exit(0)
42
+
43
+
44
+ def main():
45
+ # Check and install dependencies
46
+ check_and_install_dependencies()
47
+
48
+ # Register signal handlers for graceful shutdown
49
+ signal.signal(signal.SIGINT, signal_handler) # Ctrl+C
50
+ signal.signal(signal.SIGTERM, signal_handler) # kill command
51
+
52
+ # Parse all arguments using parse_args
53
+ args = parse_args(is_uvicorn_mode=False)
54
+
55
+ # Display startup information
56
+ display_splash_screen(args)
57
+
58
+ print("πŸš€ Starting LightRAG with Gunicorn")
59
+ print(f"πŸ”„ Worker management: Gunicorn (workers={args.workers})")
60
+ print("πŸ” Preloading app: Enabled")
61
+ print("πŸ“ Note: Using Gunicorn's preload feature for shared data initialization")
62
+ print("\n\n" + "=" * 80)
63
+ print("MAIN PROCESS INITIALIZATION")
64
+ print(f"Process ID: {os.getpid()}")
65
+ print(f"Workers setting: {args.workers}")
66
+ print("=" * 80 + "\n")
67
+
68
+ # Import Gunicorn's StandaloneApplication
69
+ from gunicorn.app.base import BaseApplication
70
+
71
+ # Define a custom application class that loads our config
72
+ class GunicornApp(BaseApplication):
73
+ def __init__(self, app, options=None):
74
+ self.options = options or {}
75
+ self.application = app
76
+ super().__init__()
77
+
78
+ def load_config(self):
79
+ # Define valid Gunicorn configuration options
80
+ valid_options = {
81
+ "bind",
82
+ "workers",
83
+ "worker_class",
84
+ "timeout",
85
+ "keepalive",
86
+ "preload_app",
87
+ "errorlog",
88
+ "accesslog",
89
+ "loglevel",
90
+ "certfile",
91
+ "keyfile",
92
+ "limit_request_line",
93
+ "limit_request_fields",
94
+ "limit_request_field_size",
95
+ "graceful_timeout",
96
+ "max_requests",
97
+ "max_requests_jitter",
98
+ }
99
+
100
+ # Special hooks that need to be set separately
101
+ special_hooks = {
102
+ "on_starting",
103
+ "on_reload",
104
+ "on_exit",
105
+ "pre_fork",
106
+ "post_fork",
107
+ "pre_exec",
108
+ "pre_request",
109
+ "post_request",
110
+ "worker_init",
111
+ "worker_exit",
112
+ "nworkers_changed",
113
+ "child_exit",
114
+ }
115
+
116
+ # Import and configure the gunicorn_config module
117
+ from lightrag.api import gunicorn_config
118
+
119
+ # Set configuration variables in gunicorn_config, prioritizing command line arguments
120
+ gunicorn_config.workers = (
121
+ args.workers if args.workers else int(os.getenv("WORKERS", 1))
122
+ )
123
+
124
+ # Bind configuration prioritizes command line arguments
125
+ host = args.host if args.host != "0.0.0.0" else os.getenv("HOST", "0.0.0.0")
126
+ port = args.port if args.port != 9621 else int(os.getenv("PORT", 9621))
127
+ gunicorn_config.bind = f"{host}:{port}"
128
+
129
+ # Log level configuration prioritizes command line arguments
130
+ gunicorn_config.loglevel = (
131
+ args.log_level.lower()
132
+ if args.log_level
133
+ else os.getenv("LOG_LEVEL", "info")
134
+ )
135
+
136
+ # Timeout configuration prioritizes command line arguments
137
+ gunicorn_config.timeout = (
138
+ args.timeout if args.timeout else int(os.getenv("TIMEOUT", 150))
139
+ )
140
+
141
+ # Keepalive configuration
142
+ gunicorn_config.keepalive = int(os.getenv("KEEPALIVE", 5))
143
+
144
+ # SSL configuration prioritizes command line arguments
145
+ if args.ssl or os.getenv("SSL", "").lower() in (
146
+ "true",
147
+ "1",
148
+ "yes",
149
+ "t",
150
+ "on",
151
+ ):
152
+ gunicorn_config.certfile = (
153
+ args.ssl_certfile
154
+ if args.ssl_certfile
155
+ else os.getenv("SSL_CERTFILE")
156
+ )
157
+ gunicorn_config.keyfile = (
158
+ args.ssl_keyfile if args.ssl_keyfile else os.getenv("SSL_KEYFILE")
159
+ )
160
+
161
+ # Set configuration options from the module
162
+ for key in dir(gunicorn_config):
163
+ if key in valid_options:
164
+ value = getattr(gunicorn_config, key)
165
+ # Skip functions like on_starting and None values
166
+ if not callable(value) and value is not None:
167
+ self.cfg.set(key, value)
168
+ # Set special hooks
169
+ elif key in special_hooks:
170
+ value = getattr(gunicorn_config, key)
171
+ if callable(value):
172
+ self.cfg.set(key, value)
173
+
174
+ if hasattr(gunicorn_config, "logconfig_dict"):
175
+ self.cfg.set(
176
+ "logconfig_dict", getattr(gunicorn_config, "logconfig_dict")
177
+ )
178
+
179
+ def load(self):
180
+ # Import the application
181
+ from lightrag.api.lightrag_server import get_application
182
+
183
+ return get_application(args)
184
+
185
+ # Create the application
186
+ app = GunicornApp("")
187
+
188
+ # Force workers to be an integer and greater than 1 for multi-process mode
189
+ workers_count = int(args.workers)
190
+ if workers_count > 1:
191
+ # Set a flag to indicate we're in the main process
192
+ os.environ["LIGHTRAG_MAIN_PROCESS"] = "1"
193
+ initialize_share_data(workers_count)
194
+ else:
195
+ initialize_share_data(1)
196
+
197
+ # Run the application
198
+ print("\nStarting Gunicorn with direct Python API...")
199
+ app.run()
200
+
201
+
202
+ if __name__ == "__main__":
203
+ main()
setup.py CHANGED
@@ -112,6 +112,7 @@ setuptools.setup(
112
  entry_points={
113
  "console_scripts": [
114
  "lightrag-server=lightrag.api.lightrag_server:main [api]",
 
115
  "lightrag-viewer=lightrag.tools.lightrag_visualizer.graph_visualizer:main [tools]",
116
  ],
117
  },
 
112
  entry_points={
113
  "console_scripts": [
114
  "lightrag-server=lightrag.api.lightrag_server:main [api]",
115
+ "lightrag-gunicorn=lightrag.api.run_with_gunicorn:main [api]",
116
  "lightrag-viewer=lightrag.tools.lightrag_visualizer.graph_visualizer:main [tools]",
117
  ],
118
  },