fix(agents): use in-memory PK pools for DML (avoid full-table scans on 54M-row tables)

This commit is contained in:
mo
2026-06-27 01:27:56 +02:00
parent 3c9661e7f9
commit 5147538b05
+57 -47
View File
@@ -23,8 +23,8 @@ from __future__ import annotations
import asyncio
import os
import random
import time
import uuid
from collections import deque
from datetime import datetime, timezone
from typing import Any
@@ -118,14 +118,34 @@ def _tag(run_id: str) -> str:
return f"{AGENT_TAG} {run_id}"
# In-memory pools of agent-created primary keys per source. All UPDATE/DELETE
# operate by PK on these (instant), so we never scan the large unindexed
# `notes` column. Bounded by _POOL_CAP; orphaned tagged rows after a restart
# are harmless (clearly marked) and simply not re-tracked.
_pools: dict[str, deque] = {
"postgres": deque(maxlen=_POOL_CAP),
"mysql": deque(maxlen=_POOL_CAP),
"mongodb": deque(maxlen=_POOL_CAP),
}
def _resolve_op(op: str, pool: deque) -> str:
"""UPDATE/DELETE require an existing agent row; otherwise fall back to INSERT."""
if op in ("update", "delete") and not pool:
return "insert"
if op == "insert" and len(pool) >= _POOL_CAP:
return "delete" # keep the agent footprint bounded
return op
def _pg_dml(op: str) -> str:
pool = _pools["postgres"]
op = _resolve_op(op, pool)
conn = _pg_conn()
try:
conn.autocommit = True
with conn.cursor() as cur:
cur.execute("SELECT count(*) FROM public.sales_orders WHERE notes LIKE %s", (AGENT_TAG + "%",))
pool = cur.fetchone()[0]
if op == "insert" or (op == "delete" and pool == 0) or (op == "update" and pool == 0):
if op == "insert":
rid = uuid.uuid4().hex[:8]
cur.execute(
"""INSERT INTO public.sales_orders
@@ -139,38 +159,29 @@ def _pg_dml(op: str) -> str:
),
)
oid = cur.fetchone()[0]
pool.append(oid)
return f"INSERT sales_orders order_id={oid} ({_tag(rid)})"
if op == "update":
oid = random.choice(list(pool))
cur.execute(
"""UPDATE public.sales_orders SET order_status=%s, amount=round(amount*%s,2)
WHERE order_id IN (
SELECT order_id FROM public.sales_orders WHERE notes LIKE %s
ORDER BY order_id DESC LIMIT 1)
RETURNING order_id""",
(random.choice(ORDER_STATUS), round(random.uniform(0.9, 1.2), 2), AGENT_TAG + "%"),
"UPDATE public.sales_orders SET order_status=%s, amount=round(amount*%s,2) WHERE order_id=%s",
(random.choice(ORDER_STATUS), round(random.uniform(0.9, 1.2), 2), oid),
)
row = cur.fetchone()
return f"UPDATE sales_orders order_id={row[0]}" if row else "UPDATE sales_orders (no agent rows)"
# delete
cur.execute(
"""DELETE FROM public.sales_orders WHERE order_id IN (
SELECT order_id FROM public.sales_orders WHERE notes LIKE %s
ORDER BY order_id ASC LIMIT 1) RETURNING order_id""",
(AGENT_TAG + "%",),
)
row = cur.fetchone()
return f"DELETE sales_orders order_id={row[0]}" if row else "DELETE sales_orders (no agent rows)"
return f"UPDATE sales_orders order_id={oid}"
oid = pool.popleft()
cur.execute("DELETE FROM public.sales_orders WHERE order_id=%s", (oid,))
return f"DELETE sales_orders order_id={oid}"
finally:
conn.close()
def _mysql_dml(op: str) -> str:
pool = _pools["mysql"]
op = _resolve_op(op, pool)
conn = _mysql_conn()
try:
with conn.cursor() as cur:
cur.execute("SELECT count(*) FROM hr.employee_events WHERE notes LIKE %s", (AGENT_TAG + "%",))
pool = cur.fetchone()[0]
if op == "insert" or (op in ("update", "delete") and pool == 0):
if op == "insert":
rid = uuid.uuid4().hex[:8]
cur.execute(
"""INSERT INTO hr.employee_events
@@ -182,28 +193,29 @@ def _mysql_dml(op: str) -> str:
round(random.uniform(-5000, 15000), 2), datetime.now(timezone.utc), _tag(rid),
),
)
return f"INSERT employee_events event_id={cur.lastrowid} ({_tag(rid)})"
eid = cur.lastrowid
pool.append(eid)
return f"INSERT employee_events event_id={eid} ({_tag(rid)})"
if op == "update":
eid = random.choice(list(pool))
cur.execute(
"""UPDATE hr.employee_events SET salary_change=%s, event_type=%s
WHERE notes LIKE %s ORDER BY event_id DESC LIMIT 1""",
(round(random.uniform(-5000, 15000), 2), random.choice(EVENT_TYPES), AGENT_TAG + "%"),
"UPDATE hr.employee_events SET salary_change=%s, event_type=%s WHERE event_id=%s",
(round(random.uniform(-5000, 15000), 2), random.choice(EVENT_TYPES), eid),
)
return f"UPDATE employee_events ({cur.rowcount} row)"
cur.execute(
"DELETE FROM hr.employee_events WHERE notes LIKE %s ORDER BY event_id ASC LIMIT 1",
(AGENT_TAG + "%",),
)
return f"DELETE employee_events ({cur.rowcount} row)"
return f"UPDATE employee_events event_id={eid}"
eid = pool.popleft()
cur.execute("DELETE FROM hr.employee_events WHERE event_id=%s", (eid,))
return f"DELETE employee_events event_id={eid}"
finally:
conn.close()
def _mongo_dml(op: str) -> str:
pool = _pools["mongodb"]
op = _resolve_op(op, pool)
client, coll = _mongo_coll()
try:
pool = coll.count_documents({"atc_agent": True}, limit=1)
if op == "insert" or (op in ("update", "delete") and pool == 0):
if op == "insert":
rid = uuid.uuid4().hex[:8]
doc = {
"event_id": str(uuid.uuid4()),
@@ -217,21 +229,18 @@ def _mongo_dml(op: str) -> str:
"agent_run": rid,
}
res = coll.insert_one(doc)
pool.append(res.inserted_id)
return f"INSERT events _id={res.inserted_id} (agent_run={rid})"
if op == "update":
doc = coll.find_one({"atc_agent": True}, sort=[("_id", -1)])
if not doc:
return "UPDATE events (no agent docs)"
oid = random.choice(list(pool))
coll.update_one(
{"_id": doc["_id"]},
{"_id": oid},
{"$set": {"type": random.choice(MONGO_TYPES), "amount": round(random.uniform(10, 50000), 4)}},
)
return f"UPDATE events _id={doc['_id']}"
doc = coll.find_one({"atc_agent": True}, sort=[("_id", 1)])
if not doc:
return "DELETE events (no agent docs)"
coll.delete_one({"_id": doc["_id"]})
return f"DELETE events _id={doc['_id']}"
return f"UPDATE events _id={oid}"
oid = pool.popleft()
coll.delete_one({"_id": oid})
return f"DELETE events _id={oid}"
finally:
client.close()
@@ -285,7 +294,8 @@ async def agent_dml_loop() -> None:
# ── Endpoints ────────────────────────────────────────────────────────────────
@router.get("/status")
async def status() -> JSONResponse:
return JSONResponse({"ok": True, **_state})
pools = {k: len(v) for k, v in _pools.items()}
return JSONResponse({"ok": True, "pools": pools, **_state})
@router.post("/toggle")