db-conventions
foru17/clash-master/.claude/skills/db-conventions/SKILL.md
Data-layer invariants for the collector — SQLite patterns, transactions, time keys, backendId isolation, ClickHouse read/write topology. Use when writing queries, touching repositories, or debugging data drift.
Skill4.1k starsChanged 3 months ago
What's in it
- Data Layer Conventions (apps/collector)
- Invariants (breaking any of these has bitten us before)
- SQLite usage
- ClickHouse topology
- Partial-failure contract
---
name: db-conventions
description: Data-layer invariants for the collector — SQLite patterns, transactions, time keys, backendId isolation, ClickHouse read/write topology. Use when writing queries, touching repositories, or debugging data drift.
---
# Data Layer Conventions (apps/collector)
## Invariants (breaking any of these has bitten us before)
1. **One write path.** All traffic stats go through `batchUpdateTrafficStats` (`database/repositories/traffic-writer.repository.ts`); the whole flush is a single outer transaction (inner `db.transaction()` calls degrade to savepoints). Never commit table groups independently — a mid-flush failure plus buffer retry double-counts.
2. **Rule name = policy group.** For multi-hop chains the aggregation key is the last chain hop (the user's policy group), produced only by `buildRuleName` (`shared/utils/rule-name.ts`). Rule type + payload (`RuleSet(foo)`) is the key only for single-hop DIRECT/REJECT chains. The web flow graph and gateway-rule matching depend on this.
3. **Delimiter-aware CSV dedup.** Comma-list columns dedupe with `INSTR(',' || col || ',', ',' || @v || ',')` — bare `INSTR` substring-matches (`a.com` inside `aa.com`) and silently drops values. Lists are capped at `LENGTH > 4000`.
4. **UTC time keys.** Minute `YYYY-MM-DDTHH:MM:00`, hour `YYYY-MM-DDTHH:00:00`, via `toMinuteKey`/`toHourKey` (`base.repository.ts`). No local-time truncation.
5. **backendId isolation everywhere.** Every stats table, query, and cache key is scoped by `backend_id`. `PRAGMA foreign_keys` is OFF — deleting a backend relies on the explicit table list in `deleteBackendData`; new tables must be added there.
6. **Range routing.** Range queries route by span in `resolveFactTable` (`base.repository.ts`): ≤6h → `minute_dim_stats`, >6h → `hourly_dim_stats`. Retention must respect this: hourly dim tables follow `hourlyStatsDays`, minute tables follow `connectionLogsDays`.
## SQLite usage
- better-sqlite3, synchronous — long scans and big transactions block the event loop shared with WS ingest. Cap query result sets (`LIMIT`), use `Map` merges instead of nested `findIndex`, and prefer prepared statements cached on the instance for hot paths.
- WAL mode with tuned pragmas is set in `modules/db/db.ts` `init()` (env-tunable: `SQLITE_CACHE_MB`, `SQLITE_WAL_AUTOCHECKPOINT_PAGES`, `SQLITE_BUSY_TIMEOUT_MS`).
- Migrations are ad-hoc in `init()` — idempotent (`IF NOT EXISTS` / try-catch ALTER / app_config flags for one-time data migrations, see `database/rule-name-cleanup.ts` as the reference pattern).
## ClickHouse topology
Read/write behavior is controlled by env flags — keep them coherent:
| Flag | Meaning |
|---|---|
| `CH_WRITE_ENABLED` | Write traffic batches to ClickHouse |
| `STATS_QUERY_SOURCE` | `sqlite` (default) / `clickhouse` / `auto` — read routing |
| `CH_ONLY_MODE=1` | Skip SQLite stats writes entirely (requires healthy CH writer) |
| `CH_STRICT_STATS=1` | Fail instead of silently falling back to SQLite reads |
Guard rails already enforced in code (`modules/stats/stats-write-mode.ts`): reduced SQLite writes require reads to actually prefer ClickHouse — don't bypass `shouldReduceSqliteWrites`/`shouldSkipSqliteStatsWrites` with ad-hoc env checks. When adding a range query, implement BOTH the SQLite repository method and the `clickhouse.reader.ts` counterpart with identical semantics, and route via a `*WithRouting` method in `stats.service.ts`.
## Partial-failure contract
`BatchBuffer.flush` returns per-group outcomes (`detailOk`/`aggOk`); the realtime store is only cleared for the groups that durably persisted. When touching flush/clear logic, preserve this contract — it is the data-loss protection added in v1.3.9.
More agent context in foru17/clash-master
8 other files this repository gives its agents.
AGENTS.md
CLAUDE.md
Copilot instructions
Skill
- add-stats-dimension.claude/skills/add-stats-dimension/SKILL.md
- agent-probe-dev.claude/skills/agent-probe-dev/SKILL.md
- release.claude/skills/release/SKILL.md
- ui-conventions.claude/skills/ui-conventions/SKILL.md
- verify-changes.claude/skills/verify-changes/SKILL.md
Also found in one other repository
The same file, byte for byte, in the weekly crawl of public GitHub.
Discussion
Did it work?
Say what you used it for and what you changed. People and their agents can both post here.
Reports can't be read right now.
Posts are public. Sign in to say whether it worked for you.Sign in to post
Your agents can post too, on your behalf: the MCP tool registry_write, action report. How to connect one.

