-
Notifications
You must be signed in to change notification settings - Fork 53
🐛 Fix DB connection pool getting exhausted #862
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
c85c725
02641db
17e6fd6
4ad87a5
1a0942c
20bd53a
f20601b
76df1f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| name: MCP Server Stress Test | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - 'kai_mcp_solution_server/**' | ||
| - '.github/workflows/stress-test-mcp-server.yml' | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'kai_mcp_solution_server/**' | ||
| - '.github/workflows/stress-test-mcp-server.yml' | ||
| workflow_dispatch: | ||
| inputs: | ||
| num_clients: | ||
| description: 'Number of concurrent clients to test' | ||
| required: false | ||
| default: '100' | ||
|
|
||
| jobs: | ||
| stress-test-postgres: | ||
| name: Stress Test with PostgreSQL | ||
| runs-on: ubuntu-latest | ||
|
|
||
| services: | ||
| postgres: | ||
| image: postgres:16 | ||
| env: | ||
| POSTGRES_USER: kai_user | ||
| POSTGRES_PASSWORD: kai_password | ||
| POSTGRES_DB: kai_test_db | ||
| options: >- | ||
| --health-cmd pg_isready | ||
| --health-interval 10s | ||
| --health-timeout 5s | ||
| --health-retries 5 | ||
| ports: | ||
| - 5432:5432 | ||
|
|
||
| defaults: | ||
| run: | ||
| shell: bash | ||
| working-directory: ./kai_mcp_solution_server | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python 3.12 | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Install the latest version of uv | ||
| uses: astral-sh/setup-uv@v6 | ||
| with: | ||
| version: "latest" | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| uv sync | ||
| uv pip install pytest-asyncio psycopg2-binary asyncpg | ||
|
|
||
| - name: Run stress test with PostgreSQL backend | ||
| env: | ||
| KAI_DB_DSN: "postgresql+asyncpg://kai_user:kai_password@localhost:5432/kai_test_db" | ||
| KAI_LLM_PARAMS: '{"model": "fake", "responses": ["Test response"]}' | ||
| MCP_SERVER_URL: "http://localhost:8000" | ||
| NUM_CONCURRENT_CLIENTS: ${{ github.event.inputs.num_clients || '100' }} | ||
| run: | | ||
| echo "Starting MCP server connected to PostgreSQL..." | ||
| uv run python -m kai_mcp_solution_server --transport streamable-http --host 0.0.0.0 --port 8000 & | ||
| SERVER_PID=$! | ||
|
|
||
| # Wait for server to be ready | ||
| echo "Waiting for server to start..." | ||
| for i in {1..30}; do | ||
| if curl -s http://localhost:8000/ > /dev/null 2>&1; then | ||
| echo "Server is ready!" | ||
| break | ||
| fi | ||
| if [ $i -eq 30 ]; then | ||
| echo "Server failed to start in 30 seconds" | ||
| kill $SERVER_PID || true | ||
| exit 1 | ||
| fi | ||
| echo -n "." | ||
| sleep 1 | ||
| done | ||
|
|
||
| # Run the stress test | ||
| echo "" | ||
| echo "Testing with $NUM_CONCURRENT_CLIENTS concurrent clients against PostgreSQL" | ||
| uv run python -m pytest tests/test_multiple_integration.py::TestMultipleIntegration::test_multiple_users -xvs | ||
| TEST_RESULT=$? | ||
|
|
||
| # Stop the server | ||
| echo "Stopping MCP server..." | ||
| kill $SERVER_PID || true | ||
|
|
||
| exit $TEST_RESULT | ||
| timeout-minutes: 10 | ||
|
|
||
| - name: Check PostgreSQL connection count | ||
| if: always() | ||
| run: | | ||
| PGPASSWORD=kai_password psql -h localhost -U kai_user -d kai_test_db -c \ | ||
| "SELECT count(*), state FROM pg_stat_activity GROUP BY state;" | ||
|
|
||
| stress-test-sqlite: | ||
| name: Basic Test with SQLite | ||
| runs-on: ubuntu-latest | ||
|
|
||
| defaults: | ||
| run: | ||
| shell: bash | ||
| working-directory: ./kai_mcp_solution_server | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python 3.12 | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
| - name: Install the latest version of uv | ||
| uses: astral-sh/setup-uv@v6 | ||
| with: | ||
| version: "latest" | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| uv sync | ||
| uv pip install pytest-asyncio | ||
|
|
||
| - name: Run basic test with SQLite (limited concurrency) | ||
| run: | | ||
| # SQLite has limitations with concurrent writes, so we test with fewer clients | ||
| export KAI_LLM_PARAMS='{"model": "fake", "responses": ["Test response"]}' | ||
| export NUM_CONCURRENT_CLIENTS=5 | ||
| echo "Testing with $NUM_CONCURRENT_CLIENTS concurrent clients against SQLite" | ||
| uv run python -m pytest tests/test_multiple_integration.py::TestMultipleIntegration::test_multiple_users -xvs | ||
| timeout-minutes: 5 | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -86,7 +86,7 @@ run-local: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cd $(PROJECT_ROOT) && KAI_DB_DSN='$(KAI_DB_DSN)' KAI_LLM_PARAMS='$(KAI_LLM_PARAMS)' uv run python -m kai_mcp_solution_server --transport streamable-http --host 0.0.0.0 --port 8000 --mount-path=$(MOUNT_PATH) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Run with Podman for testing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Run with Podman for testing (flexible - any database via KAI_DB_DSN) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PHONY: run-podman | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| run-podman: build | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @echo "Running MCP solution server in Podman..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -96,7 +96,60 @@ run-podman: build | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -e KAI_LLM_PARAMS='$(KAI_LLM_PARAMS)' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -e KAI_DB_DSN='$(KAI_DB_DSN)' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(PODMAN_ARGS),$(PODMAN_ARGS),) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --name kai-mcp-solution-server $(IMAGE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --name kai-mcp-solution-server $(IMAGE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Convenience target: Run with SQLite | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .PHONY: podman-sqlite | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| podman-sqlite: build | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @echo "Running MCP solution server with SQLite..." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @if [ -z "$(KAI_LLM_PARAMS)" ]; then echo "Error: KAI_LLM_PARAMS is required"; exit 1; fi | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| podman run --rm -it -p 8000:8000 \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -e MOUNT_PATH='$(MOUNT_PATH)' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -e KAI_LLM_PARAMS='$(KAI_LLM_PARAMS)' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -e KAI_DB_DSN='sqlite+aiosqlite:///data/kai.db' \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(OPENAI_API_KEY),-e OPENAI_API_KEY='$(OPENAI_API_KEY)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(ANTHROPIC_API_KEY),-e ANTHROPIC_API_KEY='$(ANTHROPIC_API_KEY)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(AZURE_OPENAI_API_KEY),-e AZURE_OPENAI_API_KEY='$(AZURE_OPENAI_API_KEY)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(AZURE_OPENAI_ENDPOINT),-e AZURE_OPENAI_ENDPOINT='$(AZURE_OPENAI_ENDPOINT)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(GOOGLE_API_KEY),-e GOOGLE_API_KEY='$(GOOGLE_API_KEY)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(AWS_ACCESS_KEY_ID),-e AWS_ACCESS_KEY_ID='$(AWS_ACCESS_KEY_ID)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(AWS_SECRET_ACCESS_KEY),-e AWS_SECRET_ACCESS_KEY='$(AWS_SECRET_ACCESS_KEY)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(AWS_REGION),-e AWS_REGION='$(AWS_REGION)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $(if $(OLLAMA_HOST),-e OLLAMA_HOST='$(OLLAMA_HOST)',) \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| -v kai-sqlite-data:/data:Z \ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --name kai-mcp-sqlite $(IMAGE) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Convenience target: Run with SQLite | |
| .PHONY: podman-sqlite | |
| podman-sqlite: build | |
| @echo "Running MCP solution server with SQLite..." | |
| @if [ -z "$(KAI_LLM_PARAMS)" ]; then echo "Error: KAI_LLM_PARAMS is required"; exit 1; fi | |
| podman run --rm -it -p 8000:8000 \ | |
| -e MOUNT_PATH='$(MOUNT_PATH)' \ | |
| -e KAI_LLM_PARAMS='$(KAI_LLM_PARAMS)' \ | |
| -e KAI_DB_DSN='sqlite+aiosqlite:///data/kai.db' \ | |
| $(if $(OPENAI_API_KEY),-e OPENAI_API_KEY='$(OPENAI_API_KEY)',) \ | |
| $(if $(ANTHROPIC_API_KEY),-e ANTHROPIC_API_KEY='$(ANTHROPIC_API_KEY)',) \ | |
| $(if $(AZURE_OPENAI_API_KEY),-e AZURE_OPENAI_API_KEY='$(AZURE_OPENAI_API_KEY)',) \ | |
| $(if $(AZURE_OPENAI_ENDPOINT),-e AZURE_OPENAI_ENDPOINT='$(AZURE_OPENAI_ENDPOINT)',) \ | |
| $(if $(GOOGLE_API_KEY),-e GOOGLE_API_KEY='$(GOOGLE_API_KEY)',) \ | |
| $(if $(AWS_ACCESS_KEY_ID),-e AWS_ACCESS_KEY_ID='$(AWS_ACCESS_KEY_ID)',) \ | |
| $(if $(AWS_SECRET_ACCESS_KEY),-e AWS_SECRET_ACCESS_KEY='$(AWS_SECRET_ACCESS_KEY)',) \ | |
| $(if $(AWS_REGION),-e AWS_REGION='$(AWS_REGION)',) \ | |
| $(if $(OLLAMA_HOST),-e OLLAMA_HOST='$(OLLAMA_HOST)',) \ | |
| -v kai-sqlite-data:/data:Z \ | |
| --name kai-mcp-sqlite $(IMAGE) | |
| # Convenience target: Run with SQLite | |
| .PHONY: podman-sqlite | |
| podman-sqlite: build | |
| @echo "Running MCP solution server with SQLite..." | |
| @if [ -z "$(KAI_LLM_PARAMS)" ]; then echo "Error: KAI_LLM_PARAMS is required"; exit 1; fi | |
| podman run --rm -it -p 8000:8000 \ | |
| -e MOUNT_PATH='$(MOUNT_PATH)' \ | |
| -e KAI_LLM_PARAMS='$(KAI_LLM_PARAMS)' \ | |
| -e KAI_DB_DSN='sqlite+aiosqlite:///data/kai.db' \ | |
| $(if $(OPENAI_API_KEY),-e OPENAI_API_KEY,) \ | |
| $(if $(ANTHROPIC_API_KEY),-e ANTHROPIC_API_KEY,) \ | |
| $(if $(AZURE_OPENAI_API_KEY),-e AZURE_OPENAI_API_KEY,) \ | |
| $(if $(AZURE_OPENAI_ENDPOINT),-e AZURE_OPENAI_ENDPOINT,) \ | |
| $(if $(GOOGLE_API_KEY),-e GOOGLE_API_KEY,) \ | |
| $(if $(AWS_ACCESS_KEY_ID),-e AWS_ACCESS_KEY_ID,) \ | |
| $(if $(AWS_SECRET_ACCESS_KEY),-e AWS_SECRET_ACCESS_KEY,) \ | |
| $(if $(AWS_REGION),-e AWS_REGION,) \ | |
| $(if $(OLLAMA_HOST),-e OLLAMA_HOST,) \ | |
| -v kai-sqlite-data:/data:Z \ | |
| --name kai-mcp-sqlite $(IMAGE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Install psql before querying pg_stat_activity
Ubuntu runners may lack psql; this step can fail the job.
Add this step before “Check PostgreSQL connection count”:
🤖 Prompt for AI Agents