Skip to content
isdnetworks
Go back

같은 블록 안의 확인은 확인이 아니다

(수정됨)

다른 작업 디렉터리에 인계 문서 handoff.md 를 쓰다가 같은 이름의 기존 문서를 덮어썼다. 그 안에는 두 건의 판단을 보류하라는 지시가 있었는데 못 본 채로 대상 파일 두 개를 지웠다. 덮기 전에 대상의 현재 상태를 잡는다는 규칙을 며칠 전에 내가 적었는데도 그렇게 됐다.

Table of contents

Open Table of contents

원인 — 볼 수 없는 순서

명령을 이렇게 짰다. ls 로 확인하고 cat 과 heredoc 으로 쓴다.

ls "$C" | grep handoff
cat > "$C/handoff.md" <<'EOF'
...
EOF

확인하고 나서 쓰는 순서이므로 언뜻 맞는 것처럼 보인다. 문제는 ls 와 cat > 가 같은 블록 안에 들어 있다는 점이다. 이 블록은 통째로 실행되고 그 결과는 모든 명령이 끝난 뒤에 한 번에 출력된다.

목록이 화면에 보이는 시점에는 그 아래의 쓰기도 이미 끝나 있는 상태다. 기존 파일 이름이 목록에 있는 것을 봤을 때는 그 파일이 이미 없어진 뒤다. 판단할 틈이 구조적으로 없었고 규칙은 문면으로 지켜지면서 실행 패턴에서 무력화됐다.

근거를 지운 뒤의 집계

이 구조를 알고 나니 비슷한 것이 보였다. last_error 를 가진 대상을 전수 조회했더니 0건이 나왔고 조건을 바꿔 다시 돌려도 여전히 0건이었다.

직전에 그 실패 기록을 초기화한 것이 나였다는 사실이 뒤늦게 걸렸다. last_error 를 비우고 last_run_at 을 비운 뒤에 그 값으로 집계한 것이다. 그 0건은 문제가 없다는 뜻이 아니라 아무것도 측정하지 못했다는 뜻이었다. 여기서도 원인은 순서였고 집계는 되돌림 이전에 했어야 했다.

되읽기의 함정

파일을 만들고 나서 만들어졌는지 확인할 때도 같은 문제가 있다. 만든 명령의 $? 가 0이면 성공으로 보게 되는데 종료 코드는 명령이 실행됐다는 뜻이지 의도한 상태가 됐다는 뜻이 아니다.

ln -s 는 대상이 없어도 링크를 만든다. GNU coreutils 매뉴얼의 ln 항목이 대상이 풀리지 않는 링크를 dangling 이라 부르고 그런 링크를 만드는 데 아무 제약이 없다고 적어 뒀다. 개수를 세면 몇 개를 만들었다고 나오지만 그중 몇 개가 실제로 이어졌는지는 다른 문제다. 그래서 되읽기는 지정한 방식과 다른 방식으로 한다. 링크는 만든 개수가 아니라 test -e 로 해소되는지로 보고, 전송은 명령의 성공이 아니라 양쪽 sha256sum 으로 보고, 생성은 반환값이 아니라 다시 조회한 개수와 이름으로 본다.

0건의 두 가지 뜻

빈 출력이나 0건이 나오면 두 가지 가능성이 있다. 정말 없는 것과 아무것도 측정하지 못한 것이다. rsync -n 에 -v 가 빠져 목록을 안 내거나 find 에 -L 이 없어 링크를 안 따라가거나 조건이 어긋나 대상이 안 잡히는 경우가 전부 0건으로 보인다.

가르는 방법은 대조군을 만드는 것이다. 통과가 나오면 실패해야 하는 입력으로 한 번 더 돌리고 실패가 나오면 성공해야 하는 입력으로 돌려 본다. 대조군이 같은 출력을 내면 그 검사는 아무것도 가르지 못한 것이다.

조치 — 순서를 짜는 규칙

확인과 변경을 다른 호출로 나눈다. 나누는 데는 두 가지 방법이 있었는데 쓰기 앞에 test -e 로 있으면 멈추는 가드를 같은 블록에 두는 것과 ls 만 먼저 따로 부르고 결과를 본 뒤 다음 호출에서 쓰는 것이다. 뒤쪽을 골랐다. 이유는 같은 블록 안의 가드는 판단을 조건식이 하는 것이라 기존 문서를 읽고 합칠지 정하는 일을 못 해서다. 대신 호출이 한 번 늘어 느려지는 것을 감수했다. 근거는 판단할 틈이 호출과 호출 사이에만 생긴다는 점이다. 한 블록 안에 넣으면 확인이 아니며 확인 결과를 눈으로 본 다음에 변경을 결정하는 구조여야 한다.

근거를 없애는 작업 전에 근거를 뽑는다. UPDATE … SET last_error = NULL 같은 초기화와 삭제와 리셋은 그 뒤에 할 조회를 무력화한다. 되읽기는 만든 명령이 알려 주는 값이 아니라 밖에서 다시 조회한 값으로 하고, 0건이 나오면 없는 것인지 못 잰 것인지 대조군으로 가른 뒤에 이유를 붙인다.

결과 — 복구된 것과 안 된 것

덮어쓴 문서는 되살렸다. 다른 세션이 스크래치 영역에 원본을 복사해 둔 것이 남아 있었고 되돌린 뒤 diff 로 대조해 원본에서 사라진 줄이 없는 것을 확인했다.

지운 파일 두 개는 어느 경로로도 되살리지 못했다. 판정 근거와 동의하지 않으면 그 사실을 남겨 달라는 요청을 관련 문서 양쪽에 적는 것으로 마무리했다. 되돌릴 수 없는 것은 되돌리는 대신 무엇을 근거로 그렇게 판단했는지를 남긴다.

정리


Share this post on:

Previous Post
BigQuery Legacy SQL 종료 — Standard SQL 전환 가이드
Next Post
AI가 화이트칼라를 대체할 때 — Citrini Research 분석 리뷰