v1.3.38.1 -- go vet gate + two cold-path queries¶
Second release of the v1.3.38 series ("dashboard paints in under a second"; scope in docs/operations/ux-performance-review.md, section 7). Three items in the order the operator set:
go vet ./...back to green (broken gate first).RecentErrorsbounded to 24 h, two indexed sub-selects.- Security overview per-host counters in one grouped query.
No schema change. Bans, reconcilers, AppSec and auth untouched. argosVersion 1.3.38.0 -> 1.3.38.1.
1. go vet¶
backend/internal/crowdsec/client_test.go held
which vet reports as "assignment copies lock value to _". The comment was wrong (a throwaway value cannot pin anything) and the lines had no effect. Removed, together with the now-unused sync/atomic import. go vet ./... exits 0 on the whole module.
2. RecentErrors (Dashboard health card)¶
Before (internal/dashboard/queries.go):
SELECT timestamp, source, level, message FROM log_entries
WHERE (source='caddy_error' AND level IN ('error','warn'))
OR (source='caddy_access' AND status >= 500)
ORDER BY timestamp DESC
LIMIT ?
MULTI-INDEX OR
INDEX 1: SEARCH log_entries USING INDEX idx_log_entries_source_ts (source=?)
INDEX 2: SEARCH log_entries USING INDEX idx_log_entries_source_ts (source=?)
USE TEMP B-TREE FOR ORDER BY
Every caddy_error row and every caddy_access row (480k) is visited, each row page read to test level/status, then sorted. 39.4 s on a cold .backup copy of prod.
After:
SELECT timestamp, source, level, message FROM (
SELECT ... FROM log_entries
WHERE source = 'caddy_error' AND timestamp >= ? AND level IN ('error','warn')
ORDER BY timestamp DESC LIMIT ?)
UNION ALL
SELECT timestamp, source, level, message FROM (
SELECT ... FROM log_entries
WHERE source = 'caddy_access' AND timestamp >= ? AND status >= 500
ORDER BY timestamp DESC LIMIT ?)
ORDER BY timestamp DESC
LIMIT ?
MERGE (UNION ALL)
LEFT: SEARCH log_entries USING INDEX idx_log_entries_source_ts (source=? AND timestamp>?)
RIGHT: SEARCH log_entries USING INDEX idx_log_entries_source_ts (source=? AND timestamp>?)
0.371 s on the same copy (cold cache). The bound is 24 h, which is what the card is for. Rows older than 24 h no longer appear; that is the one intentional behaviour change of this release.
3. Security overview¶
Before (internal/api/security_overview.go), per host:
SELECT COUNT(*) FROM log_entries
WHERE source = 'waf_audit' AND host_id = ? AND timestamp >= ?
AND waf_severity IN ('CRITICAL','ERROR');
SELECT MAX(timestamp) FROM log_entries
WHERE source = 'waf_audit' AND host_id = ?;
Both plan through idx_log_entries_host_ts (host_id=?). The MAX one has no timestamp bound, so when a host has no waf_audit row (every host in prod: the source has 0 rows) it walks every row of that host: 21.9 s for the busiest host on the copy, 2.9 s for the bounded COUNT. Times 19 hosts, serialised on the single DB connection: 30-45 s cold for the endpoint.
After, one query for all hosts:
SELECT host_id,
SUM(CASE WHEN timestamp >= ? AND waf_severity IN ('CRITICAL','ERROR') THEN 1 ELSE 0 END),
MAX(timestamp)
FROM log_entries
WHERE source = 'waf_audit' AND host_id IS NOT NULL
GROUP BY host_id
3 ms on the copy (0 waf_audit rows); 1.0 s when run against the 21k-row caddy_error source as a stand-in for a populated waf_audit. Semantics unchanged: blocked_24h counts CRITICAL/ERROR rows in the last 24 h, last_triggered_at is the newest waf_audit row for the host, hosts without rows get 0 and no timestamp. The aggregate's timestamp comes back from the driver as text, so it is parsed with the same lenient layouts the dashboard package uses.
Smoke gate¶
scripts/smoke/dashboard-latency.sh (new). With ARGOS_SESSION_TOKEN set, waits 40 s (cache TTL is 30 s), then one curl -w %{time_total} per endpoint. PASS when both are 200 and <= 2 s.
| Panel | dashboard/health cold | security/overview cold | Verdict |
|---|---|---|---|
| 1.3.38.0 (before) | 16.5 s | 44.8 s | FAIL |
| 1.3.38.1 (after) | 0.044 s | 0.003 s | PASS |
Deploy EFFECT: image id sha256:99f02c29... (1.3.38.0) -> sha256:ec58cb56... (1.3.38.1), /api/system/version 1.3.38.0/5ca2c90 -> 1.3.38.1/9bcf244, only the panel container recreated (Caddy and CrowdSec kept their 2026-09-13 start time). Build + recreate 146 s with a cold build cache (the operator had pruned it after v1.3.38.0), peak load 10.9 on 2 vCPU, minimum free RAM 57 MB with 3.0 GB available.
go build ./..., go vet ./... (green), go test ./..., check-no-personal-data.sh, mkdocs build --strict all clean before commit.
Files changed¶
backend/internal/crowdsec/client_test.go-- vet fix.backend/internal/dashboard/queries.go(+queries_test.go) -- RecentErrors.backend/internal/api/security_overview.go(+security_overview_test.go) -- grouped query.scripts/smoke/dashboard-latency.sh-- new smoke.backend/cmd/argos/main.go,frontend/package.json-- version.CHANGELOG.md,docs/release-notes/v1.3.38.1.md,mkdocs.yml,docs/operations/verification-report.md.