프로세스가 죽어도 남는다 — 가장 안 똑똑한 방법으로
5 분목표MCP 서버 강좌 5편. 대기 인원이 변수에 있으면 프로세스와 함께 사라진다. 파일에 적는데, 매번 전체를 다시 쓴다. 부분 갱신보다 느리지만 반쯤 쓰이다 만 기록이 생기지 않는다.
4편까지 대기 인원은 이렇게 살았다.
var waiting = 3;프로세스가 죽으면 3으로 돌아간다. 데스크 화면을 뽑았다 꽂으면 줄이 없던 일이 된다.
적는다
const _statePath = 'desk-state.json';
int _load() {
final f = File(_statePath);
if (!f.existsSync()) return 3;
final m = jsonDecode(f.readAsStringSync()) as Map<String, dynamic>;
return (m['waiting'] as num).toInt();
}
void _save(int waiting) =>
File(_statePath).writeAsStringSync(jsonEncode({'waiting': waiting}));그리고 값이 바뀔 때 부른다.
waiting -= n;
_save(waiting);왜 전체를 다시 쓰나
값 하나 바뀌는데 파일 전체를 다시 쓴다. 비효율적이다. 그런데 이 방식에는 성질이 하나 있다.
반쯤 쓰인 기록이 생기지 않는다. 쓰기가 일어났거나 안 일어났거나 둘 중 하나다. 중간에 죽으면 옛 파일이 그대로 남는다.
부분 갱신은 그렇지 않다. 레코드 하나를 제자리에서 고치다 죽으면 어느 쪽도 아닌 파일이 남고, 다음 부팅에서 파싱이 깨진다. 그때부터는 복구가 일이 된다.
기록이 수만 건이 되면 이 방식은 못 쓴다. 그때가 DB 를 쓸 자리다. 다만 그전까지는 이게 제일 안전하다.
어느 층에 두는가
상태가 있을 수 있는 자리가 셋이다.
| 층 | 여러 화면이 같은 값을 보나 | 프로세스가 죽으면 |
|---|---|---|
| 화면 안 | 아니다 — 화면마다 다른 값 | 사라진다 |
| 서버 프로세스 | 그렇다 | 사라진다 |
| 서버 밖(파일·DB) | 그렇다 | 남는다 |
4편까지는 두 번째였다. 이번 편이 세 번째로 옮긴다.
정하지 않으면 두 번째가 된다. 그리고 그게 문제가 되는 날은 대개 밤이다.
검증 — 프로세스를 실제로 죽인다
영속은 "저장했다" 를 보면 안 된다. 끊고 다시 붙어서 읽어야 한다.
rm -f desk-state.json
ask step5 '...{"name":"desk.admit","arguments":{"count":2}}}' >/dev/null # 프로세스 1
ask step5 '...{"name":"desk.state","arguments":{}}}' > captures/s5.txt # 프로세스 2
grep -q 'waiting.*1' captures/s5.txt || die "the count did not survive a restart"ask 가 매번 새 프로세스를 띄운다. 첫 호출이 끝나면 그 프로세스는 죽는다.
step5 admitted 2, process ended, a new process still reads waiting 1서버 로그에도 남는다.
course step5: up, state in desk-state.json (waiting=3)
course step5: up, state in desk-state.json (waiting=1)두 번째 부팅이 1로 시작한다.
직접 돌려보기
dart run bin/step5.dart
bash verify.sh가져갈 것
_load/_save두 함수 — 없으면 3부터 시작한다는 기본값 포함- 전체 재기록 — 반쯤 쓰인 기록을 만들지 않는다
- 재시작 왕복 검사 — 저장 로그가 아니라 새 프로세스의 읽기로 확인
다음 편
값이 바뀐 걸 화면이 알게 한다. 묻지 않고.
샘플 실행하기
makemind-academy/course_server/git clone https://github.com/makemind-academy/course_server cd course_server dart pub get dart run bin/step5.dart