Files
NBA_Go/README.md
2026-09-11 01:09:37 -05:00

396 lines
18 KiB
Markdown

# NBA_Go
A high-performance NBA statistics REST API built with Go (Fiber), PostgreSQL, and NGINX. Data is scraped from Basketball Reference and served through a load-balanced, containerized stack with dual-stack IPv4/IPv6 support, read/write database splitting, and built-in Prometheus & Grafana observability.
---
## Live Production Endpoints
| Service / Endpoint | URL | Description |
| :--- | :--- | :--- |
| **API Base URL** | `https://nba.turbo-data.com` | Production API root (redirects to Swagger UI) |
| **Swagger UI Docs** | `https://nba.turbo-data.com/swagger/index.html` | Interactive API documentation & sandbox |
| **Player Totals** | `https://nba.turbo-data.com/api/playertotals` | Season totals with pagination & sorting |
| **Games & Box Scores** | `https://nba.turbo-data.com/api/games` | Game schedules, box scores, line scores |
| **Advanced Stats** | `https://nba.turbo-data.com/api/playeradvancedstats` | Advanced metrics (PER, WS, VORP, BPM) |
| **Shot Charts** | `https://nba.turbo-data.com/api/playershotchart` | Shot coordinate & outcome data |
| **Liveness Check** | `https://nba.turbo-data.com/health/live` | Process liveness probe |
| **Readiness Check** | `https://nba.turbo-data.com/health/ready` | Database readiness probe |
| **Metrics** | `https://nba.turbo-data.com/metrics` | Prometheus metrics endpoint |
| **Coolify Dashboard** | `https://turbo-data.com` | Infrastructure & deployment management |
---
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ Traefik (Coolify Ingress Proxy :80 / :443 SSL) │
└──────────────────────────────┬───────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ NGINX (Reverse Proxy, API Cache, Round-Robin Load Balancer) │
│ - Container port 8080 (Mapped to Host :8081) │
│ - Dual-stack IPv4/IPv6 upstream resolution │
│ - 30s response caching for /api/* with stale-while-revalidate│
├──────────────────────────────────────────────────────────────┤
│ API Instances x3 (Fiber on [::]:5000 Dual-Stack) │
│ ┌──────────┐ ┌─────────────┐ ┌───────────────────────┐ │
│ │ Routes │→ │ Controllers │→ │ GORM DBResolver (R/W) │ │
│ └──────────┘ └─────────────┘ └───────────┬───────────┘ │
├─────────────────────────────────────────────┼────────────────┤
│ Database Layer (PostgreSQL Cluster) │ │
│ ┌──────────────────────────────────────────┴─────────────┐ │
│ │ HAProxy Write Ingress (:5437) ➔ Primary DB (:5434) │ │
│ │ HAProxy Read Ingress (:5438) ➔ Read Replicas (:5435/6) │ │
│ └────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Observability │
│ - Prometheus (Scrapes /metrics on each instance, Host :9091)│
│ - Grafana (Pre-provisioned dashboards, Host :3001) │
└──────────────────────────────────────────────────────────────┘
```
### Key Architectural Highlights:
1. **Dual-Stack Networking**: The Go Fiber backend listens on `[::]:5000` (`net.Listen("tcp", ":5000")` with `app.Listener`), accepting incoming connections seamlessly over both IPv6 and IPv4 within Docker and Coolify networks.
2. **Read/Write DB Splitting**: GORM uses the `dbresolver` plugin to automatically route all write operations to the PostgreSQL Primary via HAProxy port 5437, while load balancing read queries across Read Replicas via HAProxy port 5438.
3. **Multi-Layer Caching & Rate Limiting**: NGINX provides an in-memory cache (`api_cache`) for 30 seconds, while Fiber middleware enforces per-client rate limiting (20 req/min per instance, ~60 req/min effective across 3 replicas).
---
### Project Structure
```
.
├── main.go # Application entry point (API server or import-data mode)
├── import.go # Data import orchestration
├── config/
│ └── database.go # Database connection & DBResolver R/W setup
├── models/ # GORM models (Game, PlayerAdvancedStat, PlayerTotalStat, etc.)
├── controllers/ # HTTP handlers, DTOs, health checks, pagination
│ ├── game_controller.go
│ ├── health_controller.go # /health/live and /health/ready handlers
│ ├── player_advanced_controller.go
│ ├── player_shot_chart_controller.go
│ └── player_total_controller.go
├── routes/ # Route registration grouped by domain
├── services/ # Scrapers (Basketball Reference via goquery)
├── utils/
│ ├── middleware/ # Rate limiter, Prometheus metrics, API key auth
│ ├── metrics/ # Prometheus counter & histogram definitions
│ └── security/ # API key hashing & generation
├── nginx/ # NGINX reverse proxy & cache configuration
├── prometheus/ # Prometheus scrape configuration
├── grafana/ # Pre-provisioned dashboards & datasources
├── docker-compose.yml # Production deployment configuration (Coolify)
├── docker-compose.local.yml # Local development configuration (includes local Postgres)
└── docker-compose.override.yml # Override for remote DB development
```
---
## API Endpoints
| Method | Path | Description | Public |
| :--- | :--- | :--- | :---: |
| `GET` | `/` | Redirects to `/swagger/index.html` | Yes |
| `GET` | `/swagger/*` | Interactive Swagger UI documentation | Yes |
| `GET` | `/api/games` | Paginated games with box scores, line scores, team/player stats | Yes |
| `GET` | `/api/playeradvancedstats` | Advanced metrics (PER, WS, VORP, BPM, etc.) | Yes |
| `GET` | `/api/playertotals` | Season totals (points, rebounds, assists, etc.) | Yes |
| `GET` | `/api/playershotchart` | Shot chart coordinates and made/missed outcomes | Yes |
| `GET` | `/health/live` | Process liveness probe | Yes |
| `GET` | `/health/ready` | Database connection readiness probe | Yes |
| `GET` | `/metrics` | Prometheus metrics scrape endpoint | Yes |
| `POST`| `/admin/keys` | Create API key (requires `X-Admin-Secret` header) | Admin |
| `POST`| `/admin/keys/:id/revoke` | Revoke API key (requires `X-Admin-Secret` header) | Admin |
---
### Query Parameters (Data Endpoints)
| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `page` | `int` | `1` | Page number |
| `pageSize` | `int` | `20` | Results per page (max 100) |
| `sortBy` | `string` | varies | Field to sort by (e.g., `points`, `date`, `winShares`) |
| `ascending` | `bool` | `false` | Sort ascending (`true`) or descending (`false`) |
| `season` | `int` | - | Filter by season year (e.g., `2024`, `2025`) |
| `team` | `string` | - | Filter by team abbreviation (e.g., `LAL`, `BOS`, `GSW`) |
| `playerId` | `string` | - | Filter by player ID (e.g., `jamesle01`, `curryst01`) |
| `isPlayoff` | `bool` | `false` | Filter for playoff games or stats |
#### Additional Parameters for `/api/games`:
- `date`: Filter by exact date (`YYYY-MM-DD`).
- `gameId`: Filter by specific game ID (e.g., `202501010LAL`).
- `include`: Comma-separated associations to preload (`lineScores`, `playerGameBasicStats`, `playerGameAdvStats`, `teamGameBasicStats`, `teamGameAdvStats`).
---
### Example Requests
#### Production (Live API)
```bash
# Get top scorers for the 2025 season
curl "https://nba.turbo-data.com/api/playertotals?season=2025&sortBy=points&pageSize=10"
# Get a specific game with full box score preloaded
curl "https://nba.turbo-data.com/api/games?gameId=202501010LAL&include=lineScores,playerGameBasicStats,teamGameBasicStats"
# Get LeBron James' advanced stats across career
curl "https://nba.turbo-data.com/api/playeradvancedstats?playerId=jamesle01&sortBy=season&ascending=true"
# Get Stephen Curry's shot chart data for 2024
curl "https://nba.turbo-data.com/api/playershotchart?playerId=curryst01&season=2024"
# Check API readiness
curl "https://nba.turbo-data.com/health/ready"
```
#### Local Development
```bash
# Query local NGINX load balancer (:8081)
curl "http://localhost:8081/api/playertotals?season=2025&pageSize=5"
# Query local Prometheus metrics
curl "http://localhost:8081/metrics"
```
---
## Response Format
All data endpoints return structured JSON with pagination metadata:
```json
{
"data": [
{
"playerId": "curryst01",
"playerName": "Stephen Curry",
"season": 2024,
"team": "GSW",
"points": 1956,
"assists": 379,
"rebounds": 330,
"threeP": 357,
"fieldPercent": 0.450
}
],
"pagination": {
"total": 450,
"page": 1,
"pageSize": 20,
"pages": 23
}
}
```
---
## Rate Limiting & Caching
- **Rate Limiting**: Configured per client IP at **20 requests per minute per instance** (~60 req/min across the 3-instance cluster). If exceeded, the API responds with `HTTP 429 Too Many Requests`.
- **Custom Limits**: Configurable via `RATE_LIMIT_MAX` (default `20`) and `RATE_LIMIT_EXPIRATION_SECONDS` (default `60s`).
- **Internal Benchmark Bypass**: If `BENCHMARK_KEY` is configured in the environment, passing matching token in `X-Benchmark-Key` header skips rate limiting for automated benchmarks.
- **Multi-Layer Caching**:
- **Edge CDN (Cloudflare)**: Caches GET requests with `s-maxage=86400` (24h) and `stale-while-revalidate=600`.
- **NGINX Reverse Proxy**: Provides an in-memory cache (`api_cache`) for **30 seconds** (`keys_zone=api_cache:10m`).
- Responses include `CF-Cache-Status` (`HIT`/`MISS`) and `X-Cache-Status` (`HIT`/`MISS`) headers.
---
## Getting Started
### Prerequisites
- Docker & Docker Compose
- Go 1.23+ (for local development)
### Environment Variables
Create a `.env` file in the project root:
```env
DB_HOST=178.105.149.129
DB_USER=your_user
DB_PASSWORD=your_password
DB_NAME=appdb
DB_PORT=5432
ADMIN_SECRET=your_admin_secret
BENCHMARK_KEY=your_internal_benchmark_token
RATE_LIMIT_MAX=20
```
### Running Locally with Docker
```bash
# Start full local stack (Postgres, 3 API replicas, NGINX, Prometheus, Grafana)
docker compose -f docker-compose.local.yml up --build -d
# Or using Makefile
make up
```
Local service ports:
- **API (via NGINX)**: [http://localhost:8081](http://localhost:8081)
- **Swagger Docs**: [http://localhost:8081/swagger/index.html](http://localhost:8081/swagger/index.html)
- **Prometheus**: [http://localhost:9091](http://localhost:9091)
- **Grafana**: [http://localhost:3001](http://localhost:3001) (`admin` / `testing`)
- **API Direct Instances**: `http://localhost:5001`, `5002`, `5003`
### Initial Data Import
To run the initial schema migrations and scrape Basketball Reference:
```bash
docker compose -f docker-compose.local.yml run --rm db-init
```
This launches the container in one-off `import-data` mode:
1. Executes GORM `AutoMigrate` against the Primary database.
2. Scrapes player advanced stats, regular season totals, playoff totals, and shot charts.
3. Upserts all records into PostgreSQL.
### Stopping Services
```bash
docker compose -f docker-compose.local.yml down
# or
make down
```
---
## Production Deployment (Coolify)
The application is deployed via Coolify on branch `shooting`:
- Uses Traefik as the edge reverse proxy with automated Let's Encrypt SSL.
- Bridges into the `coolify-shared` network to communicate with external databases and proxy services.
- Configured with multi-container services (`api1`, `api2`, `api3`, `nginx`, `prometheus`, `grafana`, `db-init`).
To trigger a redeployment from the command line on the server:
```bash
docker compose -f /data/coolify/applications/<app_uuid>/docker-compose.yaml up -d --build
```
---
## Observability
### Prometheus Metrics
Exposed at `/metrics`:
- `nba_http_requests_total` — Counter partitioned by `method`, `endpoint`, and `status`.
- `nba_http_request_duration_seconds` — Histogram tracking request latencies.
- `nba_db_operations_total` — Counter tracking database read/write queries.
### Grafana Dashboards
Pre-provisioned dashboards in `grafana/dashboards` visualize:
- Request throughput and error rates per endpoint.
- NGINX cache hit ratio.
- Latency percentiles (p50, p95, p99).
- Database read/write distribution across Primary and Replicas.
---
## Swagger API Documentation
To regenerate Swagger documentation after modifying controllers or annotations:
```bash
swag init -g main.go -o docs
```
---
## Testing & Load Testing
### Unit & Integration Tests
```bash
go test -v ./...
```
### Advanced Load Testing (`test/loadtest.go`)
The built-in load testing engine simulates high-volume, realistic traffic patterns across edge caches, reverse proxies, and the origin PostgreSQL database.
See the complete [Load Testing Guide](docs/loadtesting.md) for full details.
#### Key Capabilities:
- **Dynamic Query Randomization (`-varyParams`, `-complexity`)**: Permutes combinations of seasons, teams, sort columns, ascending/descending, page sizes, playoff filters, and heavy database relation joins (`include=lineScores,playerGameBasicStats,teamGameAdvStats`).
- **Cold-Cache Stress Testing (`-cacheBust`)**: Injects unique request nonces to ensure a 100% cache-miss rate for benchmarking raw database execution.
- **Cache Telemetry Split**: Inspects `CF-Cache-Status` (`HIT`/`MISS`) and `Age` headers to provide separate latency profiles for Edge Cache Hits vs. Origin / Database Misses.
- **Latency Percentiles**: Reports **Min**, **p50 (Median)**, **p75**, **p90**, **p95**, **p99**, **Max**, and **Average** latency.
- **Distributed Client Simulation (`-rotateIPs`)**: Injects rotating `X-Real-IP` and `X-Forwarded-For` headers to accurately simulate thousands of distinct clients.
- **Benchmark Token Support (`-benchmarkKey`)**: Passes `X-Benchmark-Key` header to bypass rate limits during testing.
- **Multi-Endpoint Traffic Mix (`-endpointMix`)**: Distributes traffic across `/api/playeradvancedstats`, `/api/playertotals`, `/api/games`, and `/api/playershotchart`.
#### Load Test CLI Flags:
| Flag | Default | Description |
| :--- | :--- | :--- |
| `-url` | `http://localhost:5000/api/playeradvancedstats` | Target endpoint or base URL |
| `-n` | `100` | Number of requests to send |
| `-c` | `10` | Concurrency level (worker goroutines) |
| `-varyParams` | `false` | Enable query parameter & filter randomization |
| `-complexity` | `standard` | Query complexity: `standard` or `high` |
| `-cacheBust` | `false` | Force 100% cold-cache misses on CDN/proxy |
| `-benchmarkKey` | `$BENCHMARK_KEY` | Token for `X-Benchmark-Key` header |
| `-rotateIPs` | `false` | Rotate synthetic client IPs |
| `-endpointMix` | `""` | Multi-endpoint traffic mix (e.g. `playeradvancedstats:40,playertotals:30,games:30`) |
| `-pageMix` | `""` | Weighted page mix (e.g. `1-3:60,4-10:30,11-20:10`) |
| `-retryOnRateLimit` | `false` | Retry 429 responses with backoff |
| `-log` | `loadtest.log` | Output log file destination |
#### Example Usage:
```bash
# 1. Realistic Traffic with Parameter Variance (Recommended)
go run ./test/loadtest.go \
-url 'https://nba.turbo-data.com/api/playeradvancedstats' \
-n 3000 \
-c 20 \
-varyParams \
-complexity high \
-rotateIPs \
-benchmarkKey 'your-benchmark-token' \
-pageMix '1-3:60,4-10:30,11-20:10' \
-log ./test/results.log
# 2. Pure Cold-Cache / Origin Database Benchmark (100% Cache Misses)
go run ./test/loadtest.go \
-url 'https://nba.turbo-data.com/api/playeradvancedstats' \
-n 1000 \
-c 15 \
-varyParams \
-cacheBust \
-benchmarkKey 'your-benchmark-token' \
-log ./test/results.log
# 3. Multi-Endpoint Traffic Mix
go run ./test/loadtest.go \
-url 'https://nba.turbo-data.com' \
-endpointMix 'playeradvancedstats:40,playertotals:30,games:30' \
-varyParams \
-n 2000 \
-c 20 \
-benchmarkKey 'your-benchmark-token' \
-log ./test/results.log
```
---
## Tech Stack
| Layer | Technology |
| :--- | :--- |
| **Language** | Go 1.24 |
| **HTTP Framework** | Fiber v2 (`valyala/fasthttp`) |
| **Database** | PostgreSQL 17 (Primary + Read Replicas) |
| **ORM & Routing** | GORM + `dbresolver` (R/W splitting) |
| **Reverse Proxy / Cache** | NGINX Stable (API caching & round-robin) |
| **Edge Routing & SSL** | Traefik v3 (Let's Encrypt automated TLS) |
| **Monitoring** | Prometheus + Grafana |
| **Scraping** | `goquery` (HTML parsing) |
| **Documentation** | Swagger 2.0 (`swaggo/swag`) |
| **Deployment** | Docker, Docker Compose, Coolify |