Skip to content

Commit 973e247

Browse files
committed
feat: release v0.1.0, add load testing suite, K8s monitoring stack, and developer docs
1 parent b1a41ab commit 973e247

12 files changed

Lines changed: 742 additions & 8 deletions

.release-please-manifest.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
".": "0.0.1",
3-
"apps/api": "0.0.1",
4-
"apps/web": "0.0.1"
2+
".": "0.1.0",
3+
"apps/api": "0.1.0",
4+
"apps/web": "0.1.0"
55
}

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Changelog
2+
3+
All notable changes to the Inferr platform will be documented in this file.
4+
5+
## [v0.1.0] - 2026-07-26
6+
7+
### 🚀 Public Release Milestone
8+
9+
#### ☸️ Infrastructure & Deployment
10+
- **Kubernetes Deployment Manifests**: Added full Kubernetes configuration files (`k8s/` and `kubernetes/`) supporting `Deployment`, `Service`, `Ingress`, `ConfigMap`, `Secret`, and `CronJob` workloads.
11+
- **Minikube Support & Local Cluster Docs**: Created local cluster setup guides and architecture diagrams for containerized deployment.
12+
13+
#### 🤖 AI & Observability
14+
- **Langfuse LLM Observability**: Integrated Langfuse for real-time RAG execution tracing, query rewrite monitoring, cost/token metrics, and prompt versioning.
15+
- **OpenTelemetry (OTel) Integration**: Added Node SDK auto-instrumentation and OTLP trace exporter for system-wide HTTP and database query tracing.
16+
17+
#### 🔌 Model Context Protocol (MCP) Server
18+
- **MCP Tool Suite**: Exposed platform actions (Tech Market reports, user interest management, article bookmarks) as standard MCP tools.
19+
- **Stateless OAuth 2.1 Server**: Implemented OAuth 2.1 authorization server endpoints for secure agent integration.
20+
21+
#### ⚡ Reliability & Performance
22+
- **API Load Test Suite**: Implemented `autocannon` load testing suite in `apps/api/test/load-test.ts` with `pnpm test:load` command.
23+
- **Scraper Hardening**: Added exponential retry logic, execution audit logging, and missed-job catchup schedulers.
24+
- **Article Retention**: Switched article retention from hard limits to 45-day age-based pruning.

apps/api/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ai-developer-feed/api",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "NestJS backend API",
55
"author": "",
66
"private": true,
@@ -18,6 +18,7 @@
1818
"test:cov": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
1919
"test:debug": "node --experimental-vm-modules --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/jest/bin/jest.js --runInBand",
2020
"test:e2e": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config ./test/jest-e2e.json",
21+
"test:load": "tsx test/load-test.ts",
2122
"db:generate": "drizzle-kit generate",
2223
"db:migrate": "drizzle-kit migrate",
2324
"db:seed": "ts-node -r tsconfig-paths/register src/db/seed.ts",
@@ -68,11 +69,13 @@
6869
"@nestjs/cli": "^11.0.0",
6970
"@nestjs/schematics": "^11.0.0",
7071
"@nestjs/testing": "^11.0.1",
72+
"@types/autocannon": "^7.12.7",
7173
"@types/express": "^5.0.0",
7274
"@types/jest": "^30.0.0",
7375
"@types/node": "^24.0.0",
7476
"@types/passport-google-oauth20": "^2.0.17",
7577
"@types/supertest": "^7.0.0",
78+
"autocannon": "^8.0.0",
7679
"jest": "^30.0.0",
7780
"source-map-support": "^0.5.21",
7881
"supertest": "^7.0.0",
@@ -126,7 +129,6 @@
126129
"functions": 75,
127130
"statements": 85
128131
},
129-
130132
"./src/scraper/scraper.service.ts": {
131133
"lines": 35,
132134
"functions": 35,

apps/api/test/load-test.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import autocannon from 'autocannon';
2+
3+
interface TestConfig {
4+
name: string;
5+
url: string;
6+
method?: 'GET' | 'POST';
7+
duration?: number;
8+
connections?: number;
9+
pipelining?: number;
10+
headers?: Record<string, string>;
11+
body?: string;
12+
expectedMinRps?: number;
13+
expectedMaxP95Ms?: number;
14+
}
15+
16+
const BASE_URL = process.env.API_URL || 'http://localhost:3001';
17+
const DURATION = parseInt(process.env.LOAD_TEST_DURATION || '10', 10);
18+
19+
const scenarios: TestConfig[] = [
20+
{
21+
name: 'GET /health Baseline Load Test',
22+
url: `${BASE_URL}/health`,
23+
method: 'GET',
24+
duration: DURATION,
25+
connections: 50,
26+
pipelining: 1,
27+
expectedMinRps: 100,
28+
expectedMaxP95Ms: 200,
29+
},
30+
{
31+
name: 'GET /feed Query Load Test',
32+
url: `${BASE_URL}/feed`,
33+
method: 'GET',
34+
duration: DURATION,
35+
connections: 20,
36+
pipelining: 1,
37+
expectedMinRps: 10,
38+
expectedMaxP95Ms: 500,
39+
},
40+
{
41+
name: 'GET /articles Listing Load Test',
42+
url: `${BASE_URL}/articles`,
43+
method: 'GET',
44+
duration: DURATION,
45+
connections: 20,
46+
pipelining: 1,
47+
expectedMinRps: 10,
48+
expectedMaxP95Ms: 500,
49+
},
50+
{
51+
name: 'Throttler / Rate Limit Gate Test',
52+
url: `${BASE_URL}/health`,
53+
method: 'GET',
54+
duration: 5,
55+
connections: 100,
56+
pipelining: 2,
57+
},
58+
];
59+
60+
async function runScenario(scenario: TestConfig): Promise<boolean> {
61+
console.log(`\n==================================================`);
62+
console.log(`Running Scenario: ${scenario.name}`);
63+
console.log(`URL: ${scenario.url} (${scenario.method || 'GET'})`);
64+
console.log(`Connections: ${scenario.connections}, Duration: ${scenario.duration}s`);
65+
console.log(`==================================================`);
66+
67+
return new Promise((resolve) => {
68+
const instance = autocannon({
69+
url: scenario.url,
70+
method: scenario.method || 'GET',
71+
connections: scenario.connections || 10,
72+
duration: scenario.duration || 10,
73+
pipelining: scenario.pipelining || 1,
74+
headers: scenario.headers,
75+
body: scenario.body,
76+
});
77+
78+
autocannon.track(instance, { renderProgressBar: false });
79+
80+
instance.on('done', (result) => {
81+
console.log(`\n📊 Results for ${scenario.name}:`);
82+
console.log(` Requests/sec (RPS): ${result.requests.average.toFixed(2)}`);
83+
console.log(` Latency Average: ${result.latency.average.toFixed(2)} ms`);
84+
console.log(` Latency P50: ${result.latency.p50} ms`);
85+
console.log(` Latency P95: ${result.latency.p95} ms`);
86+
console.log(` Latency P99: ${result.latency.p99} ms`);
87+
console.log(` Total Requests: ${result.requests.total}`);
88+
console.log(` 2xx Successes: ${result['2xx']}`);
89+
console.log(` 4xx Rate Limits: ${result['4xx']}`);
90+
console.log(` 5xx Errors: ${result['5xx']}`);
91+
console.log(` Non-2xx Responses: ${result.non2xx}`);
92+
93+
let passed = true;
94+
if (result['5xx'] > 0) {
95+
console.error(`❌ FAILED: Received ${result['5xx']} 5xx server errors!`);
96+
passed = false;
97+
}
98+
99+
if (scenario.expectedMinRps && result.requests.average < scenario.expectedMinRps) {
100+
console.warn(
101+
`⚠️ WARNING: RPS (${result.requests.average.toFixed(2)}) below target threshold of ${scenario.expectedMinRps}`,
102+
);
103+
}
104+
105+
if (scenario.expectedMaxP95Ms && result.latency.p95 > scenario.expectedMaxP95Ms) {
106+
console.warn(
107+
`⚠️ WARNING: P95 Latency (${result.latency.p95}ms) exceeded target threshold of ${scenario.expectedMaxP95Ms}ms`,
108+
);
109+
}
110+
111+
resolve(passed);
112+
});
113+
});
114+
}
115+
116+
async function main() {
117+
console.log(`🚀 Starting Inferr API Load Test Suite`);
118+
let allPassed = true;
119+
120+
for (const scenario of scenarios) {
121+
const passed = await runScenario(scenario);
122+
if (!passed) {
123+
allPassed = false;
124+
}
125+
}
126+
127+
console.log(`\n==================================================`);
128+
if (allPassed) {
129+
console.log(`✅ All load test scenarios completed cleanly with 0 server errors.`);
130+
process.exit(0);
131+
} else {
132+
console.error(`❌ Load test suite completed with failures/errors.`);
133+
process.exit(1);
134+
}
135+
}
136+
137+
main().catch((err) => {
138+
console.error('Fatal load test error:', err);
139+
process.exit(1);
140+
});

apps/web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ai-developer-feed/web",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "Next.js frontend",
55
"private": true,
66
"scripts": {

docs/KUBERNETES_MONITORING.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Kubernetes Monitoring & Logging Architecture
2+
3+
This guide covers setting up and operating observability tools (Prometheus, Grafana, Loki, Promtail, and OpenTelemetry) within the Inferr Kubernetes cluster.
4+
5+
---
6+
7+
## 🏗️ Observability Stack Architecture
8+
9+
```
10+
┌───────────────────┐
11+
│ Grafana (UI) │
12+
│ Port: 3000 │
13+
└─────────┬─────────┘
14+
15+
┌────────────────────┴────────────────────┐
16+
▼ ▼
17+
┌─────────────────────┐ ┌─────────────────────┐
18+
│ Prometheus │ │ Grafana Loki │
19+
│ (Metrics Engine) │ │ (Log Aggregator) │
20+
└──────────▲──────────┘ └──────────▲──────────┘
21+
│ │
22+
┌──────────┴──────────┐ ┌──────────┴──────────┐
23+
│ Inferr API Pods │ │ Promtail DaemonSet │
24+
│ (OpenTelemetry / │ │ (Node Log Scraper) │
25+
│ /metrics Endpoint) │ └─────────────────────┘
26+
└─────────────────────┘
27+
```
28+
29+
---
30+
31+
## 🚀 1. Deploying Monitoring Stack
32+
33+
Deploy the namespace, Prometheus, and Grafana:
34+
35+
```bash
36+
kubectl apply -f kubernetes/monitoring/prometheus-grafana.yaml
37+
```
38+
39+
Deploy Loki and Promtail log collector daemonset:
40+
41+
```bash
42+
kubectl apply -f kubernetes/monitoring/loki-promtail.yaml
43+
```
44+
45+
Verify all pods are running in the `monitoring` namespace:
46+
47+
```bash
48+
kubectl get pods -n monitoring
49+
```
50+
51+
---
52+
53+
## 📊 2. Accessing Grafana Dashboards
54+
55+
Port-forward the Grafana service to local machine:
56+
57+
```bash
58+
kubectl port-forward svc/grafana-service 3000:3000 -n monitoring
59+
```
60+
61+
Open browser at `http://localhost:3000`:
62+
- **Default Username:** `admin`
63+
- **Default Password:** `admin`
64+
65+
---
66+
67+
## 🔍 3. Data Source Configuration
68+
69+
### Prometheus Metrics Data Source
70+
1. Navigate to **Configuration > Data Sources > Add Data Source**.
71+
2. Select **Prometheus**.
72+
3. Set URL to `http://prometheus-service.monitoring.svc.cluster.local:9090`.
73+
4. Click **Save & Test**.
74+
75+
### Loki Logs Data Source
76+
1. Navigate to **Configuration > Data Sources > Add Data Source**.
77+
2. Select **Loki**.
78+
3. Set URL to `http://loki-service.monitoring.svc.cluster.local:3100`.
79+
4. Click **Save & Test**.
80+
81+
---
82+
83+
## 📈 4. Core Metrics Tracked
84+
85+
| Metric | Target / Description |
86+
|---|---|
87+
| `http_requests_total` | Total HTTP requests handled by API pods |
88+
| `http_request_duration_seconds` | Latency distribution (p50, p95, p99) |
89+
| `process_cpu_seconds_total` | CPU utilization per pod replica |
90+
| `process_resident_memory_bytes` | Node.js Heap & Resident Memory |
91+
| `pg_pool_active_connections` | Drizzle / Postgres pool active connections |

0 commit comments

Comments
 (0)