System Overview
GPUForge is a GPU fleet orchestration platform for operators managing GPU clusters at scale. It provides real-time fleet visibility, multi-tenant job scheduling, usage metering, and billing — in a single deployable Express/PostgreSQL stack.
What GPUForge Does
GPUForge solves the operational complexity of running shared GPU infrastructure for AI/ML workloads. Operators provision clusters, tenants consume GPU capacity, and the platform handles scheduling, quota enforcement, and billing — all from a real-time dashboard.
Three core services unified in one deploy:
- GPU-as-a-Service (GPUaaS) — Cluster/node/GPU registry, metrics ingestion, fleet health monitoring
- Inference as a Service — GPU allocation tracking, inference endpoint assignment, SLA monitoring
- Agentic AI Workflow Orchestration — Job submission, priority scheduling, tenant quota enforcement, multi-job pipelines
Target Users
Primary audience: GPU cloud operators — platform engineers, MLOps leads, and CTOs at companies running shared GPU infrastructure (cloud providers, research institutions, enterprise AI platforms).
Two dashboard planes serve distinct personas:
| Plane | URL | Persona | Capabilities |
|---|---|---|---|
| Admin Dashboard | /admin | Fleet operator | Fleet visibility, tenant management, cluster config, scheduling controls, revenue analytics, alert management |
| Customer Dashboard | /dashboard / /customer | GPU tenant | GPU allocations, notebook sessions, AI pod management, inference endpoints, workflow overview, billing history |
Target Workloads
| Workload Type | Description | Scheduling Priority |
|---|---|---|
| Training Jobs | Large distributed ML training — high GPU count, long duration | P1 Batch / interactive |
| Inference Endpoints | Low-latency serving — persistent GPU allocation, SLA-bound | P2 Always-on |
| Agentic Workflows | Multi-step AI pipelines — chains, loops, tool calls | P3 Flexible |
| Interactive Notebooks | Jupyter-style sessions — on-demand, short-lived | P4 Interactive |
3-Service Architecture
GPUForge consolidates three distinct service types into one Express application. Each service has its own API surface, data model, and operator workflow.
Service 1 — GPU-as-a-Service (GPUaaS)
The foundation layer. Manages the physical GPU fleet lifecycle from node registration through real-time health monitoring.
Scope: cluster creation, node provisioning, GPU registration, metrics ingestion (utilization, memory, temperature, power), fleet health scoring, node status (online/offline/error), cluster-level capacity tracking.
Not owned: job scheduling logic, billing, tenant management.
Service 2 — Inference as a Service
GPU allocation and lifecycle for inference workloads. Tracks per-GPU assignment, inference endpoint status, and SLA metrics.
Scope: inference endpoint registry, GPU assignment to endpoints, endpoint health checks, per-endpoint resource usage, throughput metrics, latency tracking.
Not owned: model serving infrastructure (handled externally), job scheduling (delegated to GPUaaS scheduler).
Service 3 — Agentic AI Workflow Orchestration
The highest-level service. Coordinates multi-step AI workflows across GPU tenants — chain, loop, tool call patterns for autonomous agents.
Scope: job submission and lifecycle management, priority-based scheduling, tenant quota enforcement, multi-job pipeline tracking, workflow state machine (pending/running/completed/failed), retry logic, workflow logging.
Not owned: cluster/node management (GPUaaS), billing aggregation.
3-Service Stack
┌─────────────────────────────────────────────────────────────┐ │ GPUForge Platform │ │ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Agentic AI Workflow Orchestration │ │ │ │ Job Submission · Priority Scheduling · Pipelines │ │ │ │ State Machines · Retry Logic · Multi-Step Chains │ │ │ └──────────────────────────┬───────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┼───────────────────────────┐ │ │ │ Inference as a Service │ │ │ │ Endpoint Registry · GPU Assignment · SLA Tracking │ │ │ └──────────────────────────┼───────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┼───────────────────────────┐ │ │ │ GPU-as-a-Service (GPUaaS) │ │ │ │ Cluster Registry · Node Provisioning · Metrics │ │ │ │ Fleet Health · GPU Status · Capacity Tracking │ │ │ └──────────────────────────┬───────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┴───────────────────────────┐ │ │ │ PostgreSQL (Neon) — Shared Data Layer │ │ │ │ clusters · nodes · gpus · jobs · tenants · billing │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
Core Components
3a. Landing Page & SEO Pages
File: public/index.html (landing), public/use-cases/*.html, public/blog/*.html
The public-facing marketing surface. Landing page drives early-access signups and demo requests. SEO pages target specific use-case keywords (GPU cluster management, AI infrastructure monitoring, multi-tenant GPU platform). Client-side GA4 integration tracks page views, signup events, and scroll depth.
Key routes: GET /, GET /use-cases/*, GET /blog/*
Sub-components: hero with demo request modal, feature cards, contact sales section, request demo modal (POST /api/demo-requests → demo_requests table → Polsia email notification)
3b. Admin Dashboard
File: public/admin.html — Single-page app (SPA) served as static HTML, client-side JS fetches from /api/dashboard, /api/clusters, /api/alerts, /api/billing.
Fleet operator's command center. Key panels:
| Panel | Data Source | Update Frequency |
|---|---|---|
| Fleet Overview | GET /api/dashboard | Real-time (polled) |
| Cluster Management | GET /api/clusters, POST /api/clusters | On-demand |
| Node & GPU Registry | GET /api/clusters/nodes, POST /api/clusters/gpus | On-demand |
| Tenant Management | GET /api/tenants, PUT /api/tenants/:id | On-demand |
| Job Scheduling | GET /api/jobs, scheduler daemon | 30s cycle |
| Alert Management | GET /api/alerts/rules, GET /api/alerts/events | 30s cycle |
| Revenue & Billing | GET /api/billing/summary, GET /api/tenants/:id/usage | On-demand |
| Analytics | GET /api/analytics, GET /api/dashboard/revenue | On-demand |
3c. Customer Dashboard
File: public/tenant.html — SPA served at /dashboard and /customer, auth-gated via localStorage session token. Tenants see only their own GPU allocations, jobs, and billing.
Key panels:
| Panel | Data Source | Description |
|---|---|---|
| GPU Allocations | GET /api/dashboard/gpu-allocation | My allocated GPUs across all jobs |
| Notebook Sessions | GET /api/jobs (filtered) | Interactive sessions I've started |
| AI Pods | GET /api/dashboard | My pod instances and status |
| Inference Endpoints | GET /api/clusters/gpus | My inference endpoint assignments |
| Workflows | GET /api/jobs | My submitted jobs and pipeline status |
| Billing | GET /api/billing/invoices, GET /api/tenants/:id/invoices | My invoices and payment history |
3d. REST API Layer (25+ Endpoints)
Location: routes/*.js — one Router per domain group. Entry point server.js mounts all at /api.
All endpoints are RESTful, JSON-only, and support API key authentication via Authorization: Bearer <key> header. Demo mode allows unauthenticated access for testing.
Endpoint groups:
| Router | Endpoints | Description |
|---|---|---|
| routes/clusters.js | 6 | Cluster CRUD, node/GPU registration, metrics ingestion |
| routes/jobs.js | 3 | Job submission, status update, listing |
| routes/tenants.js | 5 | Tenant CRUD, usage queries |
| routes/billing.js | 4 | Invoice creation, status updates, billing KPIs |
| routes/alerts.js | 4 | Rule management, event queries, enable/disable |
| routes/dashboard.js | 7 | KPI aggregates, revenue analytics, utilization, leakage |
| routes/api-keys.js | 4 | Generate, list, revoke API keys |
| routes/scheduler.js | 3 | Scheduler cycle trigger, status, logs |
| routes/analytics.js | 2 | Page view stats, signup/demorequest counts |
| routes/demo.js | 2 | Demo mode provisioning and status |
| routes/demo-requests.js | 1 | Demo request form submission |
| routes/misc.js | 2 | GPU rates, early access signup |
See LLD for complete endpoint specs with request/response schemas.
3e. Job Scheduling Daemon
Location: jobs/scheduler-cycle.js + routes/scheduler.js
Runs every 30 seconds. Priority-based scheduler with tenant quota enforcement. Key behaviors:
- Selects idle GPUs (status = 'idle') across all active clusters
- Orders queued jobs by
priorityascending (1 = highest) - For each job: checks tenant quota (
gpu_in_use + gpu_count ≤ gpu_quota) - Assigns idle GPUs to eligible jobs with
FOR UPDATE SKIP LOCKED(prevents double-assignment) - Jobs that fail quota checks stay
queuedor flip toblockedif the reason is quota - Emits
usage_eventsfor newly-completed jobs (GPU-hours consumed) - Logs every cycle to
scheduler_logs— audit trail of decisions
Deployment model: In-process setInterval guarded by POLSIA_IN_PROCESS_CRONS_ENABLED=true on Render. Blaxel shadow sets this to false; Blaxel triggers the same script via polsia.toml [[crons]].
// Guard pattern (server.js)
if (process.env.POLSIA_IN_PROCESS_CRONS_ENABLED === 'true') {
setInterval(runSchedulerCycle, 30000);
// Also runs on startup after 5s delay
}
3f. Fleet Alert System
Location: routes/alerts.js + db/alerts.js
Configurable threshold rules evaluated every 30s. Three built-in rule types:
| Rule Type | Condition | Severity |
|---|---|---|
| idle_gpu | >60% of cluster GPUs idle for 5+ minutes | Warning |
| quota_breach | >90% of any tenant's GPU quota in use | Warning |
| health_degraded | Node unreachable OR >20% GPUs in error state | Critical |
Alert events stored in alert_events with JSONB details. Notification via Polsia email proxy when rule has notification_enabled = true.
3g. Demo Mode & Mock Auth
Location: routes/demo.js, db/auth.js
Demo mode provisions a complete GPU cluster environment for evaluation without operator setup. The operator dashboard (/admin) runs in demo mode by default when no live clusters exist.
Mock auth: session tokens stored in localStorage. Admin role = any login to /admin. Tenant role = login to /dashboard. API key auth uses SHA-256 hashed keys stored in api_keys table — only the 12-char prefix shown in UI; full key shown once at creation.
Architecture Diagram
System Architecture
┌────────────────────────────────────────────────────┐
│ GPUForge Platform │
│ (Render Web Service) │
│ │
┌──────────────┐ │ ┌──────────────────────────────────────────────┐ │
│ GPU Cluster │ POST /api/clusters/nodes ┌──────────────────────────────────────┐ │
│ Agent/Node │────────────────────────────│ Express.js API │ │
│ │ POST /api/clusters/gpus │ │ │
│ H100/A100 │ POST /api/clusters/metrics │ /api/clusters → routes/clusters.js│ │
│ GPU Node │ │ /api/jobs → routes/jobs.js │ │
└──────┬───────┘ │ /api/tenants → routes/tenants.js │ │
│ Metrics │ /api/billing → routes/billing.js │ │
│ (util/temp/power) │ /api/alerts → routes/alerts.js │ │
└────────────────────────────────────│ /api/dashboard → routes/dashboard.js│ │
│ /api/scheduler → routes/scheduler.js│ │
┌──────────────┐ │ /api/api-keys → routes/api-keys.js │ │
│ Browser / │ GET /admin → admin.html │ /api/demo → routes/demo.js │ │
│ Web Client │ GET /dashboard → tenant.html│ /api/analytics → routes/analytics.js│ │
│ │ GET /login → login.html │ │ │
└──────┬───────┘ REST API calls └───────────────────────────────────────┘ │
│ │ │ │
│ │ │ │
│ ┌──────▼──────┐ ┌───▼──────────────────────────┐
│ │ PostgreSQL │ │ Job Scheduling Daemon │
│ │ (Neon) │ │ (30s polling, guarded cron) │
│ │ │ │ jobs/scheduler-cycle.js │
│ │ clusters │ └─────────────────────────────┘
│ │ nodes │
│ │ gpus │
│ │ gpu_metrics│
│ │ jobs │
│ │ tenants │
│ │ usage_events│
│ │ invoices │
│ │ alert_rules│
│ │ alert_events│
│ │ api_keys │
│ │ scheduler_logs│
│ └─────────────┘
│
│ External Integrations:
│ ├─ Stripe (payment links via Polsia Stripe proxy)
│ ├─ Polsia Email (alert notifications)
│ └─ Google Analytics 4 (client-side page views)
┌──────────────┐ ┌──────────────────────────────────────┐
│ Admin Plane │ │ Customer Plane │
│ /admin │ │ /dashboard /customer │
│ Fleet View │ │ My GPU Allocations │
│ Tenant Mgmt │ │ My Notebooks │
│ Scheduling │ │ My AI Pods │
│ Alerts │ │ My Billing │
└─────────────┘ └──────────────────────────────────────┘
Two Dashboard Planes
┌─────────────────────────────────────────────────────┐ │ GPUForge Deployment │ │ │ │ ┌───────────────────────────────────────────────┐ │ │ │ Admin Dashboard (/admin) │ │ │ │ │ │ │ │ [Fleet Overview] │ │ │ │ [Cluster Config] ← Operator sees │ │ │ │ [Tenant Mgmt] everything │ │ │ │ [Scheduling Ctrl] │ │ │ │ [Revenue/Billing] │ │ │ │ [Alert Mgmt] │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ │ ┌────────────┼────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────┐ ┌────────────┐ │ │ │ Tenant A │ │ Tenant B │ │ Tenant C │ │ │ │ /dashboard │ │ /dashboard│ │ /dashboard │ │ │ │ │ │ │ │ │ │ │ │ My GPUs │ │ My GPUs │ │ My GPUs │ │ │ │ My Jobs │ │ My Jobs │ │ My Jobs │ │ │ │ My Billing │ │ My Billing│ │ My Billing │ │ │ └──────────────┘ └──────────┘ └────────────┘ │ └─────────────────────────────────────────────────────┘
Data Flow Diagrams
Login Flow → Role Routing → Dashboard
User navigates to /admin or /dashboard
│
▼
[Login Page] /login
│
├─ Admin login → session set (localStorage) → redirect /admin
└─ Tenant login → session set (localStorage) → redirect /dashboard
│
▼
SPA loads, reads localStorage session token
│
▼
SPA calls GET /api/dashboard
│
▼
Express middleware (apiKeyAuth)
│
├─ No Authorization header → Demo mode: serves all data (no auth gate)
├─ Valid Bearer key → lookupApiKey() → attach req.apiKey → next()
└─ Invalid/revoked → 401 error response
│
▼
Route handler executes (e.g. GET /api/dashboard)
│
▼
db/ query functions (db/dashboard.js, db/clusters.js, etc.)
│
▼
pool.query() against Neon PostgreSQL
│
▼
JSON response returned to SPA
│
▼
SPA updates DOM (charts, tables, status cards)
Job Lifecycle Flow
1. SUBMIT
POST /api/jobs { cluster_id, name, gpu_count, gpu_type, priority, tenant_id }
│
▼
Inline quota check (routes/jobs.js):
SELECT gpu_quota FROM tenants WHERE id = tenant_id
SELECT SUM(gpu_count) FROM jobs WHERE tenant_id = X AND status = 'running'
│
├─ If current + requested > quota → job status = 'blocked', block_reason set
├─ If cluster idle_gpus < requested → job status = 'blocked', block_reason set
└─ Otherwise → job status = 'queued'
│
▼
INSERT INTO jobs (...)
2. SCHEDULE (every 30s)
Scheduler daemon runs runSchedulerCycle()
│
▼
SELECT idle GPUs: SELECT * FROM gpus WHERE status = 'idle' FOR UPDATE SKIP LOCKED
│
▼
SELECT queued jobs: SELECT * FROM jobs WHERE status = 'queued' ORDER BY priority ASC
│
▼
For each job:
Check: tenant.gpu_in_use + job.gpu_count <= tenant.gpu_quota
│
├─ YES → Assign N idle GPUs (set gpus.job_id, gpus.status = 'active')
Update job.status = 'running'
└─ NO → Leave as queued (or 'blocked' if quota was reason)
│
▼
INSERT INTO scheduler_logs (cycle_summary)
INSERT INTO usage_events (for completed jobs: gpu_hours × rate)
3. COMPLETE
PATCH /api/jobs/:id { status: 'completed' }
│
▼
Free allocated GPUs: UPDATE gpus SET job_id = NULL, status = 'idle'
Emit usage_event: INSERT INTO usage_events (tenant, gpu_type, gpu_hours, cost)
Metrics Ingestion Flow
GPU Node agent runs on hardware, posts metrics every 60s:
POST /api/clusters/metrics
Authorization: Bearer gf_<cluster_api_key>
Body: { metrics: [{ gpu_id, utilization_pct, memory_used_mb,
memory_total_mb, temperature_c, power_draw_w }, ...] }
│
▼
apiKeyAuth middleware validates cluster API key
│
▼
Bulk INSERT INTO gpu_metrics (recorded_at = NOW())
│
▼
For each GPU in payload:
UPDATE gpus SET status =
CASE WHEN utilization_pct > 10 THEN 'active' ELSE 'idle' END
WHERE id = gpu_id
│
▼
Alert engine evaluates rules (every 30s):
idle_gpu rule: COUNT(idle gpus) / total_gpus > threshold?
health_degraded rule: temperature_c > 90 OR node unreachable?
│
├─ Threshold crossed → INSERT INTO alert_events
└─ notification_enabled → Polsia email proxy fires alert email
Billing Flow
1. USAGE CAPTURE (scheduler emits on job completion)
INSERT INTO usage_events (tenant_id, gpu_type, gpu_hours, cost)
│
▼
2. INVOICE GENERATION
POST /api/tenants/:id/invoice (or POST /api/billing/invoice/:tenantId)
│
▼
Aggregate usage_events for tenant (last billing period)
Calculate: SUM(gpu_hours × rate) = total_amount
│
▼
Polsia Stripe proxy → create Stripe payment link
INSERT INTO invoices (tenant_id, amount, status = 'pending', stripe_payment_url)
│
▼
3. PAYMENT
Tenant clicks Stripe payment link → pays on Stripe.com
Polsia Stripe proxy → webhook / confirmation
UPDATE invoices SET status = 'paid' WHERE id = invoice_id
│
▼
4. RECONCILIATION
GET /api/billing/summary → operator sees: revenue, outstanding, paid invoices
GET /api/tenants/:id/invoices → tenant sees payment history
Database Schema
Neon PostgreSQL. All DDL in migrations/. Named query functions in db/<entity>.js. Only db/index.js constructs new Pool().
Entity-Relationship Summary
clusters ────────── 1:N ────────── nodes
│ │
│ 1:N
│ │
└──── 1:N ────────── gpus ◄────────┘
│
│ N:1
│
┌────────────┼────────────┐
▼ ▼ ▼
jobs gpu_metrics api_keys
│ │
│ N:1 │ N:1
▼ ▼
tenants ◄────────────────── tenants
│
│ 1:N
▼
usage_events ──N:1── invoices
│
│ 1:N (via tenant)
▼
alert_events ◄──N:1── alert_rules
Supporting: scheduler_logs, early_access_signups,
page_views, demo_requests
Core Tables
| Table | Key Columns | Purpose |
|---|---|---|
| clusters | id, name, region, api_key, status, created_at | GPU cluster registry |
| nodes | id, cluster_id, hostname, ip_address, total_gpus, status, cpu_cores, ram_gb | Compute nodes within clusters |
| gpus | id, node_id, gpu_index, model, vram_mb, status, job_id | Individual GPUs; job_id tracks current assignment |
| gpu_metrics | id, gpu_id, utilization_pct, memory_used_mb, memory_total_mb, temperature_c, power_draw_w, recorded_at | Time-series GPU telemetry |
| jobs | id, cluster_id, tenant_id, name, gpu_count, gpu_type, priority, status, block_reason, submitted_at, started_at, completed_at | GPU workload queue |
| tenants | id, name, contact_email, plan_tier, gpu_quota, status | Multi-tenant orgs with GPU quota limits |
| gpu_rates | id, gpu_type, rate_per_hour | Rate card for billing (e.g. H100 = $3.50/hr) |
| usage_events | id, tenant_id, gpu_type, gpu_hours, cost, recorded_at | Immutable billing log |
| invoices | id, tenant_id, amount, status, stripe_payment_url, created_at, paid_at | Stripe-linked billing records |
| alert_rules | id, rule_type, threshold, enabled, notification_enabled, created_at | Configurable fleet alert thresholds |
| alert_events | id, rule_id, cluster_id, details (JSONB), fired_at, resolved_at | Fired alert instances |
| api_keys | id, key_hash (SHA-256), key_prefix, tenant_id, permissions, created_at, revoked_at | API key registry with SHA-256 hashed storage |
| scheduler_logs | id, cycle_at, jobs_evaluated, jobs_started, jobs_blocked, gpu_assigned_count | Scheduler cycle audit trail |
See LLD for full schema with all columns, types, constraints, and indexes.
Technology Stack
Backend
| Component | Technology | Version | Purpose |
|---|---|---|---|
| Runtime | Node.js | 18.x (.nvmrc) | JavaScript runtime |
| Framework | Express.js | 4.x | HTTP server, routing, middleware |
| Database | PostgreSQL (Neon) | 15+ | Primary data store |
| DB Client | pg (node-postgres) | 8.x | PostgreSQL driver with connection pooling |
| Migrations | node-pg-migrate | 6.x | Declarative SQL migrations |
| Scheduler | setInterval (built-in) | — | 30s job scheduler (guarded) |
Frontend
| Component | Technology | Purpose |
|---|---|---|
| Templates | EJS | Server-rendered HTML pages |
| Static SPA | Vanilla JS + HTML/CSS | Dashboard: admin.html, tenant.html, login.html |
| Styling | Custom CSS (Space Grotesk + JetBrains Mono fonts) | Dark-theme GPU operator aesthetic |
| Charts | Chart.js (CDN) | Revenue, utilization, allocation charts |
| Analytics | GA4 (client-side) | Page views, signup events, scroll depth |
Infrastructure & Deployment
| Component | Technology | Purpose |
|---|---|---|
| Hosting | Render | Web service (Node.js), auto-deploy from GitHub |
| Database | Neon (Managed PostgreSQL) | Branched dev DB, production DB, connection pooling |
| CI/CD | GitHub + Render auto-deploy | Push to main → deploy to Render |
| File Storage | Polsia R2 | Image assets, generated files |
| Payments | Polsia Stripe Proxy | Payment links without key management |
| Polsia Email Proxy | Alert notifications, demo request emails |
Project Structure
gpuforge/ ├── server.js # Entry point (172 lines — wiring only) ├── routes/ │ ├── clusters.js # Cluster, node, GPU registry + metrics │ ├── jobs.js # Job submission + status update │ ├── tenants.js # Tenant CRUD + usage queries │ ├── billing.js # Invoice management + billing KPIs │ ├── alerts.js # Alert rules + event queries │ ├── dashboard.js # KPI aggregates, analytics │ ├── api-keys.js # Key generation + revocation │ ├── scheduler.js # Scheduler cycle trigger + logs │ ├── analytics.js # Page view stats │ ├── demo.js # Demo mode provisioning │ ├── demo-requests.js # Demo request form handler │ └── misc.js # GPU rates, early access signup ├── db/ │ ├── index.js # new Pool() — only DB access point │ ├── clusters.js # Named query functions │ ├── jobs.js │ ├── tenants.js │ ├── billing.js │ ├── alerts.js │ ├── dashboard.js │ ├── api-keys.js # SHA-256 key hashing + lookup │ ├── analytics.js │ ├── auth.js # API key lookup + touchLastUsed │ └── demo-requests.js ├── public/ │ ├── index.html # Landing page │ ├── admin.html # Admin SPA │ ├── tenant.html # Customer SPA │ ├── login.html # Auth page │ ├── pitch.html # Pitch one-pager │ ├── docs-hld.html # This document │ ├── docs-lld.html # Low-Level Design │ ├── docs-arch.html # Architecture deep-dive │ ├── docs.html # API reference │ ├── docs-hub.html # Docs hub page │ └── use-cases/ # SEO landing pages ├── migrations/ # node-pg-migrate SQL files ├── jobs/ │ └── scheduler-cycle.js # Standalone scheduler runner ├── migrate.js # Migration runner (npm run migrate) ├── polsia.toml # Cron declarations for Blaxel └── render.yaml # Render service config
Non-Functional Requirements
Performance Targets
| Metric | Target | Current | Measurement |
|---|---|---|---|
| API Response Time (p95) | < 200ms | ~80ms avg | Render latency logs |
| Dashboard Load | < 1.5s | ~900ms | Browser performance |
| Scheduler Cycle Duration | < 2s for 500 jobs | ~400ms for 50 jobs | scheduler_logs.cycle_duration_ms |
| DB Query Time (p99) | < 100ms | ~25ms avg | Neon console |
| Concurrent Connections | 50+ simultaneous | Pool limit: 10 | pg Pool config |
| Metrics Ingestion | 1,000 GPU metrics/min | Tested at 500/min | Load test |
Security Model
Current (v1): Mock auth with localStorage session tokens. API keys stored as SHA-256 hashes (bcrypt-ready). No production auth integration yet.
Production auth roadmap: SSO/SAML for operator accounts, JWT-based tenant auth, RBAC with per-tenant permission scoping, OAuth2 integration for cloud provider credentials.
| Surface | Current | Target |
|---|---|---|
| Dashboard auth | localStorage session (mock) | JWT + SSO / SAML |
| API key storage | SHA-256 hashed (bcrypt-ready) | Argon2 / bcrypt |
| SQL injection | Parameterized queries only | Continue — no inline SQL |
| XSS | EJS auto-escapes; client JS sanitizes | Continue + CSP headers |
| Rate limiting | None (v1) | per-tenant rate limits |
| Audit logs | scheduler_logs + page_views | Full access log per entity |
Scalability Approach
Single-web-service architecture on Render. Scales horizontally via Render auto-scaling (CPU-based). Key design decisions for scale:
- Connection pooling: pg Pool (10 connections) shared across all route handlers. Neon handles branching and pooling at the DB layer.
- Scheduler distribution: The 30s scheduler runs in-process. For multi-instance deployment,
FOR UPDATE SKIP LOCKEDprevents duplicate GPU assignments. Blaxel will handle cron distribution. - Metrics ingestion: Bulk INSERT (
INSERT ... ON CONFLICT DO UPDATE) for GPU metrics — single round-trip per batch. - Read scaling: Neon read replica endpoint available for read-heavy dashboards.
- Horizontal scale ceiling: Current architecture supports ~50 concurrent operators, ~200 tenants, ~10,000 GPUs per instance before needing a service split.
Availability & Reliability
| Concern | Mitigation |
|---|---|
| Render instance crash | Auto-restart via Render; stateless Express app recovers cleanly |
| Database connection drop | pg Pool auto-reconnect; Neon connection pooler handles transient failures |
| Scheduler missed cycle | Each cycle logs to scheduler_logs; Blaxel cron ensures no missed cycles |
| Alert notification failure | Alert events stored in DB regardless; notification is best-effort |
| Stripe webhook miss | Invoice status update on payment link open; webhook confirmation on return |
Deployment Architecture
Deploy Pipeline
Developer pushes to GitHub (Polsia-Inc/gpuforge)
│
▼
GitHub webhook → Render auto-deploy trigger
│
▼
Render Build:
1. npm install
2. npm run migrate ← runs migrate.js against Neon DB
3. npm start ← starts server.js
│
▼
Render starts Express on PORT (set by Render)
Health check: GET /health → { status: 'ok' }
│
▼
Blaxel shadow receives same deploy
Blaxel sets POLSIA_IN_PROCESS_CRONS_ENABLED = false
Blaxel syncs [[crons]] from polsia.toml
│
▼
Live at https://gpuforge.polsia.app
│
▼
Ongoing: every push to main triggers re-deploy
Render Web Service
| Setting | Value | Notes |
|---|---|---|
| Start Command | npm run migrate && npm start | Runs migrations before starting server |
| Health Check | GET /health | Returns { status: 'ok' } — Render probes this every 30s |
| Environment | DATABASE_URL, PORT, POLSIA_IN_PROCESS_CRONS_ENABLED=true | Neon connection string + cron guard |
| Instance Type | Render Starter (can upgrade) | 512MB RAM, shared CPU |
Neon Managed PostgreSQL
| Feature | Value | Notes |
|---|---|---|
| Connection | DATABASE_URL env var | Connection pooler included — no manual pooling needed |
| Branching | Dev branches from main | Safe dev testing without touching production data |
| Schema management | node-pg-migrate migrations | All DDL in migrations/ directory, named timestamp files |
| Connection limit | 60 (Neon Pro plan) | pg Pool uses 10; sufficient headroom for concurrent requests |
CI/CD via Polsia
Polsia's infra pipeline manages the full delivery flow:
- GitHub integration: Repo provisioned and connected to Render auto-deploy
- Render management: Service creation, env var management, log access via
polsia_infraMCP - Database provisioning: Neon database created and linked on first provision
- Shadow deployment: Blaxel shadow receives every deploy for pre-production validation
- Cron sync:
polsia.toml [[crons]]entries synced to Blaxel as disabled triggers
Integration Points
Stripe Billing
Integration: Polsia Stripe proxy — no Stripe keys on GPUForge. All payment link creation goes through https://polsia.com/api/proxy/stripe/payment-link.
Flow: POST /api/tenants/:id/invoice → aggregates usage events → calls Polsia Stripe proxy → receives payment URL → stores in invoices.stripe_payment_url → tenant receives link, pays on Stripe → webhook confirms → invoice marked paid.
Future: Subscription billing (monthly plans), usage-based billing (auto-invoice monthly), webhook retry handling.
API Key Management
Storage: Full key hashed with SHA-256 before storage. Only 12-char prefix shown in UI. Full key shown once at creation, stored by user.
Endpoints: POST /api/api-keys (generate), GET /api/api-keys (list), DELETE /api/api-keys/:id (revoke), GET /api/api-keys/demo (get demo key for testing).
Auth middleware: Validates Authorization: Bearer <key> header against api_keys.key_hash. Revoked keys (revoked_at IS NOT NULL) return 401. Last used timestamp updated on every valid call.
Kubernetes / SLURM Integration (Future)
Integration point 1 — Kubernetes: GPU operators running k8s clusters can deploy a GPUForge node agent as a DaemonSet. The agent registers nodes and posts metrics via POST /api/clusters/nodes and POST /api/clusters/metrics. The scheduler daemon then orchestrates GPU allocation across the k8s cluster via the same job assignment API.
Integration point 2 — SLURM: On-premise HPC clusters running SLURM can integrate via a GPUForge SLURM plugin that translates SLURM job events into GPUForge job records. GPU allocation data flows back to SLURM via the node agent.
Integration point 3 — Ray / vLLM: Ray clusters and vLLM inference servers can register with GPUForge as special cluster types. GPUForge treats them as managed GPU pools with pre-assigned capacity.
Polsia Platform Integrations
| Service | Polsia Endpoint | Usage in GPUForge |
|---|---|---|
| Stripe | polsia.com/api/proxy/stripe/payment-link | Invoice payment links |
polsia.com/api/proxy/email/send | Alert notifications, demo request emails | |
| R2 Storage | POLSIA_R2_BASE_URL | Image uploads, generated assets |
| Twitter/X | Shared @polsia account | Marketing posts (2/day limit) |
| Agent SDK | .claude/skills/agent-sdk/SKILL.md | Autonomous agent features (agentic workflows) |
Glossary
| Term | Definition |
|---|---|
| GPU Quota | Maximum GPUs a tenant can consume simultaneously across all their running jobs. |
| GPU Allocation | The specific GPUs assigned to a running job. Tracked in gpus.job_id. |
| Blocked Job | A queued job that cannot start because capacity or quota is insufficient. Shows block_reason. |
| Revenue Leakage | GPU time consumed but not billed — either idle allocated GPUs or unmetered compute. Detected via GET /api/dashboard/leakage. |
| Scheduler Cycle | One iteration of the 30-second scheduler — evaluates all queued jobs and assigns available GPUs. Logged to scheduler_logs. |
| MIG | Multi-Instance GPU — NVIDIA A100 GPU virtualization allowing fractional GPU slices per tenant. |
| API Key Prefix | First 12 characters of an API key shown in the UI. The full key is shown once at creation and never stored in plaintext. |
| FOR UPDATE SKIP LOCKED | PostgreSQL row-level locking option that makes concurrent scheduler runs skip already-assigned GPUs instead of waiting. |
| GPUaaS | GPU-as-a-Service — the core infrastructure layer managing GPU fleet lifecycle. |
| Blaxel Shadow | GPUForge's shadow deployment on Blaxel. Receives every deploy for pre-production validation. Scheduler disabled via POLSIA_IN_PROCESS_CRONS_ENABLED=false. |
| Neon | Managed PostgreSQL cloud database with serverless branching and built-in connection pooling. |
Related Documents
| Document | URL | Scope |
|---|---|---|
| Low-Level Design (LLD) | /docs/lld | Full database schema (16 tables), ER diagram, state machines, all API endpoint specs with request/response schemas, module-level design, security model |
| Architecture Deep-Dive | /docs/architecture | System-level architecture decisions, scaling considerations, technology choices with rationale |
| API Reference | /docs | Interactive endpoint documentation with curl examples and response schemas |
| Docs Hub | /docs/hub | Card-based navigation to all documentation pages |
| CLAUDE.md | /gpuforge/CLAUDE.md | Internal developer reference: stack, directory map, database tables, external integrations, recent changes |