Stage A of review fixes: single read snapshot, redacted /health, batched diff

/addresses reads the cursor and data in one session and one snapshot,
/health exposes only time and backup file name of the last restore,
get_changes checks presence in batches instead of per value (6.8 s -> 88 ms
on a 12k-entry journal). Adds the plan for review fixes 5-10.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5 committed 2026-09-21 10:06:31 +03:00
1 parent 0155fd3f38
commit d6b69d0842
6 files changed
+103 -33

No files matched your search

+13 -23
View File
@@ -6,7 +6,7 @@ import os
import re
import secrets
import sqlite3
from typing import List, Literal, Optional
from typing import Literal, Optional
from apscheduler.triggers.cron import CronTrigger
from fastapi import Depends, FastAPI, Header, HTTPException, Path, Query, Response
@@ -121,16 +121,6 @@ def ensure_data_ready(conn, kinds):
detail="The database was recreated after corruption; data is being collected again.")
def get_cidrs() -> List[str]:
with db.session() as conn:
return db.get_values(conn, "asn")
def get_fqdn_ips() -> List[str]:
with db.session() as conn:
return db.get_values(conn, "fqdn")
@app.get("/addresses", response_model=None, responses={
200: {"description": "JSON list for format=json, text/plain configuration script for other formats",
"content": {"application/json": {"schema": {"type": "array", "items": {"type": "string"}}},
@@ -144,17 +134,14 @@ def get_addresses(
description="List/set name used in generated configuration"),
response: Response = None,
):
# Курсор читаем до данных: изменения между чтением курсора и данных повторятся в diff, что безвредно
with db.session() as conn:
ensure_data_ready(conn, kinds_of(type))
headers = {"X-Changes-Cursor": str(db.journal_head(conn))}
# Одна сессия и один снимок: курсор в заголовке точно соответствует выданным данным
kinds = kinds_of(type)
results = set()
if type in [AddressType.cidr, AddressType.all_types]:
results.update(get_cidrs())
if type in [AddressType.fqdn, AddressType.all_types]:
results.update(get_fqdn_ips())
with db.session() as conn, db.read_transaction(conn):
ensure_data_ready(conn, kinds)
headers = {"X-Changes-Cursor": str(db.journal_head(conn))}
for kind in kinds:
results.update(db.get_values(conn, kind))
output = formatters.build_output(results, format.value, ip_version.value, aggregate, name)
if format == OutputFormat.json:
@@ -233,9 +220,12 @@ def health():
pending = db.recreated_pending(conn, ("asn", "fqdn"))
try:
last_restore = load_json(cc.RESTORE_FILE, None) # след автовосстановления базы из копии
restore = load_json(cc.RESTORE_FILE, None) # след автовосстановления базы из копии
except StorageError:
last_restore = None
restore = None
# Наружу - только время и имя файла копии; каталоги и текст ошибки остаются в last_restore.json
last_restore = ({"at": restore.get("at"), "backup": os.path.basename(str(restore.get("backup") or ""))}
if isinstance(restore, dict) else None)
try:
recreated = load_json(cc.RECREATED_FILE, None) # база пересоздана после порчи без копий