perf: migrate hot-path JSON serialization to orjson

stdlib json was FastAPI's default. Every response body, every SSE frame,
and every add_log/state/payload write paid the stdlib encode cost.

- pyproject.toml: add orjson>=3.10 as a core dep.
- decnet/web/api.py: default_response_class=ORJSONResponse on the
  FastAPI app, so every endpoint return goes through orjson without
  touching call sites. Explicit JSONResponse sites in the validation
  exception handlers migrated to ORJSONResponse for consistency.
- health endpoint's explicit JSONResponse → ORJSONResponse.
- SSE stream (api_stream_events.py): 6 json.dumps call sites →
  orjson.dumps(...).decode() — the per-event frames that fire on every
  sse tick.
- sqlmodel_repo.py: encode sites on the log-insert path switched to
  orjson (fields, payload, state value). Parser sites (json.loads)
  left as-is for now — not on the measured hot path.
This commit is contained in:
2026-04-17 15:07:28 -04:00
parent f1e14280c0
commit 32340bea0d
5 changed files with 26 additions and 21 deletions

View File

@@ -13,6 +13,8 @@ from __future__ import annotations
import asyncio
import json
import orjson
import uuid
from datetime import datetime, timezone
from typing import Any, Optional, List
@@ -146,7 +148,7 @@ class SQLModelRepository(BaseRepository):
async def add_log(self, log_data: dict[str, Any]) -> None:
data = log_data.copy()
if "fields" in data and isinstance(data["fields"], dict):
data["fields"] = json.dumps(data["fields"])
data["fields"] = orjson.dumps(data["fields"]).decode()
if "timestamp" in data and isinstance(data["timestamp"], str):
try:
data["timestamp"] = datetime.fromisoformat(
@@ -391,7 +393,7 @@ class SQLModelRepository(BaseRepository):
async def add_bounty(self, bounty_data: dict[str, Any]) -> None:
data = bounty_data.copy()
if "payload" in data and isinstance(data["payload"], dict):
data["payload"] = json.dumps(data["payload"])
data["payload"] = orjson.dumps(data["payload"]).decode()
async with self._session() as session:
dup = await session.execute(
@@ -478,7 +480,7 @@ class SQLModelRepository(BaseRepository):
result = await session.execute(statement)
state = result.scalar_one_or_none()
value_json = json.dumps(value)
value_json = orjson.dumps(value).decode()
if state:
state.value = value_json
session.add(state)