tiguclaw 데이터베이스(대화·기억·세션)의 백업·용량·아카이브를 다룬다. "백업 해줘·백업 되고 있어?·DB 가 너무 커·용량·디스크·오래된 대화 정리·데이터 옮기기·복구·날아가면" 류 요청, 그리고 데이터가 걸린 유지보수를 시작하기 전에 사용한다. ★백업은 v0.27.0 부터 **자동**이다(스케줄이 아니라 데몬 유지보수 루프) — 스케줄 목록에 없다고 '백업이 없다'고 결론내지 말고 `/status` 나 `<home>/data/backup/` 을 보라. 정리 ≠ 삭제: 콜드 레코드는 옮기되 지우지 않는다.
Scanned 9/3/2026
Install to Claude Code
npx -y skills add tigu77/tiguclaw --skill db-maintenance --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Db Maintenance?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/tigu77-db-maintenance)More formats (shields.io, HTML) on the badges page.
---
name: db-maintenance
description: tiguclaw 데이터베이스(대화·기억·세션)의 백업·용량·아카이브를 다룬다. "백업 해줘·백업 되고 있어?·DB 가 너무 커·용량·디스크·오래된 대화 정리·데이터 옮기기·복구·날아가면" 류 요청, 그리고 데이터가 걸린 유지보수를 시작하기 전에 사용한다. ★백업은 v0.27.0 부터 **자동**이다(스케줄이 아니라 데몬 유지보수 루프) — 스케줄 목록에 없다고 '백업이 없다'고 결론내지 말고 `/status` 나 `<home>/data/backup/` 을 보라. 정리 ≠ 삭제: 콜드 레코드는 옮기되 지우지 않는다.
---
# DB 유지보수
`<home>/data/tiguclaw.db` 한 파일에 **기억·세션·대화 기록이 전부** 있다. 그래서 이 문서의
순서는 백업 → 용량 → 아카이브다. 크기는 나중에 문제가 되지만 **백업은 처음부터 문제다.**
## 0. 먼저 잰다 — **숫자를 여기서 읽지 말고 지금 재라**
★이 문서에 "현재 몇 MB · 몇 년 뒤 1GB" 같은 **기준선을 적어두지 않는다.** 그런 값은 적는
순간부터 늙고, 다음 사람이 그걸 사실로 믿는다(실제로 이 문서가 두 번 틀린 수치를 들고
있었다 — 한 번은 증가율을, 한 번은 그 근거를). **재는 법만 둔다.**
```bash
DB="<home>/data/tiguclaw.db"
sqlite3 "$DB" "
WITH sz AS (SELECT
(SELECT SUM(pgsize) FROM dbstat WHERE name='transcripts') t,
(SELECT SUM(pgsize) FROM dbstat WHERE name LIKE 'transcripts_fts%') f,
(SELECT page_count*page_size FROM pragma_page_count(), pragma_page_size()) total),
grow AS (SELECT sum(length(content))*1.0 c, (max(ts)-min(ts))/86400000.0 days
FROM transcripts WHERE ts > (strftime('%s','now')-30*86400)*1000)
SELECT
'현재 '||round(total/1048576.0)||'MB (대화 '||round(t/1048576.0)||' + 색인 '||round(f/1048576.0)||' = '||round(100.0*(t+f)/total)||'%)',
'월 증가 ~'||round((c/nullif(days,0))*30*(1.0+f*1.0/nullif(t,0))/1048576.0)||'MB',
'1GB 까지 '||round((1073741824.0-total)/nullif((c/nullif(days,0))*30*(1.0+f*1.0/nullif(t,0)),0)/12.0,1)||'년'
FROM sz, grow;"
```
★**증가는 대화 원문만 세면 틀린다** — 검색 색인이 원문에 비례해 따라 붙는다(그 비율도 위
쿼리가 현재 값으로 잰다). 원문만 세서 증가율을 절반으로 잡은 적이 있다.
**판정:** `transcripts`(대화 원문) + `transcripts_fts`(그 색인)가 대부분이면 정상이다 —
둘 다 설계상 무한 증가고 대화가 쌓이는 만큼 는다. `events` 는 상한이 있어 안 는다.
**1GB 까지 몇 년이 남았는지**가 구조를 바꿀 때인지 아닌지의 눈금이고, SQLite 는 수 GB 도
잘 다루므로 **수백 MB 에서 손대는 건 이르다.**
## 1. 백업 — **이미 자동으로 돈다**
★**먼저 이것부터 알아라: 백업은 v0.27.0 부터 자동이다.** 스케줄이 아니라 **데몬 유지보수
루프**(코어 자기보전 루프(`core/self-maintenance`) → `store/backup.ts`)에서 하루 한 벌씩 `VACUUM INTO` 로 뜨고 7벌만
남긴다. **스케줄 목록에는 안 보인다** — 없다고 "백업 체계가 없다"고 결론내지 마라(실제로
그렇게 오판한 적이 있다. 그리고 중복 스케줄을 제안할 뻔했다).
**상태 확인은 두 가지로:**
```
/status → "백업: 3시간 전 · 7벌 (940MB)" 한 줄
ls -lt <home>/data/backup/ → 실제 파일
```
성공은 **일부러 조용하다**(매일 알림은 배경 소음이 된다). 실패와 첫 벌만 알린다.
끄려면 `settings.json` 의 `backup.enabled: false` — 기본은 켜짐이다.
**손으로 한 벌 더 뜨려면:**
```bash
mkdir -p "<home>/data/backup"
sqlite3 "$DB" "VACUUM INTO '<home>/data/backup/tiguclaw-$(date +%F).db'"
```
- ★**`cp` 로 복사하지 마라.** 이 DB 는 WAL 모드라 최신 내용이 `-wal` 파일에 따로 있다 —
그냥 복사하면 **깨지거나 옛 상태**가 나온다. `VACUUM INTO` 는 돌고 있는 데몬을 멈추지 않고
**락 없이 일관된** 사본을 만들고, 조각 모음까지 돼서 원본보다 작다.
- **주기·보관은 이미 매일 1회·7벌**이다(위 자동 경로). ★**같은 일을 하는 스케줄을 새로
걸지 마라** — 중복이고, 스케줄은 실패해도 조용하다.
- ★**같은 디스크에만 있다.** 디스크가 죽으면 원본과 백업이 같이 간다. 외장·클라우드로
한 벌 더 빼는 것은 **사용자 결정 사안**이니 제안은 하되 임의로 하지 마라.
- 복구는 그 파일을 `tiguclaw.db` 자리에 놓고 데몬을 재시작하면 된다 —
★**데몬을 먼저 멈춰라.** 도는 중에 바꿔치기하면 WAL 과 어긋난다.
## 2. WAL 파일이 커 보일 때 — **최고수위지 누수가 아니다**
`ls` 에서 `-wal` 이 수십~수백 MB 로 보여도 그 자체는 이상이 아니다. SQLite 는 WAL 파일을
**줄이지 않고 재사용**한다 — 큰 작업이 한 번 지나가면 그 크기가 그대로 박힌다.
★**파일 크기로 판정하지 마라. 실제 내용은 pragma 로 잰다:**
```bash
sqlite3 "$DB" "PRAGMA wal_checkpoint(PASSIVE);" # → busy | log | checkpointed (단위: 페이지)
```
`busy=0` 이고 `log` 가 작으면 **정상**이다(1페이지 = 4KB). 실제로 파일이 134MB인데 내용은
134페이지(=549KB)뿐인 경우가 있었고, 파일 크기만 보고 "체크포인트가 막혔다"고 오판했다.
파일 자체를 줄이려면 `PRAGMA wal_checkpoint(TRUNCATE)` 인데 **짧은 쓰기 락**이 걸린다 —
데몬을 멈춘 뒤나 재시작 직후에 한 번 하면 된다. 급한 일이 아니다(디스크만 차지한다).
## 3. 아카이브 — **"오래된"이 아니라 "안 읽힌 지 오래된"**
날짜로 자르면 **3년 전 것인데 어제도 읽은 대화**가 잘려나간다. 기준은 마지막으로 쓰인 때다.
- ★**정리 ≠ 삭제.** 콜드 레코드는 **별도 파일로 옮기고** 본 DB 에선 뺀다 — 지우지 않는다.
사용자가 "정리해줘" 라고 해도 **삭제는 명시 승인 없이는 하지 않는다**(비가역).
- **파생물은 다르다.** `transcripts_fts` 는 원문에서 **다시 만들 수 있으므로** 아카이브
대상이 아니다. 색인만 재생성해도 큰 폭으로 줄어드는 경우가 있다(원문은 안 건드린다).
- **착수 조건**: DB 가 수 GB 이거나 검색·부팅이 체감되게 느려질 때. 그 전엔 하지 마라 —
안 쓰는 구조를 몇 년 유지보수하게 된다.
★**설계 전에 재료부터 확인하라.** 세션 마지막 사용 시각(`threads.last_used_at`)은 있지만,
`transcripts` 행이 그 세션과 **이어지지 않는 경우가 많다**(서브에이전트·백그라운드 작업 등이
남긴 기록으로 **추정** — 확인된 바 아님). 한 인스턴스에선 대부분이 그랬다. 비율은 인스턴스마다
다르니 **아래 쿼리로 지금 재라.** 이걸 모르고 "마지막 사용 시각으로 자르면 된다"고 설계하면 **대부분을 못 건드리거나, 못
건드리는 것을 오래됐다고 지운다.** 먼저 그 비율부터 세라:
```bash
sqlite3 "$DB" "SELECT CASE WHEN th.claude_session_id IS NULL THEN '세션 연결 없음' ELSE '연결됨' END,
count(*), round(sum(length(t.content))/1048576.0,1)||'MB'
FROM transcripts t LEFT JOIN threads th ON th.claude_session_id = t.claude_session_id GROUP BY 1;"
```
## 4. 하지 말 것
- **DB 파일을 `cp`·`rsync` 로 백업하지 마라**(§1).
- **대화 기록을 지우지 마라.** 용량이 급해도 먼저 옮긴다. 삭제는 사용자 명시 승인 사안이다.
- **크기만 보고 손대지 마라** — 수백 MB 는 SQLite 에게 작다. 바꿀 근거는 *체감되는 느려짐*이다.
- **`VACUUM`(INTO 없이)을 습관처럼 돌리지 마라** — 전체를 다시 쓰느라 오래 걸리고 그동안 잠긴다.
조각이 실제로 문제일 때만(`PRAGMA freelist_count` 가 크다) 데몬을 멈추고 한다.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!