Last active
October 31, 2025 15:45
-
-
Save mvdbeek/6a2a0e473fc85e7f4876f2d95d7e474b to your computer and use it in GitHub Desktop.
Rate limit test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ========================================= | |
| Galaxy API Rate Limiting Test (Parallel) | |
| ========================================= | |
| Target: https://test.galaxyproject.org | |
| Endpoint: https://test.galaxyproject.org/api/version | |
| Temp dir: /var/folders/df/6xqpqpcd7h73b6jpx9t6cwhw0000gn/T/tmp.9BmqdrrwVf | |
| === Test 1: Burst Capacity Test === | |
| Testing burst capacity with 0c918d2fbb... | |
| Test: 60 parallel requests with key1_burst | |
| ---------------------------------------------- | |
| Launching 60 requests... | |
| Results: | |
| ✓ Success (200): 51 | |
| ✗ Rate Limited (429): 9 | |
| ⏱ Avg response time: 588.2ms | |
| Waiting 15 seconds for rate limit to fully refill... | |
| === Test 2: Sustained Load Test === | |
| Sending requests in waves to test rate refill... | |
| Wave 1: 60 parallel requests... | |
| Waiting 5 seconds for rate limit to refill... | |
| Wave 2: 30 parallel requests (should have ~20 tokens refilled @ 4r/s)... | |
| Wave 1 success: 51/60 | |
| Wave 2 success: 22/30 | |
| === Test 3: Independent Rate Limit Test === | |
| Testing if API keys have independent rate limits... | |
| Waiting 15 seconds for API Key 1 to fully refill... | |
| Step 1: Exhausting API Key 1... | |
| Test: 60 parallel requests with key1_exhaust | |
| ---------------------------------------------- | |
| Launching 60 requests... | |
| Results: | |
| ✓ Success (200): 51 | |
| ✗ Rate Limited (429): 9 | |
| ⏱ Avg response time: 455.0ms | |
| Step 2: Testing API Key 2 (should have full burst if independent)... | |
| Test: 60 parallel requests with key2_fresh | |
| ---------------------------------------------- | |
| Launching 60 requests... | |
| Results: | |
| ✓ Success (200): 51 | |
| ✗ Rate Limited (429): 9 | |
| ⏱ Avg response time: 462.7ms | |
| Independence Analysis: | |
| ✓ PASS: API keys have independent rate limits | |
| Key 2 handled 51 requests after Key 1 was exhausted | |
| === Test 4: Session Cookie Rate Limiting === | |
| Testing with session cookie (should use cookie as zone key)... | |
| Waiting 15 seconds for rate limit to fully refill... | |
| Testing Session Cookie 1 with burst test... | |
| Test: 60 parallel requests with session1_burst | |
| ---------------------------------------------- | |
| Launching 60 requests... | |
| Results: | |
| ✓ Success (200): 51 | |
| ✗ Rate Limited (429): 9 | |
| ⏱ Avg response time: 501.1ms | |
| Testing Session Cookie 2 immediately (should have full burst if independent)... | |
| Test: 60 parallel requests with session2_fresh | |
| ---------------------------------------------- | |
| Launching 60 requests... | |
| Results: | |
| ✓ Success (200): 51 | |
| ✗ Rate Limited (429): 9 | |
| ⏱ Avg response time: 486.4ms | |
| Session Cookie Independence Analysis: | |
| ✓ PASS: Session cookies have independent rate limits | |
| Cookie 2 handled 51 requests after Cookie 1 was exhausted | |
| === Test 5: IP-Based Rate Limiting === | |
| Testing without API key or cookie (should use IP address)... | |
| Waiting 15 seconds for rate limit to fully refill... | |
| Success (200): 51 | |
| Rate Limited (429): 9 | |
| ========================================= | |
| SUMMARY | |
| ========================================= | |
| Configuration: | |
| - Rate: 4 requests/second | |
| - Burst: 50 | |
| - Zone key priority: API key > Session cookie > IP | |
| - Exempted: /api/upload/resumable_upload, /api/job_files | |
| Expected behavior with burst=50: | |
| - First ~50-54 parallel requests: SUCCESS (burst bucket) | |
| - Subsequent requests: RATE LIMITED (429) | |
| - After waiting: ~4 requests/second refill rate | |
| Recommendations: | |
| ✓ Burst capacity appears correct (~50) | |
| Test 1 got 51 successful requests | |
| ✓ Rate refill appears to work correctly | |
| Test completed. Results saved in: /var/folders/df/6xqpqpcd7h73b6jpx9t6cwhw0000gn/T/tmp.9BmqdrrwVf | |
| To preserve results, copy the directory before script exits. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # | |
| # Parallel Rate Limiting Test for Galaxy API | |
| # Tests rate limiting with concurrent requests to verify limits apply correctly | |
| # | |
| # Rate limit configuration: | |
| # - Rate: 4 requests/second | |
| # - Burst: 50 | |
| # - Key: API key (if present) > session cookie > IP address | |
| # | |
| # Usage: ./test_rate_limiting_parallel.sh <galaxy_url> <api_key_1> [api_key_2] [session_cookie_1] [session_cookie_2] | |
| set -e | |
| if [ $# -lt 2 ]; then | |
| echo "Usage: $0 <galaxy_url> <api_key_1> [api_key_2] [session_cookie_1] [session_cookie_2]" | |
| echo "" | |
| echo "Examples:" | |
| echo " $0 https://test.galaxyproject.org api_key_1" | |
| echo " $0 https://test.galaxyproject.org api_key_1 api_key_2" | |
| echo " $0 https://test.galaxyproject.org api_key_1 '' session_cookie_1" | |
| echo " $0 https://test.galaxyproject.org api_key_1 api_key_2 session_cookie_1 session_cookie_2" | |
| exit 1 | |
| fi | |
| GALAXY_URL="$1" | |
| API_KEY_1="$2" | |
| API_KEY_2="${3:-}" | |
| SESSION_COOKIE_1="${4:-}" | |
| SESSION_COOKIE_2="${5:-}" | |
| TEST_ENDPOINT="${GALAXY_URL}/api/version" | |
| # Colors for output | |
| GREEN='\033[0;32m' | |
| RED='\033[0;31m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' | |
| # Create temporary directory for results | |
| TEMP_DIR=$(mktemp -d) | |
| trap "rm -rf ${TEMP_DIR}" EXIT | |
| echo "=========================================" | |
| echo "Galaxy API Rate Limiting Test (Parallel)" | |
| echo "=========================================" | |
| echo "Target: ${GALAXY_URL}" | |
| echo "Endpoint: ${TEST_ENDPOINT}" | |
| echo "Temp dir: ${TEMP_DIR}" | |
| echo "" | |
| # Function to make a single request and record result | |
| make_request() { | |
| local api_key="$1" | |
| local index="$2" | |
| local output_file="$3" | |
| local session_cookie="${4:-}" | |
| local start_time=$(date +%s.%N) | |
| # Build curl command based on what's provided | |
| local curl_args=() | |
| if [ -n "$api_key" ]; then | |
| curl_args+=(-H "x-api-key: ${api_key}") | |
| fi | |
| if [ -n "$session_cookie" ]; then | |
| curl_args+=(-b "galaxysession=${session_cookie}") | |
| fi | |
| local http_code=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| --max-time 10 \ | |
| "${curl_args[@]}" \ | |
| "${TEST_ENDPOINT}" 2>/dev/null || echo "000") | |
| local end_time=$(date +%s.%N) | |
| # Calculate duration in milliseconds | |
| local duration=$(echo "($end_time - $start_time) * 1000" | bc) | |
| echo "${index},${http_code},${duration}" >> "${output_file}" | |
| } | |
| # Function to run parallel requests | |
| run_parallel_test() { | |
| local api_key="$1" | |
| local num_requests="$2" | |
| local key_name="$3" | |
| local session_cookie="${4:-}" | |
| local output_file="${TEMP_DIR}/${key_name}.csv" | |
| echo "Test: ${num_requests} parallel requests with ${key_name}" | |
| echo "----------------------------------------------" | |
| # Launch all requests in parallel | |
| echo "Launching ${num_requests} requests..." | |
| for i in $(seq 1 $num_requests); do | |
| make_request "${api_key}" "$i" "${output_file}" "${session_cookie}" & | |
| done | |
| # Wait for all background jobs to complete | |
| wait | |
| # Analyze results | |
| local total=$(wc -l < "${output_file}" | tr -d ' ') | |
| local success=$(grep -c ",200," "${output_file}" || true) | |
| local limited=$(grep -c ",429," "${output_file}" || true) | |
| local errors=$(grep -c ",000," "${output_file}" || true) | |
| local other=$((total - success - limited - errors)) | |
| echo " Results:" | |
| echo " ✓ Success (200): ${success}" | |
| echo " ✗ Rate Limited (429): ${limited}" | |
| if [ $errors -gt 0 ]; then | |
| echo " ! Errors/Timeouts: ${errors}" | |
| fi | |
| if [ $other -gt 0 ]; then | |
| echo " ? Other responses: ${other}" | |
| fi | |
| # Calculate average response time for successful requests | |
| if [ $success -gt 0 ]; then | |
| local avg_duration=$(grep ",200," "${output_file}" | cut -d',' -f3 | \ | |
| awk '{sum+=$1; count++} END {if(count>0) printf "%.1f", sum/count; else print "0"}') | |
| echo " ⏱ Avg response time: ${avg_duration}ms" | |
| fi | |
| echo "" | |
| } | |
| # Test 1: Single API key with burst test | |
| echo -e "${BLUE}=== Test 1: Burst Capacity Test ===${NC}" | |
| echo "Testing burst capacity with ${API_KEY_1:0:10}..." | |
| echo "" | |
| run_parallel_test "${API_KEY_1}" 60 "key1_burst" | |
| # Expected: ~50-54 successful requests (burst=50 + rate refill during execution) | |
| # Rest should be rate limited (429) | |
| # Wait for rate limit to refill before next test | |
| echo "Waiting 15 seconds for rate limit to fully refill..." | |
| sleep 15 | |
| echo "" | |
| # Test 2: Sustained load over time | |
| echo -e "${BLUE}=== Test 2: Sustained Load Test ===${NC}" | |
| echo "Sending requests in waves to test rate refill..." | |
| echo "" | |
| KEY1_SUSTAINED="${TEMP_DIR}/key1_sustained.csv" | |
| echo "Wave 1: 60 parallel requests..." | |
| for i in $(seq 1 60); do | |
| make_request "${API_KEY_1}" "wave1_$i" "${KEY1_SUSTAINED}" "" & | |
| done | |
| wait | |
| echo "Waiting 5 seconds for rate limit to refill..." | |
| sleep 5 | |
| echo "Wave 2: 30 parallel requests (should have ~20 tokens refilled @ 4r/s)..." | |
| for i in $(seq 1 30); do | |
| make_request "${API_KEY_1}" "wave2_$i" "${KEY1_SUSTAINED}" "" & | |
| done | |
| wait | |
| # Analyze sustained test | |
| wave1_success=$(grep "wave1_.*,200," "${KEY1_SUSTAINED}" | wc -l | tr -d ' ') | |
| wave2_success=$(grep "wave2_.*,200," "${KEY1_SUSTAINED}" | wc -l | tr -d ' ') | |
| echo " Wave 1 success: ${wave1_success}/60" | |
| echo " Wave 2 success: ${wave2_success}/30" | |
| echo "" | |
| # Test 3: Independent API key limits (if second key provided) | |
| if [ -n "${API_KEY_2}" ]; then | |
| echo -e "${BLUE}=== Test 3: Independent Rate Limit Test ===${NC}" | |
| echo "Testing if API keys have independent rate limits..." | |
| echo "" | |
| # Wait for key 1 to refill first | |
| echo "Waiting 15 seconds for API Key 1 to fully refill..." | |
| sleep 15 | |
| echo "" | |
| # Exhaust key 1 | |
| echo "Step 1: Exhausting API Key 1..." | |
| run_parallel_test "${API_KEY_1}" 60 "key1_exhaust" | |
| # Immediately test key 2 | |
| echo "Step 2: Testing API Key 2 (should have full burst if independent)..." | |
| run_parallel_test "${API_KEY_2}" 60 "key2_fresh" | |
| # Compare results | |
| key1_success=$(grep -c ",200," "${TEMP_DIR}/key1_exhaust.csv" || true) | |
| key2_success=$(grep -c ",200," "${TEMP_DIR}/key2_fresh.csv" || true) | |
| echo -e "${BLUE}Independence Analysis:${NC}" | |
| if [ $key2_success -ge 45 ]; then | |
| echo -e " ${GREEN}✓ PASS: API keys have independent rate limits${NC}" | |
| echo " Key 2 handled ${key2_success} requests after Key 1 was exhausted" | |
| elif [ $key2_success -lt 10 ]; then | |
| echo -e " ${RED}✗ FAIL: API keys appear to share rate limits${NC}" | |
| echo " Key 2 only handled ${key2_success} requests (expected ~50)" | |
| else | |
| echo -e " ${YELLOW}⚠ BORDERLINE: Key 2 handled ${key2_success} requests${NC}" | |
| echo " Expected ~50 if fully independent" | |
| fi | |
| echo "" | |
| fi | |
| # Test 4: Session cookie rate limiting (if provided) | |
| if [ -n "${SESSION_COOKIE_1}" ]; then | |
| echo -e "${BLUE}=== Test 4: Session Cookie Rate Limiting ===${NC}" | |
| echo "Testing with session cookie (should use cookie as zone key)..." | |
| echo "" | |
| # Wait for any previous limits to clear | |
| echo "Waiting 15 seconds for rate limit to fully refill..." | |
| sleep 15 | |
| echo "" | |
| echo "Testing Session Cookie 1 with burst test..." | |
| run_parallel_test "" 60 "session1_burst" "${SESSION_COOKIE_1}" | |
| # If second session cookie provided, test independence | |
| if [ -n "${SESSION_COOKIE_2}" ]; then | |
| echo "" | |
| echo "Testing Session Cookie 2 immediately (should have full burst if independent)..." | |
| run_parallel_test "" 60 "session2_fresh" "${SESSION_COOKIE_2}" | |
| session1_success=$(grep -c ",200," "${TEMP_DIR}/session1_burst.csv" || true) | |
| session2_success=$(grep -c ",200," "${TEMP_DIR}/session2_fresh.csv" || true) | |
| echo -e "${BLUE}Session Cookie Independence Analysis:${NC}" | |
| if [ $session2_success -ge 45 ]; then | |
| echo -e " ${GREEN}✓ PASS: Session cookies have independent rate limits${NC}" | |
| echo " Cookie 2 handled ${session2_success} requests after Cookie 1 was exhausted" | |
| elif [ $session2_success -lt 10 ]; then | |
| echo -e " ${RED}✗ FAIL: Session cookies appear to share rate limits${NC}" | |
| echo " Cookie 2 only handled ${session2_success} requests (expected ~50)" | |
| else | |
| echo -e " ${YELLOW}⚠ BORDERLINE: Cookie 2 handled ${session2_success} requests${NC}" | |
| echo " Expected ~50 if fully independent" | |
| fi | |
| fi | |
| echo "" | |
| fi | |
| # Test 5: IP-based rate limiting (no API key or cookie) | |
| test_num=4 | |
| if [ -n "${SESSION_COOKIE_1}" ]; then | |
| test_num=5 | |
| fi | |
| echo -e "${BLUE}=== Test ${test_num}: IP-Based Rate Limiting ===${NC}" | |
| echo "Testing without API key or cookie (should use IP address)..." | |
| echo "" | |
| # Wait for previous tests to clear | |
| echo "Waiting 15 seconds for rate limit to fully refill..." | |
| sleep 15 | |
| echo "" | |
| IP_OUTPUT="${TEMP_DIR}/ip_based.csv" | |
| for i in $(seq 1 60); do | |
| ( | |
| start_time=$(date +%s.%N) | |
| http_code=$(curl -s -o /dev/null -w "%{http_code}" \ | |
| --max-time 10 \ | |
| "${TEST_ENDPOINT}" 2>/dev/null || echo "000") | |
| end_time=$(date +%s.%N) | |
| duration=$(echo "($end_time - $start_time) * 1000" | bc) | |
| echo "${i},${http_code},${duration}" >> "${IP_OUTPUT}" | |
| ) & | |
| done | |
| wait | |
| ip_success=$(grep -c ",200," "${IP_OUTPUT}" || true) | |
| ip_limited=$(grep -c ",429," "${IP_OUTPUT}" || true) | |
| echo " Success (200): ${ip_success}" | |
| echo " Rate Limited (429): ${ip_limited}" | |
| echo "" | |
| # Final Summary | |
| echo "=========================================" | |
| echo "SUMMARY" | |
| echo "=========================================" | |
| echo "" | |
| echo "Configuration:" | |
| echo " - Rate: 4 requests/second" | |
| echo " - Burst: 50" | |
| echo " - Zone key priority: API key > Session cookie > IP" | |
| echo " - Exempted: /api/upload/resumable_upload, /api/job_files" | |
| echo "" | |
| echo "Expected behavior with burst=50:" | |
| echo " - First ~50-54 parallel requests: SUCCESS (burst bucket)" | |
| echo " - Subsequent requests: RATE LIMITED (429)" | |
| echo " - After waiting: ~4 requests/second refill rate" | |
| echo "" | |
| # Provide recommendations | |
| echo "Recommendations:" | |
| # Get the burst test results (from Test 1) | |
| burst_success=$(grep -c ",200," "${TEMP_DIR}/key1_burst.csv" || true) | |
| if [ $burst_success -ge 48 ] && [ $burst_success -le 56 ]; then | |
| echo -e " ${GREEN}✓ Burst capacity appears correct (~50)${NC}" | |
| echo " Test 1 got ${burst_success} successful requests" | |
| elif [ $burst_success -lt 45 ]; then | |
| echo -e " ${YELLOW}⚠ Burst capacity seems low (got ${burst_success}, expected ~50)${NC}" | |
| echo " Check nginx burst configuration" | |
| elif [ $burst_success -gt 56 ]; then | |
| echo -e " ${YELLOW}⚠ More requests succeeded than expected (got ${burst_success})${NC}" | |
| echo " Rate limiting may not be applied correctly" | |
| fi | |
| if [ $wave2_success -ge 15 ] && [ $wave2_success -le 30 ]; then | |
| echo -e " ${GREEN}✓ Rate refill appears to work correctly${NC}" | |
| elif [ $wave2_success -lt 10 ]; then | |
| echo -e " ${YELLOW}⚠ Rate refill seems slow (got ${wave2_success}, expected ~20)${NC}" | |
| fi | |
| echo "" | |
| echo "Test completed. Results saved in: ${TEMP_DIR}" | |
| echo "To preserve results, copy the directory before script exits." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment