Skip to content

feat: Admin token list server-side pagination + stats aggregation + model pool filtering#550

Open
hhhaiai wants to merge 5 commits into
chenyme:mainfrom
hhhaiai:feat/admin-pagination-stats
Open

feat: Admin token list server-side pagination + stats aggregation + model pool filtering#550
hhhaiai wants to merge 5 commits into
chenyme:mainfrom
hhhaiai:feat/admin-pagination-stats

Conversation

@hhhaiai
Copy link
Copy Markdown

@hhhaiai hhhaiai commented May 19, 2026

Summary

Admin 后台在 10 万+ 账号场景下页面无法加载,根因有三:

  1. GET /admin/api/tokens 全量扫描 — 原实现每页请求都遍历全部记录,12 万条数据直接超时
  2. GET /admin/api/tokens/stats Python 遍历 — 逐行反序列化 JSON 字段做聚合,O(n) 极慢
  3. _available_pools() 调用 runtime_snapshot() — 每次请求都从 DB 加载全量记录

改动内容:

模块 改动
control/account/commands.py ListAccountsQuery 新增 exclude_statuses 字段
control/account/repository.py Protocol 新增 get_stats() 接口
control/account/backends/local.py SQLite: exclude_statuses 过滤 + get_stats() SQL 聚合(GROUP BY + json_extract
control/account/backends/redis.py Redis: exclude_statuses 过滤 + get_stats() scan 实现
control/account/backends/sql.py MySQL/PG: exclude_statuses 过滤 + get_stats() 聚合(dialect-aware JSON 提取)
products/web/admin/tokens.py GET /tokens 改为服务端分页(page/page_size/pool/status/exclude_statuses),新增 GET /tokens/stats 端点
products/web/webui/chat.py WebUI /models 应用 pool 过滤,与 /v1/models 同步
products/openai/router.py _available_pools() 改用内存 AccountRuntimeTable,不再调 runtime_snapshot()
statics/admin/account.html 前端改为按页加载,stats 单独请求,所有 load() 正确 await

Testing

本地 Docker 环境验证(122,091 条 SQLite 账号):

# Stats 端点 — SQL 聚合,890ms(原实现超时)
$ curl -H "Authorization: Bearer grok2api" http://127.0.0.1:8000/admin/api/tokens/stats
{"stats":{"total":122091,"active":118304,...},"quota_sums":{"fast":3661683,...},...}

# 分页查询 — 87ms/页
$ curl -H "Authorization: Bearer grok2api" "http://127.0.0.1:8000/admin/api/tokens?page=1 & page_size=50"
{"tokens":[...],"total":122091,"page":1,"page_size":50,"total_pages":2442}

# 模型列表 — 内存表查询,17ms
$ curl -H "Authorization: Bearer sk-sanbo" http://127.0.0.1:8000/v1/models
{"object":"list","data":[{"id":"grok-4.20-fast",...},...]}

# WebUI 模型 — 同样应用 pool 过滤
$ curl http://127.0.0.1:8000/webui/api/models

Related

关联 issue: #535

hhhaiai added 5 commits May 19, 2026 19:10
Support filtering out specific account statuses (e.g. expired, disabled)
from list results via a comma-separated exclude_statuses parameter.
Applied across all three backends: SQLite, Redis, and SQL (MySQL/PostgreSQL).
Replace Python-side full table scan with efficient SQL aggregation
for admin stats. SQLite uses json_extract(), MySQL/PostgreSQL use
dialect-aware JSON functions. Reduces 120k stats query from timeout
to ~900ms.
- GET /admin/api/tokens now supports page/page_size/pool/status/exclude_statuses
  query params with server-side filtering and pagination (max 2000/page)
- New GET /admin/api/tokens/stats returns aggregated stats via get_stats()
- Added fail_count to token serialization
- Added timing logs for performance monitoring
- Replaced full-scan token loading with paginated API calls (max 2000/page)
- Stats loaded separately via /tokens/stats endpoint
- Status/pool filters use server-side filtering, NSFW filter client-side
- All load() calls properly awaited for async consistency
- Pagination controls with configurable page size
- _available_pools() now uses in-memory AccountRuntimeTable instead of
  repo.runtime_snapshot(), eliminating full DB scan on every request
- WebUI /models endpoint applies same pool filtering as /v1/models
- Added timing logs for model list performance monitoring
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant