fix: telemetry single persistent connection + lock (kill concurrent write lock)
This commit is contained in:
+36
-27
@@ -8,13 +8,11 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sqlite3
|
||||
from typing import TYPE_CHECKING
|
||||
import threading
|
||||
from pathlib import Path
|
||||
|
||||
from loguru import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class SQLiteTelemetryRecorder:
|
||||
"""基于 SQLite 的 LLM 调用遥测记录器。
|
||||
@@ -57,16 +55,30 @@ class SQLiteTelemetryRecorder:
|
||||
"""
|
||||
|
||||
def __init__(self, db_path: Path) -> None:
|
||||
self._db_path = db_path
|
||||
self._table_ready = False
|
||||
"""建单持久连接 + 进程内 Lock(对齐 app/harness/log.py:HarnessLog 并发写模式)。
|
||||
|
||||
def _ensure_table(self, conn: sqlite3.Connection) -> None:
|
||||
"""懒初始化:首次写入时创建 llm_calls 表。"""
|
||||
if self._table_ready:
|
||||
return
|
||||
conn.execute(self._CREATE_TABLE_SQL)
|
||||
conn.commit()
|
||||
self._table_ready = True
|
||||
把并发控制拉到进程内(threading.Lock 串行化写),消除"每次新连接并发写同一
|
||||
db、靠 SQLite busy_timeout 跨连接协调"在高频下撑爆 timeout → database is locked
|
||||
的根因。check_same_thread=False:record_llm_call 经 asyncio.to_thread 在线程池
|
||||
不同线程调用,共享连接跨线程访问需此 flag,串行性由 self._lock 保证。
|
||||
|
||||
遥测哲学(P5):连接初始化失败降级不冒泡(self._conn=None,写入直接丢弃 warning),
|
||||
绝不因遥测故障拖垮 LLM 调用 / 训练。
|
||||
"""
|
||||
self._db_path = db_path
|
||||
self._lock = threading.Lock()
|
||||
self._conn: sqlite3.Connection | None = None
|
||||
try:
|
||||
Path(db_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA busy_timeout=5000")
|
||||
conn.execute(self._CREATE_TABLE_SQL)
|
||||
conn.commit()
|
||||
self._conn = conn
|
||||
except sqlite3.Error as exc:
|
||||
logger.warning("遥测连接初始化失败(已降级,后续写入丢弃): {}", exc)
|
||||
self._conn = None
|
||||
|
||||
def _write(
|
||||
self,
|
||||
@@ -87,20 +99,19 @@ class SQLiteTelemetryRecorder:
|
||||
cache_hit: bool,
|
||||
error: str | None,
|
||||
) -> None:
|
||||
"""同步写入一条 LLM 调用记录到 SQLite。
|
||||
"""同步写入一条 LLM 调用记录(单持久连接 + Lock 串行化,对齐 HarnessLog)。
|
||||
|
||||
三层防御加固:
|
||||
1. INSERT OR IGNORE — 主键冲突静默忽略
|
||||
2. WAL + busy_timeout — 并发写锁容忍
|
||||
3. try/except sqlite3.Error — DB 错误不冒泡到调用方
|
||||
三层防御:
|
||||
1. INSERT OR IGNORE — call_id 主键冲突静默忽略(幂等)
|
||||
2. 进程内 threading.Lock 串行化写 — 消除并发锁竞争(非依赖 SQLite busy_timeout)
|
||||
3. try/except sqlite3.Error — DB 错误降级不冒泡,遥测失败绝不拖垮 LLM 调用
|
||||
"""
|
||||
if self._conn is None:
|
||||
logger.warning("遥测连接不可用(已降级),丢弃 call_id={}", call_id)
|
||||
return
|
||||
try:
|
||||
conn = sqlite3.connect(str(self._db_path), timeout=10.0)
|
||||
try:
|
||||
conn.execute("PRAGMA journal_mode=WAL")
|
||||
conn.execute("PRAGMA busy_timeout=5000")
|
||||
self._ensure_table(conn)
|
||||
conn.execute(
|
||||
with self._lock:
|
||||
self._conn.execute(
|
||||
self._INSERT_SQL,
|
||||
(
|
||||
call_id,
|
||||
@@ -120,9 +131,7 @@ class SQLiteTelemetryRecorder:
|
||||
error,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
self._conn.commit()
|
||||
except sqlite3.Error as exc:
|
||||
logger.warning("遥测写入失败(已降级),call_id={}: {}", call_id, exc)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user