114 lines
4.7 KiB
Bash
Executable File
114 lines
4.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Telegram dedup 修復驗證 — commit b3a0f0d7 (fingerprint dedup + 24h TTL)
|
||
# 部署時間: 2026-05-02 16:25 Asia/Taipei
|
||
# 用法: ssh wooo@192.168.0.121 'bash -s' < verify_telegram_dedup_b3a0f0d7.sh
|
||
# 或 scp 上去後 sudo bash verify_telegram_dedup_b3a0f0d7.sh
|
||
# 純讀,不寫任何 prod 資料
|
||
|
||
set -e
|
||
|
||
POD=$(sudo kubectl get pods -n awoooi-prod -l app=awoooi-api -o jsonpath='{.items[0].metadata.name}')
|
||
echo "=== Pod: $POD ==="
|
||
echo "=== Image SHA (應含 b3a0f0d7) ==="
|
||
sudo kubectl get pod -n awoooi-prod "$POD" -o jsonpath='{.spec.containers[0].image}'
|
||
echo
|
||
echo
|
||
|
||
echo "=== A. 過去 1h Telegram 發送 top(部署後)==="
|
||
sudo kubectl exec -n awoooi-prod "$POD" -- python -c "
|
||
import asyncio, os, asyncpg
|
||
async def q():
|
||
conn = await asyncpg.connect(os.environ['DATABASE_URL'])
|
||
await conn.execute(\"SELECT set_config('app.project_id', 'awoooi', FALSE)\")
|
||
rows = await conn.fetch(\"\"\"
|
||
SELECT
|
||
COALESCE(i.title, 'unknown') AS alertname,
|
||
COALESCE(i.affected_services[1], 'unknown') AS target,
|
||
COUNT(t.id) AS msg_count,
|
||
MIN(t.created_at) AS first_sent,
|
||
MAX(t.created_at) AS last_sent
|
||
FROM notification_outcomes t
|
||
JOIN approval_records a ON t.approval_id = a.id
|
||
JOIN incidents i ON a.incident_id = i.id
|
||
WHERE t.channel='telegram' AND t.created_at > now() - interval '1 hour'
|
||
GROUP BY 1,2 ORDER BY 3 DESC LIMIT 10
|
||
\"\"\")
|
||
for r in rows:
|
||
print(f\" {r['msg_count']:>3} | {r['alertname'][:40]:<40} | {r['target'][:30]:<30} | first={r['first_sent']:%H:%M} last={r['last_sent']:%H:%M}\")
|
||
await conn.close()
|
||
asyncio.run(q())
|
||
"
|
||
|
||
echo
|
||
echo "=== B. 過去 24h(含部署前對照)==="
|
||
sudo kubectl exec -n awoooi-prod "$POD" -- python -c "
|
||
import asyncio, os, asyncpg
|
||
async def q():
|
||
conn = await asyncpg.connect(os.environ['DATABASE_URL'])
|
||
await conn.execute(\"SELECT set_config('app.project_id', 'awoooi', FALSE)\")
|
||
rows = await conn.fetch(\"\"\"
|
||
SELECT
|
||
COALESCE(i.title, 'unknown') AS alertname,
|
||
COALESCE(i.affected_services[1], 'unknown') AS target,
|
||
COUNT(t.id) AS msg_count
|
||
FROM notification_outcomes t
|
||
JOIN approval_records a ON t.approval_id = a.id
|
||
JOIN incidents i ON a.incident_id = i.id
|
||
WHERE t.channel='telegram' AND t.created_at > now() - interval '24 hours'
|
||
GROUP BY 1,2 ORDER BY 3 DESC LIMIT 10
|
||
\"\"\")
|
||
for r in rows:
|
||
print(f\" {r['msg_count']:>3} | {r['alertname'][:40]:<40} | {r['target'][:30]:<30}\")
|
||
await conn.close()
|
||
asyncio.run(q())
|
||
"
|
||
|
||
echo
|
||
echo "=== C. 截圖兩 INC 最後發送時刻 ==="
|
||
sudo kubectl exec -n awoooi-prod "$POD" -- python -c "
|
||
import asyncio, os, asyncpg
|
||
async def q():
|
||
conn = await asyncpg.connect(os.environ['DATABASE_URL'])
|
||
await conn.execute(\"SELECT set_config('app.project_id', 'awoooi', FALSE)\")
|
||
rows = await conn.fetch(\"\"\"
|
||
SELECT i.id, i.title, COUNT(t.id) AS total_24h,
|
||
MAX(t.created_at) AS last_sent,
|
||
COUNT(t.id) FILTER (WHERE t.created_at > '2026-05-02 16:25 Asia/Taipei'::timestamptz) AS post_deploy
|
||
FROM notification_outcomes t
|
||
JOIN approval_records a ON t.approval_id = a.id
|
||
JOIN incidents i ON a.incident_id = i.id
|
||
WHERE i.id IN ('INC-20260501-6FE3BD','INC-20260502-FD6E21')
|
||
AND t.channel='telegram' AND t.created_at > now() - interval '24 hours'
|
||
GROUP BY 1,2 ORDER BY 1
|
||
\"\"\")
|
||
for r in rows:
|
||
print(f\" {r['id']} | {r['title'][:40]:<40} | 24h={r['total_24h']} 部署後={r['post_deploy']} last={r['last_sent']:%H:%M}\")
|
||
await conn.close()
|
||
asyncio.run(q())
|
||
"
|
||
|
||
echo
|
||
echo "=== D. Redis dedup key 結構(fingerprint 應已建立)==="
|
||
sudo kubectl exec -n awoooi-prod "$POD" -- python -c "
|
||
import asyncio, os
|
||
from redis.asyncio import Redis
|
||
async def q():
|
||
r = Redis.from_url(os.environ['REDIS_URL'])
|
||
fp_keys = await r.keys('telegram_sent:fp:*')
|
||
inc_keys = await r.keys('telegram_sent:INC-*')
|
||
print(f' telegram_sent:fp:* (新格式) = {len(fp_keys)} (應 > 0)')
|
||
print(f' telegram_sent:INC-* (舊格式) = {len(inc_keys)} (應 = 0 或減少中)')
|
||
if fp_keys:
|
||
print(f' 範例 fp key: {fp_keys[0].decode() if isinstance(fp_keys[0], bytes) else fp_keys[0]}')
|
||
sweeper_keys = await r.keys('sweeper_done:*')
|
||
print(f' sweeper_done:* = {len(sweeper_keys)} (24h TTL,整個 INVESTIGATING 集合)')
|
||
asyncio.run(q())
|
||
"
|
||
|
||
echo
|
||
echo "=== 驗收標準 ==="
|
||
echo "✅ A 段任何 fingerprint msg_count ≤ 2 → 修復生效"
|
||
echo "✅ C 段兩 INC 部署後 ≤ 1 → 鐵證生效"
|
||
echo "✅ D 段 telegram_sent:fp:* 已建立 → 新 dedup 邏輯有跑"
|
||
echo "❌ 任何 fingerprint 部署後仍 ≥ 5 → 未生效,回報 Claude"
|