Initial Aurora Core 0.1 implementation
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
Production-grade media metadata backend per docs/aurora-core-spec.md: Aurora-owned UUIDv7 identity system, normalized movie/TV/people/credits schema, provider abstraction with a TMDb adapter, an idempotent async ingestion pipeline (normalize -> match -> upsert -> provenance), a Postgres FTS/trigram search layer, a stateless FastAPI v1 API with Redis caching/rate-limiting, arq workers, dev/prod Docker Compose topologies, unit + integration + load tests, CI, and architecture/ database/API/ERD documentation. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"""Load test for Aurora Core 0.1 (section 30/2).
|
||||
|
||||
Simulates the read-heavy interactive traffic Core 0.1 targets: searching,
|
||||
opening movie/series/person detail pages, and paging through search
|
||||
results. Run against a seeded environment (ingest a reasonable movie/series/
|
||||
person catalog first — an empty database will pass load tests trivially but
|
||||
tell you nothing about real query cost).
|
||||
|
||||
Usage:
|
||||
|
||||
pip install locust
|
||||
locust -f tests/load/locustfile.py --host http://localhost:8000
|
||||
|
||||
Then open http://localhost:8089 and start a run with up to 500 users to
|
||||
verify the section 2 targets (search p95 < 500ms, other endpoints p95 <
|
||||
250ms, 5xx rate < 0.1%). Locust reports p50/p95/p99 latency and failure
|
||||
rate directly; compare them against docs/architecture.md's targets.
|
||||
|
||||
See docs/architecture.md for how to seed sample data before running this.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
||||
from locust import HttpUser, between, task
|
||||
|
||||
SEARCH_TERMS = [
|
||||
"matrix",
|
||||
"star wars",
|
||||
"alien",
|
||||
"the office",
|
||||
"breaking bad",
|
||||
"keanu reeves",
|
||||
"christopher nolan",
|
||||
"marvel",
|
||||
"batman",
|
||||
"inception",
|
||||
]
|
||||
|
||||
|
||||
class AuroraCoreUser(HttpUser):
|
||||
"""A typical interactive client: search, then drill into a result."""
|
||||
|
||||
wait_time = between(1, 3)
|
||||
|
||||
def on_start(self) -> None:
|
||||
self._known_movie_ids: list[str] = []
|
||||
self._known_series_ids: list[str] = []
|
||||
self._known_person_ids: list[str] = []
|
||||
|
||||
@task(5)
|
||||
def search(self) -> None:
|
||||
query = random.choice(SEARCH_TERMS)
|
||||
limit = random.choice([5, 10, 20])
|
||||
with self.client.get(
|
||||
f"/api/v1/search?q={query}&limit={limit}",
|
||||
name="/api/v1/search",
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code != 200:
|
||||
response.failure(f"search failed: {response.status_code}")
|
||||
return
|
||||
body = response.json()
|
||||
self._known_movie_ids.extend(m["id"] for m in body.get("movies", []))
|
||||
self._known_series_ids.extend(s["id"] for s in body.get("series", []))
|
||||
self._known_person_ids.extend(p["id"] for p in body.get("people", []))
|
||||
# Keep the pools bounded so memory doesn't grow unbounded over a
|
||||
# long run.
|
||||
self._known_movie_ids = self._known_movie_ids[-200:]
|
||||
self._known_series_ids = self._known_series_ids[-200:]
|
||||
self._known_person_ids = self._known_person_ids[-200:]
|
||||
|
||||
@task(4)
|
||||
def open_movie(self) -> None:
|
||||
if not self._known_movie_ids:
|
||||
return
|
||||
movie_id = random.choice(self._known_movie_ids)
|
||||
self.client.get(f"/api/v1/movies/{movie_id}", name="/api/v1/movies/[id]")
|
||||
self.client.get(
|
||||
f"/api/v1/movies/{movie_id}/credits", name="/api/v1/movies/[id]/credits"
|
||||
)
|
||||
|
||||
@task(2)
|
||||
def open_series(self) -> None:
|
||||
if not self._known_series_ids:
|
||||
return
|
||||
series_id = random.choice(self._known_series_ids)
|
||||
self.client.get(f"/api/v1/series/{series_id}", name="/api/v1/series/[id]")
|
||||
self.client.get(
|
||||
f"/api/v1/series/{series_id}/seasons", name="/api/v1/series/[id]/seasons"
|
||||
)
|
||||
|
||||
@task(2)
|
||||
def open_person(self) -> None:
|
||||
if not self._known_person_ids:
|
||||
return
|
||||
person_id = random.choice(self._known_person_ids)
|
||||
self.client.get(f"/api/v1/people/{person_id}", name="/api/v1/people/[id]")
|
||||
|
||||
@task(1)
|
||||
def health_probe(self) -> None:
|
||||
self.client.get("/health", name="/health")
|
||||
Reference in New Issue
Block a user