Enable a single LightRAG server instance to serve multiple isolated workspaces via HTTP header-based routing. This allows multi-tenant SaaS deployments where each tenant's data is completely isolated. Key features: - Header-based workspace routing (LIGHTRAG-WORKSPACE, X-Workspace-ID fallback) - Process-local pool of LightRAG instances with LRU eviction - FastAPI dependency (get_rag) for workspace resolution per request - Full backward compatibility - existing deployments work unchanged - Strict multi-tenant mode option (LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=false) - Configurable pool size (LIGHTRAG_MAX_WORKSPACES_IN_POOL) - Graceful shutdown with workspace finalization Configuration: - LIGHTRAG_DEFAULT_WORKSPACE: Default workspace (falls back to WORKSPACE) - LIGHTRAG_ALLOW_DEFAULT_WORKSPACE: Require explicit header when false - LIGHTRAG_MAX_WORKSPACES_IN_POOL: Max concurrent workspace instances (default: 50) Files: - New: lightrag/api/workspace_manager.py (core multi-workspace module) - New: tests/test_multi_workspace_server.py (17 unit tests) - New: render.yaml (Render deployment blueprint) - Modified: All route files to use get_rag dependency - Updated: README.md, env.example with documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
236 lines
6.9 KiB
Markdown
236 lines
6.9 KiB
Markdown
# Quickstart: Multi-Workspace LightRAG Server
|
|
|
|
**Date**: 2025-12-01
|
|
**Feature**: 001-multi-workspace-server
|
|
|
|
## Overview
|
|
|
|
This guide shows how to deploy LightRAG Server with multi-workspace support, enabling a single server instance to serve multiple isolated tenants.
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
Add these new environment variables to your deployment:
|
|
|
|
```bash
|
|
# Multi-workspace configuration
|
|
LIGHTRAG_DEFAULT_WORKSPACE=default # Workspace for requests without header
|
|
LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=true # Allow requests without workspace header
|
|
LIGHTRAG_MAX_WORKSPACES_IN_POOL=50 # Max concurrent workspace instances
|
|
|
|
# Existing configuration (unchanged)
|
|
WORKSPACE=default # Backward compatible, used if DEFAULT_WORKSPACE not set
|
|
WORKING_DIR=/data/rag_storage # Base directory for all workspace data
|
|
INPUT_DIR=/data/inputs # Base directory for workspace input files
|
|
```
|
|
|
|
### Configuration Modes
|
|
|
|
#### Mode 1: Backward Compatible (Default)
|
|
|
|
No changes needed. Existing deployments work unchanged.
|
|
|
|
```bash
|
|
# .env file
|
|
WORKSPACE=my_workspace
|
|
```
|
|
|
|
All requests use `my_workspace` regardless of headers.
|
|
|
|
#### Mode 2: Multi-Workspace with Default
|
|
|
|
Allow multiple workspaces, with a fallback for headerless requests.
|
|
|
|
```bash
|
|
# .env file
|
|
LIGHTRAG_DEFAULT_WORKSPACE=default
|
|
LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=true
|
|
LIGHTRAG_MAX_WORKSPACES_IN_POOL=50
|
|
```
|
|
|
|
- Requests with `LIGHTRAG-WORKSPACE` header → use specified workspace
|
|
- Requests without header → use `default` workspace
|
|
|
|
#### Mode 3: Strict Multi-Tenant
|
|
|
|
Require workspace header on all requests. Prevents accidental data leakage.
|
|
|
|
```bash
|
|
# .env file
|
|
LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=false
|
|
LIGHTRAG_MAX_WORKSPACES_IN_POOL=100
|
|
```
|
|
|
|
- Requests with `LIGHTRAG-WORKSPACE` header → use specified workspace
|
|
- Requests without header → return `400 Bad Request`
|
|
|
|
## Usage Examples
|
|
|
|
### Starting the Server
|
|
|
|
```bash
|
|
# Standard startup (works the same as before)
|
|
lightrag-server --host 0.0.0.0 --port 9621
|
|
|
|
# Or with environment variables
|
|
export LIGHTRAG_DEFAULT_WORKSPACE=default
|
|
export LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=true
|
|
lightrag-server
|
|
```
|
|
|
|
### Making Requests
|
|
|
|
#### Single-Workspace (No Header)
|
|
|
|
```bash
|
|
# Uses default workspace
|
|
curl -X POST http://localhost:9621/query \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-d '{"query": "What is LightRAG?"}'
|
|
```
|
|
|
|
#### Multi-Workspace (With Header)
|
|
|
|
```bash
|
|
# Ingest document to tenant-a
|
|
curl -X POST http://localhost:9621/documents/text \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "LIGHTRAG-WORKSPACE: tenant-a" \
|
|
-d '{"text": "Tenant A confidential document about AI."}'
|
|
|
|
# Query from tenant-a (finds the document)
|
|
curl -X POST http://localhost:9621/query \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "LIGHTRAG-WORKSPACE: tenant-a" \
|
|
-d '{"query": "What is this workspace about?"}'
|
|
|
|
# Query from tenant-b (does NOT find tenant-a's document)
|
|
curl -X POST http://localhost:9621/query \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "LIGHTRAG-WORKSPACE: tenant-b" \
|
|
-d '{"query": "What is this workspace about?"}'
|
|
```
|
|
|
|
### Python Client Example
|
|
|
|
```python
|
|
import httpx
|
|
|
|
class LightRAGClient:
|
|
def __init__(self, base_url: str, api_key: str, workspace: str | None = None):
|
|
self.base_url = base_url
|
|
self.headers = {
|
|
"Authorization": f"Bearer {api_key}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
if workspace:
|
|
self.headers["LIGHTRAG-WORKSPACE"] = workspace
|
|
|
|
async def query(self, query: str) -> dict:
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.post(
|
|
f"{self.base_url}/query",
|
|
headers=self.headers,
|
|
json={"query": query}
|
|
)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
# Usage
|
|
tenant_a_client = LightRAGClient(
|
|
"http://localhost:9621",
|
|
api_key="your-api-key",
|
|
workspace="tenant-a"
|
|
)
|
|
tenant_b_client = LightRAGClient(
|
|
"http://localhost:9621",
|
|
api_key="your-api-key",
|
|
workspace="tenant-b"
|
|
)
|
|
|
|
# Each client accesses only its own workspace
|
|
result_a = await tenant_a_client.query("What documents do I have?")
|
|
result_b = await tenant_b_client.query("What documents do I have?")
|
|
```
|
|
|
|
## Data Isolation
|
|
|
|
Each workspace has completely isolated:
|
|
|
|
- **Documents**: Files ingested in one workspace are invisible to others
|
|
- **Embeddings**: Vector indices are workspace-scoped
|
|
- **Knowledge Graph**: Entities and relationships are workspace-specific
|
|
- **Query Results**: Queries only return data from the specified workspace
|
|
|
|
### Directory Structure
|
|
|
|
```
|
|
/data/rag_storage/
|
|
├── tenant-a/ # Workspace: tenant-a
|
|
│ ├── kv_store_*.json
|
|
│ ├── vdb_*.json
|
|
│ └── graph_*.json
|
|
├── tenant-b/ # Workspace: tenant-b
|
|
│ ├── kv_store_*.json
|
|
│ ├── vdb_*.json
|
|
│ └── graph_*.json
|
|
└── default/ # Default workspace
|
|
└── ...
|
|
|
|
/data/inputs/
|
|
├── tenant-a/ # Input files for tenant-a
|
|
├── tenant-b/ # Input files for tenant-b
|
|
└── default/ # Input files for default workspace
|
|
```
|
|
|
|
## Memory Management
|
|
|
|
The workspace pool uses LRU (Least Recently Used) eviction:
|
|
|
|
- First request to a workspace initializes its LightRAG instance
|
|
- Instances stay loaded for fast subsequent requests
|
|
- When pool reaches `LIGHTRAG_MAX_WORKSPACES_IN_POOL`, least recently used workspace is evicted
|
|
- Evicted workspaces are re-initialized on next request (data persists in storage)
|
|
|
|
### Tuning Pool Size
|
|
|
|
| Deployment Size | Recommended Pool Size | Notes |
|
|
|-----------------|----------------------|-------|
|
|
| Development | 5-10 | Minimal memory usage |
|
|
| Small SaaS | 20-50 | Handles typical multi-tenant load |
|
|
| Large SaaS | 100+ | Depends on available memory |
|
|
|
|
**Memory Estimate**: Each workspace instance uses approximately 50-200MB depending on LLM/embedding bindings and cache settings.
|
|
|
|
## Troubleshooting
|
|
|
|
### "Missing LIGHTRAG-WORKSPACE header"
|
|
|
|
**Cause**: `LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=false` and no header provided
|
|
|
|
**Solution**: Either:
|
|
- Add `LIGHTRAG-WORKSPACE` header to all requests
|
|
- Set `LIGHTRAG_ALLOW_DEFAULT_WORKSPACE=true`
|
|
|
|
### "Invalid workspace identifier"
|
|
|
|
**Cause**: Workspace ID contains invalid characters
|
|
|
|
**Solution**: Use only alphanumeric characters, hyphens, and underscores. Must start with alphanumeric, max 64 characters.
|
|
|
|
### "Failed to initialize workspace"
|
|
|
|
**Cause**: Storage backend unavailable or misconfigured
|
|
|
|
**Solution**: Check storage backend connectivity (Postgres, Neo4j, etc.) and verify configuration.
|
|
|
|
### Slow First Request to New Workspace
|
|
|
|
**Expected Behavior**: First request to a workspace initializes storage connections.
|
|
|
|
**Mitigation**: Pre-warm frequently used workspaces at startup (implementation-specific).
|