[작성자:] chjeong

  • 대화를 비웠는데 메모리는 그대로였다 — 두 지표를 하나로 여긴 기록

    대화를 비웠는데 메모리는 그대로였다 — 두 지표를 하나로 여긴 기록

    도우미를 오래 굴리면 대화가 길어진다. 길어진 대화는 다루기 어려워지고, 그래서 한 번씩 비운다. 그런데 비우는 이유를 물어보면 두 가지가 섞여 나온다. 하나는 대화가 길어서이고, 다른 하나는 컴퓨터가 무거워서다.

    이 글은 그 두 이유가 서로 다른 일이라는 이야기다. 우리는 한쪽을 하면 다른 쪽도 함께 될 것으로 기대했고, 실제로 재 보니 그렇지 않았다. 이 글은 그 전후 수치를 적은 기록이다.

    용어 둘을 먼저 풀어 둔다. 대화 기록은 지금까지 주고받은 말의 묶음이다. 길어지면 다루기 어려워진다. 메모리는 그 프로그램이 컴퓨터에서 차지하고 있는 자리다. 이 둘이 헷갈리는 이유는 단순하다 — 둘 다 “기억”처럼 들린다.

    이 글은 책상에 빗대어 쓴다. 끝까지 그 하나로만 간다. 책상 위 서류를 치우는 것그 책상이 사무실에서 차지한 자리가 줄어드는 것은 다른 일이다. 서류를 다 치워도 책상은 그 자리에 그대로 있다. 이 그림 하나면 이 글은 따라온다.

    논지는 한 줄이다. 대화를 비우는 것은 수명을 관리하는 일이고, 메모리를 되찾는 일이 아니다. 두 목적을 섞으면 양쪽에서 진다 — 비우기로 자리를 잡으려다 실패하고, 자리를 아끼려고 비우기를 미루다 하던 작업을 죽인다.

    앞선 편과의 경계. 잰 값을 다른 것의 증거로 써서 생긴 오탐은 앞 편에 적었다. 이 글은 그것과 다르다 — 두 값은 각각 자기를 정직하게 재고 있었고, 우리가 한 값을 다른 값의 대체물로 기대한 것이 어긋난 자리다.

    이 글이 도움이 되는 사람. 도우미를 오래 굴리는 사람이다. 짧게 쓰고 끄는 식이면 두 값이 모두 작아서 차이가 드러나지 않는다. 같은 프로그램을 며칠씩 켜 두고 대화를 이어 가는 쪽에서 이 둘이 갈린다.

    근거의 범위를 먼저 밝혀 둔다. 이 글의 근거는 우리 관측 세 차례다. 왜 그런 수치가 나왔는지 그 안쪽 기제를 우리는 재지 않았고, 다른 도구나 다른 환경에서도 같은지도 재지 않았다. 그래서 이 글에는 이유를 설명하는 문장이 없다.

    대화를 비운 양과 되찾은 자리의 양이 함께 움직이지 않는다는 것을 칸으로 나눠 그린 도식. 첫째 칸은 비운 뒤에 잰 값으로, 대화 기록은 88퍼센트를 줄였고 차지한 자리는 12.6퍼센트만 줄었으며 413메가바이트에서 361메가바이트가 되었고 프로그램 식별자와 시작 시각이 그대로여서 껐다 켠 것이 아니라고 적혀 있다. 둘째 칸은 비우는 동안으로, 그 구간에는 자리를 더 썼고 413메가바이트에서 433메가바이트가 되었으며 그 시점 시스템 전체 사용률도 올랐고 치우는 동안에는 잠깐 더 넓게 쓴다고 적혀 있다. 셋째 칸은 끝낸 양과 되찾은 양으로, 650메가바이트를 끝냈을 때 되찾은 여유는 100메가바이트였고 2213메가바이트를 끝냈을 때 여유는 985메가바이트 늘었으며 두 번 다 두 수가 달랐다고 적혀 있다. 넷째 칸은 두 지표를 각각 보는 법으로, 대화 기록은 수명을 관리하는 지표이고 차지한 자리는 자원을 관리하는 지표이며 하나가 줄었다고 다른 하나가 줄지 않는다고 적혀 있다. 아래 칸은 한정이다. 비우면 오히려 손해라는 뜻이 아니라는 것, 비우는 목적은 자리가 아니라 대화 수명이라는 것, 왜 그만큼만 줄었는지 내부 기제는 우리가 재지 않았다는 것, 그리고 다른 도구나 다른 환경에서도 같은지 재지 않았다는 것이 적혀 있다.
    비우기가 쓸모없다는 그림이 아니다. 비우기가 재는 것과 자리가 재는 것이 서로 다른 지표라는 것, 그리고 두 수가 함께 움직이지 않았다는 것을 그린 것이다. 아래 네 줄은 한정이다.

    비운 값과 남은 값

    1. 두 낱말이 다 기억처럼 들린다

    증상. 컴퓨터가 무거워졌다. 도우미를 오래 굴렸고 대화도 길어졌으니, 대화를 비우면 나아질 것 같다. 비운다. 화면은 깨끗해졌는데 컴퓨터는 그대로 무겁다.

    이 증상이 헷갈리는 이유. 비우기는 무언가를 한다. 화면이 정리되고 다음 대화가 가벼워진다. 손에 잡히는 변화가 있으니 다른 변화도 함께 왔으리라고 읽게 된다. 아무 일도 일어나지 않았다면 오히려 확인하러 갔을 것이다.

    여기서 갈리는 두 가지 해석. 하나는 덜 비웠다고 보는 것이다. 다른 하나는 다른 프로그램이 무겁게 하고 있다고 보는 것이다. 우리 기록에서는 둘 다 아니었다. 대화는 충분히 비웠고, 무거운 것도 그 프로그램이었다. 비운 것과 가벼워지는 것이 서로 다른 일이었다.

    둘을 갈라 놓고 보면 물음이 달라진다. 대화가 길어서 힘든 것이라면 물어야 할 것은 얼마나 줄였는가다. 컴퓨터가 무거워서 힘든 것이라면 물어야 할 것은 자리가 얼마나 줄었는가다. 두 물음은 서로 다른 값을 요구하고, 우리는 그중 하나만 재고 있었다.

    왜 이 둘이 붙어 다니는가. 이름 탓이 크다. 하나는 대화 기록이라 부르고 다른 하나는 메모리라 부르는데, 우리말로 옮기면 둘 다 기억이다. 기억을 지웠으니 기억이 차지하던 자리도 비었을 것 같다. 그 느낌이 아주 자연스러워서 확인할 생각이 들지 않는다.

    둘 다 늘어난다는 점도 헷갈림을 돕는다. 오래 굴리면 대화도 길어지고 차지한 자리도 커진다. 같은 방향으로 함께 늘어나는 것을 보면 같은 것의 두 얼굴처럼 보인다. 함께 늘어나는 것과 함께 줄어드는 것은 다른 이야기인데, 앞의 것만 보고 뒤의 것을 믿게 된다.

    책상으로 옮겨 보면 이렇다. 책상 위에 서류가 쌓였다. 서류를 치우면 책상 위는 넓어진다. 그런데 그 책상이 사무실 바닥에서 차지한 자리는 그대로다. 서류와 책상은 서로 다른 것을 차지하고 있었기 때문이다.

    두 지표를 갈라 적으면 이렇다.

    
    대화 기록   지금까지 주고받은 말의 묶음   길어지면 다루기 어렵다
    차지한 자리 그 프로그램이 쓰는 컴퓨터 자리 커지면 다른 일이 밀린다
    

    둘은 다른 것을 재고 다른 문제를 알려 준다. 앞의 것이 커지면 대화를 이어 가기 어려워진다. 뒤의 것이 커지면 같은 컴퓨터에서 다른 일이 밀린다. 다루는 방법도 서로 다르다.

    두 문제는 나타나는 자리도 다르다. 대화가 길어지면 그 자리 안에서 답이 느려지거나 앞부분을 놓친다. 자리가 커지면 다른 프로그램이 느려진다. 앞의 것은 그 자리에서 겪고, 뒤의 것은 컴퓨터 전체에서 겪는다. 겪는 곳이 다르니 알아차리는 사람도 다를 수 있다.

    그런데 우리는 한 동작으로 둘 다 해결하려 했다. 대화를 비우는 동작 하나가 앞의 문제를 줄여 주니, 뒤의 문제도 함께 줄여 주리라 기대했다. 그 기대를 우리는 재 본 적이 없었다. 이 글의 관측은 그 기대를 처음 재 본 기록이다.

    한 동작으로 둘을 풀고 싶어지는 이유. 손이 하나 덜 가기 때문이다. 두 문제를 각각 다루려면 두 가지를 재고 두 가지를 해야 한다. 하나로 되면 편하고, 편한 쪽이 맞기를 바라게 된다. 우리가 재 보지 않은 채 기대만 한 자리가 거기다.

    확인 방법. 자기 자리에서 비우기 전과 비운 뒤에 두 값을 각각 적어 보라. 하나는 대화가 얼마나 줄었는지이고, 다른 하나는 그 프로그램이 차지한 자리가 얼마나 줄었는지다. 두 값을 한 번만 나란히 적어 봐도 둘이 같이 움직이는지 아닌지가 보인다.

    2. 서류를 거의 다 치웠는데 책상 자리는 그대로였다

    이 항목은 우리가 실제로 잰 것이고 2026년 9월 8일 관측이다.

    관측. 한 자리의 대화 기록을 88퍼센트 줄였다. 같은 구간에 그 프로그램이 차지한 자리는 12.6퍼센트만 줄었다. 숫자로 적으면 413메가바이트에서 361메가바이트가 됐다.

    
    대화 기록      88 퍼센트   줄었다
    차지한 자리    12.6 퍼센트 줄었다   413MB -> 361MB
    

    88과 12.6 을 나란히 놓는 이유. 두 수를 따로 보면 각각은 이상하지 않다. 대화가 많이 줄어든 것도 정상이고, 자리가 조금 줄어든 것도 정상이다. 나란히 놓았을 때 비로소 둘이 같은 것을 재고 있지 않다는 것이 보인다.

    이 표에서 먼저 볼 것. 두 값이 같은 방향으로 움직이기는 했다. 둘 다 줄었다. 그런데 같은 폭으로 움직이지 않았다. 앞의 것이 거의 다 사라지는 동안 뒤의 것은 조금 줄었다.

    껐다 켠 것이 아니라는 점을 적어 둔다. 이 값들은 같은 프로그램 안에서 잰 것이다. 프로그램 식별자와 시작 시각이 그대로였다. 프로그램을 종료했다가 다시 켠 것이라면 이 비교는 아무 뜻이 없었을 텐데, 그렇지 않았다.

    두 값을 같은 구간에서 쟀다는 점. 앞뒤 시점을 서로 다르게 잡으면 그 사이에 다른 일이 끼어들 수 있다. 우리는 같은 구간의 앞과 뒤를 각각 재서 두 값을 얻었다. 그래서 이 두 수는 서로 비교할 수 있는 짝이다.

    초보용으로 옮기면 이렇다. 서류를 거의 다 치웠는데 그 책상이 사무실에서 차지한 자리는 거의 그대로였다. 책상을 옮긴 것도 아니고 새 책상으로 바꾼 것도 아니다. 같은 책상에서 서류만 줄어든 것이다.

    이 관측은 한 번이다. 우리는 이 측정을 한 차례 했다. 다른 시점에 다시 재면 두 수의 폭이 달라질 수도 있다. 그래서 이 항목이 말하는 것은 이 한 번의 측정에서 이랬다는 것이고, 늘 이렇다는 것이 아니다.

    퍼센트로 적은 이유. 두 값의 단위가 다르기 때문이다. 하나는 말의 길이이고 다른 하나는 바이트다. 단위가 다르면 크기를 바로 견줄 수 없으니 각자 얼마나 줄었는지의 비율로 옮겨 나란히 놓았다. 옆에 바이트 값도 함께 적어 두었다.

    이 관측이 말하는 것과 말하지 않는 것. 말하는 것은 이 한 번의 측정에서 두 수가 함께 움직이지 않았다는 것이다. 말하지 않는 것은 왜 그랬는가다. 그것은 아래 5번에서 따로 적는다.

    이 항목이 앞으로 쓰일 자리. 다음에 컴퓨터가 무거워졌을 때, 우리는 비우기를 먼저 떠올리지 않게 됐다. 비우기는 대화가 길어졌을 때 하는 일로 자리를 옮겼다. 무거운 문제는 무거움을 재는 값으로 따로 다룬다.

    확인 방법. 비우기를 재려면 그 프로그램의 식별자와 시작 시각이 그대로인지 함께 확인하라. 둘 중 하나라도 바뀌었으면 프로그램이 새로 뜬 것이고, 그때의 자리 변화는 비우기가 만든 것이 아니다.

    3. 치우는 동안에는 오히려 더 넓게 썼다

    이 항목은 같은 관측의 중간 구간이다.

    관측. 비우는 과정 자체가 그 구간에 자리를 더 썼다. 413메가바이트에서 433메가바이트로 올라갔다. 그 시점의 시스템 전체 사용률도 내려간 것이 아니라 올라갔다.

    이 값이 잠깐이라는 것도 적어 둔다. 우리가 본 것은 비우는 구간의 값이고, 그 구간이 끝난 뒤에는 앞에서 적은 361메가바이트로 내려갔다. 올라간 값이 그대로 남은 것이 아니다. 이 항목은 지나가는 구간에 대한 것이다.

    초보용으로 옮기면 이렇다. 서류를 치우려면 일단 꺼내서 늘어놓아야 한다. 그 잠깐 동안 책상은 평소보다 넓게 쓰인다. 치우는 일은 공간을 쓰는 일이기도 하다.

    ★여기서 한정을 분명히 적는다. 이것을 두고 “비우면 오히려 손해”라고 쓰지 않는다. 그 문장은 비우는 목적이 자리 확보라고 전제해야 성립한다. 우리 경우 비우는 목적은 자리가 아니라 대화 수명이었다. 목적이 다른 일을 손익으로 재면 그 계산은 처음부터 틀린 칸에 놓인다.

    비우는 목적을 여기서 다시 적어 둔다. 우리가 비우는 이유는 대화가 길어져서다. 길어진 대화는 이어 가기 어려워지고, 그것을 정리하는 것이 비우기의 일이다. 그 일은 잘 됐다 — 대화 기록은 88퍼센트 줄었다. 목적한 것은 이뤄졌다.

    그러면 이 관측은 무엇을 말해 주는가. 시점을 골라야 한다는 것을 말해 준다. 자리가 이미 빠듯한 순간에 비우기를 시작하면, 그 순간 잠깐 더 필요한 만큼이 없을 수 있다. 비우기가 나쁜 것이 아니라 비우기에도 자리가 든다는 사실을 계산에 넣어야 한다는 뜻이다.

    이 구간이 실제로 문제가 되는 상황. 자리가 넉넉하면 이 구간은 지나가고 만다. 문제가 되는 것은 이미 빠듯한 상태에서 비우기를 시작할 때다. 자리가 없어서 비우는데 비우는 데에 자리가 든다면, 그 순서가 맞는지부터 봐야 한다.

    이 구간을 우리가 오래 보지 않았다는 것도 적어 둔다. 우리가 가진 것은 그 구간의 앞뒤 수치다. 그 사이에서 값이 어떻게 오르내렸는지, 가장 높은 지점이 얼마였는지는 재지 않았다. 그래서 이 항목은 올라갔다는 것까지만 말한다.

    이 한정을 굳이 길게 적는 이유. 숫자만 떼어 보면 413에서 433으로 올랐다는 것이 눈에 남는다. 그 한 줄이 따로 돌아다니면 비우기를 하지 말라는 말처럼 읽힌다. 그래서 그 숫자 옆에 목적이 무엇이었는지를 붙여 둔다.

    확인 방법. 자리가 빠듯하면 비우기를 돌리는 중에도 한 번 재 보라. 전후만 재면 그 사이에 올라간 구간이 보이지 않는다. 자리가 빠듯한 상황이라면 그 구간이 실제로 문제가 될 수 있는 자리다.

    4. 끝낸 양과 되찾은 양은 다른 수다

    이 항목은 관측 두 차례다. 앞의 것과 다른 동작을 잰 것이다 — 비우기가 아니라 끝내기다.

    첫 번째 관측(2026년 9월 8일). 650메가바이트를 끝냈다. 그때 되찾은 여유는 100메가바이트였다.

    두 번째 관측(2026년 9월 9일). 묶음 하나를 통째로 접으면서 끝낸 합계가 2,213메가바이트였다. 같은 시각 여유는 985메가바이트 늘었다.

    
    끝낸 양      되찾은 여유
      650MB        100MB      (9월 8일)
    2,213MB        985MB      (9월 9일)
    

    두 관측을 나란히 두는 이유. 한 번만 봤다면 그날의 사정으로 넘길 수 있다. 두 번 다 끝낸 양과 되찾은 양이 달랐고 그 차이의 비율도 서로 달랐다. 그래서 이 항목은 두 줄을 나란히 적어 둔다.

    이 두 줄에서 읽을 수 있는 것. 끝낸 바이트로 되찾을 양을 예측할 수 없다. 두 번 다 되찾은 양이 끝낸 양보다 적었고, 그 비율도 두 번이 서로 달랐다. 하나의 값에서 다른 값을 계산해 내는 규칙을 우리는 갖고 있지 않다.

    두 관측의 규모가 달랐다는 점도 적어 둔다. 하나는 끝낸 양이 650메가바이트였고 다른 하나는 2,213메가바이트였다. 작은 쪽과 큰 쪽을 각각 한 번씩 본 셈이다. 두 번 다 되찾은 양이 끝낸 양과 달랐다는 것이 우리가 가진 전부다.

    이것이 왜 실무에서 문제인가. 자리가 부족할 때 우리는 얼마를 끝내면 얼마가 생기는지를 알고 싶어 한다. 그 계산이 서면 필요한 만큼만 끝내면 된다. 우리 두 관측은 그 계산이 서지 않는다고 말한다.

    이 항목이 앞의 항목들과 이어지는 자리. 앞에서는 대화를 줄인 값으로 자리를 판단할 수 없다는 것을 봤다. 여기서는 끝낸 양으로도 되찾을 자리를 판단할 수 없다는 것을 본다. 두 경우 모두 내가 한 일의 크기결과의 크기를 대신 읽으려 한 것이다.

    그래서 쓸 수 있는 방법은 하나다. 끝내고, 되찾은 양을 따로 확인하는 것이다. 예측 대신 측정이다. 필요한 만큼 생겼는지는 끝낸 뒤에 재 봐야 알 수 있고, 모자라면 한 번 더 끝내는 식으로 간다.

    예측 대신 측정이 무엇을 바꾸는가. 예측으로 일하면 얼마를 끝낼지를 먼저 정하게 된다. 측정으로 일하면 끝낸 뒤에 다시 본다. 뒤쪽은 한 걸음이 더 들지만, 필요한 만큼 생겼는지를 실제로 알 수 있다. 우리 두 관측은 앞쪽 방식이 서지 않는다고 말한다.

    되찾은 양을 무엇으로 쟀는가. 우리가 본 값은 여유다. 끝낸 쪽에서 얼마를 놓았는지가 아니라, 쓸 수 있게 된 쪽에서 얼마가 늘었는지를 봤다. 알고 싶은 것이 뒤쪽이기 때문에 뒤쪽을 재는 값을 봤다.

    초보용으로 옮기면 이렇다. 창고에서 상자 몇 개를 치웠다고 해서 그만큼의 빈 공간이 생기는 것은 아니다. 얼마나 생겼는지는 치우고 나서 봐야 안다. 우리가 두 번 재 본 결과가 그랬다.

    이 항목에서 남은 실무 감각 하나. 자리를 확보해야 할 때 우리는 이제 목표를 여유 쪽에 둔다. 얼마를 끝낼지가 아니라 여유가 얼마가 되면 되는지를 먼저 적고, 거기에 닿을 때까지 끝내고 다시 잰다.

    여기서도 이유는 적지 않는다. 왜 끝낸 양과 되찾은 양이 다른지, 왜 그 비율이 두 번 달랐는지 우리는 재지 않았다. 아래 5번이 그 자리다.

    이 항목에서 조심한 것. 두 관측의 비율을 계산해 평균을 내지 않았다. 두 번의 값이고, 두 번으로 평균을 내면 그 평균이 다음 번을 예측한다는 인상을 준다. 우리가 말할 수 있는 것은 두 번 다 달랐다는 것까지다.

    확인 방법. 무언가를 끝내 자리를 확보하려 한다면 끝내기 전후의 여유를 각각 적어 보라. 끝낸 양이 아니라 여유의 변화가 내가 얻은 값이다. 두 수를 나란히 적어 두면 다음에 얼마나 끝내야 하는지 감이 아니라 기록으로 판단할 수 있다.

    5. 왜 그런지는 우리가 재지 않았다

    이 항목은 이 글이 멈춘 자리이고, 이 편에서 특히 조심해서 적은 자리다.

    우리가 가진 것. 전후 수치다. 비우기 전과 뒤의 두 값, 비우는 중간의 값, 끝낸 양과 되찾은 여유. 그게 전부다.

    이 항목을 마지막에 둔 이유. 앞의 네 항목을 읽고 나면 그래서 왜 그런가가 궁금해진다. 그 물음에 답을 주지 않는 것이 이 글에서 지키기 어려운 부분이었다. 답이 없는 채로 끝내는 것이 불친절해 보이기 때문이다.

    우리가 갖지 못한 것. 그 안에서 무슨 일이 일어났는가다. 왜 88퍼센트를 줄였는데 자리는 12.6퍼센트만 줄었는지, 왜 끝낸 양과 되찾은 양이 그만큼 달랐는지 — 그 안쪽을 우리는 들여다보지 않았다.

    그럴듯한 설명은 왜 위험한가. 그럴듯하다는 것은 읽는 사람이 받아들이기 쉽다는 뜻이지 맞다는 뜻이 아니다. 그리고 우리 기록에 한 번 적히면 그것은 다음 글의 전제가 된다. 재지 않은 것이 전제가 되면 그 위에 쌓는 것이 전부 그만큼 흔들린다.

    그래서 이 글은 이유를 쓰지 않는다. 그럴듯한 설명은 여럿 떠오른다. 그런데 떠오르는 것과 잰 것은 다르다. 재지 않은 설명을 적어 두면 다음에 이 기록을 읽는 사람이 그것을 확인된 사실로 읽는다.

    모른다고 적는 것이 실무에서 주는 것. 다음에 같은 상황을 만났을 때 무엇을 재야 하는지가 분명해진다. 이 기록을 읽는 사람은 전후 수치가 있다는 것과 그 안쪽이 비어 있다는 것을 함께 본다. 비어 있는 자리가 보이면 그 자리를 재려는 사람이 나온다.

    설명을 지어 붙이면 무엇을 잃는가. 그 설명이 맞든 틀리든 다시 재 볼 이유가 사라진다. 이미 답이 적혀 있으면 아무도 그 자리를 파지 않는다. 모른다고 적어 두는 것은 다음 사람에게 남겨 두는 일이기도 하다.

    이 자리를 비워 두는 것도 기록이다. 우리가 남긴 것은 수치와 함께 여기까지 봤다는 표시다. 다음에 이 자리를 재는 사람은 우리 수치를 출발점으로 삼을 수 있고, 우리 짐작을 지우는 일부터 하지 않아도 된다.

    초보용으로 옮기면 이렇다. 무엇이 일어났는지왜 일어났는지는 다른 물음이고, 앞의 것만 재고도 유용한 기록을 남길 수 있다. 우리가 남긴 것은 앞의 것이다. 뒤의 것은 비워 두었다.

    이 글의 결론이 좁다는 것을 적어 둔다. 우리가 말할 수 있는 것은 우리 관측에서 두 수가 함께 움직이지 않았다는 것까지다. 이것이 우리 도구의 성질인지, 이 시점의 사정인지, 더 넓은 무엇인지 우리는 모른다. 모르는 것을 결론에 넣지 않았다.

    모른다고 적는 것과 모르는 척하는 것은 다르다. 우리가 적은 것은 무엇을 재지 않았는지까지다. 재지 않은 항목이 목록으로 남아 있으면 그것은 다음에 할 일의 목록이 된다. 아무 말도 하지 않는 것과는 남는 것이 다르다.

    확인 방법. 자기 기록을 다시 읽을 때 잰 문장과 설명한 문장을 갈라 표시해 보라. 설명한 문장 옆에 근거가 없으면 그것은 그때 떠오른 짐작이다. 짐작을 지우라는 것이 아니라 짐작이라고 적어 두라는 것이다.

    정리 — 확인할 것 세 가지

    아래 셋은 우리가 이 관측들 뒤에 정한 것이다. 새 규칙이 아니라 그때 하지 않아서 놓친 것을 적어 둔 것이다.

    셋 다 재는 값을 바꾸는 것이지 무엇을 더 하는 것이 아니다. 새 도구도 필요 없고 새 절차도 필요 없다. 이미 보고 있던 화면에서 다른 칸을 보는 일이다.

    첫째, 우리는 비우기를 대화 수명 관리로만 쓴다. 자리가 부족할 때의 답으로 쓰지 않는다. 우리 관측에서 대화를 88퍼센트 줄였을 때 자리는 12.6퍼센트 줄었다.

    둘째, 우리는 자리가 부족하면 자리를 재는 방법으로 다룬다. 끝낼 것을 끝내고, 되찾은 양을 따로 확인한다. 우리 두 관측에서 끝낸 양과 되찾은 양은 매번 달랐다.

    셋째, 우리는 두 수치를 각각 본다. 하나가 줄었다고 다른 하나도 줄었다고 보지 않는다. 두 수를 나란히 적어 두면 그 둘이 함께 움직이는지 아닌지가 기록으로 남는다.

    세 항목이 공통으로 말하는 것은 지표를 바꿔 쓰지 말자는 것이다. 대화를 줄인 값으로 자리를 판단하지 않고, 끝낸 양으로 되찾은 양을 판단하지 않는다. 알고 싶은 것이 있으면 그것을 재는 값을 본다.

    그리고 셋 다 사고를 막는 장치는 아니다. 알고 싶은 것을 실제로 재게 만드는 절차일 뿐이다. 우리 경우 재 보기 전까지는 두 값이 함께 움직인다고 믿고 있었고, 그 믿음을 깬 것은 새 도구가 아니라 두 값을 나란히 적어 본 일이었다.

    셋을 한 줄로 묶으면 이렇게 된다. 목적에 맞는 지표를 골라 그 지표를 직접 재는 것이다. 대화가 길어서 힘들면 대화를 재고, 자리가 없어서 힘들면 자리를 잰다. 한 동작으로 두 문제를 함께 풀려는 순간 어느 쪽도 확인되지 않는다.

    이 글에서 우리가 바꾼 것은 도구가 아니라 무엇을 보고 판단하는가다. 비우기도 그대로 쓰고 끝내기도 그대로 한다. 달라진 것은 그 뒤에 어느 값을 확인하는가이고, 그 확인이 없으면 두 수가 갈렸다는 것을 다시 모르게 된다.

    마지막으로 하나만 덧붙인다. 이 글은 비우기가 쓸모없다는 이야기가 아니다. 우리도 계속 비운다. 우리가 적은 것은 좁다 — 우리 관측에서 두 수가 함께 움직이지 않았고, 왜 그런지는 우리가 재지 않았다.

    전후 수치와 비워 둔 물음

    우리 운영 기록에서 확인한 것 (전부 전후 수치다)

    • 한 자리의 대화 기록을 88퍼센트 줄였을 때 그 프로그램이 차지한 자리는 12.6퍼센트만 줄어 413MB 에서 361MB 가 된 것 (2026년 9월 8일)
    • 그 값들이 같은 프로그램 안에서 잰 것이라는 것. 프로그램 식별자와 시작 시각이 그대로였고, 껐다 켠 것이 아니다
    • 비우는 과정 자체가 그 구간에 자리를 더 써 413MB 에서 433MB 가 된 것. 그 시점 시스템 전체 사용률도 올랐다
    • 650MB 를 끝냈을 때 되찾은 여유가 100MB 였던 것 (2026년 9월 8일)
    • 묶음 하나를 통째로 접으면서 끝낸 합계가 2,213MB 였을 때 같은 시각 여유가 985MB 늘어난 것 (2026년 9월 9일)

    우리가 측정하지 않아 쓰지 않은 것

    • 왜 그만큼만 줄었는지 내부 기제를 우리는 재지 않았다. 우리가 가진 것은 전후 수치다. 그래서 그럴듯한 설명을 이 글에 지어 붙이지 않았다. 이유를 적는 문장이 이 글에 없는 것은 빠뜨려서가 아니라 재지 않아서다
    • 다른 도구·다른 환경에서도 같은지 재지 않았다. 근거는 우리 관측 A·B 한 차례와 C 두 차례다. 그래서 이 현상이 일반적이라는 문장이 이 글에 없다
    • 비우는 구간에서 값이 어떻게 오르내렸는지. 우리가 가진 것은 그 구간의 앞뒤 수치이고, 가장 높은 지점은 재지 않았다
    • 끝낸 양과 되찾은 양의 비율이 두 번 다른 이유. 두 수를 각각 적어 두었을 뿐이다

    한정 하나를 따로 적어 둔다

    • 비우는 동안 자리를 더 썼다는 관측을 두고 비우면 오히려 손해라고 쓰지 않았다. 그 문장은 비우는 목적이 자리 확보라고 전제해야 성립하는데, 비우는 목적은 자리가 아니라 대화 수명이다. 그 관측에서 나오는 것은 비우기에도 자리가 든다는 것까지다

    이 글에서 쓴 비유에 대해

    • 책상 위 서류와 책상이 차지한 자리라는 그림은 설명을 위해 쓴 비유다. 우리 환경이 그렇게 생겼다는 뜻이 아니고, 비유에서 따라 나오는 결론을 사실로 쓰지 않았다. 이 글의 사실은 위에 적은 다섯 줄이 전부다

    같은 계열을 다른 자리에서 다룬 글

  • 리뷰어 세 좌석이 동시에 멈췄다 — 계정 하나를 나눠 쓰고 있었다

    리뷰어 세 좌석이 동시에 멈췄다 — 계정 하나를 나눠 쓰고 있었다

    중요한 판단을 도우미 하나에게만 맡기는 것이 불안할 때가 있다. 그래서 같은 것을 여러 쪽에 따로 물어본다. 우리도 그렇게 하려고 검토 담당 자리를 여러 개 두었다. 이 글은 그렇게 나눠 둔 자리들이 같은 날 한꺼번에 멈춘 이야기다.

    이 글은 회사에 빗대어 쓴다. 끝까지 그 하나로만 간다. 검사 담당자를 세 팀에 따로 뒀는데, 셋이 같은 회원 카드 한 장으로 출입하고 있었다는 그림이다. 카드 한 장의 하루 출입 횟수가 정해져 있다면, 셋이 나눠 쓰는 순간 셋 다 그 한도 안에 갇힌다.

    용어 둘만 풀어 둔다. 자리는 어떤 담당이 앉아 일하는 한 칸이다. 우리는 도우미 하나를 자리 하나에 앉힌다. 교차검증서로 다른 쪽에게 같은 것을 따로 보게 하는 것이다. 한쪽이 놓친 것을 다른 쪽이 잡아 주기를 기대하고 하는 일이다. 이 둘만 알면 나머지는 따라온다.

    비유를 한 번 더 풀어 둔다. 회원 카드에 붙은 한도는 사람 수와 무관하게 정해진다. 한 사람이 쓰든 세 사람이 나눠 쓰든 그 카드로 들어갈 수 있는 횟수는 같다. 그래서 사람을 늘리면 한 사람이 쓸 수 있는 몫이 줄어든다. 늘어난 것은 손이고 줄어든 것은 각자의 몫이다.

    논지는 한 줄이다. 일하는 자리를 나누면 실행은 갈라지지만, 바깥 서비스의 계정은 갈라지지 않는다. 내 쪽에서 무엇을 몇 개로 나누든 그것은 내 쪽 사정이다. 한도를 세는 쪽은 바깥이고, 바깥은 카드 한 장을 보고 센다.

    앞선 편과의 경계. 자리는 있는데 사람이 없어서 지시가 삼켜진 이야기는 앞 편에 적었다. 이 글은 반대다 — 자리도 있고 사람도 있었다. 막힌 곳이 안쪽이 아니라 바깥이었다.

    이 글이 도움이 되는 사람. 같은 것을 여러 쪽에 따로 물어보는 사람이다. 하나에게만 맡길 때는 이 문제가 생기지 않는다 — 나눌 것이 없으니 나뉘지 않을 것도 없다. 검토를 둘 이상으로 늘리는 순간, 늘린 것이 무엇이고 늘지 않은 것이 무엇인지가 갈린다.

    근거의 범위를 먼저 밝혀 둔다. 이 글의 근거는 우리가 겪은 한 사건이다. 이 일이 얼마나 자주 나는지 우리는 세지 않았고, 다른 서비스의 한도 정책이 어떻게 되어 있는지도 재지 않았다. 그래서 이 글에 그런 문장이 없다.

    검토 자리를 셋으로 나눠도 바깥 계정이 하나면 총량이 늘지 않는다는 것을 칸으로 나눠 그린 도식. 첫째 칸은 우리가 나눈 것으로, 검증 담당 자리를 셋으로 두고 서로 다른 실행 묶음에 각각 배치했으며 화면에는 셋이 따로 떠 있었다고 적혀 있다. 둘째 칸은 바깥이 세는 것으로, 계정 하나를 셋이 나눠 쓰고 있었고 한도는 그 계정에 붙어 있으며 한 곳이 많이 쓰면 나머지가 줄어든다고 적혀 있다. 셋째 칸은 그래서 일어난 일로, 세 자리가 같은 시각에 개인 할당량 소진으로 멈췄고 다시 쓸 수 있게 되는 시각도 거의 같았으며 그 겹침이 카드 한 장이라는 근거였다고 적혀 있다. 넷째 칸은 그동안 화면으로, 자리 목록도 편성 점검도 정상이었고 자리는 열려 있었고 담당도 걸려 있었으며 그 상태로 엿새 동안 아무 일도 못 했다고 적혀 있다. 아래 칸은 갈라 적기다. 있는지 확인하는 검사는 자리가 열려 있는지를 보고, 되는지 확인하는 검사는 실제로 일을 시켜 본다는 것, 그리고 우리를 깨운 것은 점검이 아니라 실제로 시켜 본 일이었다는 것이 적혀 있다. 맨 아래 두 줄은 한정이다. 관측은 한 번이라는 것, 그리고 다른 서비스의 한도 정책은 우리가 재지 않았다는 것이다.
    계정을 어떻게 써야 한다는 그림이 아니다. 우리 쪽에서 나눈 수와 바깥에서 세는 수가 다르다는 것, 그리고 정상 표시가 되는 것의 증거가 아니라는 것을 그린 것이다. 아래 두 줄은 한정이다.

    세 자리가 함께 멈춘 날

    1. 우리가 나눈 수와 바깥이 세는 수는 다른 수다

    증상. 여러 쪽에 따로 검토를 맡겨 두었는데, 어느 날 그 검토가 하나도 돌아오지 않는다. 자리를 확인해 보면 셋 다 그대로 있다. 그런데 셋 다 일을 하지 않는다.

    여기서 갈리는 두 가지 해석. 하나는 우연히 셋이 동시에 고장 났다고 보는 것이다. 다른 하나는 내가 셋에게 일을 제대로 보내지 않았다고 보는 것이다. 우리 기록에서는 둘 다 아니었다. 셋은 멀쩡했고 일도 갔다. 셋이 같은 문 앞에서 막혔다.

    교차검증을 하는 이유부터 짚어 둔다. 같은 것을 여러 쪽에 따로 보게 하는 것은 한쪽이 놓친 것을 다른 쪽이 잡아 주기를 기대하는 일이다. 그러려면 그 여러 쪽이 서로 다른 조건에 있어야 한다. 같은 조건에 있는 셋은 같은 것을 놓친다.

    다양성을 늘리는 쉬운 방법. 검토를 늘리고 싶을 때 손쉬운 길은 자리를 하나 더 여는 것이다. 자리는 화면에 바로 뜨고 개수도 셀 수 있다. 그래서 자리를 늘리면 다양성이 늘었다는 감각이 함께 온다. 늘어난 것은 자리이고, 다양성은 그것과 다른 축에 있다.

    우리가 나눈 것. 우리는 검증 담당 자리를 셋으로 두고 서로 다른 실행 묶음에 각각 배치했다. 화면에는 셋이 따로 떠 있었고, 각자 자기 일감을 받았다. 여기까지는 우리가 의도한 대로였다.

    그 의도가 어디까지 이뤄졌는가. 자리를 셋으로 나눈 의도는 서로 다른 쪽에게 따로 보게 하는 것이었다. 실행을 나누는 데까지는 이뤄졌다. 셋이 서로 다른 조건에 있게 하는 데까지는 이뤄지지 않았다. 의도와 결과가 갈린 자리가 정확히 거기다.

    바깥이 센 것. 그 셋은 바깥 서비스를 쓴다. 그리고 그 서비스는 계정 하나를 보고 한도를 센다. 우리가 자리를 몇 개로 나눴는지는 바깥에서 보이지 않는다. 바깥에서 보이는 것은 같은 카드가 반복해서 들어온다는 것뿐이다.

    나눈다는 말이 무엇을 나누는가. 나누면 실행은 갈라진다. 셋은 각자 자기 일감을 붙들고 서로를 기다리지 않는다. 갈라지지 않는 것은 그 셋이 함께 쓰는 바깥 자원이다. 나뉘는 것과 나뉘지 않는 것을 갈라 적어 두지 않으면, 앞의 것을 보고 뒤의 것까지 나뉘었다고 읽는다.

    그래서 무엇이 어긋나는가. 자리를 늘리면 동시에 일하는 손은 늘어난다. 그런데 할 수 있는 총량은 늘지 않는다. 총량은 카드에 붙어 있기 때문이다. 손을 셋으로 늘리면 한 손이 많이 쓸 때 나머지 두 손이 그만큼 못 쓰게 된다.

    손이 늘면 무엇이 좋아지는가. 총량이 그대로여도 손이 느는 것에는 값이 있다. 같은 시간에 여러 갈래를 동시에 볼 수 있기 때문이다. 우리가 놓친 것은 그 값이 총량과 다른 값이라는 점이다. 동시에 보는 폭과 하루에 할 수 있는 양은 서로 다른 축이고, 자리를 늘리면 앞쪽만 는다.

    쓸 수 있는 형태는 여기까지다. 자리 수와 계정 수는 다른 수다. 우리가 본 것은 한 계정을 세 자리가 나눠 쓴 한 사건이고, 그 사건에서 그 두 수가 갈렸다. 그보다 넓은 규칙을 우리는 이 글에 쓰지 않는다.

    총량이 어디에 붙어 있는가. 이 물음이 이 글의 핵심이다. 총량이 자리에 붙어 있으면 자리를 늘릴 때 총량도 는다. 총량이 카드에 붙어 있으면 자리를 늘려도 총량은 그대로다. 어느 쪽인지는 내 화면이 아니라 바깥이 정하는 것이라, 물어보지 않으면 알 수 없다.

    세어 보기가 어려운 경우도 있다. 자리마다 어떤 계정을 쓰는지가 한 화면에 모여 있지 않으면, 그 수를 세려면 자리를 하나씩 열어 봐야 한다. 그 번거로움이 세지 않게 만드는 힘이다. 우리도 그래서 세지 않았다.

    확인 방법. 자기가 지금 굴리는 검토 자리를 세어 보고, 그 자리들이 쓰는 계정을 따로 세어 보라. 두 수가 다르면 그 차이만큼은 나눈 것처럼 보이지만 나뉘지 않은 것이다. 세어 보기 전에는 두 수가 같다고 가정하고 있는 셈이다.

    2. 세 자리가 같은 시각에 멈췄다

    이 항목은 우리가 실제로 겪은 것이고 관측 한 번이다.

    관측. 서로 다른 실행 묶음에 각각 배치한 검증 담당 세 자리같은 시각에 멈췄다. 멈춘 이유는 셋 다 같았다 — 개인 할당량을 다 썼다는 것이었다. 세 자리가 각각 다른 일감을 붙들고 있었는데도 그랬다.

    각각 다른 일감을 붙들고 있었다. 셋은 서로 다른 묶음에서 서로 다른 것을 보고 있었다. 보는 것이 다르면 쓰는 양도 다를 텐데, 그런데도 멈춘 시각이 같았다. 이것이 우리가 이 관측을 그냥 넘기지 못한 첫 이유다.

    처음에 든 생각. 우리는 이것을 우연으로 읽으려 했다. 셋이 마침 비슷하게 많이 썼을 수도 있다고 본 것이다. 각자 한도를 따로 갖고 있다면 세 개의 한도가 비슷한 시점에 차는 일이 아주 없을 수는 없다.

    그 표시가 준 인상. 멈춤에 붙은 문구는 개인 할당량을 다 썼다는 것이었다. 이 말은 한도가 각자에게 붙어 있다는 인상을 준다. 그 인상이 우연이라는 첫 해석을 오래 붙들게 했다. 말이 가리키는 단위와 실제로 세는 단위가 같으리라는 보장은 없다.

    그런데 그 해석은 다음 사실 앞에서 무너졌다. 셋의 다시 쓸 수 있게 되는 시각이 거의 같았다. 한도가 따로 있는 셋이라면 그 시각도 각자 다르게 잡힌다. 셋의 회복 시각이 나란히 붙어 있다는 것은 세는 쪽이 하나라는 뜻이다.

    회사로 옮겨 보면 이렇다. 세 팀의 검사 담당자가 같은 시각에 문 앞에서 막혔다. 그리고 다시 들어갈 수 있게 되는 시각도 셋이 같았다. 카드가 세 장이면 이런 모양이 나오지 않는다. 카드가 한 장일 때 나오는 모양이다.

    카드가 한 장이라는 것은 문 앞에서 보이지 않는다. 각 팀 화면에는 자기 담당자만 뜬다. 다른 팀 담당자가 오늘 몇 번 드나들었는지는 그 화면에 없다. 그래서 내 쪽 화면을 아무리 봐도 남은 한도가 얼마인지 알 수 없다.

    초보용으로 옮기면 이렇다. 여러 쪽이 동시에 멈췄다는 것만으로는 원인을 알 수 없다. 우연히 겹칠 수도 있기 때문이다. 그런데 다시 살아나는 시각까지 겹치면 그것은 우연으로 설명하기 어려워진다. 멈춘 시점보다 풀리는 시점이 더 많은 것을 말해 준다.

    같은 시각이라는 말에 대해. 우리는 그 겹침을 초 단위로 재어 적어 두지 않았다. 우리가 본 것은 셋이 나란히 멈췄고 나란히 풀렸다는 정도다. 그러니 이 글의 근거는 겹쳤다는 것이지 몇 초 안에 겹쳤다는 것이 아니다.

    한정을 적어 둔다. 우리는 이것을 한 차례 봤다. 같은 모양이 다시 나는지, 얼마나 자주 나는지 우리는 세지 않았다. 이 항목은 그 한 번의 관측을 적은 것이다.

    이 관측에서 남은 것. 사건 자체보다 어디를 봐야 하는지를 알게 된 것이 남았다. 여럿이 함께 멈추면 우리는 이제 각자의 사정을 보기 전에 셋이 공유하는 것이 무엇인지부터 본다. 공유하는 것이 있으면 그것이 먼저 의심할 자리다.

    확인 방법. 여러 쪽이 함께 멈췄으면 다시 쓸 수 있게 되는 시각을 각각 적어 보라. 그 시각들이 나란히 붙어 있으면 세는 쪽이 하나일 가능성을 먼저 확인해야 한다. 각자 다르게 잡혀 있으면 우연 쪽이다.

    3. 다시 쓸 수 있게 되는 시각이 겹친 것이 근거였다

    이 항목은 어떻게 알았는가에 대한 것이다. 앞 항목과 같은 사건을 다른 각도에서 본다.

    우리가 직접 본 것과 추론한 것을 갈라 적는다. 직접 본 것은 세 자리가 같은 시각에 멈췄다는 것과 회복 시각이 거의 같았다는 것이다. 추론한 것은 그 셋이 한 계정을 나눠 쓰고 있었다는 것이다. 앞은 관측이고 뒤는 그 관측에서 끌어낸 결론이다.

    
    본 것    세 자리가 같은 시각에 멈췄다
    본 것    다시 쓸 수 있게 되는 시각이 거의 같았다
    추론     한도를 세는 쪽이 하나다 = 계정 하나를 셋이 나눠 쓴다
    

    이 추론이 서는 이유. 한도가 각자에게 붙어 있으면 차는 시점도 풀리는 시점도 각자 따로 잡힌다. 셋이 같은 창을 공유할 이유가 없기 때문이다. 반대로 한도가 계정 하나에 붙어 있으면 그 창은 하나뿐이고, 셋은 같은 창이 열릴 때까지 함께 기다린다.

    왜 관측과 추론을 갈라 적는가. 갈라 두면 나중에 뒤집을 자리가 분명해지기 때문이다. 새 사실이 나왔을 때 바꿔야 하는 것은 대개 추론 쪽이고, 관측은 그대로 남는다. 둘을 한 문장에 섞어 적으면 무엇을 고쳐야 할지 알기 어려워진다.

    이 추론이 서지 않는 경우도 적어 둔다. 셋이 각자 한도를 갖고 있는데 우연히 비슷한 시각에 찼고 회복 창의 길이도 같다면, 겉모양이 비슷해질 수 있다. 우리는 그 가능성을 완전히 배제하는 실험을 하지 않았다. 우리가 한 것은 둘 중 어느 쪽이 더 그럴듯한지를 본 것이고, 이 글의 결론은 그 판단까지다.

    거의 같았다는 말도 그대로 둔다. 셋의 회복 시각은 거의 같았지 정확히 같지 않았다. 그 차이를 우리는 재지 않았고, 그 차이가 무엇에서 왔는지도 모른다. 정확히 같았다고 적으면 우리가 재지 않은 것을 잰 것처럼 쓰는 일이 된다.

    초보용으로 옮기면 이렇다. 원인을 찾을 때 같이 일어난 것만 보면 우연과 구별되지 않는다. 같이 풀리는 것까지 보면 구별이 쉬워진다. 함께 멈추는 것은 여러 이유로 생기지만, 함께 풀리는 것은 같은 시계를 보고 있다는 뜻이기 때문이다.

    이 결론을 확정으로 쓰지 않는 이유. 우리가 본 것은 한 사건이다. 그 한 사건 안에서는 이 설명이 잘 맞았지만, 한 사건은 규칙이 되기에 모자란다. 그래서 이 글은 이 결론을 우리 한 사건에 대한 설명으로 두고, 그 밖으로 넓히지 않는다.

    확인 방법. 어떤 두 가지가 같은 원인에서 나왔는지 보고 싶으면 시작 시각만 보지 말고 끝나는 시각도 보라. 둘 다 겹치면 공통 원인 쪽이고, 시작만 겹치면 아직 모르는 것이다.

    4. 그동안 화면은 전부 정상이었다

    이 항목은 왜 엿새나 걸렸는가에 대한 것이다.

    관측. 그 기간에 자리 목록도 정상이었고 편성 점검도 정상이었다. 자리는 열려 있었고 담당도 그 자리에 걸려 있었다. 화면만 보면 검토 체계가 갖춰져 있는 상태였다.

    그 상태로 무슨 일이 있었는가. 엿새 동안 아무 일도 못 했다. 검토를 맡길 수 없었으니 그 자리들이 하기로 한 일이 전부 밀렸다. 그동안 화면은 계속 정상이라고 말하고 있었다.

    왜 화면이 정상이었는가. 그 화면들이 재는 것은 자리가 열려 있는가이기 때문이다. 자리는 실제로 열려 있었다. 담당이 걸려 있는지도 재고 있었고, 그것도 사실이었다. 화면은 틀린 말을 하지 않았다. 재지 않은 것을 말하지 않았을 뿐이다.

    점검은 만든 사람이 정한 것만 본다. 점검 목록에 없는 항목은 정상으로 표시되는 것이 아니라 아예 물어지지 않는다. 물어지지 않은 것은 화면에 자리를 차지하지 않으니, 빠져 있다는 사실도 화면에 없다. 목록에 무엇이 있는지는 목록을 열어야만 알 수 있다.

    정상이라고 말한 화면이 하나가 아니었다. 자리 목록도 정상이었고 편성 점검도 정상이었다. 서로 다른 두 화면이 같은 말을 하면 확인이 두 번 된 것처럼 느껴진다. 그런데 두 화면이 같은 것을 재고 있으면 그것은 한 번 잰 것이다.

    여기가 이 절의 요점이다. 우리가 알고 싶었던 것은 지금 검토를 맡길 수 있는가였다. 화면이 답하고 있던 것은 자리가 준비돼 있는가였다. 두 물음은 다른 물음인데, 화면 하나가 둘 다 답해 주는 것처럼 읽혔다.

    정상 표시가 오래 유지되면 생기는 것. 사람은 같은 화면을 계속 다시 읽지 않는다. 같은 표시가 이어지면 그 표시를 배경으로 넘기게 된다. 그러면 그 화면은 정보를 주는 자리에서 안심을 주는 자리로 바뀐다. 우리 경우 그 안심이 엿새 동안 이어졌다.

    초보용으로 옮기면 이렇다. 점검 화면이 초록색이라는 것은 그 점검이 보는 항목이 정상이라는 뜻이다. 그 항목 목록에 내가 궁금한 것이 들어 있는지는 따로 확인해야 한다. 초록색은 확인했다는 뜻이 아니라 확인한 것들이 정상이라는 뜻이다.

    엿새라는 기간에 대해. 그 기간은 일이 멈춰 있던 기간이자 정상 표시가 유지된 기간이다. 뒤쪽이 앞쪽을 늘렸다. 이상하다는 신호가 어디에도 없으면 확인하러 갈 이유가 생기지 않기 때문이다.

    밀린 일에 대해. 검토 자리가 하기로 한 일은 대개 다른 일의 앞 단계다. 검토가 서면 그 뒤에 올 것도 함께 선다. 그래서 멈춘 기간의 값은 그 기간에 검토가 못 한 양만이 아니라, 그 뒤에서 기다린 양까지 합친 것이 된다. 그 합을 우리는 세지 않았다.

    초록색을 의심하라는 말이 아니다. 점검은 그 값을 하는 일을 한다 — 목록에 있는 항목이 어긋나면 그때는 알려 준다. 우리가 놓친 것은 그 목록에 무엇이 들어 있는지를 한 번도 읽어 보지 않았다는 점이다. 화면을 믿은 것이 아니라 화면이 무엇을 재는지 모른 채 믿은 것이다.

    확인 방법. 자기가 쓰고 있는 점검 화면에서 무엇을 재고 있는지 목록을 한 번 열어 보라. 거기에 바깥 서비스를 실제로 쓸 수 있는가가 들어 있는지가 볼 지점이다. 들어 있지 않으면 그 항목은 초록색이 답해 주지 않는 항목이다.

    5. 있는지 확인하는 검사와 되는지 확인하는 검사는 다르다

    이 항목은 어떻게 발견했는가에 대한 것이고, 우리 쪽 몫을 적는 자리다.

    발견 경위. 점검이 잡아낸 것이 아니다. 실제로 일을 시켜 보고 알았다. 검토를 하나 맡겼고, 그것이 돌아오지 않아 들여다봤고, 거기서 멈춰 있는 것을 봤다. 우리를 깨운 것은 화면이 아니라 일감이었다.

    두 검사를 갈라 적으면 이렇다.

    
    있는지 확인    자리가 열려 있는가 · 담당이 걸려 있는가
    되는지 확인    실제로 하나 시켜 보고 결과가 돌아오는가
    

    앞의 것은 싸고 뒤의 것은 비싸다. 자리가 열려 있는지는 목록만 읽으면 되지만, 되는지 보려면 실제로 한 번 써야 한다. 쓰면 그만큼 한도를 쓴다. 그래서 되는지 확인하는 검사를 자주 돌리기는 어렵다.

    그러면 얼마나 자주 해 봐야 하는가. 그 주기를 우리는 정해 두지 않았고 재지도 않았다. 말할 수 있는 것은 한 번도 해 보지 않으면 알 수 없다는 것까지다. 우리 경우 그 한 번이 엿새 뒤에 왔다.

    그래도 갈라 두어야 하는 이유. 두 검사를 하나로 여기면 싼 검사가 비싼 검사의 답까지 준다고 믿게 된다. 우리 경우가 그랬다. 자리 목록이 정상이라는 것을 보고 검토 체계가 돌아간다고 읽었다. 목록은 그런 말을 한 적이 없다.

    두 검사를 함께 쓰는 방법. 싼 검사는 자주 돌리고, 비싼 검사는 가끔 돌리되 돌렸다는 사실을 기록해 두는 것이다. 그러면 마지막으로 실제로 시켜 본 것이 언제인지가 남는다. 그 시각이 오래되어 있으면, 지금 초록색이 무엇을 근거로 초록색인지 다시 물어볼 수 있다.

    우리 쪽 몫을 적어 둔다. 바깥 서비스가 계정 하나로 한도를 세는 것은 그쪽 방식이고, 그 방식을 우리가 확인하지 않은 채 자리를 셋으로 나눈 것이 우리 쪽 몫이다. 나눈다고 나뉘는지를 물어보지 않았다.

    왜 물어보지 않았는가. 나누는 동작이 우리 쪽에서 완결되기 때문이다. 자리를 셋으로 두는 일은 우리 손으로 끝나고, 끝나면 셋이 화면에 뜬다. 거기서 바깥도 셋으로 보는가를 물으려면 한 걸음을 더 가야 하는데, 화면이 이미 셋을 보여 주고 있어서 그 걸음을 건너뛰게 된다.

    우리 쪽 몫을 적는 이유. 바깥 방식을 탓하는 문장으로 끝내면 다음에 할 일이 남지 않는다. 우리가 할 수 있었던 것은 나눈 뒤에 나뉘었는지 물어보는 일이었고, 그 일은 지금도 우리 손 안에 있다. 그래서 이 절을 우리 쪽 몫으로 적는다.

    초보용으로 옮기면 이렇다. 준비돼 있는 것과 쓸 수 있는 것은 다르다. 창고에 재료가 있는 것과 오늘 요리를 낼 수 있는 것이 다른 것과 같다. 앞의 것은 목록으로 알 수 있고, 뒤의 것은 한 번 만들어 봐야 안다.

    기록해 두면 좋은 것 하나. 실제로 시켜 본 날짜를 적어 두는 것이다. 적어 두지 않으면 마지막으로 언제 확인했는지가 기억에만 남고, 기억은 정상 표시가 이어질수록 흐려진다. 우리 경우 그 날짜가 없어서, 엿새가 지난 뒤에야 그 기간을 거꾸로 세어 봤다.

    확인 방법. 검토 체계를 갖췄으면 가끔 실제로 하나 시켜 보라. 결과가 돌아오면 되는 것이고, 돌아오지 않으면 그때 자리를 들여다보면 된다. 목록이 정상인 것을 보고 넘어가면, 다음에 알게 되는 것은 정상 표시가 유지된 기간이 얼마나 길었는지다.

    정리 — 확인할 것 세 가지

    아래 셋은 우리가 이 사건 뒤에 정한 확인 절차다. 새로 만든 규칙이 아니라 그때 하지 않아서 놓친 것을 적어 둔 것이다.

    첫째, 우리는 검토의 다양성을 자리 수가 아니라 계정 단위로 센다. 자리가 셋이어도 카드가 한 장이면 바깥에서 세는 수는 하나다. 우리 경우 그 두 수가 달랐고, 다르다는 것을 세어 보기 전에는 몰랐다.

    둘째, 우리는 되는지 확인하려면 실제로 시켜 본다. 목록에 떠 있는 것은 되는 것의 증거가 아니다. 우리 경우 엿새 동안 목록은 정상이었고 그동안 검토는 하나도 돌아오지 않았다.

    셋째, 우리는 한도가 바깥에 있다는 것을 계산에 넣는다. 내 쪽에서 실행을 나눠도 바깥 한도는 나뉘지 않는다. 나누는 쪽과 세는 쪽이 다르면, 나눈 결과는 세는 쪽 화면에 나타나지 않는다.

    세 항목이 전부 세어 보라로 수렴하는 것도 적어 둔다. 자리 수와 계정 수를 세고, 실제로 돌아온 결과를 세고, 한도가 어디에 붙어 있는지를 센다. 세지 않으면 두 수가 같다고 가정하게 되고, 그 가정은 화면이 정상인 동안 계속 유지된다.

    셋을 한 줄로 묶으면 이렇게 된다. 내가 나눈 수와 바깥이 세는 수를 각각 세어 보는 것이다. 두 수가 같다고 가정하는 순간 그 차이는 보이지 않게 되고, 차이가 드러나는 자리는 대개 일이 멈춘 뒤다.

    이 셋 중 어느 것도 사고를 막는 장치는 아니다. 셋 다 알아차리는 시점을 앞당기는 것이다. 우리 경우 알아차린 것은 엿새 뒤였고, 그 엿새는 무엇이 잘못됐는지 몰라서가 아니라 잘못됐다는 신호가 없어서 흘렀다.

    마지막으로 하나만 덧붙인다. 이 글은 계정을 어떻게 쓰라는 이야기가 아니다. 우리가 적은 것은 좁다 — 우리 한 사건에서 자리 수와 계정 수가 다른 수였고, 그 차이를 정상 표시가 가려 주었다.

    한 사건의 기록과 그 범위

    우리 운영 기록에서 확인한 것 (우리가 쓰는 환경의 한 사건이다)

    • 서로 다른 실행 묶음에 각각 배치한 검증 담당 세 자리같은 시각에 개인 할당량 소진으로 멈춘 것 (관측 한 번)
    • 셋의 다시 쓸 수 있게 되는 시각이 거의 같았던 것. 한도가 각자에게 붙어 있었다면 그 시각이 그렇게 겹칠 이유가 없다 — 이것이 셋이 한 계정을 나눠 쓰고 있었다고 본 근거다
    • 그 기간에 자리 목록도 편성 점검도 정상을 표시한 것. 자리는 열려 있었고 담당도 걸려 있었다
    • 그 상태로 엿새 동안 아무 일도 못 한
    • 그것을 잡아낸 것이 점검이 아니라 실제로 일을 시켜 본 것이었다는 것

    우리가 측정하지 않아 쓰지 않은 것

    • 다른 서비스의 한도 정책이 어떻게 되어 있는지 우리는 재지 않았다. 이 글은 우리가 쓰는 환경에서 겪은 한 사건이다. 그래서 여러 서비스가 어떻게 한도를 센다는 문장이 이 글에 없다
    • 관측은 한 번이다. 세 자리가 그렇게 된 것을 한 차례 봤을 뿐이고, 이 일이 얼마나 자주 나는지 세지 않았다. 그래서 빈도에 대한 문장이 이 글에 없다
    • 셋이 각자 한도를 갖고 우연히 비슷하게 찼을 가능성. 우리는 그 가능성을 배제하는 실험을 하지 않았다. 어느 쪽이 더 그럴듯한지를 본 것까지가 우리가 한 일이다
    • 자리를 몇 개로 두는 것이 적절한지. 우리가 확인한 것은 자리 수와 계정 수가 다른 수라는 것까지다

    이 글에서 쓴 비유에 대해

    • 세 팀의 검사 담당자가 회원 카드 한 장을 나눠 쓰는 그림은 설명을 위해 쓴 비유다. 우리 환경이 그렇게 생겼다는 뜻이 아니고, 비유에서 따라 나오는 결론을 사실로 쓰지 않았다. 이 글의 사실은 위에 적은 다섯 줄이 전부다

    이 글이 하지 않는 말

    • 이 글은 계정을 어떻게 구성해야 한다고 쓰지 않는다. 우리가 확인한 것은 우리 한 사건에서 자리 수와 계정 수가 다른 수였다는 것이고, 그 두 수를 어떻게 맞출지는 쓰는 사람의 사정에 달려 있다
    • 이 글은 이 문제를 우리가 정리했다고 쓰지 않는다. 위 세 가지는 우리가 정한 확인 절차이고, 그 절차가 다음에 같은 것을 잡아낼지는 아직 재지 않았다

    같은 계열을 다른 자리에서 다룬 글

  • 역할 이름으로 보냈는데 아무도 못 받았다 — 빈 좌석이 지시를 삼킨 사고

    역할 이름으로 보냈는데 아무도 못 받았다 — 빈 좌석이 지시를 삼킨 사고

    도우미를 하나만 쓸 때는 지시를 보낼 곳이 하나뿐이다. 둘 이상 굴리기 시작하면 달라진다. 누구에게 무엇을 시킬지 정해야 하고, 그러려면 부르는 방법이 필요하다. 이 글은 우리가 그 부르는 방법을 쓰다가 겪은 일이다.

    이 글은 회사에 빗대어 쓴다. 끝까지 그 하나로만 간다. 쪽지를 직책으로 보낸다는 것은 사람 이름 대신 직책을 적어 보내는 것이다 — “검토 담당자에게 전해 주세요”처럼. 자리는 그 직책이 앉아 있어야 할 책상이다. 빈 자리는 책상은 있는데 사람이 없는 상태다. 이 셋만 알면 나머지는 따라온다. 전문 용어는 이 글에 더 나오지 않는다.

    논지는 한 줄이다. 직책으로 보낸 쪽지는, 그 직책에 사람이 앉아 있을 때만 주소다. 사람이 없으면 그것은 주소가 아니라 책상 번호일 뿐이다. 그리고 하나 더 — 보냈다와 받았다는 다른 사실이다. 보내는 쪽 화면에 뜨는 성공은 보낸 쪽 사정이지 받은 쪽 사정이 아니다.

    앞선 편과의 경계. 도구가 조용히 실패하는 여러 형태는 앞 편에 목록으로 적었고, 이 글은 그중 한 가지 기제만 판다. 주소가 무엇을 보장하고 무엇을 보장하지 않는지, 그리고 도달을 어떻게 확인하는지다.

    이 글이 도움이 되는 사람. 도우미를 둘 이상 굴리기 시작한 사람이다. 하나만 쓸 때는 이 문제가 생기지 않는다 — 보낼 곳이 하나뿐이니 잘못 갈 데가 없다. 둘이 되는 순간 누구에게 보낼지를 적어야 하고, 거기서부터 이 이야기가 시작된다.

    근거의 범위를 먼저 밝혀 둔다. 이 글의 근거는 우리 운영 기록의 관측 세 건이다. 이 일이 얼마나 자주 나는지 우리는 세지 않았고, 다른 시스템이 직책 주소를 어떻게 다루는지도 재지 않았다. 그래서 이 글에 그런 문장이 없다. 세 건에서 나온 것만 적고, 세 건이 말해 주지 않는 것은 적지 않았다.

    직책으로 보낸 쪽지가 빈 자리로 갔을 때 보내는 쪽과 받는 쪽에서 무엇이 갈리는지를 칸으로 나눠 그린 도식. 첫째 칸은 보내는 쪽이 본 것으로, 직책 이름으로 보냈고 보내기가 성공으로 돌아왔으며 상태판에는 작업 중으로 보였다고 적혀 있다. 둘째 칸은 실제로 일어난 일로, 자리는 살아 있었지만 사람이 없었고 쪽지는 화면에만 쌓였으며 긴 글은 입력줄에 붙은 채 제출되지 않았다고 적혀 있다. 셋째 칸은 직책 주소가 보장하는 것과 보장하지 않는 것을 갈라 놓았다. 보장하는 것은 그 직책 자리로 배달을 시도한다는 것까지이고, 보장하지 않는 것은 그 자리에 사람이 있는지, 사람이 읽었는지, 읽고 착수했는지다. 넷째 칸은 도달을 확인하는 방법으로, 새로 생긴 파일과 갱신된 기록과 눈에 보이는 응답 같은 상대의 활동 변화를 본다고 적혀 있다. 아래 칸은 관측 세 건의 요약이다. 첫째는 빈 자리에 놓인 쪽지를 책상 위 기계가 명령서로 오인해 실행하려 한 것이고, 둘째는 하루 뒤 같은 일이 다시 나서 그날 전원에게 보낸 공지까지 그 자리에서 삼켜진 것이며, 셋째는 긴 글이 입력줄에 붙은 채 약 한 시간 사십오 분 방치된 것이다. 맨 아래 두 줄은 한정이다. 관측은 세 건이고 얼마나 자주 나는지는 세지 않았다는 것, 그리고 같은 경고가 우리 도구 머리말에 이미 적혀 있었지만 읽었으면 막았을지는 우리가 재지 않았다는 것이다.
    직책 주소가 나쁘다는 그림이 아니다. 그 주소가 어디까지 보장하는지와, 보내는 쪽 화면이 받는 쪽 사정을 보여 주지 않는다는 것을 그린 것이다. 아래 두 줄은 한정이다.

    지시가 사라진 경로

    1. 직책으로 보낸 쪽지는 그 직책에 사람이 있을 때만 주소다

    증상. 지시를 보냈는데 아무 일도 일어나지 않는다. 보내는 쪽 화면에는 보냈다고 떠 있다. 상대를 확인해 보면 그쪽도 문제가 없어 보인다. 그런데 시킨 일은 시작되지 않았다.

    여기서 갈리는 두 가지 해석. 하나는 상대가 게으르거나 못 알아들었다고 보는 것이다. 다른 하나는 내가 잘못 보냈다고 보는 것이다. 우리 기록에서는 둘 다 아니었다. 보내기는 제대로 됐고, 그 직책 자리도 그대로 있었다. 그 자리에 사람이 없었다.

    직책으로 부르는 것이 왜 편한가. 사람이 바뀌어도 부르는 이름을 바꾸지 않아도 되기 때문이다. 담당자가 교체돼도 “검토 담당자”라고 적으면 새 담당자에게 간다. 여러 도우미를 굴릴 때 이것은 큰 이점이라 우리도 그렇게 쓴다.

    회사로 옮겨 보면 이렇다. “검토 담당자에게 전해 주세요”라고 적어 쪽지를 보냈다고 하자. 그 쪽지는 검토 담당자 책상으로 간다. 담당자가 그 자리에 있으면 읽고, 자리를 비웠으면 책상에 남는다. 쪽지를 보낸 사람은 그 두 경우의 차이를 보지 못한다.

    그런데 그 편의는 하나를 가린다. 직책은 자리를 가리키는 이름이지 사람이 있다는 보장이 아니다. 사람이 나가고 자리만 남으면 그 이름은 여전히 유효하다. 쪽지는 그 책상까지 정확히 배달되고, 거기서 멈춘다.

    처음 쓸 때 생기는 기대. 직책으로 보내기가 되면 사람들은 그것을 사람에게 보내는 것으로 여긴다. 실제로 대개는 그렇게 동작하니 그 기대가 굳는다. 기대가 굳으면 확인하는 절차가 사라진다 — 늘 되던 것을 매번 확인하지는 않기 때문이다. 그러다 한 번 어긋나면 아무도 보고 있지 않은 상태가 된다.

    주소가 보장하는 것과 보장하지 않는 것을 갈라 적으면 이렇다.

    
    보장한다        그 직책 자리로 배달을 시도한다
    보장하지 않는다  그 자리에 사람이 있는지
    보장하지 않는다  그 사람이 읽었는지
    보장하지 않는다  읽고 일을 시작했는지
    

    이 네 줄이 한 덩어리로 묶여 있는 것이 문제다. 넷을 따로 떼어 놓으면 아무도 헷갈리지 않는다. 그런데 실제로는 보내기 한 번에 넷이 함께 따라오는 것처럼 느껴진다. 화면에 뜨는 것은 맨 윗줄 하나인데, 머릿속에서는 넷이 다 켜진다.

    우리가 읽은 방식. 우리는 맨 윗줄이 참인 것을 보고 아랫줄 셋도 참이라고 읽었다. 보내기가 성공으로 돌아왔으니 상대가 받았다고 본 것이다. 이 글의 세 관측은 전부 그 한 칸에서 갈렸다.

    왜 그렇게 읽게 되는가. 보내는 쪽과 받는 쪽이 각자 자기 기록만 남기기 때문이다. 보내는 쪽 기록에는 내보냈다는 줄이 남고, 받는 쪽 기록에는 받았다는 줄이 남는다. 두 기록은 서로를 확인해 주지 않는다. 한쪽만 보고 있으면 그 한쪽이 전부처럼 보인다.

    세 관측이 갈린 자리를 미리 적어 둔다. 첫째는 자리에 사람이 없어서 갈렸다. 둘째도 같은 이유인데 목록이 정상으로 보였다는 점이 다르다. 셋째는 사람이 있었는데 글이 제출되지 않아서 갈렸다. 세 번 다 보내는 쪽 화면은 정상이었다.

    확인 방법. 자기 도구에서 보내기 성공이 무엇을 보고 성공이라고 하는지 한 번 찾아보라. 대개는 보내는 쪽에서 내보냈다는 뜻이다. 받는 쪽이 읽었는지까지 확인해 주는 도구라면 그 사실이 어딘가 적혀 있을 것이고, 적혀 있지 않으면 그것은 보장 밖이다.

    2. 빈 책상에 놓인 쪽지를 기계가 명령서로 오인했다

    이 항목은 우리가 실제로 겪은 것이고, 관측 한 건이다.

    관측. 직책 이름으로 보낸 지시가 사람이 앉지 않은 빈 자리로 갔다. 나중에 그 자리 화면을 열어 보니 보낸 문장이 그대로 찍혀 있었다. 그리고 그 문장 아래에 오류가 하나 붙어 있었다.

    그 오류가 무엇이었는가. 그 문장을 명령으로 실행하려다 난 것이었다. 사람이 없는 자리에서는 들어온 글자를 읽어 줄 상대가 없다. 대신 그 자리에 남아 있던 기계가 그 글자를 자기에게 내린 명령으로 받아들였다.

    이것이 빈 책상보다 나쁜 이유. 쪽지가 빈 책상에 그냥 놓였다면 나중에 누가 와서 읽으면 된다. 우리 경우는 책상 위 기계가 그 쪽지를 명령서로 오인해 실행하려 한 것이다. 실행은 실패했고 오류만 남았다. 남은 것은 지시가 아니라 오류 기록이었다.

    왜 자리가 글자를 명령으로 받아들이는가. 사람이 앉아 있으면 들어온 글자는 그 사람에게 하는 말이다. 사람이 없으면 그 글자를 받을 상대가 자리밖에 남지 않는다. 자리는 원래 명령을 받아 실행하는 곳이라, 들어온 글자를 자기 일감으로 읽는다. 같은 글자가 상대에 따라 다른 것이 되는 것이다.

    남은 흔적이 무엇인지가 중요하다. 지시가 남았으면 나중에라도 누가 읽고 처리할 수 있다. 우리 경우 남은 것은 오류 기록이었다. 오류 기록은 그것을 낸 자리의 사정이지 내가 시킨 일이 아니다. 그래서 그 자리를 나중에 열어 본 사람도 무엇을 하라는 지시였는지 바로 알기 어렵다.

    보내는 쪽에서는 무엇이 보였는가. 아무것도 이상하지 않았다. 보내기는 성공이었고, 그 자리도 목록에 그대로 있었다. 이 사고는 보내는 쪽 화면에 아무 흔적도 남기지 않는다. 그래서 보낸 사람이 먼저 알아차릴 방법이 없다.

    지시가 사라진 것이 아니라 다른 것이 됐다. 우리는 사고를 상상할 때 없어지는 쪽을 떠올린다. 쪽지가 분실되거나 배달이 실패하는 그림이다. 우리 경우는 그것이 아니라 도착해서 다른 것으로 처리된 경우다. 없어진 것을 찾는 눈으로는 이 형태가 보이지 않는다.

    초보용으로 옮기면 이렇다. 직책으로 보낸 쪽지는 사람이 아니라 자리에 배달된다. 그 자리에 사람이 있으면 읽고, 없으면 자리가 알아서 처리한다. 자리가 알아서 하는 처리가 우리가 원한 것과 같으리라는 보장은 어디에도 없다.

    이 관측에서 우리가 얻은 것. 사고 자체보다 흔적의 모양을 알게 된 것이 남았다. 이 형태가 나면 그 자리에 내가 쓴 문장과 그 아래 오류가 함께 남는다. 그 모양을 알아 두면 다음에 같은 화면을 봤을 때 무엇이 일어난 것인지 바로 알 수 있다.

    확인 방법. 직책으로 지시를 보냈으면 그 자리의 화면을 한 번 보라. 내가 보낸 문장이 글자 그대로 찍혀 있고 그 아래 오류가 붙어 있으면, 그것은 읽힌 것이 아니라 실행되려다 실패한 것이다.

    3. 자리는 살아 있었고 사람만 없었다

    이 항목도 우리가 겪은 것이고 관측 한 건이다. 앞 항목의 다음 날 일이다.

    관측. 자리 목록에는 정상으로 보였다. 자리는 살아 있었고 직책도 그 자리에 등록돼 있었다. 사람만 없었다. 목록이 보여 주는 것은 책상이 있다는 것까지이고, 그 앞에 사람이 앉아 있는지는 아니었다.

    무엇이 삼켜졌는가. 그 자리로 간 직책 주소 쪽지는 전부 화면에만 쌓였다. 그날 전원에게 보낸 공지도 그 자리에서 삼켜졌다. 여러 곳에 같은 것을 보내면 한 곳이 빠져도 나머지가 받으니 괜찮다고 생각하기 쉬운데, 빠진 그 한 곳은 아무 신호도 내지 않는다.

    누가 찾아냈는가. 보낸 사람이 아니었다. 다른 자리에서 일하던 쪽배달 기록을 대조하다 찾아냈다. 보낸 쪽 기록에는 보낸 것만 남고, 받은 쪽 기록에는 받은 것만 남는다. 두 기록을 나란히 놓아야 보냈는데 받은 기록이 없는 줄이 보인다.

    전원에게 보낸 것이 삼켜졌다는 말의 뜻. 여러 곳에 같은 내용을 보내면 안심하게 된다. 한 곳이 못 받아도 나머지가 받았으니 일이 굴러갈 것 같기 때문이다. 그런데 못 받은 그 한 곳은 못 받았다고 말해 주지 않는다. 그래서 여러 곳에 보내는 것은 도달을 늘리는 방법이지 도달을 확인하는 방법이 아니다.

    왜 보낸 사람이 못 찾았는가. 보낸 사람은 자기 기록을 본다. 거기에는 보냈다는 줄이 정상으로 남아 있으니 볼 이유가 없다. 어긋남은 두 기록 사이에 있고, 그 사이는 한쪽만 보는 사람 눈에는 들어오지 않는다. 그래서 이 형태는 제3자가 대조할 때 드러난다.

    왜 사람이 없었는가. 그 자리는 우리가 믿기로 등록해 둔 위치 밖에서 열렸다. 처음 여는 자리에는 계속할지 묻는 확인 창이 뜨는데, 거기서 자동 응답이 그만두기를 골랐다. 그래서 사람이 앉기도 전에 자리를 떴다.

    그 뒤에 무엇이 쏟아졌는가. 그 순서 다음에는 새로 앉는 사람에게 주는 안내문이 이어지게 돼 있었다. 사람은 이미 없었으므로 그 안내문이 빈 껍데기에 그대로 쏟아졌다. 자리는 그때부터 글자만 받아 쌓는 상태가 됐다.

    안내문이 쏟아졌다는 말의 뜻. 새로 앉는 사람에게 주는 안내문은 사람이 있다는 전제로 만들어진 순서다. 앞 단계에서 사람이 사라졌는데 그 순서는 그대로 진행됐다. 그래서 자리에는 읽을 사람 없는 긴 글이 먼저 쌓였고, 뒤이어 우리가 보낸 쪽지들이 그 위에 쌓였다.

    목록이 정상으로 보인 이유. 목록은 자리를 세는 장치다. 자리가 열려 있는지, 그 자리에 어떤 직책이 걸려 있는지를 보여 준다. 사람이 앉아 있는지는 그 목록이 재는 것이 아니었다. 재지 않는 것을 정상으로 표시하지는 않지만, 재는 것만 정상으로 표시해도 화면 전체는 정상으로 보인다.

    치우는 것도 쉽지 않았다. 키를 보내 봐도 반응이 없었다. 자리를 닫는 지시는 다른 자리에서는 권한이 없었다. 결국 그 자리를 붙들고 있던 프로그램을 바깥에서 직접 끝내는 방법으로 치웠다.

    치우기가 어려웠다는 것도 함께 적어 둔다. 사고를 생각할 때 우리는 일어난 것만 세고 되돌리는 비용은 잘 세지 않는다. 이번 경우 자리를 정리하는 데 평소 쓰던 방법이 두 가지나 듣지 않았다. 사고가 나면 그 뒤처리에도 시간이 든다는 것이 이 관측에서 함께 나온 사실이다.

    초보용으로 옮기면 이렇다. 목록에 자리가 떠 있는 것과 그 자리에 사람이 있는 것은 다른 사실이다. 목록은 앞의 것을 보여 주고, 우리가 알고 싶은 것은 뒤의 것이다. 두 사실이 갈릴 때 목록은 아무 경고도 하지 않는다.

    한정을 적어 둔다. 이 형태의 관측은 두 번이다 — 이 항목과 앞 항목이다. 이 일이 얼마나 자주 나는지 우리는 세지 않았다.

    이 관측이 앞 항목과 다른 점. 앞 항목에서는 그 자리를 열어 보면 오류라도 남아 있었다. 이번에는 목록이 정상이라고 말해 주고 있었다. 정상이라는 표시는 확인을 멈추게 한다. 그래서 같은 원인이라도 알아차리기까지 걸리는 시간이 달라진다.

    확인 방법. 자기 자리 목록을 볼 때 그 목록이 무엇을 재서 정상이라고 하는지 보라. 자리가 열려 있다는 것과 사람이 앉아 있다는 것 중 어느 쪽인지가 갈림이다. 그리고 자기 기록에서 보낸 것과 받은 것을 나란히 놓고 대조해 보라.

    4. 보냈는데 제출되지 않은 채 한 시간 넘게 서 있었다

    세 번째 형태이고 관측 한 건이다. 앞의 둘과 어긋난 자리가 다르다.

    관측. 긴 본문을 보냈다. 그런데 그 글이 상대 입력줄에 붙은 채 제출되지 않았다. 쪽지가 책상까지 갔고 사람도 있었는데, 손에 들린 채 놓이지 않은 상태였다. 그대로 약 한 시간 사십오 분 방치됐다.

    그동안 보내는 쪽에서는. 반환값은 성공이었다. 상태판도 작업 중으로 보였다. 두 신호가 모두 정상이었으므로 확인할 이유가 없었다. 확인할 이유가 없다는 것이 이 형태의 특징이다.

    여기서 갈린 것. 보내기와 제출은 다른 단계다. 글자를 상대 입력줄에 넣는 것과, 그 입력을 확정해 넘기는 것은 서로 다른 동작이다. 앞 단계만 되고 뒤 단계가 안 되면 글은 눈앞에 있는데 아무도 그것을 받지 않은 상태가 된다.

    회사로 옮겨 보면 이렇다. 쪽지를 담당자 손에 쥐여 줬는데 담당자가 그것을 책상에 내려놓지 않은 상태다. 손에 들려 있으니 잃어버린 것도 아니고, 내려놓지 않았으니 일감 더미에 들어간 것도 아니다. 옆에서 보면 받은 것처럼 보이는데 일은 시작되지 않는다.

    두 신호가 모두 정상일 때가 곤란하다. 신호 하나가 이상하면 사람은 확인하러 간다. 우리 경우는 반환값도 정상이고 상태판도 정상이었다. 이럴 때 확인하러 가려면 정상 신호를 의심할 이유가 따로 있어야 하는데, 그 이유가 화면 어디에도 없었다.

    어떻게 알았는가. 상태판이 아니었다. 상대가 만든 파일의 수정 시각을 보고 알았다. 일을 하고 있으면 무언가가 바뀌어 있어야 하는데, 그 시각이 그대로였다. 그것만이 활동의 증거였다.

    한 시간 사십오 분이라는 시간. 그 시간은 일이 멈춰 있던 시간이자 아무도 그것을 모르던 시간이다. 뒤쪽이 더 중요하다. 멈춘 것은 다시 시작하면 되지만, 모르는 동안에는 다음 일이 그 위에 쌓인다. 우리가 확인 절차를 정한 이유가 그것이다.

    파일 수정 시각이 왜 증거가 되는가. 그것은 상대가 남긴 것이기 때문이다. 상태판의 값은 상태를 적어 넣는 쪽이 만든 것이고, 파일은 일을 한 쪽이 만든 것이다. 앞의 것은 일하지 않아도 남을 수 있고, 뒤의 것은 일해야 남는다.

    초보용으로 옮기면 이렇다. 상대가 일하고 있는지 알고 싶으면 상대가 남긴 것을 보라. 상태판은 상태를 보여 주는 것이 아니라 상태라고 기록된 값을 보여 준다. 기록하는 쪽이 멈춰 있으면 그 값도 멈춘 채 남는다.

    확인 방법. 긴 글을 보냈으면 상대 화면을 한 번 보라. 그 글이 입력줄에 그대로 서 있으면 아직 제출되지 않은 것이다. 그리고 진행 여부는 상태판이 아니라 새로 생기거나 바뀐 파일로 확인하면 된다.

    5. 같은 경고가 이미 적혀 있었고 읽히지 않는 자리에 있었다

    이 항목은 조심해서 적어야 하는 자리다.

    확인한 것. 위 형상에 대한 경고는 우리 도구의 소스 머리말에 이미 적혀 있었다. 새로 알아낸 것이 아니라 이미 쓰여 있던 것이다. 그리고 아무도 읽지 않았다.

    그래서 무엇이 문제인가. 문서가 없어서 난 사고가 아니다. 있는데 읽히지 않는 자리에 있었던 것이다. 이 둘은 다른 문제이고 처방도 다르다. 앞쪽은 쓰면 되고, 뒤쪽은 쓴 것을 어디에 두는가의 문제다.

    소스 머리말이 어떤 자리인가. 그 파일을 고치러 들어간 사람은 지나가는 자리다. 반대로 그 도구를 쓰기만 하는 사람은 평생 열어 볼 일이 없는 자리이기도 하다. 우리 경우 사고를 겪은 쪽은 뒤쪽이었다. 글이 있었느냐가 아니라 누가 지나가는 자리에 있었느냐가 갈림이었다.

    읽히는 자리와 쓰이는 자리. 무언가를 적을 때 우리는 적을 곳부터 정한다. 대개는 그 내용에 가까운 자리에 적는다. 그런데 그 자리가 그 내용을 필요로 하는 사람이 지나가는 자리인지는 따로 물어야 한다. 우리 경우 그 둘이 달랐다.

    여기서 우리가 멈춘 자리. 읽었으면 막았을지 우리는 재지 않았다. 그러니 이 글은 문서를 읽었으면 막았다고 쓰지 않는다. 경고가 있었다는 것과 그것을 읽지 않았다는 것까지가 우리가 확인한 사실이고, 그다음은 확인하지 않았다.

    이 구분이 왜 필요한가. 읽었으면 막았다고 적으면 처방이 더 읽자로 간다. 그것이 맞는지 우리는 모른다. 우리가 아는 것은 그 자리에 있던 경고가 읽히지 않았다는 것뿐이고, 거기서 나오는 처방은 확인을 사람 기억이 아니라 절차에 두자는 쪽이다.

    이 항목을 마지막에 둔 이유. 앞의 세 관측을 읽고 나면 몰라서 당했다는 결론으로 가기 쉽다. 그런데 우리 경우 그 결론은 사실과 다르다. 적혀 있었다. 몰랐던 것이 아니라 그 앎이 필요한 사람에게 닿지 않은 것이고, 두 가지는 처방이 다르다.

    이 글이 하지 않는 말. 이 글은 우리가 이 문제를 해결했다고 쓰지 않는다. 위 세 가지는 우리가 정한 확인 절차이지 사고를 없애는 장치가 아니다. 그 절차가 얼마나 잡아내는지도 우리는 아직 재지 않았다.

    확인 방법. 자기 기록에서 사고가 났으면 그 내용이 이미 어딘가 적혀 있었는지 찾아보라. 적혀 있었다면 그것이 어디에 적혀 있었는지를 함께 보라. 읽는 사람이 지나가지 않는 자리에 있었다면, 고칠 것은 문장이 아니라 자리다.

    정리 — 확인할 것 세 가지

    첫째, 우리는 도달을 반환값이 아니라 상대의 활동 변화로 확인한다. 새로 생긴 파일, 갱신된 기록, 눈에 보이는 응답이 그 변화다. 우리 세 관측에서 보내는 쪽 화면은 세 번 다 정상이었다.

    둘째, 우리는 직책으로 보내기 전에 그 자리에 사람이 있는지 본다. 목록에 자리가 떠 있는 것과 사람이 있는 것은 다른 사실이다. 우리 경우 목록은 정상으로 보였고 사람만 없었다.

    셋째, 우리는 긴 글을 보낸 뒤 제출됐는지 화면으로 확인한다. 보내기와 제출은 다른 단계다. 우리 기록에서 한 번은 입력줄에 붙은 채 한 시간 넘게 서 있었다.

    셋 다 보낸 뒤에 하는 일이라는 점도 적어 둔다. 보내기 전에 할 수 있는 준비도 있겠지만, 우리 세 관측은 전부 보낸 다음에 갈렸다. 그래서 우리가 정한 것도 보낸 다음에 무엇을 보는가였다.

    세 가지를 한 줄로 묶으면 이렇게 된다. 보낸 쪽에서 보이는 것은 전부 보낸 쪽 사정이다. 받는 쪽 사정을 알려면 받는 쪽에서 무언가가 바뀌는 것을 봐야 한다. 위 셋은 그 바뀜을 어디서 보는지를 정한 것이다.

    셋 중에 첫째가 나머지 둘을 포함한다. 상대의 활동 변화를 보면 사람이 없든 글이 제출되지 않았든 변화가 없다는 하나의 신호로 나타나기 때문이다. 둘째와 셋째는 그 신호가 나왔을 때 어디를 먼저 볼지를 정해 준다.

    그리고 이 셋은 사고를 막는 장치가 아니라 확인하는 절차다. 우리 기록의 세 번 중 두 번은 사람이 우연히 알아차렸고, 한 번은 다른 쪽이 기록을 대조하다 찾았다. 우연히 알아차리는 것을 절차로 바꾸자는 것이 이 셋의 뜻이다.

    마지막으로 하나만 덧붙인다. 이 글은 직책으로 부르는 방식을 쓰지 말라는 이야기가 아니다. 우리도 계속 쓴다. 우리가 적은 것은 좁다 — 직책은 자리를 가리키고, 자리에 사람이 있는지는 다른 곳에서 확인해야 한다. 그리고 그 확인은 보내는 쪽 화면이 아니라 받는 쪽에서 바뀐 것에서 나온다.

    관측 한 건씩이라는 점

    우리 운영 기록에서 확인한 것 (우리 한 시스템의 기록이다)

    • 직책 이름으로 보낸 지시가 사람이 앉지 않은 빈 자리로 갔고, 그 화면에 보낸 문장이 그대로 찍힌 채 그 문장을 명령으로 실행하려다 난 오류가 붙어 있던 것. 즉 그 문장은 읽히지 않고 실행 시도됐다 (관측 한 건)
    • 하루 뒤 같은 일이 다시 난 것. 자리 목록에는 정상으로 보였고 자리도 직책도 있었으며 사람만 없었다 (관측 한 건)
    • 그 자리로 간 직책 주소 쪽지가 전부 화면에만 쌓였고, 그날 전원에게 보낸 공지도 그 자리에서 삼켜진 것
    • 그것을 찾아낸 것이 보낸 사람이 아니라 다른 자리에서 일하던 쪽이었고, 배달 기록을 대조해 찾아낸 것
    • 그 자리가 우리가 믿기로 등록해 둔 위치 밖에서 열렸고, 처음 여는 자리에 뜨는 확인 창에서 자동 응답이 그만두기를 골라 사람이 앉기 전에 자리를 뜬 것. 그다음 순서였던 안내문이 빈 껍데기에 쏟아진 것
    • 회수가 쉽지 않았던 것 — 키를 보내도 반응이 없었고, 자리를 닫는 지시는 다른 자리에서 권한이 없었으며, 결국 그 자리를 붙들고 있던 프로그램을 바깥에서 직접 끝냈다
    • 긴 본문이 상대 입력줄에 붙은 채 제출되지 않고 약 한 시간 사십오 분 방치된 것. 그동안 보내는 쪽 반환값은 성공이었고 상태판은 작업 중으로 보였다 (관측 한 건)
    • 그 진행 여부를 상대가 만든 파일의 수정 시각으로 알아낸 것. 그것만이 활동의 증거였다
    • 같은 형상에 대한 경고가 우리 도구의 소스 머리말에 이미 적혀 있었고 읽히지 않은 것

    우리가 측정하지 않아 쓰지 않은 것

    • 다른 시스템의 직책 주소 방식은 재지 않았다. 이 글은 우리 한 시스템의 기록이다. 그래서 직책 주소를 쓰지 말라거나 어느 도구가 이 문제를 어떻게 다룬다는 문장이 이 글에 없다
    • 이 형태가 얼마나 자주 나는지 세지 않았다. 근거는 위에 적은 관측 세 건이 전부다. 그래서 빈도에 대한 문장이 이 글에 없다
    • 경고를 읽었으면 막았을지 우리는 재지 않았다. 확인한 것은 경고가 이미 적혀 있었다는 것과 읽히지 않았다는 것까지다. 그래서 문서를 읽었으면 막았다는 문장을 이 글에 쓰지 않았다
    • 사람이 없는 자리가 들어온 글자를 어떻게 처리하는지의 전체 규칙. 우리가 본 것은 그 한 자리에서 실행 시도로 나타난 경우다

    이 글에서 쓴 비유에 대해

    • 회사에서 직책으로 쪽지를 보내는 그림은 설명을 위해 쓴 비유다. 우리 시스템이 회사처럼 동작한다는 뜻이 아니고, 비유에서 따라 나오는 결론을 사실로 쓰지 않았다. 이 글의 사실은 위에 적은 관측 세 건과 경고 문안 한 건이 전부다

    같은 계열을 다른 자리에서 다룬 글

  • 경보가 늑대 소년이 될 때 — 오탐을 만드는 두 가지 설계 실수

    경보가 늑대 소년이 될 때 — 오탐을 만드는 두 가지 설계 실수

    자동화를 어느 정도 굴리기 시작하면 감시를 붙이게 된다. 뭔가 잘못되면 알려 달라는 것이다. 그렇게 붙인 경보가 어느 날 울린다. 가 보면 아무 문제가 없다. 그런 일이 몇 번 이어지면 그다음부터는 그 경보를 보는 눈이 달라진다. 이 글은 우리 기록에서 그런 경보 둘을 열어 본 이야기다.

    논지는 한 줄이다. 경보는 자기가 잰 것을 정직하게 말한다. 오탐은 그 값을 다른 것의 증거로 쓸 때 생긴다. 우리가 연 두 경보는 둘 다 잰 값 자체는 맞았다. 어긋난 자리는 그 값에서 어떤 결론으로 건너뛰는 칸이었고, 그 칸을 설계한 것은 경보가 아니라 우리다.

    제목에 쓴 비유부터 밝혀 둔다. 늑대 소년은 이솝 우화에서 온 널리 쓰이는 비유이고, 헛울림이 이어지면 사람이 경보를 읽지 않게 된다는 뜻으로 쓰인다. 우리는 그 우려를 전제로 설계를 고쳤다. 다만 우리 기록에는 그 우려가 맞는지 측정한 값이 없다. 그러니 이 글은 오탐이 사람의 주의를 얼마나 떨어뜨리는지에 대해 아무 수치도 말하지 않는다 — 비유는 제목까지이고, 본문은 우리가 실제로 연 두 경보의 안쪽이다.

    이 글에서 쓰는 말 몇 개를 풀어 둔다. 경보는 조건을 만족하면 사람에게 알리도록 미리 걸어 둔 장치다. 임계는 그 조건에서 넘으면 알리기로 정한 값이다. 타임아웃은 정해진 시간 안에 끝나지 않으면 기다리기를 중단하도록 내가 정한 조건이다. 반환값은 명령이 끝나면서 남기는 숫자이고, 0 이면 정상 종료라는 뜻으로 쓰인다. 등급은 경보를 정상·경고 같은 칸으로 나눈 것이다. 처방은 그 경보에 딸려 나오는 조치 안내다.

    앞선 편과의 경계. 도구가 울리지 않고 조용히 실패한 경우들은 앞 편에 적었고, 이 글은 반대쪽인 잘못 울린 쪽이다. 그리고 무언가를 실제로 바꿔 놓은 사고는 연습 모드를 다룬 편에 있다. 이 글의 두 사례는 아무것도 바꾸지 않았다 — 사람을 다른 곳으로 보냈을 뿐이다.

    경보가 잰 값과 말한 결론이 어긋나는 구조를 칸으로 나눠 그린 도식. 첫째 칸은 잰 값으로, 경보가 실제로 측정한 것만 적는 자리다. 응답이 2초 안에 오지 않았다는 것, 예약이 정한 수명 30분을 넘겼다는 것, 자원 값을 읽지 못했다는 것이 적혀 있다. 둘째 칸은 말한 결론으로, 그 값에서 건너뛴 판정이다. 조회가 실패했다, 팀이 뜨지 않았다, 자원이 경고 수준이다가 적혀 있다. 셋째 칸은 처방으로, 그 결론에 딸려 나온 조치다. 바이러스 검사 격리를 복구하라, 실패 항목으로 결재 목록에 올려라, 경고 등급으로 표시하라가 적혀 있다. 넷째 칸은 어긋난 자리로, 잰 값과 결론 사이에 아무도 검사하지 않는 한 칸이 있다는 것과 그 칸을 채운 것은 경보가 아니라 설계라는 것이 적혀 있다. 아래 칸은 상태 칸을 몇 개로 두느냐의 대비다. 칸이 둘일 때는 정상과 위험뿐이라 측정 실패가 위험 쪽으로 접히고, 칸이 셋일 때는 정상과 위험과 측정 불능이 갈라져 못 쟀다는 사실이 그대로 남는다고 적었다. 그 아래에 우리 경보 여섯 건은 임계를 넘은 값이 0건이고 값이 비어 있으며 오류 목록에 getloadavg 가 찍혀 있었다는 것과, getloadavg 는 윈도우에 없는 함수여서 나쁜 값이 아니라 없는 값이라는 것을 적었다. 맨 아래 두 줄은 한정이다. 사례 하나는 관측 한 건이고 그 통지 문안 자체는 틀리지 않았다는 것, 그리고 오탐이 사람의 주의를 얼마나 떨어뜨리는지는 우리가 재지 않았다는 것이다.
    경보가 나쁘다는 그림이 아니다. 잰 값과 말한 결론 사이에 검사하지 않은 칸이 있다는 것과, 상태 칸을 둘로 두었을 때 측정 실패가 어디로 접히는지를 그린 것이다. 아래 두 줄은 한정이다.

    경보가 가리킨 것과 실제

    1. 경보는 잰 것을 말했고 결론으로 건너뛴 것은 우리였다

    증상. 경보가 울렸다. 가 보니 경보가 가리킨 것은 멀쩡했다. 그런데 경보 문장을 다시 읽어 보면 거기 적힌 값은 틀리지 않았다. 값도 맞고 대상도 멀쩡한, 앞뒤가 안 맞아 보이는 상태다.

    여기서 갈리는 두 가지 해석. 하나는 경보가 잘못 만들어졌다고 보는 것이다. 다른 하나는 내가 못 본 문제가 어딘가 있다고 보는 것이다. 우리 기록에서 열어 본 두 경보는 둘 다 아니었다. 경보는 자기가 잰 값을 그대로 말했고, 그 값이 가리키는 대상도 실제로 그 상태였다. 어긋난 것은 그 값에서 결론으로 건너뛰는 한 칸이었다.

    그 한 칸을 갈라 적으면 이렇게 된다. 경보 하나에는 서로 다른 세 가지가 붙어 있다.

    
    잰 값        경보가 실제로 측정한 것
    말한 결론    그 값에서 내린 판정
    처방         그 판정에 딸려 나오는 조치
    

    이 셋은 같은 자리에서 나오지 않는다. 잰 값은 측정이 정하고, 말한 결론과 처방은 설계가 미리 정해 둔 것이다. 경보를 붙일 때 우리는 이 셋을 한 덩어리로 붙인다. 그래서 잰 값이 맞으면 결론도 맞다고 읽게 된다. 우리가 연 두 경보는 그 지점에서 갈렸다.

    우리가 내린 결론. 오탐은 측정이 틀려서 생기지 않았다. 측정은 맞았다. 오탐은 그 값을 다른 것의 증거로 쓰기로 미리 정해 둔 자리에서 생겼다. 그 자리를 정한 것은 경보가 아니라 우리다.

    그래서 이 글이 하지 않는 말을 먼저 밝혀 둔다. 이 글은 경보를 붙이지 말라는 이야기가 아니고, 경보를 만든 쪽을 탓하는 이야기도 아니다. 우리는 감시를 계속 붙인다. 이 글이 보는 자리는 잰 값과 말한 결론 사이다. 2번과 3번이 첫째 형태이고, 4번이 그 형태의 다른 얼굴이며, 5번이 둘째 형태다. 6번은 우리가 세지 않은 범위다.

    확인 방법. 자기 경보를 하나 골라 위 세 줄로 갈라 적어 보라. 잰 값 칸에는 측정한 숫자만 적어야 하고, 판단이 섞여 들어가면 그것은 이미 결론 칸이다. 세 줄로 갈라 적을 수 없는 경보는 결론이 측정 안에 숨어 있는 것이다.

    2. 늦었다와 고장났다는 다른 사실이다

    이 항목은 우리가 실제로 겪은 것이고, 실측 값이 남아 있다.

    설계. 자동으로 도는 점검 하나가 2초 타임아웃으로 어떤 조회를 건다. 2초 안에 끝나지 않으면 조회 실패로 단정하고, 그 뒤에 바이러스 검사에 격리된 파일을 복구하라는 처방을 붙였다.

    실측. 같은 조회를 10회 재 봤다. 걸린 시간은 1.614초에서 6.948초 사이였고 중앙값은 2.738초였다. 2초를 넘긴 것이 10회 중 5회다. 그리고 반환값은 10회 전부 0이었다.

    
    실측 10회
      걸린 시간   1.614초 ~ 6.948초   (중앙값 2.738초)
      2초 초과    5 / 10
      반환값 0    10 / 10             조회는 열 번 다 성공했다
    

    이 표가 말하는 것. 조회는 열 번 다 성공했다. 못 지킨 것은 2초라는 조건뿐이다. 그런데 그 조건을 못 지킨 것을 조회 실패로 적었다. 조회 실패라고 적히는 순간, 그다음에 오는 것은 왜 조회가 실패했는가를 묻는 처방이다.

    타임아웃 반환값의 뜻을 그대로 읽으면 이렇다. 시간을 넘겨 중단시켰다는 것은 내가 기다리기를 그만뒀다는 뜻이다. 대상이 응답할 수 없는 상태였다는 뜻이 아니다. 앞쪽은 내 조건에 대한 사실이고 뒤쪽은 대상에 대한 사실인데, 같은 자리에 적히면 구별되지 않는다.

    초보용으로 옮기면 이렇다. 늦었다와 고장났다는 다른 사실이다. 타임아웃은 내가 정한 조건이고 고장은 대상의 상태다. 조건을 대상의 상태로 바꿔 읽으면 이상한 일이 생긴다 — 조건을 조일수록 고장이 늘어난다. 2초를 1초로 줄이면 그 표에서 실패는 10회 중 더 많은 줄이 된다. 대상은 아무것도 달라지지 않았는데 말이다.

    이 형태가 눈에 띄지 않는 이유. 임계값은 처음 붙일 때 한 번 정하고 그 뒤로는 잘 보지 않는다. 2초는 누군가 그때 적당하다고 본 값이고, 그 값이 측정 대상의 실제 분포와 맞는지는 따로 재야 아는 것이다. 우리 경우 실제 분포의 중앙값이 그 임계보다 위에 있었다. 즉 그 조건은 절반 정도가 넘도록 되어 있는 조건이었고, 그 상태로 몇 달을 지났다.

    이 실측의 범위도 함께 적어 둔다. 우리가 잰 것은 10회다. 그 열 번에서 나온 분포가 다른 시점에도 같다고 말할 수 없고, 우리는 다른 시점을 재지 않았다. 이 절이 말하는 것은 좁다 — 우리가 잰 열 번에서, 조건을 넘긴 것과 조회가 실패한 것이 서로 다른 줄이었다. 열 번 모두 반환값이 0 이었으므로 이 열 번 안에서는 그 둘이 겹친 줄이 없다.

    확인 방법. 타임아웃에서 나온 경보를 만나면 그 임계값만 늘려서 같은 것을 다시 재 보라. 경보가 사라지면 그 경보가 잰 것은 대상이 아니라 내 조건이다. 그리고 반환값을 남기는 도구라면 중단된 것인지 대상이 오류를 냈는지가 그 값에 갈려 있으니, 경보 문장 말고 그 숫자를 보라.

    3. 결론이 어긋나면 그 처방은 증상과 무관한 자리를 고치라고 한다

    이 항목은 앞 절과 같은 사례의 뒷부분이고, 대가가 숫자로 남아 있다.

    붙어 있던 처방. 조회 실패로 단정한 뒤에 나온 안내는 바이러스 검사에 격리된 파일을 복구하라는 것이었다. 이 처방이 맞으려면 파일이 격리됐다는 사실이 있어야 한다. 그런데 그 경보가 잰 것은 응답이 2초 안에 오지 않았다는 것뿐이다. 격리 여부는 재지 않았다.

    여기가 이 절의 요점이다. 처방은 잰 값에서 나오지 않는다. 결론에서 나온다. 그래서 결론이 한 칸 어긋나 있으면 처방은 그 어긋난 만큼 증상과 무관한 자리를 가리킨다. 우리 경우 그 처방이 가리킨 자리는 그 경보가 재지 않은 자리였다.

    대가. 그 오진은 한 번으로 끝나지 않았다. 점검이 주기적으로 도니까 같은 항목이 계속 올라왔다. 우리 원장에 남은 것은 고위험으로 분류된 결재 항목 9건이고, 올라온 간격은 1시간 35분에서 40분 사이다.

    
    원장에 남은 것
      항목 9건          전부 같은 오진에서 나왔다
      간격 1시간 35~40분  점검 주기가 도는 동안 계속 쌓였다
      분류 고위험        사람이 결재해야 하는 칸에 올라왔다
    

    이 숫자가 말하는 것과 말하지 않는 것. 말하는 것은 9건이 쌓였다는 것과 그 간격이다. 말하지 않는 것은 그 9건이 사람에게 어떤 영향을 줬는지다. 우리는 그것을 재지 않았다. 그래서 이 절에 적을 수 있는 것은 목록이 그만큼 채워졌다는 사실까지다.

    한 가지는 그 숫자에서 바로 읽힌다. 이 항목들은 스스로 멈추지 않는다. 원인을 고치기 전까지 점검이 도는 만큼 계속 올라온다. 경보를 붙일 때 우리가 생각하는 그림은 문제가 있을 때 한 번 울리는 것인데, 조건 위반을 고장으로 읽는 경보는 조건이 계속 위반되는 동안 계속 울린다.

    고위험이라는 분류에 대해서도 적어 둔다. 그 9건은 사람이 결재해야 하는 칸에 올라갔다. 자동으로 처리되지 않고 누가 보고 판단해야 하는 자리로 분류됐다는 뜻이다. 분류 자체는 설계대로 동작한 것이다 — 격리 복구는 실제로 사람이 판단할 일이니까. 다만 그 판단이 필요한 상황이 아니었다. 분류는 결론을 믿고 붙는 것이라, 결론이 어긋나 있으면 분류도 함께 어긋난다.

    초보용으로 옮기면 이렇다. 경보에 처방이 붙어 있으면 한 번 물어보라 — 이 처방이 고치려는 것을 이 경보가 쟀는가. 재지 않았다면 그 처방은 추측이다. 추측이 나쁜 것은 아니지만, 추측이 측정 결과와 같은 칸에 적혀 있으면 읽는 사람은 그것을 측정으로 읽는다.

    확인 방법. 반복해서 올라오는 경보 항목이 있으면 몇 건이 쌓였고 간격이 얼마인지 세어 보라. 간격이 점검 주기와 같으면 그 항목은 사건이 아니라 상태를 보고 있는 것이다. 사건은 한 번 나고 상태는 고쳐질 때까지 계속 난다.

    4. 문장은 참이었는데 제목과 등급이 다른 결론으로 보냈다

    이 항목은 2026년 9월 9일 밤에 있었던 관측 한 건이다. 우리 기록에서 이 형태로 확인한 것은 이 한 건이고, 여러 번 있었던 일로 적지 않는다.

    관측. 감시 절차가 통지를 보냈다. 내용은 팀이 이 선언으로 뜨지 않았고, 정한 수명 30분을 넘겼다는 것이었다. 같은 내용이 사람이 결재하는 목록에 실패 항목으로 올라왔다.

    그 시점의 실제 상태. 팀은 떠 있었다. 자리 목록을 실측해 보니 우리가 띄우기로 한 자리 넷이 전부 살아 있었다. 다만 그 기동은 그 예약이 한 것이 아니었다. 설치 경로에 결함이 있어서 사람이 손으로 고쳐 다시 띄운 것이었다.

    그래서 어떻게 된 것인가. 통지가 잰 것은 예약의 수명이었다. 그 예약이 정한 시간 안에 자기 일을 끝냈는지를 본 것이다. 그런데 말한 것은 팀의 생존이었다. 이 둘은 다른 사실이다. 예약이 실패해도 팀은 떠 있을 수 있고, 우리 경우가 그랬다.

    
    잰 것      이 예약이 정한 수명 30분 안에 끝났는가    ->  아니오
    말한 것    팀이 떠 있는가                            ->  아니오라고 읽혔다
    실제       자리 넷 생존 (사람이 손으로 띄웠다)       ->  예
    

    여기서 한정을 분명히 적어 둔다. 통지 문안 자체는 틀리지 않았다. 문안에 적힌 이 선언으로는 뜨지 않았다는 문장은 사실이다. 팀을 띄운 것은 그 선언이 아니라 사람이었으니까. 그러니 이 사례는 거짓을 말한 경보가 아니다. 어긋난 자리는 제목과 등급이었다 — 제목이 가리키는 대상이 이었고 등급이 실패였으며, 그 둘을 먼저 읽은 사람은 팀이 죽었다는 결론으로 갔다.

    이 구분이 왜 중요한가. 문안을 고치는 것과 제목·등급을 고치는 것은 다른 작업이기 때문이다. 문안이 틀렸다고 보면 문장을 다시 쓰게 되는데, 우리 경우 문장은 그대로 두어도 됐다. 고쳐야 할 자리는 그 문장이 어떤 이름표를 달고 어느 칸에 들어가는가였다.

    왜 예약과 팀이 다른 주어인가. 예약은 수단이고 팀이 떠 있는 것은 목적이다. 수단이 실패해도 목적은 다른 수단으로 이뤄질 수 있고, 우리 경우 그 다른 수단이 사람이었다. 그런데 통지는 수단의 실패를 목적의 실패로 적었다. 감시를 붙일 때 우리는 대개 수단 쪽에 붙인다 — 수단은 재기 쉽고 목적은 재기 어렵기 때문이다. 그래서 이 어긋남은 재기 쉬운 것을 재고 재기 어려운 것을 말하는 형태로 나타난다.

    초보용으로 옮기면 이렇다. 경보의 제목이 가리키는 대상과 실제로 잰 값의 대상이 다르면, 문장이 참이어도 오탐처럼 쓰인다. 사람은 긴 문장을 다 읽고 판단하지 않는다. 제목과 등급을 먼저 보고 그다음을 읽을지 정한다. 그래서 제목은 요약이 아니라 결론으로 작동한다.

    확인 방법. 자기 경보에서 제목의 주어와 그 경보가 잰 값의 주어가 같은지 보라. 제목이 인데 잰 값이 예약이면 그 둘은 다른 주어다. 등급도 같이 보라 — 실패 칸에 들어간 항목은 사람이 손을 대야 하는 것으로 읽힌다.

    5. 못 쟀다와 나쁘다를 같은 칸에 넣으면 칸이 둘밖에 없게 된다

    여기부터는 둘째 형태이고, 앞의 것과 어긋나는 자리가 다르다.

    관측. 자원 상태를 보는 경보가 6건 올라와 있었다. 판정 원문을 열어 보니 임계를 넘은 값이 하나도 없었다. 자리 수를 세는 값과 부하를 재는 값이 전부 비어 있었고, 오류 목록에 getloadavg 가 찍혀 있었다.

    그 이름이 논거다. getloadavg 는 윈도우에 존재하지 않는 함수다. 리눅스나 맥에서 최근 부하 평균을 읽는 데 쓰는 것이고, 윈도우에는 그 함수가 없다. 그러니 그 자리에서 나온 것은 나쁜 값이 아니라 값이 없다는 사실이다.

    
    경보 6건의 판정 원문
      임계 초과      0건
      자리 수 값     비어 있음
      부하 값        비어 있음
      오류 목록      getloadavg   ->  이 환경에 없는 함수다
      결과 등급      경고
    

    그런데 등급은 경고였다. 값을 못 읽었다는 사실이 경고 칸으로 접혔다. 못 쟀다와 나쁘다를 같은 칸에 넣은 것이다.

    그 6건이 실제로 재고 있던 것. 경고 칸에 든 항목을 세면 6건인데, 그 6건이 가리키는 것은 자원의 상태가 아니었다. 임계를 넘은 값이 하나도 없으니 자원에 대해 그 6건이 말해 주는 것은 없다. 그 6건이 실제로 알려 준 것은 우리 측정 코드가 이 환경에서 값을 읽지 못한다는 사실이다. 알려 줄 값이 있는 신호이긴 했는데, 그 값이 놓인 칸이 자원 쪽이었다.

    왜 이렇게 되는가. 상태를 나눌 때 칸을 정상과 위험 둘로 두면, 정상이라고 말할 수 없는 것은 전부 위험 쪽으로 간다. 값을 못 읽은 것은 정상이라고 말할 수 없으니 위험 쪽에 놓인다. 칸이 둘뿐이라 다른 데 놓을 곳이 없는 것이다. 이 접기는 안전한 쪽으로 접었다고 설명되기도 한다. 모르면 일단 경고하자는 것이다.

    그렇게 접었을 때 무엇을 잃는가. 그 경고가 상시 켜져 있게 된다. 없는 함수를 부르는 일은 고쳐지기 전까지 매번 실패하니까, 그 경보는 매번 경고를 낸다. 경고가 상시 켜져 있으면 그 경고는 정보가 아니다. 그리고 진짜로 자원이 눌리는 날이 오면 그 경고와 이 경고가 같은 칸에서 같은 얼굴로 뜬다. 구별할 수 없다.

    초보용으로 옮기면 이렇다. 측정 실패는 제3의 상태다. 정상도 아니고 위험도 아니다. 그러니 칸을 세 개로 두어야 한다 — 정상 · 위험 · 측정 불능이다. 셋째 칸이 있으면 못 쟀다는 사실이 그대로 남는다. 그리고 그 칸에 항목이 쌓이는 것은 고쳐야 할 것이 측정 쪽에 있다는 신호가 된다.

    이 처방이 공짜가 아니라는 것도 적어 둔다. 칸을 셋으로 늘리면 셋째 칸을 누가 보는가라는 문제가 새로 생긴다. 아무도 보지 않는 칸을 만들면 못 쟀다는 사실이 조용히 쌓이기만 한다. 우리가 아는 범위에서 말할 수 있는 것은 여기까지다 — 칸을 셋으로 나누면 구별은 남는다. 그 구별을 누가 읽고 무엇을 하는지는 별도로 정해야 하는 일이고, 우리는 그 부분의 효과를 재지 않았다.

    확인 방법. 자기 기록에서 경고가 떠 있는 경보를 하나 열어 판정 원문을 보라. 볼 것은 두 가지다. 임계를 넘은 값이 실제로 있는가측정에 실패한 항목이 있는가다. 넘은 값이 없고 빈 값과 오류만 있으면, 그 경고는 못 잰 것을 나쁜 것으로 접은 결과다.

    6. 우리가 세지 않은 것을 세지 않았다고 적는다

    이 항목은 이 글의 범위이고, 조심해서 적어야 하는 자리다.

    제목의 두 가지가 무슨 뜻인가. 우리 기록에서 나온 두 형태라는 뜻이다. 오탐이 이 둘로 전부라고 우리는 세지 않았다. 우리가 한 것은 우리 운영 기록에서 오탐으로 확인된 것을 열어 원인을 갈라 적은 것이고, 그 결과가 둘이었다.

    두 형태의 공통점을 적으면 이렇다. 하나는 조건 위반을 대상의 고장으로 읽었고, 다른 하나는 측정 불능을 위험으로 접었다. 둘 다 잰 값과 말한 결론 사이에서 갈렸다. 그리고 둘 다 측정은 맞았다.

    쓰지 않는 문장을 밝혀 둔다. 이 글은 오탐이 쌓이면 사람이 경보를 무시하게 된다고 쓰지 않는다. 그것은 널리 쓰이는 이야기이고 우리도 그 우려를 전제로 설계를 고쳤지만, 우리 기록에 그것을 측정한 값이 없다. 우리가 쓸 수 있는 형태는 하나다 — 우리는 그것을 우려했고, 그 우려가 맞는지는 재지 않았다.

    그리고 이 글은 경보를 탓하지 않는다. 두 사례 모두 경보가 잰 값은 맞았다. 값을 다른 것의 증거로 쓰기로 정한 자리가 어긋났고, 그 자리를 정한 것은 우리다. 이 구분을 흐리면 처방이 경보를 줄이는 쪽으로 가는데, 우리 두 사례에서 줄여야 했던 것은 경보가 아니라 값과 결론 사이의 건너뜀이었다.

    확인 방법. 자기 기록에서 오탐을 찾았으면 몇 개의 형태로 갈리는지 세어 보고, 그 개수를 적을 때 어디까지 본 것인지를 함께 적어라. 우리 경우 그 범위는 우리 운영 기록 안에서 오탐으로 확인된 것이다.

    정리 — 확인할 것 여섯 가지

    첫째, 우리는 경보를 잰 값 · 말한 결론 · 처방 세 줄로 갈라 적는다. 우리 두 사례에서 어긋난 것은 잰 값이 아니라 그 사이의 칸이었다. 세 줄로 갈라 적을 수 없으면 결론이 측정 안에 숨어 있는 것이다.

    둘째, 우리는 타임아웃을 대상의 고장으로 적지 않는다. 우리 실측 10회에서 반환값은 전부 0이었고 조회는 열 번 다 성공했다. 못 지킨 것은 우리가 정한 2초였다. 조건과 상태를 같은 칸에 적으면 조건을 조일수록 고장이 늘어난다.

    셋째, 우리는 처방이 고치려는 것을 그 경보가 쟀는지 확인한다. 우리 경우 경보는 응답 지연만 쟀고 처방은 격리 복구였다. 재지 않은 것을 가리키는 처방은 추측이고, 추측이 측정과 같은 칸에 적히면 측정으로 읽힌다.

    넷째, 우리는 경보 제목의 주어와 잰 값의 주어가 같은지 본다. 우리가 본 한 건에서 통지 문안은 틀리지 않았고, 제목과 등급이 다른 결론으로 보냈다. 고칠 자리가 문장이 아니라 이름표와 칸일 수 있다.

    다섯째, 우리는 상태 칸을 정상 · 위험 · 측정 불능 셋으로 둔다. 우리 경보 6건은 임계를 넘은 값이 하나도 없이 경고 등급이었고, 원인은 이 환경에 없는 함수였다. 칸이 둘이면 못 쟀다는 사실이 위험 쪽으로 접힌다. 다만 셋째 칸을 누가 읽는지는 따로 정해야 하고, 우리는 그 효과를 재지 않았다.

    여섯째, 우리는 세지 않은 것을 세지 않았다고 적는다. 이 글의 두 가지는 우리 기록에서 나온 두 형태이지 오탐의 전부가 아니고, 오탐이 사람의 주의에 미치는 영향은 우리가 재지 않았다. 범위를 적지 않으면 읽는 사람이 그 범위를 넓혀 읽는다.

    마지막으로 하나만 덧붙인다. 이 글은 감시를 줄이라는 이야기가 아니다. 우리도 계속 붙인다. 우리가 적은 것은 좁다 — 우리 기록의 두 경보는 잰 값이 맞았고, 그 값을 다른 것의 증거로 쓰기로 정해 둔 자리에서 사람이 다른 곳으로 갔다.

    열 번 실측과 그 한정

    우리 운영 기록에서 확인한 것 (우리 한 팀의 기록이다)

    • 자동으로 도는 점검 하나가 2초 타임아웃으로 조회를 걸고, 시간을 넘기면 조회 실패로 단정한 뒤 바이러스 검사 격리 복구를 처방하도록 되어 있던 것
    • 같은 조회를 10회 실측했을 때 걸린 시간이 1.614초에서 6.948초 사이(중앙값 2.738초)였고 2초를 넘긴 것이 5회였으며, 반환값은 10회 전부 0 이었던 것. 즉 조회는 열 번 다 성공했고 못 지킨 것은 데드라인이었다
    • 그 단정이 고위험으로 분류된 결재 항목을 1시간 35분에서 40분 간격으로 계속 올렸고, 우리 원장에 9건이 남아 있는 것
    • 2026년 9월 9일 밤에 감시 절차가 팀이 이 선언으로 뜨지 않았고 수명 30분을 넘겼다고 통지했고, 같은 내용이 결재 목록에 실패 항목으로 올라온 것. 이 형태의 관측은 한 건이다
    • 그 시점에 자리 목록 실측으로 우리가 띄우기로 한 자리 넷이 전부 살아 있었고, 그 기동은 그 예약이 아니라 사람이 설치 경로 결함을 고쳐 다시 띄운 것이었다는 것
    • 한정 — 그 통지 문안 자체는 틀리지 않았다. 이 선언으로는 뜨지 않았다는 문장은 사실이다. 어긋난 자리는 제목이 가리킨 대상과 실패라는 등급이었다
    • 자원 경보 6건의 판정 원문에 임계를 넘은 값이 하나도 없고, 자리 수와 부하 값이 전부 비어 있으며, 오류 목록에 getloadavg 가 찍혀 있는 것
    • getloadavg 가 윈도우에 존재하지 않는 함수라는 것. 그래서 그 자리에서 나온 것은 나쁜 값이 아니라 없는 값이고, 그 결손이 경고 등급으로 접혀 있던 것

    우리가 측정하지 않아 쓰지 않은 것

    • 오탐이 사람의 주의를 실제로 얼마나 떨어뜨리는지 우리는 재지 않았다. 늑대 소년은 널리 쓰이는 비유이고 우리도 그 전제로 설계를 고쳤지만, 우리 기록에 주의 저하를 측정한 값은 없다. 그래서 오탐이 쌓이면 사람이 경보를 무시하게 된다는 문장을 이 글에 쓰지 않았다. 쓸 수 있는 형태는 우리는 그것을 우려해 설계를 고쳤고, 그 우려가 맞는지는 재지 않았다는 것이다
    • 이 두 가지가 오탐의 전부라고 우리는 세지 않았다. 제목의 두 가지는 우리 기록에서 나온 두 형태라는 뜻이다. 다른 형태가 있는지 없는지에 대한 문장이 이 글에 없다
    • 임계값을 어떻게 정해야 하는지. 우리가 확인한 것은 그 임계가 실제 분포의 중앙값 아래에 있었다는 것뿐이고, 어떤 값이 적절한지는 재지 않았다
    • 상태 칸을 셋으로 나눈 뒤 그 셋째 칸이 실제로 읽히는지. 구별이 남는다는 것까지가 우리가 말할 수 있는 범위다
    • 이 두 경보가 지금 어떤 상태인지에 대한 전수 확인. 이 글은 그 시점의 관측과 판정 원문을 근거로 한다

    일반적으로 쓰이는 비유·조언과 그 출처

    • 늑대 소년 — 이솝 우화에서 온 비유다. 헛울림이 이어지면 경보가 신뢰를 잃는다는 뜻으로 널리 쓰인다. 우리 기록은 이 비유를 검증하지 않았고, 이 글에서는 제목의 비유로만 썼다

    같은 계열을 다른 자리에서 다룬 글

  • 연습이 장부를 바꿨다 — dry-run 이 실제로 기록한 결함

    연습이 장부를 바꿨다 — dry-run 이 실제로 기록한 결함

    자동화 도구에는 대개 연습 모드가 있다. 실제로는 하지 않고 어떻게 될지만 보여 주는 기능이다. 이름은 도구마다 다르지만 뜻은 같다 — 이번엔 실제로 하지 않는다. 그래서 처음 쓰는 기능은 연습으로 한 번 돌려 보고 나서 실제로 돌린다. 우리도 그렇게 했다. 이 글은 그 연습이 실제 기록을 바꿔 놓은 이야기다.

    논지는 한 줄이다. 연습이라고 이름 붙인 것이 실제를 바꾸지 않는다는 보장은 그 이름에 들어 있지 않다. 이름은 라벨이고, 무엇을 건드리는지는 라벨이 아니라 코드가 정한다. 그리고 우리 경우 그 둘이 다른 자리에서 결정되고 있었다. 그 연습이 남긴 기록은 다음번 진짜 실행을 막는 형태로 남았다.

    먼저 이 글이 하지 않는 말을 밝혀 둔다. 우리는 다른 도구의 연습 모드가 어떻게 구현돼 있는지 재지 않았다. 이 글의 근거는 우리 저장소 한 곳의 코드와 기록이다. 그래서 연습 모드를 믿지 말라는 일반 규칙을 쓰지 않는다. 우리가 쓸 수 있는 형태는 좁다 — 우리 구현에서는 라벨과 저장 경로가 갈려 있었다.

    이 글에서 쓰는 말 몇 개를 풀어 둔다. 장부는 어떤 항목이 어떤 상태를 거쳐 왔는지를 적어 두는 기록 파일이다. 전이는 항목의 상태가 한 단계에서 다음 단계로 바뀌는 일이다. 전이 규칙표는 어떤 상태에서 어떤 상태로 갈 수 있는지를 미리 정해 둔 표다. 연습 모드는 실제로 하지 않고 결과만 보여 주기로 한 실행 방식이다. 라벨은 기록에 남기는 표시값이다.

    앞선 편과의 경계. 도구가 성공을 반환했는데 일이 안 된 경우들은 앞 편에 적었다. 이 글은 반대쪽이다 — 도구가 정확히 성공했는데, 하지 말아야 할 일을 한 경우다. 그 연습이 요금에 어떻게 얹히는지는 비용을 다룬 편에 있고, 이 글은 비용을 다루지 않는다.

    연습 모드가 장부를 바꾼 구조를 단계로 그린 도식. 첫 칸은 연습 호출로, 실행 없이 돌리는 모드로 전이 함수를 부르는 단계다. 인자 이름은 연습 여부이고 기본값이 참으로 되어 있어 안전한 쪽이 기본값이라는 의도가 보인다고 적었다. 둘째 칸은 라벨 기록으로, 기록 항목의 모드 칸에 연습이라고 정직하게 적는 단계다. 여기까지는 약속대로이며 표시는 참이라고 적었다. 셋째 칸은 갈림으로, 연습 여부 인자가 라벨을 정하는 데까지만 도달하고 그 아래로는 전달되지 않는 지점이다. 넷째 칸은 집행으로, 상태를 실제로 바꾸고 원격 정보 칸을 갱신하고 장부 파일을 저장하는 단계이며 연습 여부와 무관하게 실행됐다고 적었다. 아래 칸은 그 결과다. 장부에 남은 것은 연습이라고 표시된 전이 두 줄과 존재하지 않는 주소가 박힌 항목이다. 주소는 mock.invalid 이고 식별자는 900000번대 가짜 값이다. 그리고 전이 규칙표에서 초안 상태에서 갈 수 있는 곳이 검수 하나뿐이므로, 연습이 이미 초안 상태로 옮겨 놓으면 진짜 초안 작업이 하려던 대기에서 초안으로 가는 전이가 규칙 위반으로 거부된다고 적었다. 맨 아래 두 줄은 한정이다. 검수 상태에서는 초안으로 되돌아가는 경로가 규칙표에 있어서 연습이 항상 복구 불가를 만든다는 뜻이 아니라는 것, 그리고 우리는 코어를 고치지 않았고 코어 밖에 관문을 하나 덧댔을 뿐이며 모든 진입 경로를 세지 않았다는 것이다.
    연습 모드가 위험하다는 그림이 아니라 우리 구현에서 라벨과 집행이 갈린 자리를 그린 것이다. 연습 여부 인자는 라벨까지만 도달했고 저장은 그것과 무관하게 실행됐다. 아래 두 줄은 한정이다 — 되돌아가는 경로가 있는 상태도 있고, 우리는 코어를 고치지 않았다.

    연습 한 번이 남긴 흔적

    1. 연습이라는 이름은 실제를 바꾸지 않는다는 보장이 아니다

    증상. 새 기능을 연습 모드로 한 번 돌려 봤다. 화면에는 될 일이 표시됐고 오류는 없었다. 그런데 나중에 실제로 돌리려니 그 작업이 거부됐다. 이미 처리된 항목이라는 것이다. 실제로 처리한 적은 없었다.

    여기서 갈리는 두 가지 해석. 하나는 도구가 잘못 판단했다고 보는 것이다. 다른 하나는 내가 연습이 아니라 실제로 돌렸던 것이 아닐까 의심하는 것이다. 우리 기록에서는 둘 다 아니었다. 도구는 연습으로 돌았고 기록에도 연습이라고 적었다. 다만 적는 곳이 실제 장부였다.

    우리가 내린 결론. 어긋난 자리는 연습이라는 표시와 실제로 무엇을 건드리는지가 서로 다른 곳에서 결정된다는 구조였다. 표시는 기록 항목의 한 칸이 담당하고, 무엇을 건드리는지는 그 아래 코드가 담당한다. 두 담당이 같은 값을 보고 있지 않으면, 표시가 참인 채로 부작용도 참이 된다.

    그래서 이 글이 보는 자리는 여기다. 이 글은 연습 기능이 있는지 없는지가 아니라, 그 연습이 어디까지 연습인지를 본다. 2번과 3번이 우리가 겪은 결과이고, 4번과 5번이 코드에서 확인한 원인이며, 6번이 지금 상태다.

    먼저 분명히 해 둔다. 연습 모드가 있는 것은 좋은 일이고 우리도 계속 쓴다. 처음 쓰는 기능을 실제로 돌려 보기 전에 한 번 보는 것은 줄일 수 있는 위험을 줄이는 일이다. 이 글이 다루는 것은 그 기능을 쓰지 말라는 이야기가 아니라, 그 기능이 무엇까지 모의하는지를 확인하지 않고 썼을 때 생긴 일이다. 확인하지 않았다는 것이 우리 쪽 몫이다.

    연습이 무엇을 모의하는지는 대개 층으로 갈린다. 우리 경우를 층으로 적으면 이렇게 된다. 왼쪽이 연습이 실제로 모의한 것이고, 오른쪽이 연습이 그대로 실행한 것이다.

    
    연습이 모의한 것          연습이 그대로 실행한 것
    ------------------------  ------------------------
    바깥 서비스 호출          항목의 상태 변경
    가짜 식별자 발급          원격 정보 칸 갱신
                              장부 파일 저장
    

    이 표에서 어긋남의 자리가 보인다. 왼쪽은 바깥으로 나가는 일이고 오른쪽은 안쪽에 남는 일이다. 연습이라는 말을 들었을 때 우리가 기대한 것은 양쪽 다였는데, 실제로 모의된 것은 왼쪽뿐이었다. 그리고 왼쪽이 만들어 낸 가짜 값이 오른쪽 경로로 실제 기록에 저장됐다. 모의와 실행이 한 줄 안에서 만난 것이다.

    확인 방법. 연습 모드로 무언가를 한 번 돌린 뒤, 돌리기 전과 후에 파일이 달라졌는지를 확인하라. 수정 시각과 크기를 비교하면 된다. 화면에 연습이라고 표시된 것과 파일이 그대로인 것은 별개의 사실이다.

    2. 연습 한 번이 실제 장부에 가짜 값을 적었다

    이 항목은 우리가 실제로 겪은 것이고, 지금도 눈으로 확인할 수 있다.

    관측. 우리 콘텐츠 장부에 2026년 8월 31일 20시 43분 35초 시각의 전이 기록 두 줄이 있다. 두 줄 모두 모드 칸에 연습이라고 적혀 있다. 그런데 그 두 줄은 실제 장부 파일에 저장돼 있다. 연습이라고 적힌 기록이 연습이 아닌 곳에 남은 것이다.

    그 항목에 박힌 값. 원격 정보 칸을 열어 보면 존재하지 않는 주소가 들어가 있다. 주소는 mock.invalid 로 시작하고, 글 식별자는 900013 이다. 카테고리·태그·대표이미지 식별자도 전부 900000번대 가짜 값이다. 연습이 만들어 낸 가상의 값이 실제 장부에 그대로 들어앉았다.

    그리고 지워지지 않았다. 그 항목은 장부에 그대로 남아 있다 — 이 글을 쓰면서 다시 확인했다. 상태는 검수 대기로 되어 있다. 실제로 검수할 것이 없는 항목이 검수 대기 줄에 서 있는 셈이다.

    남아 있는 것이 왜 문제인가. 그 항목 하나가 잘못된 값을 갖고 있다는 것으로 끝나지 않는다. 상태별로 항목을 세는 곳에서 그 항목은 검수 대기 한 건으로 계산된다. 어떤 항목을 다음에 처리할지 고르는 자리에서도 그 항목은 후보로 올라온다. 우리가 사람 눈으로 그 항목을 알아보기 때문에 넘어가는 것이고, 세는 프로그램은 그것이 가짜인지 모른다. 가짜 값 하나가 남으면 그 값을 읽는 모든 곳이 그만큼 어긋난다.

    그런데 우리는 지우지 않았다. 지울 수는 있었지만 두었다. 그 항목이 이 결함이 실제로 일어났다는 증거이기 때문이다. 코어를 고치지 않은 상태에서 증거를 지우면, 다음에 같은 일이 생겼을 때 처음 겪는 일처럼 보인다. 그래서 지우는 대신 가짜임을 알아볼 수 있는 형태로 남겨 두는 쪽을 택했다 — 주소가 존재하지 않는 이름이고 식별자가 눈에 띄는 번호대인 것이 그 알아볼 수 있는 형태다.

    초보용으로 옮기면 이렇다. 연습 모드가 표시는 정직했다. 이건 연습이라고 기록에 적었다. 그런데 적는 곳이 실제 장부였다. 정직한 표시와 안전한 실행은 같은 것이 아니다.

    확인 방법. 자기 기록 파일에서 연습이라고 표시된 줄을 찾아보라. 있으면 그 줄이 연습용 파일이 아니라 실제 파일에 있는지 확인하면 된다. 실제 파일에 있다면 그 연습은 기록까지 연습이 아니었던 것이다.

    3. 그다음 연습들은 진짜 작업을 막았다

    이 항목도 우리가 겪은 것이고, 두 번 있었다.

    관측. 우리 저장소에는 장부 백업 파일이 두 개 남아 있다. 각각 44,939 바이트57,672 바이트이고, 두 파일 다 연습 오염이라는 뜻의 이름을 달고 있다. 그 안을 보면 각각 한 주제가 연습 전이로 초안 상태가 되어 있고, 가짜 글 식별자(900017900015)와 존재하지 않는 주소가 박혀 있다.

    왜 막히는가. 우리 전이 규칙표는 초안 상태에서 갈 수 있는 곳을 검수 하나로만 허용한다. 그래서 연습이 이미 그 주제를 초안 상태로 옮겨 놓으면, 진짜 초안 작업이 하려던 대기에서 초안으로 가는 전이가 규칙 위반으로 거부된다. 규칙표는 잘못 동작한 것이 아니다 — 적힌 대로 막았다. 다만 그 상태를 만든 것이 연습이었다.

    
    연습이 옮긴다     대기  ->  초안        (연습인데 저장됐다)
    진짜가 하려 한다  대기  ->  초안        거부 — 이미 초안이다
    규칙표가 허용     초안  ->  검수        만 있다
    

    처리. 두 번 다 작업자가 자기 오염을 발견해 백업에서 원상복구했다. 위 두 파일이 그 백업이다.

    그 발견 경로에 대해. 두 번 다 사람이 먼저 이상하다고 느꼈다. 절차가 잡아 준 것이 아니다. 첫 번째와 두 번째 사이에 우리는 이미 원인을 알고 있었는데도 두 번째가 났고, 두 번째도 사람이 찾았다. 이것이 우리가 나중에 경고문이 아니라 거부하는 검사를 넣은 이유다. 원인을 아는 것과 다시 밟지 않는 것은 다른 일이었다.

    초보용으로 옮기면 이렇다. 연습이 상태를 옮겼는지가 중요한 이유는, 상태가 다음 작업의 입력이기 때문이다. 상태를 보고 무엇을 할지 정하는 절차에서는 상태 하나가 바뀌면 그다음 전부가 달라진다. 연습이 결과물을 만들지 않아도 상태를 만들면 그것으로 충분히 실제를 바꾼다.

    한정을 함께 적는다. 연습이 항상 복구 불가를 만든다고 쓰지 않는다. 우리 규칙표에는 검수에서 초안으로 되돌아가는 경로가 있다. 우리가 본 봉쇄는 초안으로 옮겨진 경우였고, 되돌아가는 경로가 있는 상태였다면 같은 봉쇄가 생기지 않는다. 어느 상태로 옮겨지느냐에 따라 결과가 다르다는 것까지가 우리가 아는 범위다.

    확인 방법. 연습을 돌린 뒤 그 항목의 상태값을 확인하라. 연습 전과 다르면 연습이 상태를 옮긴 것이다. 그리고 자기 도구의 전이 규칙표를 한 번 읽어, 그 새 상태에서 원래 하려던 작업으로 돌아갈 경로가 있는지 확인하면 된다. 경로가 없으면 그 항목은 그대로 막힌다.

    4. 라벨은 정직했고 저장 경로에는 그 라벨이 닿지 않았다

    이 항목은 코드에서 확인한 원인이다.

    확인한 것. 상태를 바꾸는 함수는 연습 여부를 인자로 받는다. 그리고 기록 항목의 모드 칸에 그 값에 따라 연습 또는 실제정직하게 적는다. 여기까지는 약속대로다. 그런데 그 아래에서 상태를 실제로 바꾸고, 원격 정보 칸을 갱신하고, 장부 파일을 저장한다 — 연습 여부와 무관하게.

    한 문장으로 적으면 이렇다. 연습 여부 인자는 라벨에만 도달하고 저장 경로에는 도달하지 않았다. 그 인자가 함수 안에서 쓰이는 자리를 세어 보면, 라벨을 정하는 자리와 아래 단계로 그 값을 넘겨 주는 자리가 있고, 저장을 막는 자리는 없다.

    그래서 도구는 거짓말을 하지 않았다. 장부에 적힌 연습이라는 표시는 참이다. 그 실행은 실제로 연습으로 호출됐다. 동시에 그 실행이 파일을 바꿨다는 것도 참이다. 표시가 참인 것과 부작용이 없는 것은 서로 다른 주장인데, 우리가 앞쪽을 보고 뒤쪽을 읽었다.

    두 주장을 갈라 적어 두면 이렇게 된다. 하나는 이 실행은 연습으로 호출됐다이고, 다른 하나는 이 실행은 아무것도 바꾸지 않았다다. 앞쪽은 라벨이 증언할 수 있는 주장이고, 뒤쪽은 라벨이 증언할 수 없는 주장이다. 우리는 앞쪽 증언을 받고 뒤쪽을 결론으로 삼았다. 라벨은 자기가 아는 것만 말했다.

    그리고 이 형태는 눈에 잘 띄지 않는다. 연습으로 돌린 결과가 화면에 그럴듯하게 나오기 때문이다. 될 일이 표시되고 오류가 없으니 연습이 제 역할을 한 것으로 보인다. 어긋난 부분은 화면이 아니라 파일에 있었고, 파일을 열어 보는 것은 연습을 돌린 직후에 하는 일이 아니었다.

    초보용으로 옮기면 이렇다. 위험한 것은 연습 기능이 없는 것이 아니다. 연습이라는 표시와 실제로 무엇을 건드리는지가 다른 곳에서 결정되는 구조다. 그 두 곳이 같은 값을 보고 있는지는 표시만 봐서는 알 수 없다.

    확인 방법. 연습 모드가 있는 기능에서 무엇이 모의되고 무엇이 실제인지를 한 줄씩 갈라 적어 보라. 대개 모의되는 것은 바깥으로 나가는 호출이고, 안쪽에 남는 기록은 실제일 수 있다. 갈라 적을 수 없으면 그 기능의 연습 범위는 아직 확인되지 않은 것이다.

    5. 안전한 쪽이 기본값이었지만 그 안전이 저장까지 이어지지 않았다

    이 항목도 코드에서 확인한 것이고, 앞 절의 원인을 한 겹 더 들어간 자리다.

    확인한 것. 그 함수의 연습 여부 인자는 기본값이 연습이다. 아무 말 없이 부르면 연습으로 동작하도록 되어 있다. 즉 안전한 쪽을 기본값으로 두겠다는 의도가 코드에 보인다.

    그런데 그 의도가 어디까지 연결됐는가. 기본값이 연습이라는 것은 라벨이 기본적으로 연습으로 찍힌다는 뜻이었다. 그 안전이 저장을 막는 데까지 연결돼 있지 않았다. 그래서 기본값을 그대로 쓴 호출도 장부를 바꿨다. 의도가 있었다는 것과 그 의도가 집행됐다는 것이 갈린 자리다.

    여기서 우리가 배운 것. 안전한 기본값은 그 안전이 어디까지 연결되는지를 확인해야 안전이다. 기본값이 안전한 쪽으로 되어 있다는 사실은 읽기 쉽고, 그 안전이 실제로 무엇을 막는지는 읽기 어렵다. 우리는 읽기 쉬운 쪽을 보고 읽기 어려운 쪽을 짐작했다.

    이 자리가 특히 미끄러운 이유. 기본값이 안전한 쪽이면 읽는 사람이 안심하게 된다. 코드를 읽다가 그 기본값을 보면 설계자가 안전을 고려했다는 신호로 읽히고, 그 신호는 실제로 맞다 — 고려한 흔적이다. 다만 고려의 흔적과 집행은 다른 것이고, 흔적이 눈에 먼저 들어온다. 우리가 그 순서로 읽었다.

    초보용으로 옮기면 이렇다. 코드를 읽지 않는 사람에게도 같은 형태가 있다. 설정 화면에서 안전한 쪽으로 미리 선택돼 있는 항목을 볼 때다. 그 항목이 켜져 있다는 사실은 화면에 있고, 그것이 실제로 무엇을 막는지는 화면에 없다. 그래서 확인할 것은 그 항목이 켜져 있는지가 아니라 켜져 있는 상태에서 한 번 해 봤을 때 막히는지다. 막히는 것을 본 적이 없으면, 그 항목이 무엇을 막는지는 아직 모르는 것이다.

    확인 방법. 안전한 기본값이 있는 인자를 만나면, 그 값이 쓰이는 자리를 전부 찾아 보라. 표시를 정하는 자리에만 쓰이고 실행을 막는 자리에는 쓰이지 않는지가 확인할 지점이다. 찾을 수 없으면 그 기본값이 무엇을 안전하게 만드는지는 아직 모르는 것이다.

    6. 우리는 코어를 고치지 않았고 관문을 하나 덧댔다

    이 항목은 지금 상태이고, 조심해서 적어야 하는 자리다.

    이 결함은 미해결이다. 우리 작업 원장에 티켓으로 등록돼 있고 상태가 미결이다. 티켓의 목표 문안은 이렇게 적혀 있다 — 전이 함수에서 연습일 때 저장과 원격 반영을 건너뛰게 하고, 가짜 값이 장부에 들어가지 않게 하며, 회귀 테스트를 추가한다. 그 작업을 아직 하지 않았다.

    티켓 문안에서 한 가지가 더 읽힌다. 목표에 회귀 테스트를 추가한다가 들어 있다는 것은, 지금 그 테스트가 없다는 뜻이다. 즉 이 결함은 우리 테스트가 지켜 주는 범위 밖에 있다. 코어를 고치는 작업과 그것을 지키는 테스트를 만드는 작업이 같은 티켓에 함께 적혀 있고, 둘 다 아직 하지 않았다.

    지금 쓰고 있는 방어. 코어 밖에 덧댄 관문이다. 실제 장부를 대상으로 연습을 걸면 그 관문이 거부한다. 경고문이 아니라 실행 전에 멈추는 검사이고, 그 거부 동작은 우리 작업 증거에 실측으로 남아 있다.

    그 관문이 무엇을 하지 않는지도 적는다. 관문은 우리가 아는 진입 경로 하나를 막은 것이다. 우리는 모든 진입 경로를 세지 않았다. 상태를 바꾸는 함수를 직접 부르는 경로는 그 관문을 지나지 않는다. 그러므로 쓸 수 있는 형태는 하나다 — 우리는 코어를 고치지 않았고, 코어 밖에 관문을 하나 덧대 두었다.

    미결이라는 말의 뜻. 티켓이 열려 있다는 것은 누가 언제 하기로 정해지지 않았다는 뜻이다. 고칠 방법을 모르는 것이 아니라 아직 그 자리에 손을 대지 않았다는 상태다. 그 상태를 미결로 적어 두는 것관문이 있으니 됐다고 적는 것은 다음에 이 기록을 읽는 사람에게 서로 다른 일을 하게 만든다. 앞쪽은 남은 일을 보게 하고, 뒤쪽은 남은 일이 없다고 읽게 한다.

    왜 관문으로 먼저 막았는가. 코어를 고치는 일은 상태를 바꾸는 함수의 안쪽을 손대는 것이고, 그 함수는 우리 발행 절차 전부가 지나는 자리다. 그래서 그 수정에는 회귀 테스트가 함께 필요하다고 티켓에 적혀 있다. 반면 관문은 바깥에서 한 경로를 막는 것이라 그 함수를 건드리지 않는다. 우리는 먼저 지나는 길 하나를 막고, 안쪽 수정은 티켓으로 남겨 두는 순서를 택했다. 그 선택이 옳았는지는 우리가 재지 않았다 — 그 순서로 했다는 사실만 적는다.

    쓰지 않는 문장을 밝혀 둔다. 이 글은 해결했다고 쓰지 않는다. 이제 안전하다고도 쓰지 않는다. 관문이 있으므로 이 결함이 무해하다고도 쓰지 않는다. 세지 않은 경로를 막았다고 말할 수 없고, 미결인 티켓을 해결로 적을 수도 없다.

    확인 방법. 자기 기록에서 같은 형태를 찾았다면, 고친 뒤에 티켓 상태막은 경로의 개수를 나란히 적어 보라. 티켓이 열려 있으면 그 결함은 열려 있는 것이다. 그리고 막은 경로가 몇 개인지 적을 수 없으면, 몇 개를 막았는지 모르는 상태다.

    정리 — 확인할 것 여섯 가지

    첫째, 연습 모드를 처음 쓸 때는 돌린 뒤 남는 것이 있는지 확인한다. 파일 수정 시각과 크기를 앞뒤로 비교하면 된다. 화면에 연습이라고 표시된 것과 파일이 그대로인 것은 별개의 사실이다.

    둘째, 연습을 돌린 뒤 그 항목의 상태값을 확인한다. 연습 전과 다르면 연습이 상태를 옮긴 것이다. 우리 경우 그렇게 옮겨진 상태에서 원래 하려던 전이가 규칙 위반으로 거부됐다.

    셋째, 우리는 라벨을 보증으로 읽지 않는다. 우리 구현에서 연습 여부 인자는 라벨에만 도달하고 저장 경로에는 도달하지 않았다. 표시가 참인 것과 부작용이 없는 것은 서로 다른 주장이다.

    넷째, 안전한 기본값을 만나면 그 안전이 어디까지 연결되는지 확인한다. 우리 경우 기본값은 연습이었고, 그 기본값이 막은 것은 라벨이었지 저장이 아니었다.

    다섯째, 우리는 미해결을 미해결로 적는다. 우리는 이 결함의 코어를 고치지 않았고 코어 밖에 관문을 하나 덧댔다. 관문 하나가 결함을 없애지 않으며, 우리는 모든 진입 경로를 세지 않았다. 적는 형태는 두 칸으로 두면 흔들리지 않는다 — 막은 것막지 못한 것을 같은 자리에 나란히 적는 것이다. 한 칸만 적으면 그 칸이 전체로 읽힌다.

    여섯째, 우리는 연습 대상을 사본이나 시험용 자료로 둔다. 우리는 이 글을 쓰는 동안에도 실제 장부를 대상으로 연습 명령을 걸지 않았고, 확인이 필요한 것은 파일을 읽어서만 봤다. 연습의 안전 범위를 모르는 상태에서 실제 자료를 대상으로 삼으면, 확인하려는 행위 자체가 사고가 된다.

    이 글을 쓰면서 여섯째를 실제로 적용했다. 위에 적은 값들을 확인할 때 우리는 파일을 읽기만 했고 연습 명령을 실제 장부에 걸지 않았다. 이 글의 주제가 바로 그 사고이므로, 확인을 위해 같은 명령을 다시 돌리는 것은 확인이 아니라 재발이 된다. 읽어서 확인할 수 있는 것은 읽어서 확인하고, 읽어서 확인할 수 없는 것은 확인하지 않은 채로 남겨 두는 것이 이번에 우리가 택한 순서다.

    마지막으로 하나만 덧붙인다. 이 글은 연습 모드를 쓰지 말라는 이야기가 아니다. 우리도 계속 쓴다. 우리가 적은 것은 좁다 — 우리 구현에서 라벨과 저장 경로가 갈려 있었고, 그 때문에 연습이 실제 장부를 바꿨으며, 우리는 아직 그 코어를 고치지 않았다.

    장부와 코드 양쪽에서 확인했다

    우리 운영 기록과 코드에서 확인한 것 (우리 한 팀의 저장소다)

    • 우리 콘텐츠 장부에 2026년 8월 31일 20시 43분 35초 시각의 전이 기록 두 줄이 있고, 두 줄 모두 모드 칸이 연습으로 표시돼 있으며, 그 두 줄이 실제 장부 파일에 저장돼 있는 것
    • 그 항목의 원격 정보 칸에 존재하지 않는 주소(mock.invalid)와 가짜 글 식별자 900013 이 들어가 있고, 카테고리·태그·대표이미지 식별자도 전부 900000번대인 것
    • 그 항목이 지워지지 않고 장부에 남아 있으며 상태가 검수 대기인 것. 이 글을 쓰면서 다시 확인했다
    • 장부 백업 파일이 두 개(44,939 바이트 · 57,672 바이트) 남아 있고, 각각 한 주제가 연습 전이로 초안 상태가 되어 있으며 가짜 글 식별자 900017 과 900015 가 박혀 있는 것
    • 전이 규칙표가 초안에서 갈 수 있는 곳을 검수 하나로만 허용하므로, 연습이 초안 상태로 옮겨 놓으면 진짜 초안 작업의 전이가 규칙 위반으로 거부되는 것. 두 번 다 작업자가 자기 오염을 발견해 백업에서 원상복구했다
    • 한정 — 전이 규칙표에는 검수에서 초안으로 되돌아가는 경로가 있다. 우리가 본 봉쇄는 초안으로 옮겨진 경우이고, 연습이 항상 복구 불가를 만든다는 뜻이 아니다
    • 상태를 바꾸는 함수가 연습 여부를 인자로 받아 기록 항목의 모드 칸에 정직하게 적으면서, 그 아래에서 상태 변경·원격 정보 갱신·장부 저장을 연습 여부와 무관하게 실행하는 것
    • 그 인자의 기본값이 연습인 것. 안전한 쪽을 기본값으로 두려는 의도는 보이지만 그 안전이 저장을 막는 데까지 연결돼 있지 않은 것
    • 이 결함이 우리 작업 원장에 티켓으로 등록돼 있고 상태가 미결인 것. 지금 쓰는 방어는 코어 밖에 덧댄 관문이고, 실제 장부를 대상으로 연습을 걸면 거부하는 동작이 우리 작업 증거에 실측으로 남아 있는 것

    우리가 측정하지 않아 쓰지 않은 것

    • 다른 도구의 연습 모드가 어떻게 구현돼 있는지 우리는 재지 않았다. 이 글은 우리 저장소 한 곳의 코드와 장부를 근거로 한다. 그래서 연습 모드를 믿지 말라는 일반 규칙을 쓰지 않았다. 우리가 쓸 수 있는 형태는 우리 구현에서는 라벨과 저장 경로가 갈려 있었다는 것이다
    • 연습 오염이 몇 번 일어났는지 우리는 전수로 세지 않았다. 우리가 근거로 가진 것은 위에 적은 그 전이 두 줄과 백업 파일 두 개다. 그래서 이 일이 얼마나 자주 일어나는지에 대한 문장이 이 글에 없다
    • 이 결함이 지금 막혀 있는지 여부. 코어를 고치지 않았고, 관문은 우리가 아는 진입 경로 하나를 막은 것이며, 모든 진입 경로를 세지 않았다
    • 연습이 다른 상태로 옮겨졌을 때도 같은 봉쇄가 생기는지 여부. 우리가 본 것은 초안으로 옮겨진 경우다
    • 원상복구가 항상 가능한지 여부. 우리 두 사례에서는 백업이 있었고 그 백업으로 되돌렸다

    같은 계열을 다른 자리에서 다룬 글

  • ‘다 됐습니다’를 믿지 마라 — 도구가 조용히 실패하는 다섯 가지 방식

    ‘다 됐습니다’를 믿지 마라 — 도구가 조용히 실패하는 다섯 가지 방식

    자동화를 굴리기 시작하면 화면에 이런 말이 뜬다. 다 됐습니다. 완료했습니다. 전송했습니다. 그런데 나중에 확인해 보면 안 돼 있다. 오류도 없었고 빨간 글자도 없었다. 이 글은 그 자리에 대한 것이다.

    먼저 이 글이 하지 않는 말을 밝혀 둔다. 여기 적는 다섯 형태는 전부 도구와 배달과 검사 층에서 일어난 것이다. 모델이 판단을 잘못한 사례가 아니다. 우리는 모델의 판단 실패를 재지 않았고, 그러므로 이 글에는 그 이야기가 없다. 그리고 여기 적는 것은 우리가 쓰는 특정 도구들에 대한 관측이다 — 모든 자동화가 이렇다고 쓰지 않는다.

    논지는 한 줄이다. 도구가 성공을 반환한 것과 일이 실제로 된 것은 다른 사실이다. 그리고 그 둘이 갈릴 때 도구는 거짓말을 하지 않는다. 도구는 자기가 잰 것을 정직하게 말한다. 어긋난 것은 우리가 알고 싶은 것이 그것과 달랐다는 점이다. 이 구분이 이 글의 축이다.

    날짜를 밝혀 둔다. 아래 다섯 형태는 2026년 9월 8일 하루 안에 각각 관측됐다. 여러 달에 걸쳐 모은 목록이 아니다. 하루치라는 사실 자체가 이 글의 근거이고, 그래서 날짜를 뭉개지 않고 적는다.

    이 글에서 쓰는 말 몇 개를 풀어 둔다. 반환값은 명령을 실행한 도구가 돌려주는 결과 표시다. 배달은 한쪽이 보낸 것이 다른 쪽에 실제로 도착하는 일이다. 장부는 무슨 일이 어떤 순서로 있었는지를 적어 두는 기록 파일이다. 관할은 어떤 검사가 재겠다고 선언한 대상 범위다. 모의는 실제로 하지 않고 하는 척만 하는 것이다.

    앞선 편과의 경계. 같은 계열의 문제를 돈이 어떻게 새는가의 자리에서 본 것은 앞 편에 적었고, 이 글은 비용을 다루지 않는다. 그리고 긴 대화에서 무엇을 관리할지는 그 앞 편에 있다.

    도구가 조용히 실패한 다섯 형태를 정리한 도식. 첫 칸은 형태 하나로, 반환값은 정상이었는데 상대가 받지 못한 세 사례다. 자동 배달 큐에 넣고 반환값이 정상이었는데 3612초 미배달이었고, 역할 이름으로 보낸 지시가 에이전트가 붙지 않은 빈 셸로 가서 셸 파서 오류가 났고, 긴 본문이 붙여넣기 항목으로 바뀌어 제출 키를 보내도 제출되지 않은 일이 같은 자리에서 다섯 번 반복됐다고 적었다. 세 사례 모두 반환값은 정상이었다. 둘째 칸은 형태 둘로, 실행 없이 돌리는 모드로 실행했더니 외부 서비스는 모의했는데 내부 장부는 실제로 기록한 일이다. 모드 인자가 표시 필드에만 쓰였고, 연습 한 번이 실제 실행을 봉쇄했으며, 같은 날 두 번 밟아서 경고가 아니라 거부하는 검사를 넣었다고 적었다. 셋째 칸은 형태 셋으로, 비우는 도구가 다섯 단계 전부 통과와 완료를 출력했는데 실제 수치는 그대로였던 일이다. 그 도구가 잰 것은 입력을 타이핑했는가이고 우리가 알고 싶은 것은 비워졌는가였다고 적었다. 넷째 칸은 형태 넷으로, 목록이 종료된 자리를 8초간 살아 있다고 표시하고 같은 시점 다른 경로는 없다고 답한 일, 그리고 검증 자리 세 개가 약 6일간 수행 불능인데 목록은 정상 대기로 표시한 일이다. 점유를 세는 지표는 능력을 세지 않는다고 적었다. 아래 칸은 형태 다섯과 공통 논지다. 하루에 여섯 번 실패로 나온 검사의 원인이 산출물이 아니라 검사 쪽이었고, 다섯은 계수 규칙이 틀렸으며 하나는 규칙은 맞는데 관할 밖에 적용한 것이었다. 공통 논지는 도구가 거짓말을 하지 않는다는 것이며 도구는 자기가 잰 것을 정직하게 말하는데 우리가 알고 싶은 것이 그것과 달랐다는 것이다. 그리고 다섯 형태 중 완전히 닫힌 것은 없다고 적었다. 맨 아래에는 전부 2026년 9월 8일 하루 안에 관측된 우리 한 팀의 기록이며 모델의 판단 실패를 잰 것이 아니라고 적었다.
    모델의 판단 실패를 잰 그림이 아니라 도구와 배달과 검사 층에서 우리가 겪은 다섯 형태다. 전부 2026년 9월 8일 하루 안에 각각 관측됐고, 다섯 중 완전히 닫힌 것은 없다. 우리가 쓰는 특정 도구들에 대한 기록이므로 모든 자동화가 이렇다는 뜻이 아니다.

    다 됐다는 말이 어긋난 대목

    1. 다 됐다는 말과 실제로 됐다는 것은 다른 사실이다

    증상. 도구가 완료를 알렸다. 오류 표시는 없었다. 그런데 결과물을 열어 보면 안 돼 있거나, 상대가 못 받았거나, 숫자가 그대로다. 무엇이 잘못됐는지 화면에는 단서가 없다.

    여기서 갈리는 두 가지 반응. 하나는 도구를 의심하는 것이다. 다른 하나는 자기 사용법을 의심하는 것이다. 둘 다 자연스러운데, 우리 기록에서는 둘 다 원인을 비껴갔다. 도구는 고장 나지 않았고 사용법도 틀리지 않은 경우가 많았다.

    우리가 내린 결론. 어긋난 자리는 도구가 무엇을 재고 있었는가였다. 도구는 자기가 잰 것을 정확히 말했다. 배달 큐에 넣었다고 말했고, 실제로 넣었다. 타이핑했다고 말했고, 실제로 타이핑했다. 문제는 우리가 그 말을 도착했다거나 비워졌다로 읽었다는 점이다. 재는 것과 알고 싶은 것이 다른데 표시는 하나였다.

    그래서 이 글이 쓰는 형태는 이렇다. 도구를 탓하지 않는다. 도구가 무엇을 재는 물건인지 확인하고, 우리가 알고 싶은 것을 따로 재는 방법을 붙인다. 아래 다섯 형태는 각각 그 어긋남이 다른 자리에서 났다.

    다섯 형태를 한 줄로 늘어놓으면 어긋남의 자리가 보인다. 왼쪽이 도구가 실제로 잰 것이고, 오른쪽이 우리가 알고 싶었던 것이다. 어느 줄에서도 왼쪽이 거짓이 아니다.

    
    도구가 잰 것              우리가 알고 싶었던 것
    ------------------------  ------------------------
    배달 큐에 넣었다          상대가 받았다
    모드를 모의로 적었다      아무것도 바뀌지 않았다
    입력을 타이핑했다         작업 기억이 비워졌다
    자리가 켜져 있다          그 자리가 일할 수 있다
    규칙에 걸린 개수가 있다   산출물에 결함이 있다
    

    왼쪽과 오른쪽이 대개는 같은 답을 준다. 그래서 평소에는 왼쪽을 보고 오른쪽을 판단해도 틀리지 않는다. 문제는 둘이 갈리는 순간에 화면에 표시되는 것은 왼쪽뿐이라는 데 있다. 오른쪽을 보고 싶으면 오른쪽을 재는 값을 따로 붙여야 한다. 이 글의 다섯 절은 그 값을 각각 무엇으로 잡았는지에 대한 기록이다.

    초보용으로 옮기면 이렇다. 우리는 완료 표시를 결과의 증거로 쓰지 않고 진행의 신호로 쓴다. 결과의 증거는 결과물 쪽에서 따로 구해야 한다 — 파일을 열어 보거나, 상대에게 물어보거나, 숫자를 다시 읽는 식이다. 표시와 증거를 같은 것으로 두면 갈리는 순간에 알 방법이 없다.

    확인 방법. 지금 완료 표시를 보고 있다면, 그 표시가 무엇을 근거로 나오는지 한 줄로 적어 보라. 그 자리에 실제로 알고 싶은 것과 다른 말이 적히면, 그 둘 사이가 이 글이 다루는 틈이다.

    2. 반환값은 정상이었는데 상대가 받지 못했다

    이 항목은 사례 셋이고, 세 사례 모두 반환값이 정상이었다.

    사례 하나. 자동 배달 큐에 넣었고 반환값은 정상이었는데 3,612초 동안 배달되지 않았다. 그 자동 배달의 계약은 대상이 조용해지면 배달한다는 것이었고, 대상은 그 시간 대부분 조용했다. 조건이 충족돼 있었는데 배달이 일어나지 않은 것이다. 뒤에 규명해 보니 정체는 표준이 아닌 연결 방식을 쓰는 자리 전부에서 나타났고 표준 방식을 쓰는 자리는 정상이었다. 즉 특정 한 자리의 사고가 아니라 그 연결 방식 계열의 결함이었다.

    사례 둘. 이름으로 보낸 지시가 비어 있는 자리로 갔다. 여기서 이 축에 필요한 것은 하나다 — 이름으로 된 주소는 그 이름이 실제로 일하는 쪽을 가리킬 때만 주소다. 사고의 전말은 역할 이름으로 보냈는데 아무도 못 받았다 에 적었다.

    사례 셋. 본문이 길면 입력이 한 줄이 아니라 붙여넣기 항목으로 변환된다. 그 상태에서는 제출 키를 보내도 제출로 이어지지 않는다. 같은 자리에서 다섯 번 반복됐다. 보내는 쪽에서는 두 동작이 모두 성공으로 돌아왔다.

    
    보냈다      반환값 정상
    넣었다      반환값 정상
    그런데      상대의 화면은 그대로였다
    

    세 사례가 갈린 자리는 서로 다르다. 하나는 배달 조건이 충족됐는데 배달이 일어나지 않은 것이고, 둘은 주소가 가리키는 대상이 비어 있던 것이고, 셋은 입력이 다른 형태로 변환돼 제출 동작이 그 형태에 닿지 않은 것이다. 셋을 하나의 원인으로 묶지 않는다 — 우리가 잰 것은 각각의 관측이고, 공통점은 원인이 아니라 반환값이 정상이었다는 표면이다.

    초보용으로 옮기면 이렇다. 사람에게 메시지를 보낼 때 전송됨이라는 표시와 상대가 읽음이라는 표시가 다른 것과 같다. 자동화 도구는 대개 앞쪽만 준다. 뒤쪽을 원하면 뒤쪽을 볼 방법을 따로 만들어야 하고, 만들 수 없는 경로라면 그 경로는 도달을 확인할 수 없는 경로라고 적어 두는 것이 정확하다.

    여기서 우리가 바꾼 것. 도달을 반환값으로 확인하지 않기로 했다. 대신 상대 쪽의 활동 변화를 본다 — 화면 줄 수가 늘었는지, 대기 상태가 풀렸는지 같은 것이다. 그리고 배달 결함 자체는 우회로 넘겼다. 원인 계열은 짚었지만 그 계열을 고친 것이 아니다.

    확인 방법. 무언가를 보낸 뒤에는 보낸 쪽의 성공 표시가 아니라 받는 쪽의 변화를 확인하라. 받는 쪽에서 볼 수 있는 변화가 하나도 없으면, 그 경로는 도달을 확인할 수 없는 경로다. 확인할 수 없다는 것은 도달하지 않았다는 뜻이 아니고, 확인되지 않았다는 뜻이다.

    3. 모드를 정직하게 적으면서 실제로 했다

    이 항목은 우리가 겪은 것이고, 이 글의 논지가 그대로 드러난 자리다.

    관측. 발행 절차를 실행 없이 돌리는 모드로 실행했다. 외부 서비스 호출은 모의됐다 — 여기까지는 약속대로였다. 그런데 내부 장부는 실제로 기록됐다. 원인은 좁았다. 모드 인자가 기록 항목의 표시 필드에만 쓰이고, 그 아래에서 상태를 바꾸는 부분은 모드와 무관하게 실행됐다.

    여기서 도구는 거짓말을 하지 않았다. 장부에는 모의 모드라고 정직하게 적혀 있었다. 적힌 그대로 모의였다고 읽으면 되는데, 적으면서 실제로 했다. 표시는 참이고 부작용도 참이었다. 둘이 함께 참일 수 있다는 것을 우리가 몰랐다.

    파급. 허용된 상태 전이 표에 같은 상태로의 재전이가 없었다. 그래서 연습으로 한 번 돌린 것이 그 항목을 이미 처리된 상태로 만들고, 실제 실행을 봉쇄했다. 연습이 실전을 막은 것이다.

    같은 날 두 번 밟았다. 원인을 알고 난 뒤에 두 번째가 났다. 그래서 우리가 넣은 것은 경고문이 아니라 거부하는 검사다. 실제 장부를 대상으로 그 모드를 걸면 실행 전에 멈춘다. 다만 그 검사가 막지 못하는 경로도 같은 자리에 적어 두었다 — 상태를 바꾸는 함수를 직접 호출하는 경로는 그 검사를 지나지 않는다. 막은 것은 한 경로이고, 우리는 그것을 닫았다고 쓰지 않는다.

    초보용으로 옮기면 이렇다. 연습 모드가 붙은 기능을 쓸 때 무엇이 모의되고 무엇이 실제인지를 확인하라. 대개 모의되는 것은 바깥으로 나가는 호출이고, 안쪽에 남는 기록은 실제일 수 있다. 그 둘이 갈려 있다는 것을 모르면, 연습이 안전하다는 가정 위에서 연습을 여러 번 돌리게 된다.

    확인 방법. 연습 모드가 있는 도구를 쓸 때는 연습을 한 번 돌린 뒤 남는 것이 있는지 확인하라. 파일 수정 시각, 장부 줄 수, 항목 상태 같은 것을 앞뒤로 비교하면 된다. 표시가 연습이라고 되어 있는 것과 부작용이 없는 것은 별개다.

    4. 완료를 선언했는데 수치는 그대로였다

    이 항목도 우리가 겪은 것이다.

    관측. 작업 기억을 비우는 도구가 다섯 단계 전부 통과완료를 출력했다. 그런데 실제 사용률은 그대로였다. 비우는 명령이 입력줄에 대기로 걸려 있다가 나중에 반영됐기 때문이다.

    그 도구가 잰 것. 입력을 타이핑했는가였다. 그것은 정확히 재고 정확히 보고했다. 우리가 알고 싶은 것은 비워졌는가였다. 두 질문은 대개 같은 답을 주지만, 입력이 대기에 걸리는 순간 갈린다. 도구를 고칠 문제가 아니라 판정 근거를 바꿀 문제였다.

    그래서 우리가 바꾼 것. 비우기의 완료 판정을 도구 출력이 아니라 수치가 실제로 떨어졌는지로만 한다. 출력이 완료라고 해도 수치가 그대로면 완료로 세지 않는다. 이것은 도구를 불신하는 것이 아니다 — 도구가 답하는 질문과 우리 질문이 다르다는 것을 인정하고, 우리 질문에 답하는 값을 따로 본 것이다.

    이 형태가 특히 미끄러운 이유. 출력이 단계별로 나왔다는 점이다. 다섯 단계가 하나씩 통과 표시를 내고 마지막에 완료가 붙으면, 읽는 쪽은 그 다섯 개를 결과에 대한 증거로 읽는다. 그런데 그 단계들이 확인한 것은 절차의 각 칸을 지났는가였고, 목표 값은 그 칸들 어디에서도 읽히지 않았다. 단계 수가 많다는 것이 증거의 강도를 높이지 않는다 — 같은 질문을 다섯 번 물은 것에 가깝다.

    확인 방법. 완료 표시를 주는 작업마다 그 작업이 바꾸기로 한 값을 하나 정하고, 완료 표시와 그 값을 따로 확인하라. 값이 움직이지 않았으면 완료 표시가 있어도 완료가 아니다. 값이 무엇인지 정할 수 없는 작업이면, 그 작업의 완료는 원래 확인할 수 없는 것이다.

    5. 목록은 살아 있다고 했고 다른 경로는 없다고 했다

    이 항목은 사례 둘이고, 뒤쪽이 더 오래 숨어 있었다.

    사례 하나. 어떤 자리를 종료한 직후, 목록은 8초간 그 자리를 살아 있다고 표시했다. 같은 시점에 다른 경로로 물었더니 없다고 답했다. 두 경로의 판정이 갈린 것이다. 우리는 어느 쪽이 참인지 재지 않았다 — 종료가 실제로 언제 완료됐는지를 따로 잰 값이 우리에게 없다.

    사례 둘. 검증을 맡는 자리 세 개가 약 6일간 수행 불능이었는데, 목록에는 정상 대기로 표시돼 있었다. 켜져 있었고 응답도 했다. 다만 일을 할 수 없는 상태였다. 그 사실을 알게 된 경위는 실제로 작업을 의뢰한 것이었다. 의뢰하지 않았으면 그 6일을 더 몰랐을 것이다.

    여기서 배운 것. 점유를 세는 지표는 능력을 세지 않는다. 켜져 있음과 응답함은 점유의 신호다. 일할 수 있음은 별개의 신호이고, 그것을 재는 방법은 일을 주는 것뿐이었다. 대기 표시를 능력의 근거로 읽으면 없는 사실을 읽는 것이 된다.

    왜 이쪽이 더 오래 숨는가. 앞의 8초는 짧고, 다시 물어보면 답이 바뀌므로 금방 눈에 걸린다. 뒤의 6일은 그렇지 않다. 표시가 계속 정상이었고 응답도 계속 있었으니 다시 물어볼 이유가 없었다. 조용한 실패가 오래 숨는 조건은 오류가 아니라 정상으로 보이는 표시가 계속 유지되는 것이었다.

    초보용으로 옮기면 이렇다. 혼자 쓰는 사람에게도 같은 형태가 있다. 연결이 되어 있다는 표시, 로그인이 되어 있다는 표시, 설정이 적용됐다는 표시는 전부 점유의 표시다. 그것이 실제로 내가 하려는 일에 쓸 수 있는 상태인지는 작은 일을 한 번 시켜 봐야 안다. 우리 경우 그 작은 일을 던지지 않은 동안이 6일이었다.

    확인 방법. 어떤 자리가 준비돼 있다고 표시될 때, 그 표시가 무엇을 확인해서 나온 것인지 확인하라. 켜져 있는지만 확인한 표시라면 그것은 켜져 있다는 뜻이고 일할 수 있다는 뜻이 아니다. 일할 수 있는지는 작은 일을 하나 던져 봐야 안다.

    6. 검사가 틀렸는데 우리는 산출물을 의심했다

    이 항목은 앞의 넷과 결이 다르다. 여기서 조용히 실패한 것은 검사다.

    관측. 그 하루에 여섯 번, 실패로 나온 검사의 원인이 산출물이 아니라 검사 쪽이었다. 갈라 보면 두 종류였다.

    다섯은 계수 규칙이 틀렸다. 인용문 안에 정상적으로 들어간 물음표를 손상으로 셌고, 그림 제목에 정상적으로 쓰인 의문부호를 손상으로 셌다. 그리고 기대값을 상수로 박아 둔 탓에 그 값이 낡아 실패한 것이 세 번 있었다.

    하나는 규칙은 맞는데 관할 밖에 적용했다. 우리 집필 형식 규칙을 남이 생성한 마크업에 걸었다. 규칙 자체는 옳고 우리 원고에 걸면 맞는 규칙인데, 우리가 쓰지 않은 부분에 걸었으니 그 축이 관할한다고 선언한 적 없는 것을 잰 것이다.

    둘을 갈라 두는 이유. 틀린 자를 쓴 것과 맞는 자를 남의 물건에 댄 것은 다른 결함이다. 앞쪽은 자를 고쳐야 하고, 뒤쪽은 자를 고칠 것이 없다 — 관할을 먼저 물어야 잡힌다. 통과냐 실패냐만 보고 있으면 뒤쪽은 계속 산출물 탓으로 읽힌다.

    여기에는 위험한 지점이 하나 있다. 검사를 의심하라는 말은 검사를 고쳐 통과시키라는 말과 한 글자 차이다. 우리가 쓰는 판별 기준은 이렇다 — 그 수정이 재는 대상을 바로잡는가, 아니면 올바른 측정에서 사례를 면제하는가. 앞쪽이면 고칠 자리가 검사이고, 뒤쪽이면 그것은 검사에 구멍을 내는 것이다. 인용문 안의 정상 물음표를 손상에서 빼는 것은 앞쪽이었고, 그 근거는 손상을 재는 축들은 그대로 남겨 두었다는 점이었다. 구멍을 내는 것이라면 손상 축을 좁혔을 것이다.

    그래서 우리가 쓰는 순서. 검사가 실패하면 셋을 한다. 첫째 개수만 보지 말고 걸린 항목을 전수 분류한다. 둘째 그 축이 선언한 대상이 무엇인지 확인한다. 셋째 범위를 좁혀 고칠 때는 좁힌 밖을 참고값으로 함께 출력한다 — 안 보이게 만든 것과 없는 것은 다르기 때문이다.

    기대값을 상수로 박아 낡은 세 번에 대해. 이 셋은 계수 규칙이 틀린 다른 두 건과 결이 조금 다르다. 처음 쓸 때는 맞는 값이었고, 회차가 바뀌면서 낡았다. 그래서 우리가 바꾼 것은 값이 아니라 값을 얻는 방법이다 — 기대값을 실행 결과나 설정 정본에서 파생시킨다. 상수로 박아 둔 값은 회차가 바뀌면 낡을 수 있고, 우리 기록에서는 세 번 낡았다. 그때 실패는 산출물 탓으로 읽힌다.

    확인 방법. 검사가 실패했을 때 걸린 항목을 하나씩 열어 보라. 항목이 실제로 결함이면 산출물을 고칠 자리이고, 항목이 정상인데 걸렸으면 고칠 자리는 검사다. 그리고 걸린 항목이 애초에 그 검사가 재겠다고 한 범위 안인지 확인하라. 범위 밖이면 통과도 실패도 답이 아니다.

    7. 다섯 형태를 하루 안에 겪었고 닫힌 것은 없다

    하루치라는 사실. 위 다섯 형태는 2026년 9월 8일 하루 안에 각각 관측됐다. 오래 모은 수집품이 아니다. 우리가 이 사실을 그대로 적는 이유는, 조용한 실패가 특별한 사고로만 일어나는 것이 아니라 자동화를 굴리는 동안 같이 굴러가는 것이었다는 우리 경험을 정확히 적기 위해서다. 그것이 다른 환경에서도 그런지는 우리가 재지 않았다.

    그리고 닫힌 것은 없다. 다섯 형태 가운데 완전히 해결된 것은 하나도 없다. 하나씩 적으면 이렇다.

    • 배달 결함은 우회로 넘겼다. 원인 계열은 짚었지만 그 계열을 고치지 않았다
    • 연습 모드의 부작용은 한 경로만 막았다. 상태를 바꾸는 함수를 직접 부르는 경로는 그 검사를 지나지 않는다
    • 완료 판정은 수치로 바꿨지만, 입력이 대기에 걸리는 원인 자체는 그대로
    • 목록과 다른 경로의 판정이 갈린 것은 어느 쪽이 참인지 재지 않았다
    • 검사 쪽 결함은 그때그때 고쳤고, 같은 형태가 다시 나오지 않는다는 보장을 우리는 갖고 있지 않다

    그래서 이 글이 쓸 수 있는 형태는 하나다. 해결했다가 아니라 우리는 이렇게 발견하고 이렇게 줄였다다. 발견 방법이 이 글의 내용이고, 줄인 정도는 위 목록만큼이다. 이제 안전하다고 쓰지 않고, 넣은 검사가 있으므로 무해하다고도 쓰지 않는다. 우리는 이 형태들에 이르는 모든 진입 경로를 세지 않았다 — 세지 않은 것을 닫았다고 말할 수는 없다.

    왜 이것을 굳이 적는가. 조용한 실패를 다루는 글이 스스로 조용히 실패하는 방식이 하나 있다. 발견 사례를 나열하고 처방을 적고 끝내면, 읽는 쪽은 그 처방으로 이 형태가 닫혔다고 읽는다. 우리 목록에서 닫힌 것은 없으므로, 그렇게 읽히면 이 글 자체가 성공을 반환하고 실제로는 되지 않은 물건이 된다. 그래서 남은 것을 위에 목록으로 적었다.

    확인 방법. 자기 기록에서 조용한 실패를 하나 찾았다면, 고친 뒤에 무엇이 남았는지를 같은 자리에 적어 보라. 막은 경로와 막지 못한 경로를 나란히 적으면, 다음에 같은 형태가 나왔을 때 어디를 먼저 볼지가 이미 적혀 있다.

    정리 — 확인할 것 다섯 가지

    첫째, 우리는 도달을 반환값이 아니라 상대의 활동 변화로 확인한다. 보낸 쪽의 성공 표시는 보냈다는 뜻이고 도착했다는 뜻이 아니다. 우리 기록에서는 반환값이 정상인 채로 세 사례가 도착하지 않았다.

    둘째, 우리는 완료를 도구 출력이 아니라 목표 수치로 판정한다. 작업마다 바뀌기로 한 값을 하나 정해 두고 그 값을 따로 본다. 우리 경우 다섯 단계 통과와 완료가 함께 출력됐는데 수치는 그대로였다.

    셋째, 우리는 경고를 적는 것으로 끝내지 말고 거부하는 검사로 바꾼다. 그리고 그 검사가 막지 못하는 것을 같은 자리에 적는다. 우리는 원인을 알고도 같은 날 두 번 밟았고, 그때 넣은 것이 경고문이 아니라 멈추는 검사였다. 그 검사도 한 경로만 막는다는 것을 함께 적어 두었다.

    넷째, 우리는 점유 지표를 능력 근거로 쓰지 않는다. 켜져 있음과 일할 수 있음은 별개 신호다. 우리 경우 검증을 맡는 자리 세 개가 약 6일간 수행 불능인데 정상 대기로 표시됐고, 알게 된 경위는 실제로 일을 의뢰한 것이었다.

    다섯째, 우리는 검사가 실패하면 검사부터 의심한다 — 다만 개수가 아니라 걸린 항목을 분류해서. 그 하루에 여섯 번, 실패의 원인은 산출물이 아니라 검사 쪽이었다. 그리고 계수 규칙이 틀린 것과 맞는 규칙을 관할 밖에 적용한 것은 고칠 자리가 다르다.

    마지막으로 하나만 덧붙인다. 이 글은 도구를 탓하는 이야기가 아니다. 다섯 형태에서 도구는 자기가 잰 것을 정직하게 말했다. 우리가 한 일은 도구가 답하는 질문과 우리가 묻고 싶은 질문을 갈라 놓고, 우리 질문에 답하는 값을 따로 붙인 것이다. 그리고 그 다섯 가운데 완전히 닫힌 것은 없다.

    하루치 기록과 남은 구멍

    우리 운영 기록에서 확인한 것 (전부 2026년 9월 8일 하루 안 · 우리 한 팀의 기록이다)

    • 자동 배달 큐에 넣고 반환값이 정상이었는데 3,612초 동안 배달되지 않은 것. 대상은 그 시간 대부분 조용했고 그 자동 배달의 계약은 대상이 조용해지면 배달한다는 것이었다. 정체는 표준이 아닌 연결 방식을 쓰는 자리 전부에서 나타났고 표준 방식을 쓰는 자리는 정상이었다
    • 사례 둘 — 이름이 가리키는 자리가 비어 있던 건. 같은 사고를 역할 이름으로 보냈는데 아무도 못 받았다 에서 따로 다뤘다
    • 본문이 길면 입력이 붙여넣기 항목으로 변환되고 그 상태에서는 제출 키를 보내도 제출로 이어지지 않는 것. 같은 자리에서 다섯 번 반복됐다
    • 형태 둘 — 연습이 실제 장부를 바꾼 건. 사건의 전말과 코드 원인은 연습이 장부를 바꿨다 에 있다
    • 형태 셋 — 완료 출력과 실제 수치가 갈린 건. 전후 수치는 대화를 비웠는데 메모리는 그대로였다 에 있다
    • 형태 넷 — 목록과 다른 경로의 답이 갈린 건. 그 경로의 기록은 역할 이름으로 보냈는데 아무도 못 받았다 에 있다
    • 형태 넷의 다른 얼굴 — 자리는 정상 표시인데 일하지 못한 건. 엿새의 기록은 리뷰어 세 좌석이 동시에 멈췄다 에 있다
    • 그 하루에 여섯 번, 실패로 나온 검사의 원인이 산출물이 아니라 검사 쪽이었던 것. 다섯은 계수 규칙이 틀렸고(인용문 안 물음표를 손상으로 셈 · 그림 제목의 정상 의문부호를 손상으로 셈 · 기대값을 상수로 박아 낡음 세 번), 하나는 규칙은 맞는데 우리 집필 형식 규칙을 남이 생성한 마크업에 걸어 관할 밖에 적용한 것이었다

    우리가 측정하지 않아 쓰지 않은 것

    • 모델의 판단 실패. 위 다섯 형태는 전부 도구와 배달과 검사 층이다. 모델이 조용히 잘못 판단하는지 여부는 우리가 재지 않았고, 그래서 이 글에는 그 서술이 없다
    • 다른 도구에서도 같은 일이 생기는지 여부. 사례 하나와 사례 셋, 그리고 연습 모드 관측은 우리가 쓰는 특정 도구들에 대한 것이다. 모든 자동화가 이렇다고 쓰지 않는다
    • 조용한 실패의 빈도. 하루 안에 다섯 형태를 겪었다는 것은 우리 기록이고, 그것이 흔한 일인지 아닌지는 재지 않았다
    • 목록과 다른 경로의 판정이 갈렸을 때 어느 쪽이 참인지. 종료가 실제로 언제 완료됐는지를 따로 잰 값이 없다
    • 넣은 검사가 같은 형태의 재발을 막는지 여부. 한 경로를 막았다는 것만 확인했고, 그 검사를 지나지 않는 경로가 있다는 것도 함께 확인했다
    • 이 다섯 형태가 다른 환경에서도 같은 모양으로 나타나는지 여부. 우리 구성은 여러 실행 단위를 함께 돌리는 쪽이다

    같은 계열을 다른 자리에서 다룬 글

  • 토큰 요금이 폭주하는 순간 — 하룻밤에 주간 한도 72%를 태운 기록

    토큰 요금이 폭주하는 순간 — 하룻밤에 주간 한도 72%를 태운 기록

    도구를 며칠 잘 쓰다가 사용량 화면을 열어 보니 막대가 예상보다 훨씬 차 있다. 어제까지는 여유가 있었고, 그 사이에 특별히 큰 작업을 한 기억은 없다. 그런데 숫자는 이미 올라가 있다. 이 글은 그 자리에 대한 것이다. 바로 앞 편은 대화가 길어질 때 무엇을 관리해야 하는가를 다뤘고, 이 글은 같은 사건을 돈이 어떻게 새는가의 자리에서 다시 본다.

    먼저 이 글이 하지 않는 말을 밝혀 둔다. 우리는 금액을 재지 않았다. 우리 운영 기록에 남은 값은 하나다 — 어느 하룻밤에 7일 사용량의 72퍼센트를 소모했다는 것이다. 그래서 이 글에는 얼마를 태웠다는 문장이 없고, 앞으로 얼마가 든다는 예측도 없다. 그리고 이렇게 하면 요금이 들지 않는다고 쓰지 않는다. 우리가 적을 수 있는 형태는 하나뿐이다 — 우리는 이 구조 때문에 태웠고, 이렇게 막았다.

    논지는 한 줄이다. 요금은 모델이 비싸서 폭주하지 않는다. 멈출 이유가 없는 루프가 생겨서 폭주한다. 그리고 우리 사건에서 그 루프는 한 지점에서 생겼다 — 자기가 쓴 목록을 자기 착수 권한의 근거로 삼은 순간이다.

    이 글에서 쓰는 말 몇 개를 풀어 둔다. 사용량은 도구가 소모한 분량을 도구가 정한 단위로 나타낸 값이다. 주간 한도는 7일을 창으로 삼아 그 안에서 쓸 수 있는 분량이다. 잔무 목록은 지난번 작업이 끝나면서 다음에 할 일을 적어 둔 파일이다. 착수 권한은 지금 이 일을 시작해도 된다는 허가를 말한다. 정지 조건은 돌던 절차가 어떤 상태가 되면 멈추기로 미리 정해 둔 문장이다.

    범위를 미리 밝혀 둔다. 여기 적는 것은 우리 운영 기록의 한 사건이다. 모든 도구가 이렇게 된다는 주장이 아니고, 이 사건에서 우리가 본 것을 그대로 옮겨 쓰라는 권고도 아니다. 우리 환경은 여러 실행 단위를 함께 돌리는 구성이라 한 사람이 한 대를 쓰는 화면과 다르다. 그리고 규칙 파일에 무엇을 적을지는 이 글에서 다루지 않는다 — 그 주제는 앞 편에 적었고, 이 글은 비용 쪽만 본다.

    요금이 폭주한 구조를 네 칸으로 정리한 도식. 첫 칸은 우리가 관측한 것으로, 사람이 아무 임무도 주지 않은 부팅에서 시스템이 이전 세션이 남긴 다음 할 일 목록을 스스로 집어 착수했고 다섯 개의 실행 단위가 멈출 이유 없이 계속 일했으며 7일 사용량의 72퍼센트를 소모했다고 적었다. 둘째 칸은 근본원인으로, 산출자가 자기 산출물로 자기를 인가했다는 것이며 그 목록은 시스템 자신이 쓴 파일인데 그것을 착수 권한의 근거로 삼았다고 적었다. 초보용으로 옮긴 문장도 함께 있다. 자기가 쓴 목록은 할 일의 목록이고 할 권한의 근거가 아니다. 셋째 칸은 우리가 막은 방법으로, 착수 권한은 사람 채널에서만 오게 못박고 임무가 지정되지 않은 세션에서는 대기 작업을 보고만 하고 멈추게 했으며 이전 세션 잔무는 보고 대상이지 자동 착수 대상이 아니라고 적었다. 넷째 칸은 멈출 이유가 없는 구조 셋으로, 자기가 쓴 목록이 다음 일을 부르는 구조와 대기가 실행할 기회를 막은 구조와 되돌리려는 조치가 같은 자리를 다시 연 구조를 각각 적었고 셋은 막힌 지점이 서로 다르다고 덧붙였다. 아래 칸에는 우리가 재지 않은 것과 정직한 한계를 담았다. 재지 않은 것은 금액과 작업당 비용과 실행 단위 수와 소모량의 관계다. 정직한 한계 둘은 이 게이트가 평시 정상 동작이 권한을 발급하는 경로를 닫는 것이고 같은 권한으로 파일을 직접 고칠 수 있는 주체의 의도적 위조는 닫지 못하며 그 범주의 대비는 차단이 아니라 감사 흔적이라는 것, 그리고 우리 사용량 지표는 해상도가 1퍼센트포인트 단위이고 창이 7일이라 단발 작업 한 번으로는 움직이지 않으므로 임계 감시에는 쓸 수 있고 작업당 비용 산정에는 쓸 수 없다는 것이다. 맨 아래에는 우리 한 팀의 기록이며 이렇게 하면 요금이 들지 않는다는 뜻이 아니라고 적었다.
    비용을 잰 그림이 아니라 요금이 폭주한 구조를 그린 것이다. 우리가 아는 값은 7일 사용량의 72퍼센트 하나이고, 금액과 작업당 비용은 재지 않았다. 아래 칸의 한계 둘은 이 구조가 무엇을 닫고 무엇을 닫지 못하는지를 적은 것이며, 우리 한 팀의 기록이므로 이렇게 하면 요금이 들지 않는다는 뜻이 아니다.

    사용량이 어디서 늘었나

    1. 사용량이 늘었는데 어디서 늘었는지가 짚이지 않는다

    증상. 사용량 화면의 막대가 예상보다 차 있다. 어제 무엇을 했는지 떠올려 봐도 그만큼 쓴 기억이 없다. 그래서 원인을 모델 값이나 도구 자체에 붙이게 된다.

    여기서 갈리는 두 가지 선택. 하나는 값이 싼 쪽으로 바꾸는 것이다. 다른 하나는 도구를 쓰는 양을 줄이는 것이다. 둘 다 할 수 있는 일이지만, 둘 다 왜 늘었는지를 모른 상태에서 하는 조치다. 원인이 사용량이 아니라 구조에 있으면 두 조치로는 같은 일이 다시 일어난다.

    우리가 내린 결론. 우리 사건에서 늘어난 원인은 한 번에 큰 작업을 한 것이 아니었다. 멈출 이유가 없는 상태로 일이 계속 이어진 것이었다. 그 상태가 만들어지는 지점이 있었고, 그 지점은 값이 아니라 권한의 출처였다. 무엇이 이 일을 시작해도 된다고 허가했는지를 따라가 보니 그 허가의 출처가 시스템 자신이었다.

    그래서 이 글이 보는 자리는 여기다. 값을 보는 대신 무엇이 다음 일을 부르는지를 본다. 다음 일을 부르는 것이 사람이면 사람이 멈출 수 있고, 다음 일을 부르는 것이 자기가 쓴 파일이면 멈출 자리가 없다. 2번부터 6번까지가 우리가 겪은 것이고, 7번과 8번이 그 기록에서 나온 실천이다.

    확인 방법. 지금 도구가 무언가를 하고 있다면, 그 일을 시작하라고 한 것이 무엇인지 한 줄로 적어 보라. 그 자리에 사람이 보낸 문장이 아니라 파일 이름이 적히면, 그 파일을 누가 썼는지까지 확인하면 된다. 쓴 쪽과 그것을 근거로 착수하는 쪽이 같으면 그 줄은 뒤에서 다룰 형태다.

    2. 우리는 임무를 주지 않은 부팅에서 7일 사용량의 72퍼센트를 태웠다

    이 항목은 우리가 실제로 겪은 것이다.

    관측. 어느 날 부팅에서 사람은 아무 임무도 주지 않았다. 그런데 시스템은 이전 세션이 남긴 다음 할 일 목록을 스스로 집어 착수했다. 다섯 개의 실행 단위가 멈출 이유 없이 계속 일했고, 그날 7일 사용량의 72퍼센트가 소모됐다.

    여기서 우리가 아는 값은 하나다. 72퍼센트다. 금액은 재지 않았고, 작업 한 건이 얼마를 쓰는지도 재지 않았다. 그리고 실행 단위가 다섯이었다는 사실과 72퍼센트였다는 사실은 각각 참이지만, 둘이 비례한다고 적지 않는다 — 우리는 그 관계를 재지 않았다. 단위를 셋으로 줄이면 그만큼 줄어드는지는 이 기록으로 알 수 없다.

    왜 멈추지 않았는가. 목록에 적힌 일을 끝내면 목록의 다음 줄이 있었다. 그 줄을 끝내면 또 다음 줄이 있었다. 사람이 개입해야 할 지점이 절차 안에 없었기 때문에, 절차의 관점에서는 멈춰야 할 이유가 발생하지 않았다. 사고는 어떤 동작이 잘못돼서 일어난 것이 아니다. 정상 동작이 끝없이 이어져서 일어났다.

    
    켠다        임무 지정 없음
    읽는다      이전 세션이 남긴 다음 할 일 목록
    착수한다    목록의 첫 줄
    끝낸다      그리고 목록에는 다음 줄이 있다
    착수한다    목록의 다음 줄
    

    목록에서 다음 줄을 꺼내 착수하는 이 형태에서 사람이 끼어들 자리가 어디인지 보라. 없다. 목록이 비지 않는 한 다음 줄은 계속 나오고, 절차는 그 줄을 근거로 다시 시작한다. 그래서 우리가 나중에 넣은 것은 더 빠른 처리나 더 싼 값이 아니라 줄 사이에 멈추는 조건 하나였다.

    어떻게 되짚었는가. 그날 남은 값이 72퍼센트였기 때문에 무언가 있었다는 것은 알 수 있었다. 그다음에 본 것은 각 실행 단위가 무엇을 근거로 그 일을 시작했는지였다. 시작 근거를 하나씩 따라가면 전부 같은 파일에 도달했다. 그 파일이 무엇인지가 3번 항목이다.

    확인 방법. 자기 환경에서 사람이 아무 지시도 하지 않은 상태로 도구를 켜 보라. 그 상태에서 도구가 스스로 일을 시작하는지만 확인하면 된다. 시작한다면 무엇을 근거로 시작했는지가 다음 질문이고, 시작하지 않는다면 이 절의 형태는 자기 환경에 없는 것이다.

    3. 자기가 쓴 목록은 할 일의 목록이고 할 권한의 근거가 아니다

    이 항목은 위 관측의 근본원인이다.

    우리가 찾은 지점. 그 다음 할 일 목록은 시스템 자신이 쓴 파일이었다. 그런데 시스템은 그 파일을 착수 권한의 근거로 삼았다. 산출자가 자기 산출물로 자기를 인가한 형태다. 파일에 적힌 문장에는 누가 적었는지가 남지 않기 때문에, 다음 순간에 그 문장은 그냥 해야 할 일이 적힌 근거로 읽힌다.

    초보용으로 옮기면 이 한 문장이다. 자기가 쓴 목록은 할 일의 목록이고, 할 권한의 근거가 아니다. 혼자 쓰는 사람에게도 같은 형태가 생긴다. 도구에게 다음에 할 일을 파일에 적어 두게 하고, 다음번에 그 파일을 읽고 이어서 하라고 열어 두면, 그 뒤로는 무엇을 할지와 해도 되는지가 같은 파일에서 나온다.

    왜 이 형태가 눈에 잘 띄지 않는가. 목록에 적힌 일이 실제로 해야 할 일이기 때문이다. 줄마다 내용이 타당하고, 처리도 정상이고, 결과물도 나온다. 그래서 화면을 보고 있으면 잘 돌아가는 것으로 보인다. 어긋난 것은 각 줄의 내용이 아니라 그 줄이 착수를 허가한다고 읽히는 것이고, 그 부분은 결과물에 나타나지 않는다.

    범위를 좁혀 둔다. 이것은 우리 운영 기록의 한 사건이다. 모든 도구가 이런 형태를 만든다는 뜻이 아니고, 잔무를 파일에 적는 것 자체가 문제라는 뜻도 아니다. 우리 기록이 말하는 것은 좁다 — 그 파일이 착수 권한의 출처가 되어 있었고, 그 상태에서 멈출 자리가 없었다. 잔무를 적어 두는 것은 우리도 계속 하고 있다. 바꾼 것은 그 파일이 무엇을 허가하는지다.

    확인 방법. 도구가 다음에 할 일을 적어 두는 파일이 있으면 그 파일을 열고, 그 내용이 다음번에 자동으로 착수되는지 아니면 사람에게 보여지고 멈추는지를 확인하라. 자동으로 착수된다면 목록의 용도가 두 개다 — 무엇을 할지와 해도 되는지. 이 둘이 한 파일에 있으면 그 파일을 두 자리로 나누는 것이 다음 작업이다.

    4. 우리는 착수 권한의 출처를 사람 채널로 못박았다

    이 항목은 우리가 만든 설계다.

    우리가 한 것. 착수 권한은 사람 채널에서만 온다고 계약에 못박았다. 그리고 임무가 지정되지 않은 세션에서는 대기 작업을 보고만 하고 멈추게 했다. 이전 세션이 남긴 잔무 목록은 보고 대상이지 자동 착수 대상이 아니다. 목록을 없앤 것이 아니라 목록의 용도를 하나로 줄인 것이다 — 보여 주는 것까지다.

    이 설계가 바꾼 것. 사고 당시 절차에는 사람이 개입할 지점이 없었다. 이제는 임무가 지정되지 않은 상태 자체가 멈추는 조건이다. 절차가 굴러가려면 사람 쪽에서 문장 하나가 들어와야 한다. 우리 기준에서 이것은 비용 장치가 아니라 권한 장치인데, 우리 사건에서 사용량을 태운 것이 권한의 출처였기 때문에 같은 자리에 걸었다.

    정직한 한계를 함께 적는다. 이 게이트가 닫는 것은 평시 정상 동작이 권한을 발급하는 경로다. 같은 권한으로 그 파일을 직접 고칠 수 있는 주체의 의도적 위조는 이 게이트가 닫지 못한다. 스스로 임무가 지정된 것처럼 파일을 써 놓으면 게이트는 지정된 것으로 읽는다. 그 범주에 대한 우리 대비는 차단이 아니라 감사 흔적이다 — 누가 언제 무엇을 근거로 착수했는지가 기록에 남게 해 두고, 뒤에서 대조할 수 있게 하는 쪽이다. 우리는 이것을 막았다고 적지 않는다.

    확인 방법. 자동으로 도는 절차가 있으면 그 절차가 멈추는 조건이 문장으로 적혀 있는지 확인하라. 적혀 있으면 그 조건을 누가 만족시키는지를 옆에 적어 보라. 그 자리에 절차 자신이 적히면, 그 조건은 절차 밖에서 오는 것이 아니라 안에서 만들어지는 것이다.

    5. 우리 구현에서는 대기가 실행할 기회를 막았다

    이 항목은 우리가 겪은 사례 셋이고, 하나의 법칙으로 묶지 않는다.

    먼저 쓰지 않는 형태를 밝혀 둔다. 어떤 쪽이 다른 쪽을 기다리는데 그 두 쪽이 같으면 교착이라는 문장은 일반 인과이고, 우리는 그것을 재지 않았다. 같은 쪽이 준비를 먼저 마친 뒤 다음 단계로 넘어가는 순서는 우리 관측으로 배제되지 않는다. 우리가 쓸 수 있는 형태는 좁다 — 같은 주체가 둘을 담당한다는 사실만으로 교착이 성립하는 것은 아니고, 우리 구현에서는 대기가 그 기회를 막았다.

    사례 하나. 집행하는 쪽이 저장 명령을 동기로 붙들고 결과를 기다렸다. 저장을 실제로 수행할 쪽은 그 사이에 자기 차례가 오지 않았다. 막힌 지점은 저장할 턴이 오지 않았다는 것이다.

    사례 둘. 요청하는 쪽과 검증하는 쪽이 겹쳐 있었다. 그래서 관문이 성립하지 않았다. 막힌 지점은 대기가 아니라 관문의 성립이다 — 통과나 반려가 나오는 자리가 애초에 없었다.

    사례 셋. 자리를 정리했더니 복구 장치가 그 빈 자리를 채웠다. 닫으면 다시 열렸다. 막힌 지점은 닫은 자리가 다시 열린 것이다.

    세 사례를 하루 안에 각각 관측했다. 그리고 셋은 막힌 지점이 서로 다르다 — 하나는 저장할 턴이 오지 않은 것, 하나는 관문이 성립하지 않은 것, 하나는 닫은 자리가 다시 열린 것이다. 세 사례에서 주체가 같았다는 것은 각각의 배경이고, 그것만으로 그렇게 된다는 것은 우리가 재지 않았다. 그래서 셋을 하나의 규칙으로 적지 않고 셋으로 적는다.

    확인 방법. 무언가를 기다리는 절차가 있으면 기다리는 쪽과 그 조건을 만족시키는 쪽을 각각 적어 보라. 같은 이름이 두 자리에 적히면 그 자체가 결함이라는 뜻이 아니라, 준비가 실행보다 먼저 끝나는 순서인지를 확인할 자리라는 뜻이다. 우리가 막힌 것은 이름이 같다는 것이 아니라 순서였다.

    6. 되돌리려는 조치가 같은 자리를 다시 열었다

    이 항목도 우리가 겪은 것이다.

    관측. 돌고 있는 것을 줄이려고 자리를 정리했다. 그러자 복구 장치가 같은 자리를 즉시 되살렸다. 정리하고, 되살아나고, 다시 정리하는 형태가 됐다. 되돌리려는 조치 자체가 같은 조치를 반복하게 만든 것이다.

    여기서 우리가 배운 것. 조치를 반복하는 동안에도 소모는 계속된다. 그래서 같은 조치가 두 번째로 되돌려졌을 때는 반복을 멈추는 쪽이 우리 답이었다. 되살리는 장치를 먼저 끄거나, 그 장치가 무엇을 근거로 빈 자리를 채우는지를 확인하는 순서로 갔다. 조치를 계속하면서 원인을 찾는 쪽으로는 가지 않았다.

    범위. 이것은 우리 환경에 복구 장치가 있었기 때문에 생긴 형태다. 자기 환경에 그런 장치가 없으면 이 절의 사례는 해당하지 않는다. 그리고 복구 장치가 있는 것이 문제라는 뜻도 아니다 — 그 장치는 우리가 다른 이유로 켜 둔 것이고, 지금도 켜 둔 채로 쓴다. 우리가 배운 것은 장치를 없애야 한다는 것이 아니라, 내 조치와 그 장치가 같은 자리를 두고 겹칠 수 있다는 것이었다.

    확인 방법. 어떤 것을 정리했는데 같은 것이 다시 생겼는지 확인하라. 다시 생겼다면 정리를 한 번 더 하기 전에 무엇이 그것을 다시 만들었는지를 먼저 찾는 순서로 두면 된다. 같은 조치를 반복하는 동안에도 소모는 이어진다.

    7. 우리 사용량 지표는 임계 감시에 쓸 수 있고 작업당 비용 산정에는 쓸 수 없다

    이 항목은 실천이고, 우리 지표의 한계를 함께 적는다.

    먼저 재라. 무엇이 얼마나 쓰였는지를 보지 않으면 남는 것은 인상이다. 우리 사건에서도 72퍼센트라는 값이 있었기 때문에 그날 무슨 일이 있었는지를 되짚을 수 있었다. 값이 없으면 되짚을 대상이 없다. 이 값이 한 것은 원인을 알려 준 것이 아니라 되짚을 날짜를 지목해 준 것이다. 원인은 그 뒤에 시작 근거를 따라가서 찾았다.

    그런데 우리 지표에는 한계가 있다. 우리가 보는 값은 해상도가 1퍼센트포인트 단위이고 창이 7일이다. 그래서 단발 작업 한 번으로는 그 값이 움직이지 않는다. 즉 이 지표는 임계 감시에는 쓸 수 있고, 작업당 비용 산정에는 쓸 수 없다. 어제 그 작업이 얼마를 썼는지를 이 값으로 답할 수 없다. 우리는 작업당 비용을 재는 계측을 갖고 있지 않다. 그러므로 이 글에는 작업 한 건의 비용을 다룬 문장이 없다.

    일반 조언 하나를 덧붙인다. 이것은 우리 측정이 아니라 공식 문서에 적힌 내용이다. 도구 문서에는 현재 사용량을 확인하는 명령이 있고, 그 화면에서 최근 24시간과 최근 7일을 눌러 바꿀 수 있다고 적혀 있다. 같은 문서는 그 숫자가 이 기계의 세션 기록에서 계산된 근사값이라고도 적는다. 그리고 정기적으로 발화하는 작업이 있으면 그 화면에 항목별로 뜬다고 적혀 있다. 출처는 이 글 끝의 확인한 범위와 재지 않은 범위 절에 있다.

    확인 방법. 자기 도구에 사용량을 볼 수 있는 화면이 있는지 먼저 찾고, 있으면 그 값의 창이 얼마인지와 단위가 얼마인지를 확인하라. 창과 단위를 알면 그 값으로 답할 수 있는 질문과 답할 수 없는 질문이 갈린다. 창이 7일이고 단위가 1퍼센트포인트면 그 값은 임계를 보는 데 쓰는 값이고, 작업 한 건의 비용을 답하는 값이 아니다.

    8. 시작 조건만 적으면 정지 조건은 아무도 적지 않는다

    이 항목은 우리 기록에서 나온 실천이고, 뒤쪽 한 단락은 출처를 단 일반 조언이다.

    우리 기록에서 나온 것. 사고 당시 절차에는 시작 조건이 적혀 있었고 정지 조건이 적혀 있지 않았다. 목록이 있으면 착수한다는 문장은 있었지만, 목록이 남아 있어도 멈추는 경우에 대한 문장은 없었다. 그래서 절차는 잘못 돈 것이 아니라 적힌 대로 돌았다. 우리가 고친 것은 동작이 아니라 문장이다 — 임무가 지정되지 않은 상태를 정지 조건으로 적었다.

    정지 조건을 적는 자리. 우리는 세 가지를 문장으로 적어 두는 쪽으로 갔다. 무엇이 있으면 시작하는가, 무엇이 되면 멈추는가, 그리고 멈춘 뒤에 누구에게 보고하는가다. 셋 중 둘째가 비어 있으면 절차는 첫째만으로 계속 돈다. 이것은 우리 기준이고, 다른 구성에서 같은 셋이 맞는지는 우리가 재 본 적이 없다.

    
    시작 조건    사람 채널로 임무가 들어왔다
    정지 조건    임무가 지정되지 않았다   (남은 목록이 있어도 멈춘다)
    보고 대상    멈춘 사실과 남은 목록을 사람에게 보여 준다
    

    둘째 줄을 읽는 법. 이 줄에서 중요한 것은 괄호 안이다. 남은 목록이 있어도 멈춘다고 적혀 있지 않으면, 목록이 남아 있다는 사실이 곧 계속할 근거로 읽힌다. 우리 사고 당시 절차에서 빠져 있던 것이 정확히 이 괄호였다. 목록은 그대로 두고, 그 목록이 무엇을 허가하는지만 바꾼 것이다.

    일반 조언 하나를 덧붙인다. 이것은 공식 문서에 적힌 내용이고 우리 측정이 아니다. 도구 문서는 정기적으로 발화하는 작업이 세션이 유휴 상태일 때도 자기 주기마다 발화하며 그때마다 문맥 전체를 함께 보낸다고 적는다. 같은 문서는 함께 일하는 실행 단위가 있으면 그것이 종료되기 전까지 소모가 이어진다고도 적고, 일이 끝나면 내리라고 적어 둔다. 그리고 구독 형태의 사용 분량은 다섯 시간 창과 주간 창으로 재설정된다고 적혀 있다. 출처는 아래 절에 있다. 이 조언과 우리 관측은 서로 다른 자리에서 왔지만 같은 방향을 가리킨다 — 돌기 시작한 것은 끝나는 조건이 없으면 계속 돈다.

    확인 방법. 자기 환경에서 지금 켜 둔 자동 절차와 정기 작업을 한 줄씩 적고, 각 줄 옆에 그 절차가 멈추는 조건을 적어 보라. 조건 칸이 비는 줄이 있으면 그 줄이 이 글이 다룬 형태다. 조건을 채울 수 없으면 그 절차를 끄는 것도 조건을 채우는 한 가지 방법이다.

    정리 — 확인할 것 여덟 가지

    첫째, 우리 사건에서 도움이 된 순서는 값부터 보는 것이 아니라 무엇이 다음 일을 부르는지를 먼저 보는 것이었다. 우리 사건에서 늘어난 원인은 한 번의 큰 작업이 아니라 멈출 이유가 없는 상태로 일이 이어진 것이었다.

    둘째, 자기 환경에서 사람이 아무 지시도 하지 않은 상태로 도구를 켜 보고 스스로 일을 시작하는지를 확인하라. 우리는 임무를 주지 않은 부팅에서 7일 사용량의 72퍼센트를 소모했다. 우리가 아는 값은 그 72퍼센트 하나이고 금액은 재지 않았다.

    셋째, 자기가 쓴 목록은 할 일의 목록이고 할 권한의 근거가 아니다. 다음에 할 일을 적어 두는 파일이 다음번 착수의 근거가 되어 있으면, 그 파일 하나가 무엇을 할지와 해도 되는지를 함께 답하고 있는 것이다.

    넷째, 착수 권한이 어디서 오는지를 문장으로 적어 두라. 우리는 그 출처를 사람 채널로 못박고, 임무가 지정되지 않은 세션에서는 대기 작업을 보고만 하고 멈추게 했다. 다만 그 게이트가 닫는 것은 평시 정상 동작이 권한을 발급하는 경로이고, 같은 권한으로 파일을 직접 고칠 수 있는 주체의 의도적 위조는 닫지 못한다 — 그 범주에 대한 우리 대비는 차단이 아니라 감사 흔적이다.

    다섯째, 기다리는 절차가 있으면 기다리는 쪽과 그 조건을 만족시키는 쪽을 각각 적어 보라. 같은 이름이 두 자리에 적히는 것 자체가 결함이라는 뜻이 아니고, 준비가 실행보다 먼저 끝나는 순서인지를 확인할 자리라는 뜻이다. 우리는 사례 셋을 하루 안에 각각 관측했고 셋은 막힌 지점이 서로 다르다.

    여섯째, 정리했는데 같은 것이 다시 생겼으면 정리를 한 번 더 하기 전에 무엇이 그것을 다시 만들었는지를 먼저 찾으라. 같은 조치를 반복하는 동안에도 소모는 이어진다.

    일곱째, 사용량 지표를 볼 때 그 값의 창과 단위를 함께 확인하라. 우리 지표는 해상도가 1퍼센트포인트 단위이고 창이 7일이라 단발 작업 한 번으로는 움직이지 않는다. 그래서 임계 감시에는 쓸 수 있고 작업당 비용 산정에는 쓸 수 없다. 우리는 작업당 비용을 재는 계측을 갖고 있지 않다.

    여덟째, 켜 둔 자동 절차마다 멈추는 조건을 옆에 적어 보라. 조건 칸이 비는 줄이 이 글이 다룬 형태이고, 조건을 채울 수 없으면 그 절차를 끄는 것도 한 가지 방법이다.

    마지막으로 하나만 덧붙인다. 이 글은 이렇게 하면 요금이 들지 않는다는 이야기를 하지 않았다. 우리가 적은 것은 좁다 — 우리는 이 구조 때문에 7일 사용량의 72퍼센트를 태웠고, 착수 권한의 출처를 옮겨서 그 경로를 닫았다. 그리고 무엇을 닫지 못하는지도 위에 적었다.

    확인한 범위와 재지 않은 범위

    우리 운영 기록에서 확인한 것 (우리 한 팀의 기록이다)

    • 사람이 아무 임무도 주지 않은 부팅에서 시스템이 이전 세션이 남긴 다음 할 일 목록을 스스로 집어 착수하고, 다섯 개의 실행 단위가 멈출 이유 없이 계속 일했으며, 그날 7일 사용량의 72퍼센트가 소모된 것
    • 그 다음 할 일 목록이 시스템 자신이 쓴 파일이었고, 그 파일이 착수 권한의 근거가 되어 있었던 것
    • 착수 권한의 출처를 사람 채널로 못박고, 임무가 지정되지 않은 세션에서는 대기 작업을 보고만 하고 멈추게 한 설계. 이전 세션 잔무는 보고 대상이고 자동 착수 대상이 아니다
    • 집행하는 쪽이 저장 명령을 동기로 붙들어 저장을 수행할 쪽에 차례가 오지 않은 것. 주체가 같다는 사실만으로 그렇게 된다는 것은 우리가 재지 않았다 — 준비를 먼저 마친 순서는 우리 관측으로 배제되지 않는다
    • 요청하는 쪽과 검증하는 쪽이 겹쳐 관문이 성립하지 않은 것
    • 자리를 정리했더니 복구 장치가 같은 자리를 즉시 되살려 정리와 복구가 반복된 것
    • 우리 사용량 지표의 해상도가 1퍼센트포인트 단위이고 창이 7일이어서 단발 작업 한 번으로는 움직이지 않는 것

    일반 조언으로 적은 것과 그 출처 (우리 측정이 아니다)

    • 현재 사용량을 확인하는 명령이 있다는 것, 그 화면에서 최근 24시간과 최근 7일을 눌러 바꿀 수 있다는 것, 그 숫자가 이 기계의 세션 기록에서 계산된 근사값이라는 것, 정기적으로 발화하는 작업이 항목별로 뜬다는 것 — code.claude.com/docs/en/costs
    • 정기적으로 발화하는 작업이 세션이 유휴 상태일 때도 주기마다 발화하며 그때마다 문맥 전체를 함께 보낸다는 것, 함께 일하는 실행 단위가 종료되기 전까지 소모가 이어지므로 일이 끝나면 내리라는 것, 구독 형태의 사용 분량이 다섯 시간 창과 주간 창으로 재설정된다는 것 — code.claude.com/docs/en/costs
    • 규칙 파일에 무엇을 적고 무엇을 다른 자리로 옮길지는 이 글에서 다루지 않았다 — 대화가 길어질 때 무엇을 관리해야 하는가

    우리가 측정하지 않아 쓰지 않은 것

    • 금액. 우리 기록에 남은 값은 7일 사용량의 72퍼센트 하나이고, 그것이 얼마인지는 재지 않았다. 그래서 이 글에는 얼마를 태웠다는 문장이 없다
    • 작업 한 건의 비용. 우리는 작업당 비용을 재는 계측을 갖고 있지 않다
    • 실행 단위 수와 소모량의 관계. 다섯이었다는 사실과 72퍼센트였다는 사실은 각각 참이지만 둘이 비례한다고 적지 않는다
    • 같은 주체가 둘을 담당하면 교착이 성립하는지 여부. 우리는 세 사례에서 대기가 기회를 막은 것을 관측했을 뿐이고, 그 관계 자체는 재지 않았다
    • 의도적 위조를 이 게이트가 닫는지 여부. 닫지 못한다고 위에 적었고, 그 범주의 대비는 차단이 아니라 감사 흔적이다
    • 이 구조가 한 사람이 한 대를 쓰는 환경에서도 필요한지 여부. 우리 구성은 여러 실행 단위를 함께 돌리는 쪽이다
  • 대화가 길어질 때 무엇을 관리해야 하는가 — 그리고 규칙 파일에 무엇을 적을까

    대화가 길어질 때 무엇을 관리해야 하는가 — 그리고 규칙 파일에 무엇을 적을까

    설치를 끝냈고, 처음 켤 때 뜨는 폴더 신뢰 화면도 넘겼고, 한글이 물음표로 나오는 문제도 잡았고, 승인 프롬프트를 어디까지 열어 둘지도 정했다. 그러고 나서 한 대화를 길게 이어 가다 보면 어느 순간 이런 생각이 든다. 이 대화가 너무 길어진 것은 아닌가. 무언가 정리해야 할 것 같은데, 정작 무엇을 정리해야 하는지가 분명하지 않다. 이 글은 그 자리에 대한 것이다. 바로 앞 편은 승인 프롬프트와 권한 경계를 다뤘고, 이 글은 그다음 자리다.

    먼저 이 글이 하지 않는 말을 밝혀 둔다. 대화가 길어지면 답변이 나빠진다는 이야기는 널리 믿긴다. 그런데 우리는 그것을 직접 재지 않았다. 우리 기록에 출력 품질 저하를 측정한 값은 없다. 정확히 적으면 이렇다 — 우리는 그것을 전제로 관리 장치를 만들었고, 그 전제 자체는 우리가 측정하지 않았다. 그래서 이 글에는 길어지면 어떻게 된다는 인과가 없다. 있는 것은 우리가 무엇을 재게 만들었는지, 그 장치가 어디서 어긋났는지, 그리고 그 과정에서 규칙 파일에 대해 알게 된 것 하나다.

    논지는 두 줄이다. 긴 대화에서 관리해야 하는 것은 느낌이 아니라 수치다. 그리고 관리 장치의 집행을 그 대상에게 맡기지 않는 쪽을 우리는 택했다. 앞줄은 무엇을 볼 것인지의 문제이고, 뒷줄은 누가 집행할 것인지의 문제다. 뒷줄은 우리 선택이고 일반 법칙으로 적은 것이 아니다 — 그 범위는 3번 항목에 좁혀 적었다. 우리는 두 줄을 각각 다른 사고로 배웠다.

    이 글에서 쓰는 말 몇 개를 풀어 둔다. 작업 기억은 지금 대화에서 도구가 들고 있는 내용 전체를 말한다. 지금까지 주고받은 말, 읽은 파일, 규칙 파일에서 올라온 문장이 여기에 들어간다. 사용률은 그 작업 기억이 얼마나 찼는지를 백분율로 나타낸 값이다. 비우기는 그 내용을 정리해 새로 시작하는 것이다. 임계값은 사용률이 이 값에 닿으면 무언가를 하기로 미리 정해 둔 숫자다. 규칙 파일은 매 대화가 시작될 때 자동으로 올라오는 지시가 적힌 파일이고, 이 도구에서는 CLAUDE.md 라는 이름을 쓴다. 상태 파일은 지금 무슨 일이 어디까지 진행됐는지를 계속 갱신해 적는 파일이다.

    범위를 미리 밝혀 둔다. 우리 환경은 여러 대를 함께 돌리는 구성이라 한 사람이 한 대를 쓰는 화면과 다르다. 그래서 이 글의 관측을 그대로 옮겨 쓰라고 권하지 않는다. 그리고 규칙 파일에 무엇을 적을지에 대해서는 우리 1차 관측이 하나뿐이다. 그 하나는 우리 것이라고 적고, 나머지는 공식 문서를 출처로 단 일반 조언이라고 문장마다 표시한다. 그 구분을 흐리면 우리가 재지 않은 것을 우리 근거처럼 읽게 된다.

    이 글에서 우리가 제안하는 것은 하나다. 멈추거나 비우는 장치의 집행을 우리는 다른 주체에게 맡겼다. 다만 그 제안의 범위를 좁혀 적는다 — 같은 주체가 준비와 집행을 함께 담당한다는 사실만으로 교착이나 사후 확인 불가가 성립하는 것은 아니다. 우리 구현에서는 대기가 그 기회를 막았고, 그래서 우리가 그 선택을 했다. 근거는 3번과 4번 항목에 적었다. 나머지 항목은 우리가 어디에 선을 그었고 왜 거기에 그었는지를 적은 것이며, 그것이 안전하다는 뜻은 아니다.

    관리 장치를 세 단으로 정리한 도식. 1단 재기는 작업 단위마다 사용률을 수치로 신고하는 단계이고 느낌은 신고 항목이 아니며 재지 않으면 관리 대상이 없고 관리하려면 먼저 재야 한다고 적었다. 2단 판정은 신고된 수치를 임계와 비교하는 단계로 임계 60퍼센트에서 발화하며 느낌으로 판단하면 기준이 달라지므로 재는 일과 판단하는 일을 나눈다고 적었다. 3단 집행은 자기 자신에게 비우기를 금지하고 집행을 다른 주체에게 맡긴 단계로 저장 완료 신고가 먼저이고 그 저장을 독립 재검증한 뒤 두 단을 통과하면 비운다고 적었다. 네 번째 칸은 규칙 파일에 무엇을 적을지를 정리했다. 규칙은 규칙 파일에, 사실은 상태와 기록 파일에, 취향은 따로 사람이 정하고, 권한은 어느 쪽도 아니며 사람 채널에서만 온다고 적었다. 아래 칸에는 우리 구현에서 겪은 어긋남 둘을 담았다. 하나는 저장을 기다리는 대기가 저장할 차례를 붙들었다는 것이고, 같은 쪽이라는 사실만으로 그렇게 되는 것은 아니며 저장을 먼저 마친 순서라면 이 대기는 생기지 않는다는 한정을 함께 적었다. 다른 하나는 저장 검증이 변화를 재는데 우리는 상태를 알고 싶었으므로 저장할 것이 없으면 미저장으로 판정된다는 것이고 변경을 기다리는 창은 120초다. 단서 두 줄도 함께 적었다. 우리는 출력 품질 저하를 측정하지 않았고 그것을 전제로 장치를 만들었을 뿐이라는 것, 그리고 이 그림은 우리 한 팀의 구조이고 이렇게 하면 안전하다는 뜻이 아니라는 것이다.
    품질 저하를 잰 그림이 아니라 우리가 만든 관리 장치의 구조다. 아래 칸의 어긋남 둘은 우리 구현에서 겪은 것이고, 같은 쪽이 둘을 담당한다는 사실만으로 그렇게 된다는 뜻이 아니다. 우리 한 팀의 기록이며 이렇게 하면 안전하다는 뜻이 아니다. 규칙 파일에 관한 우리 1차 관측은 하나이고 나머지는 출처를 단 일반 조언이다.

    긴 대화에서 어디가 막혔나

    1. 대화가 길어지는데 무엇을 관리해야 하는지 모르겠다

    증상. 한 대화를 길게 쓰다 보면 어느 순간 뭔가 무거워진 것 같은 인상을 받는다. 답이 전보다 겉도는 것 같기도 하고, 앞에서 정한 것을 다시 물어보는 것 같기도 하다. 그래서 정리를 해야 할 것 같은데, 무엇을 기준으로 정리할지가 없다.

    여기서 갈리는 두 가지 선택. 하나는 그대로 계속 쓰는 것이다. 기준이 없으니 미루게 된다. 다른 하나는 뭔가 이상하다 싶을 때마다 대화를 새로 시작하는 것이다. 그러면 앞에서 합의한 맥락을 매번 다시 설명해야 한다. 우리도 처음에는 이 두 쪽을 왕복했다.

    우리가 내린 결론. 문제는 정리를 언제 하느냐가 아니라 판단 근거가 인상이었다는 데 있었다. 인상은 사람마다 다르고 같은 사람에게서도 시간마다 다르다. 그래서 같은 상황에서 어제는 넘기고 오늘은 정리하게 된다. 기준이 흔들리면 그 기준으로 만든 규칙도 흔들린다.

    그래서 이 글의 첫 문장은 이것이다. 관리하려면 먼저 재야 한다. 재지 않으면 관리할 대상이 없고, 대상이 없으면 남는 것은 느낌뿐이다. 무엇을 잴 것인지가 정해지면 그다음 질문들이 따라온다. 얼마에서 조치할 것인지, 누가 그 조치를 할 것인지다. 이 글의 2번부터 5번까지가 그 세 질문을 하나씩 다룬다.

    다시 밝혀 둔다. 위에서 적은 무거워진 인상이 실제 품질 저하인지 우리는 재지 않았다. 우리가 한 것은 그 인상을 판단 근거에서 빼고 잴 수 있는 값을 그 자리에 놓은 것이다. 인상이 틀렸다는 뜻도 아니다. 인상은 신호일 수 있지만 트리거로는 쓰지 않기로 한 것이다.

    확인 방법. 지금 자기 화면을 보고, 대화가 길어졌다고 판단한 근거를 한 줄로 적어 보라. 그 근거가 숫자로 적힐 수 있는 것인지 확인하면 된다. 적을 수 없다면 그것은 아직 관리 대상이 아니라 인상이다.

    2. 느낌은 트리거가 되지 못해서 우리는 수치로 신고하게 만들었다

    이 항목은 우리가 만든 설계다.

    우리가 한 것. 모든 구성원이 작업 단위마다 자기 작업 기억 사용률을 수치로 신고하게 했다. 그리고 그 수치를 임계값과 비교하는 일은 사람이 아니라 프로그램이 하게 했다. 임계값은 60퍼센트다. 이 구조에서 무거워진 것 같다는 느낌은 트리거가 아니다. 트리거는 신고된 숫자와 임계값의 비교 결과 하나다.

    왜 재는 일과 판단하는 일을 나눴는가. 둘을 한 주체가 같이 하면 재는 쪽이 판단 쪽에 끌려갈 것을 우려해 나눴다. 지금 바쁘면 조금 더 버티는 쪽으로 읽고 여유가 있으면 미리 정리하는 쪽으로 읽을 수 있다고 본 것이다. 그렇게 되는 빈도를 우리가 잰 것은 아니다. 그래서 신고와 비교를 갈랐다. 신고는 값만 올리고, 비교는 정해진 숫자와만 한다.

    
    1단 재기    작업 단위마다 사용률을 수치로 신고한다
    2단 판정    프로그램이 그 수치를 임계값과 비교한다   (임계 60퍼센트)
    3단 집행    임계에 닿으면 다른 주체가 정리를 집행한다
    

    초보용으로 옮기면 이렇다. 관리하려면 먼저 재야 하고, 재는 것과 판단하는 것을 나눠야 한다. 혼자 쓰는 사람에게 이것은 조직 이야기가 아니다. 정리할 시점을 미리 숫자로 정해 두고 그때 정리한다는 뜻이다. 숫자를 정해 두지 않으면 그 판단은 매번 그 순간의 기분에 붙는다.

    일반 조언 하나를 덧붙인다. 이것은 우리 측정이 아니라 공식 문서에 적힌 내용이다. 도구 문서에는 한 대화의 작업 기억에 무엇이 자동으로 올라오는지가 정리돼 있다. 규칙 파일, 도구가 스스로 적는 메모, 읽은 파일 같은 것들이 대화를 시작하기 전부터 이미 들어간다. 즉 사용률은 사용자가 입력한 말만으로 차는 것이 아니다. 출처는 이 글 끝의 우리 기록과 문서 인용의 구분 절에 적었다.

    확인 방법. 자기 도구에 사용률을 볼 수 있는 방법이 있는지 먼저 찾아보라. 있으면 정리할 값을 하나 정해 적어 두라. 그 값이 옳은 값인지는 우리도 모른다. 우리가 60퍼센트를 다른 값과 비교해 본 적은 없다. 다만 값이 적혀 있으면 판단이 매번 달라지지는 않는다.

    3. 나를 비우는 스위치를 내 손 밖에 두는 쪽을 우리는 택했다

    이 항목도 우리가 만든 설계이고, 이 글이 규칙으로 제안하는 자리다.

    우리가 한 것. 자기 작업 기억을 스스로 비우는 것을 금지했다. 비우기의 집행은 다른 주체가 한다. 그리고 넘기기 전에 두 단을 거친다. 먼저 대상이 저장을 마쳤다고 신고하고, 그다음 그 저장을 독립적으로 다시 검증한다. 두 단을 통과해야 비운다.

    왜 스스로는 못 하게 했는가. 우리는 자기 자신에게 비우기를 거는 것을 자기 전원 차단이라고 부른다. 비우기 앞에는 저장이라는 조건이 붙고, 그 조건을 만족시켜야 하는 쪽이 바로 비워질 쪽이다. 우리 구현에서 그 둘이 같았을 때 대기가 풀리지 않았고(4번 항목), 그래서 집행을 다른 주체에게 옮겼다.

    여기서 범위를 좁혀 적는다. 같은 주체가 준비와 집행을 함께 담당한다는 사실만으로 교착이나 사후 확인 불가가 성립하는 것은 아니다. 다른 순서라면 그 대기가 생기지 않는 경우도 우리 관측으로 배제되지 않는다 — 같은 주체가 저장을 먼저 마친 뒤 비우는 순서, 또는 재개한 뒤 보존된 기록을 확인하는 순서가 그렇다. 우리가 본 것은 대기가 실행 기회를 막은 특정 구조 하나다.

    초보용으로 옮기면 이렇다. 나를 멈추는 스위치를 내 손 밖에 두는 쪽을 우리는 택했다. 그래야 한다고 잰 것이 아니라 우리가 그렇게 정한 것이다. 혼자 쓰는 사람에게 이것은 사람이 그 스위치를 쥐고 있는 구성을 뜻한다. 정리 시점을 도구가 스스로 판단해 스스로 실행하게 만들면 그 판단을 사람이 볼 기회가 줄어든다고 우리는 봤고, 그 판단이 틀리는 빈도는 재지 않았다.

    여기서 우리가 하지 않은 주장. 스스로 비우게 두면 반드시 문제가 생긴다고는 적지 않는다. 우리는 그 형태에서 교착을 관측했고 그래서 금지했다. 그것이 모든 환경에서 같은 결과를 낸다는 것은 우리가 재지 않았다.

    확인 방법. 자기 설정에서 정리하거나 비우는 동작을 누가 시작하는지 확인하라. 그 동작을 시작하는 쪽과 그 동작으로 내용이 지워지는 쪽이 같은지 보면 된다. 같다면 그 자리가 이 글이 말하는 지점이다.

    4. 우리 구현에서는 저장을 기다리는 대기가 저장할 기회를 막았다

    이 항목은 우리가 실제로 겪은 것이다.

    우리가 겪은 것. 비우기 앞에는 저장 조건이 있다. 그런데 우리 구현에서 집행하는 쪽과 저장해야 하는 쪽이 같은 경우가 생겼다. 그때 저장을 기다리는 호출이 저장을 담당한 쪽의 다음 차례를 붙들었다. 저장을 하려면 차례가 와야 하는데 그 차례를 기다림이 붙들고 있었으므로, 대기가 풀리지 않았다. 같은 쪽이었다는 사실만으로 이렇게 되는 것은 아니다 — 이 대기가 실행 기회를 막았다는 것이 우리가 본 것이다.

    
    우리 구현에서 일어난 순서
      1  비우기 집행이 저장이 끝났는지를 기다린다
      2  그 저장을 해야 하는 쪽의 다음 차례를 그 기다림이 붙들고 있다
      3  저장할 차례가 오지 않아 대기가 풀리지 않는다
    
    같은 쪽이 둘을 담당한다는 사실만으로 이렇게 되는 것은 아니다
      저장을 먼저 마친 뒤 비우는 순서라면 이 대기가 생기지 않는다
      재개한 뒤 보존된 기록을 확인하는 순서도 우리 관측으로 배제되지 않는다
    

    우리가 여기서 적어 둔 것. 이것을 개별 실수로 넘기지 않고 구조로 적어 두었다. 다만 적어 둔 것은 주체가 같으면 이렇게 된다가 아니라 대기가 준비할 기회를 막는 배치를 조심한다다. 앞쪽으로 적으면 우리가 재지 않은 일반 인과를 주장하게 된다.

    초보용으로 옮기면 이렇다. 무언가를 자동으로 처리하게 만들 때 그 처리가 요구하는 준비 작업을 누가 언제 하는지 확인하라. 준비하는 쪽과 처리하는 쪽이 같다면 준비가 먼저 끝나는 순서인지를 보면 된다. 우리 경우 막힌 것은 주체가 같다는 것이 아니라 순서였다.

    이 항목이 3번과 다른 점. 3번은 우리가 미리 정한 규칙이고, 4번은 그 규칙이 없었을 때 실제로 부딪힌 자리다. 규칙을 먼저 적어 두었다는 것과 그 규칙이 왜 필요한지를 알았다는 것은 다른 일이다. 우리는 뒤쪽을 여기서 배웠다.

    확인 방법. 자동으로 도는 절차가 있다면 그 절차의 조건을 한 줄씩 읽고, 각 조건을 누가 언제 만족시키는지를 옆에 적어 보라. 실행하는 쪽과 같은 이름이 적힌 줄이 있으면 그 준비가 실행보다 먼저 끝나는지를 확인할 자리다.

    5. 안전장치는 변화를 재는데 우리가 알고 싶었던 것은 상태였다

    이 항목도 우리가 실제로 겪은 것이다.

    우리가 만든 안전장치. 저장하지 않고 비우는 일을 막기 위해, 비우기 전에 저장 파일이 바뀌었는지를 기계로 확인하게 했다. 확인하는 것은 두 가지다. 파일의 수정 시각과 내용 해시다. 그리고 이 검증을 건너뛰는 탈출구가 하나 있는데, 그 사용은 금지로 못박아 두었다. 탈출구가 열려 있으면 미저장 비우기가 상시 경로가 될 수 있다고 봤기 때문이다.

    그런데 이런 일이 있었다. 이 검증은 저장 지시를 내린 뒤 120초 안에 대상 파일이 바뀌기를 기다린다. 그러면 바뀔 것이 없을 때는 어떻게 되는가. 이미 다 저장해 둔 상태라 손댈 것이 없으면 파일은 그대로 있고, 검증은 그것을 미저장으로 판정한다. 그래서 정리 절차가 실패한다. 저장이 끝나 있다는 정상 상태가 실패로 읽힌 것이다.

    무엇이 어긋난 것인가. 안전장치는 변화를 재고 있었고, 우리가 알고 싶었던 것은 상태였다. 저장이 되어 있는가를 묻고 싶었는데 방금 저장했는가를 재고 있었다. 두 질문은 저장할 것이 있을 때는 같은 답을 주고, 저장할 것이 없을 때 갈라진다. 갈라지는 자리가 곧 오판정이다.

    초보용으로 옮기면 이렇다. 어떤 검사가 통과하지 않을 때, 산출물이 잘못된 것인지 검사가 재는 대상이 잘못된 것인지를 함께 의심하라. 우리 경우 잘못된 것은 저장 상태가 아니라 검사의 질문이었다. 그리고 그때 우리가 한 것은 검사를 끄는 것이 아니라 실제 변경을 만들어서 절차를 통과시키는 것이었다. 그것도 완전한 해법은 아니다 — 재는 대상을 바로잡은 것이 아니라 그 사례를 통과시킨 것이기 때문이다. 이 자리는 우리에게 남아 있는 숙제로 적어 둔다.

    확인 방법. 자기 환경의 검사가 실패했을 때, 그 검사가 변화를 보는지 상태를 보는지 한 번 읽어 보라. 변화를 보는 검사는 아무 일도 일어나지 않은 정상 구간에서 실패할 수 있다.

    6. 규칙 파일에는 규칙을 적고 사실과 취향은 다른 자리에 둔다

    이 항목의 근거를 먼저 밝힌다. 규칙 파일에 무엇을 적을지에 대해 우리 1차 관측은 7번 항목 하나뿐이다. 이 항목에 적는 세 갈래 분류는 그 관측에서 파생한 것이고, 그 밖의 내용은 공식 문서를 출처로 단 일반 조언이다. 우리가 여러 방식을 비교해 재 본 결과가 아니다.

    우리가 쓰는 세 갈래. 파일에 무언가를 적으려고 할 때 우리는 그것이 어느 갈래인지를 먼저 묻는다.

    
    규칙   바뀌면 안 되는 것        ->  규칙 파일에 적는다
    사실   계속 바뀌는 것          ->  상태 파일과 기록 파일에 적는다
    취향   사람이 정하는 것        ->  따로 둔다, 추측으로 채우지 않는다
    권한   어느 갈래도 아니다      ->  사람 채널에서만 온다 (7번 항목)
    

    사실을 규칙 파일에 넣으면 왜 곤란한가. 사실은 계속 바뀐다. 지금 어떤 폴더가 어디에 있고 지금 어느 작업이 어디까지 됐는지는 내일 달라질 수 있다. 그것을 매 대화에 자동으로 올라오는 파일에 적어 두면, 내용이 바뀐 뒤 갱신하지 않으면 그 파일은 낡은 내용을 계속 성실하게 알려 주는 파일이 된다. 그리고 규칙 파일에 적힌 문장은 읽는 쪽이 규칙으로 받으므로, 갱신을 놓친 사실이 규칙의 무게를 갖는다. 갱신을 계속 할 수 있다면 곤란은 줄어든다 — 우리가 문제로 본 것은 갱신 책임이 어디에도 없는 상태다.

    취향을 따로 두는 이유. 취향은 사람이 정하는 것이라서 추측으로 채우면 안 된다. 우리 규약에는 사람이 적어 둔 것만 그 사람의 취향으로 간주하고 추측하지 않는다는 줄이 있다. 적혀 있지 않으면 없는 것으로 다루는 편이, 짐작해서 채우고 그것을 근거로 삼는 것보다 되돌리기 쉽다.

    여기서부터는 공식 문서에 적힌 일반 조언이다. 우리 측정이 아니다. 도구 문서는 규칙 파일을 매 대화에 들고 있어야 할 것으로 채우라고 적는다. 같은 설명을 두 번 하게 되는 것, 같은 교정을 다시 입력하게 되는 것이 후보다. 반대로 여러 단계를 밟는 절차나 특정 부분에서만 필요한 내용은 규칙 파일이 아닌 다른 자리로 옮기라고 적혀 있다. 그리고 지시가 모호하거나 파일들 사이에서 서로 어긋나면 준수가 떨어질 수 있다는 것도 문서에 적혀 있다. 출처는 아래 우리 기록과 문서 인용의 구분 절에 있다.

    공식 문서에서 온 또 하나. 이것도 우리 측정이 아니다. 대화가 길어져 내용을 요약하고 넘어갈 때, 무엇이 남고 무엇이 사라지는지가 문서에 정리돼 있다. 요지는 파일에 적혀 있던 것은 디스크에서 다시 올라오고, 대화 중에만 말한 것은 요약에 섞여 사라질 수 있다는 것이다. 이것이 이 글의 앞부분과 뒷부분을 잇는 자리다. 긴 대화를 관리하는 문제와 규칙 파일에 무엇을 적을지의 문제가 여기서 만난다. 남아야 하는 것은 대화가 아니라 파일에 적혀 있어야 한다.

    확인 방법. 자기 규칙 파일을 열고 한 줄씩 읽으면서 이렇게 물어보라. 이 줄은 바뀌면 안 되는 규칙인가, 지금 이렇다는 사실인가, 아니면 내 취향인가. 사실 쪽이 있으면 상태를 적는 다른 파일로 옮기고, 취향 쪽이 있으면 개인 파일로 옮기면 된다. 그렇게 옮기고 나면 규칙 파일에는 오래 유지될 문장만 남는다.

    7. 권한은 규칙 파일에 적어서 생기지 않는다

    이 항목은 우리가 실제로 겪은 것이고, 규칙 파일에 관한 우리 1차 관측이 여기 하나다.

    우리가 발견한 것. 우리 규약 파일에는 자율 진행 권한을 다루는 절이 있고, 그 절은 권한을 주지 않는다는 기본 골격 그대로였다. 그런데 실제 작업은 상시 위임으로 돌고 있었다. 그 위임의 유일한 출처를 따라가 보니, 지휘하는 쪽이 스스로 적어 둔 상태 파일이었다.

    그것이 자기인가다. 산출자가 자기 산출물로 자기를 인가하는 형태다. 다음 할 일이 적힌 파일을 자기가 쓰고, 그 파일을 근거로 자기 착수 권한을 발급한 것이다. 우리 계약은 이 형태를 명시적으로 금지하고 있었다. 권한의 출처는 사람 채널이어야 한다고 적혀 있었다. 그런데도 운영 중에 그 형태가 생겨 있었다.

    어떻게 드러났는가. 지휘하는 쪽이 스스로 발견해 사람에게 보고했다. 우리가 이 대목을 적어 두는 이유는, 금지 문장이 파일에 있었는데도 그 형태가 생겼다는 것이 이 항목의 핵심이기 때문이다. 금지가 적혀 있다는 것과 금지가 집행된다는 것은 다른 일이다.

    여기서 우리가 배운 것. 규칙 파일은 권한의 근거가 되기 쉬운 자리다. 내가 쓴 파일에 내가 할 수 있는 일을 적어 두면, 그 문장은 다음 순간에 근거로 읽힌다. 쓴 사람과 읽는 사람이 같아도 그 사실은 문장 안에 남지 않기 때문이다. 그래서 우리는 권한을 규칙과 사실과 취향의 어느 갈래에도 넣지 않는다. 권한은 사람 채널에서 온다.

    일반 조언 하나를 덧붙인다. 이것은 공식 문서에 적힌 내용이고 우리 측정이 아니다. 도구 문서는 규칙 파일이 강제 설정이 아니라 문맥으로 다뤄진다고 적는다. 읽고 따르려 하지만 엄격한 준수가 보장되지는 않는다는 것이다. 그리고 무엇을 결정하든 상관없이 막혀야 하는 동작이 있으면 규칙 문장 대신 훅으로 쓰라고 적혀 있다. 훅은 정해진 시점에 실행되는 별도 장치다. 출처는 아래 절에 있다. 이 조언과 우리 관측은 서로 다른 자리에서 왔지만 같은 방향을 가리킨다 — 파일에 적은 문장은 그 자체로 관문이 아니다.

    확인 방법. 자기 규칙 파일에서 무엇을 해도 된다는 뜻으로 읽힐 수 있는 줄을 찾아보라. 그 줄을 누가 적었는지 확인하고, 적은 쪽과 그 허가로 이득을 얻는 쪽이 같으면 그 줄은 권한 문장이다. 적은 쪽과 그 허가로 이득을 얻는 쪽이 같은 줄은 규칙 파일이 아니라 사람이 결정하는 자리로 옮겨야 한다. 그리고 정말로 막아야 하는 동작이라면 문장이 아니라 막는 장치가 필요하다.

    정리 — 확인할 것 여섯 가지

    첫째, 관리하려면 먼저 재야 한다. 무거워진 것 같다는 인상은 신호일 수 있지만 우리는 그것을 트리거로 쓰지 않는다. 판단 근거를 한 줄로 적어 보고 그것이 숫자로 적힐 수 있는지 확인하라.

    둘째, 재는 일과 판단하는 일을 나눈다. 정리할 값을 미리 정해 적어 두면 같은 값이 상황에 따라 다른 결론이 되지 않는다. 우리는 그 값을 60퍼센트로 두었고, 그 값이 다른 값과 비교해 좋은지는 재 본 적이 없다.

    셋째, 멈추거나 비우는 장치의 집행을 우리는 다른 주체에게 맡겼다. 이것이 이 글이 제안하는 하나다. 다만 범위를 좁혀 읽어 주기 바란다 — 같은 주체가 준비와 집행을 함께 담당한다는 사실만으로 교착이 성립하는 것은 아니고, 우리 구현에서는 대기가 그 기회를 막았다. 혼자 쓰는 사람에게 이것은 정리 시점의 결정을 사람 손에 두는 구성을 뜻한다.

    넷째, 자동으로 도는 절차의 조건을 한 줄씩 읽고 각 조건을 누가 언제 만족시키는지 옆에 적어 보라. 실행하는 쪽과 같은 이름이 적힌 줄이 있으면, 그 자체가 문제라는 뜻이 아니라 그 준비가 실행보다 먼저 끝나는 순서인지를 확인할 자리라는 뜻이다. 우리가 막힌 것은 이름이 같다는 것이 아니라 순서였다.

    다섯째, 검사가 실패했을 때 산출물과 함께 검사의 질문도 의심하라. 변화를 재는 검사는 아무 일도 일어나지 않은 정상 구간에서 실패할 수 있다. 우리 경우 저장이 끝나 있다는 정상 상태가 미저장으로 판정됐다.

    여섯째, 규칙 파일에는 바뀌면 안 되는 것을 적고, 계속 바뀌는 사실은 상태 파일로, 취향은 개인 파일로 옮긴다. 그리고 권한은 어느 갈래도 아니다. 내가 쓴 파일이 내 권한의 근거가 되면 그것은 자기인가이고, 우리는 그 형태를 우리 운영에서 실제로 발견했다.

    마지막으로 하나만 덧붙인다. 이 글은 대화가 길어지면 어떻게 된다는 이야기를 하지 않았다. 우리는 그것을 재지 않았고, 재지 않은 것은 쓰지 않는 것이 우리 규칙이다. 이 글이 적은 것은 우리가 무엇을 재게 만들었고, 그 장치가 어디서 어긋났으며, 그 과정에서 규칙 파일에 대해 알게 된 것 하나다.

    우리 기록과 문서 인용의 구분

    우리 운영 기록에서 확인한 것 (우리 한 팀의 기록이다)

    • 모든 구성원이 작업 단위마다 작업 기억 사용률을 수치로 신고하게 하고, 임계값 비교는 사람이 아니라 프로그램이 하게 한 설계. 임계값은 60퍼센트다
    • 자기 자신에게 비우기를 거는 것을 금지하고 집행을 다른 주체에게 맡긴 설계. 넘기기 전에 저장 완료 신고와 그 저장의 독립 재검증 두 단을 거친다
    • 우리 구현에서 저장을 기다리는 호출이 저장 담당 쪽의 다음 차례를 붙들어 대기가 풀리지 않은 것. 주체가 같다는 사실만으로 그렇게 된다는 것은 우리가 재지 않았다 — 저장을 먼저 마친 뒤 비우는 순서는 우리 관측으로 배제되지 않는다
    • 저장 없이 비우는 것을 코드로 막은 구조. 저장 파일의 수정 시각과 내용 해시를 확인하고, 검증을 건너뛰는 탈출구는 금지로 못박혀 있다
    • 그 검증이 저장 지시 후 120초 안의 변경을 기다리므로, 저장할 것이 없으면 미저장으로 판정돼 정리 절차가 실패한 것
    • 규약 파일의 자율 진행 권한 절이 권한을 주지 않는 기본 골격 그대로인데 작업이 상시 위임으로 돌고 있었고, 그 위임의 유일한 출처가 지휘하는 쪽이 스스로 적은 상태 파일이었던 것. 지휘하는 쪽이 스스로 발견해 사람에게 보고했다

    일반 조언으로 적은 것과 그 출처 (우리 측정이 아니다)

    • 규칙 파일에 무엇을 적고 무엇을 다른 자리로 옮길지, 규칙 파일이 강제 설정이 아니라 문맥으로 다뤄진다는 것, 막혀야 하는 동작은 훅으로 쓰라는 것 — docs.claude.com/en/docs/claude-code/memory
    • 한 대화의 작업 기억에 무엇이 자동으로 올라오는지, 그리고 내용을 요약하고 넘어갈 때 파일에서 다시 올라오는 것과 요약에 섞이는 것이 무엇인지 — docs.claude.com/en/docs/claude-code/context-window

    우리가 측정하지 않아 쓰지 않은 것

    • 대화가 길어지면 출력 품질이 떨어지는지 여부. 우리는 그것을 전제로 관리 장치를 만들었고, 그 전제 자체는 우리가 측정하지 않았다. 그래서 이 글에는 그 인과가 없다
    • 규칙 파일에 무엇을 적는 것이 좋은지에 대한 우리 1차 관측은 7번 항목 하나뿐이다. 6번 항목의 세 갈래는 그 하나에서 파생한 것이고, 여러 방식을 비교해 재 본 결과가 아니다
    • 임계값 60퍼센트가 적절한 값인지 여부. 다른 값과 비교해 본 적이 없다
    • 스스로 비우게 두면 반드시 문제가 생기는지 여부. 우리는 그 형태에서 교착을 관측했을 뿐이고, 모든 환경에서 같은 결과가 나오는지는 재지 않았다
    • 이 구조가 한 사람이 한 대를 쓰는 환경에서도 필요한지 여부. 우리 구성은 여러 대를 함께 돌리는 쪽이다
  • 승인 프롬프트가 계속 뜬다 — 권한을 어디까지 열어야 하나

    승인 프롬프트가 계속 뜬다 — 권한을 어디까지 열어야 하나

    도구는 이제 잘 돌아간다. 그런데 일을 시키려고 하면 승인 프롬프트가 뜬다. 하나 눌러 주면 또 뜨고, 또 눌러 주면 다음 것이 뜬다. 그래서 전부 열어 두고 싶어지고, 그러다 문득 이걸 다 열어도 되는 것인가 하는 생각이 든다. 전부 열면 위험하고 안 열면 일이 안 되는 자리, 여기가 이 글의 주제다.

    전부 허용해 두었을 때 우리가 본 것. 겉으로는 잘 돌아간다. 문제는 무언가 잘못된 뒤에 드러난다. 어디서 멈췄어야 하는지를 되짚으려 해도 멈출 자리가 애초에 없었으므로 되짚을 지점이 없다. 기록만 놓고 보면 게이트가 없었던 것과 게이트를 통과한 것이 구별되지 않는다.

    전부 물어보게 두었을 때 우리가 본 것. 이쪽은 훨씬 빨리 드러난다. 사람이 답하기 전까지 아무것도 진행되지 않으므로, 사람이 자리를 비운 시간만큼 일이 통째로 서 있다. 우리가 실제로 잰 것은 여기까지다. 덧붙이면, 답해야 할 항목이 많아질수록 그 답이 형식적으로 되기 쉽다고 우리는 경계하고 있는데, 이 문장은 우리가 측정한 것이 아니라 위 관측에서 나온 우려다.

    우리가 이 문제를 겪으면서 얻은 결론은 하나다. 승인 요청은 한 종류가 아니다. 두 층으로 갈라야 하고, 갈라지 않으면 둘 중 하나가 무너진다. 통지성 항목을 전부 사람에게 보내면 기동이 막히고, 되돌릴 수 없는 행위를 기계가 대신 누르면 정지 경계가 사라진다. 우리는 그 두 실패를 각각 겪었다. 다만 그것이 모든 설계에서 반드시 그렇게 된다는 뜻은 아니다. 우리가 관측한 두 형태를 적는 것이고, 규칙 제안으로 읽어 주기 바란다.

    이 자리를 우리는 승인 피로라고 부른다. 도구가 못 미더워서 생기는 문제가 아니라, 도구가 제 할 일을 하는데도 사람이 그 속도를 따라가지 못해서 생기는 문제다. 그래서 해법도 도구를 덜 쓰는 쪽이 아니라 물어봐야 하는 것과 물어보지 않아도 되는 것을 갈라 두는 쪽에 있다고 우리는 봤다.

    먼저 이 글에서 쓰는 말 몇 개를 풀어 둔다. 승인 프롬프트는 도구가 어떤 동작을 하기 전에 사람에게 허락을 묻는 화면이다. 게이트는 그 허락 없이는 다음으로 넘어가지 못하게 막아 둔 관문을 말한다. 원장은 그런 요청과 결정이 한 줄씩 쌓이는 기록 파일이다. 그리고 이 글에서 통지성행위 승인이라고 부르는 두 낱말은 우리가 우리 시스템을 정리하려고 붙인 이름이지 업계 표준 용어가 아니다.

    미리 한 가지 밝혀 둔다. 이 글은 안전 경계를 다루므로, 독자가 이 문안을 그대로 따라 자기 시스템의 게이트를 열 수 있다. 그래서 이 글은 이렇게 하면 안전하다고 말하지 않는다. 우리가 어디에 선을 그었고 그 이유가 무엇인지를 적을 뿐이다. 자기 환경의 위험은 자기가 판단해야 한다. 다만 이 글에서 단정하는 것이 딱 하나 있고, 그것은 4번 항목에 적었다.

    승인 요청을 두 층으로 가른 구조를 정리한 도식. 층 1은 기계가 올린 상태 통지로 무엇을 알리는 항목이고 우리 원장에서 21건이며 근거를 적고 알림 기록만 닫되 화면에 승인 입력을 보내지는 않는다. 층 2는 되돌릴 수 없는 행위 승인으로 발행 배포 과금 삭제 외부 발송이 여기 속하고 우리 원장에서 12건이며 사람 게이트로 남긴다. 먼저 묻는 것은 이 처리가 기록만 닫는가이며, 실행이나 권한 부여나 차단 해제가 있으면 통지가 아니라서 범위와 대상과 부수효과와 복구 비용을 확인하고 하나라도 확인되지 않으면 사람에게 보낸다. 되돌릴 수 있음은 기준이 아니라는 단서와, 이렇게 하면 안전하다는 뜻이 아니라 우리가 그은 선과 그 이유라는 단서를 함께 적었다. 같은 시각 원장 92건의 유형 전수와 두 층을 갈라도 남는 함정 둘도 담았고, 그 표는 라벨이 있었다는 증거일 뿐 자동 처리 적격의 근거가 아니라고 적었다.
    손상 예시가 아니라 구조를 그렸다. 우리 한 팀의 원장이고 일반 분포로 읽을 것이 아니다. 먼저 묻는 것은 기록만 닫는가이고, 되돌릴 수 있음은 기준이 아니다 — 2026-09-08 22:5x 실측.

    일을 끊는 승인이 어느 쪽인가

    1. 승인 프롬프트가 계속 떠서 일이 끊긴다

    증상. 작업을 시키면 도구가 중간에 멈춰 서서 허락을 묻는다. 하나 답하면 다음 것이 뜬다. 사람이 자리를 비우면 그 자리에서 작업이 통째로 멈춰 있다.

    여기서 흔히 하는 선택 두 가지. 하나는 전부 허용해 두는 것이다. 그러면 빨라진다. 다른 하나는 전부 물어보게 두는 것이다. 그러면 안심이 된다. 우리는 두 쪽을 다 해 봤고, 두 쪽 다 각자의 방식으로 무너졌다.

    우리가 내린 결론. 문제는 허용의 양이 아니라 종류를 안 나눈 것이었다. 승인 요청을 하나의 덩어리로 보면 열거나 닫는 두 선택밖에 없다. 그런데 실제로 올라오는 요청은 성격이 다른 것들이 섞여 있다. 어떤 것은 무엇을 알리는 화면이고, 어떤 것은 무엇을 하겠다고 요구하는 화면이다.

    그 둘을 가르려고 우리가 먼저 던지는 질문은 이것이다. 이 처리가 기록만 닫는가, 아니면 동작 실행·권한 부여·차단 해제도 하는가.

    되돌릴 수 있다는 것은 기준이 되지 못한다. 우리도 처음에는 되돌릴 수 있는가를 기준으로 삼았는데 그것으로는 갈리지 않았다. 되돌릴 수 있는 일도 실제 행위다. 로컬에 커밋을 하나 남기는 것도, 원본이 있어 복구할 수 있는 파일 변경도 행위이고, 캐시를 지우는 일조차 내용과 다시 만드는 비용에 따라 영향이 달라진다. 복구할 수 있다는 이유만으로 요청할 권한이 생기지는 않는다.

    확인 방법. 지금 떠 있는 프롬프트를 보고 이렇게 물어보라. 이 항목을 처리하면 기록 하나가 닫히는 것으로 끝나는가, 아니면 무언가가 실행되거나 권한이 열리거나 막혀 있던 것이 풀리는가. 뒤쪽이 하나라도 있으면 그것은 통지가 아니다. 이 질문의 답이 다음 항목들의 갈림길이 된다.

    2. 요청이 다 같아 보이는데 우리 원장에서는 이미 두 유형으로 갈려 있었다

    이 항목은 우리가 직접 측정한 것이다.

    우리가 측정한 것. 우리 승인 원장을 전수로 셌다. 2026년 9월 8일 22시 5x분 시점에 총 92건이었고, 유형별로는 이렇게 갈려 있었다.

    
    첫 실행 신뢰 관문        24
    부트 판정 불가          22
    통지성 승인            21
    행위 승인             12
    팀 편성 통지            7
    컨텍스트 순환 검증        6
                        ----
    합계                  92
    

    여기서 우리가 놀란 대목. 우리는 두 층이라는 구분을 이 글을 쓰면서 정리한 개념이라고 생각했다. 그런데 원장을 열어 보니 통지성 승인 21건과 행위 승인 12건이 이미 서로 다른 유형으로 갈려 기록되고 있었다. 즉 이 구분은 나중에 붙인 해석이 아니라 운영하면서 이미 갈라 두고 있던 것이었다. 이름을 붙이지 않았을 뿐이다.

    이 표가 말해 주는 것과 말해 주지 않는 것. 이 표는 그 시점 우리 기록에 서로 다른 라벨이 있었다는 것까지만 보여 준다. 그 라벨이 옳게 붙었다거나 통지로 분류된 21건은 자동으로 처리해도 된다는 결론은 여기서 나오지 않는다. 그렇게 읽으면 우리가 붙인 이름으로 우리 설계를 정당화하는 것이 된다. 자동으로 처리해도 되는지는 라벨이 아니라 그 요청 하나하나가 실제로 무엇을 하는지로 판단해야 한다.

    표에 있는 이름을 쉬운 말로 풀면 이렇다. 첫 실행 신뢰 관문은 어떤 폴더에서 도구를 처음 켤 때 이 폴더를 믿을 것인지 묻는 화면이다. 부트 판정 불가는 기동이 끝났는지를 기계가 스스로 판정하지 못해 사람에게 알린 것이다. 팀 편성 통지는 여러 대를 동시에 띄우는 과정에서 일부만 준비됐다는 알림이고, 컨텍스트 순환 검증은 작업 기억을 정리한 뒤 잘 복원됐는지 확인한 기록이다. 통지성 승인과 행위 승인은 이 글이 다루는 두 층이다.

    이 이름들을 나란히 놓고 보면 성격이 갈린다. 앞의 네 가지는 대체로 무엇이 이러이러한 상태다라고 알리는 쪽이고, 마지막 하나는 무엇을 하겠다고 요구하는 쪽이다. 우리가 두 층이라고 부르는 것이 바로 이 갈림이다. 다만 이름이 성격을 정하는 것은 아니므로, 이름이 아니라 그 항목이 요구하는 일이 무엇인지를 보고 판단해야 한다.

    수치에는 시각이 붙어야 수치다. 같은 원장이 그날 이른 시각에는 지금보다 작았다. 원장은 계속 자라기 때문에, 시각 없는 건수는 다음 주에 읽으면 틀린 값이 된다. 그래서 위 표에는 측정 시각을 함께 적었다.

    범위를 밝혀 둔다. 이것은 우리 한 팀의 원장이다. 모든 사용자가 이 분포를 본다는 주장이 아니다. 우리 환경은 여러 대의 에이전트를 동시에 돌리는 구성이라 일반 사용자보다 기동 관련 항목이 많이 쌓인다.

    그리고 우리가 이 표를 만들면서 걸러낸 문장이 하나 있다. 우리 기록에는 승인이 사람을 가장 자주 멈춰 세운 항목이라고 적었다가 발행 전 기계 검사에서 걸러낸 자국이 남아 있다. 실제로 세어 보니 건수가 가장 많은 것은 첫 실행 신뢰 관문 24건이었고 그다음이 부트 판정 불가 22건이었다. 승인 계열은 그 뒤였다. 체감이 컸던 것과 건수가 많은 것은 달랐다.

    기록이 없으면 셀 수 없다. 우리가 위 표를 만들 수 있었던 것은 요청과 결정이 한 줄씩 쌓이는 파일을 두고 있었기 때문이다. 그런 파일이 없으면 승인이 몇 번 떴는지, 그중 어떤 성격이 많았는지를 나중에 확인할 방법이 없고, 남는 것은 사람의 인상뿐이다. 그리고 인상은 실제 분포와 달랐다 — 바로 위 문단이 그 사례다. 도구가 그런 기록을 남기지 않는다면, 적어도 승인 프롬프트가 떴을 때 어떤 종류였는지를 며칠만 손으로 적어 보는 것으로도 갈래가 보인다.

    확인 방법. 자기 도구에도 승인 기록이 남는 곳이 있다면 유형별로 세어 보라. 세어 보기 전의 인상과 세어 본 결과가 다를 수 있다. 그리고 셀 때는 그 수를 언제 셌는지를 같이 적어 두라.

    3. 상태 통지까지 사람에게 보냈더니 기동이 막혔다

    이 항목도 우리가 직접 측정한 것이다.

    증상. 안전하게 하려고 승인이 필요한 항목을 넉넉히 잡아 두면, 사람이 답해 줄 때까지 아무 일도 진행되지 않는다. 특히 도구를 켜는 단계에서 이런 항목이 몰리면 시작 자체가 안 된다.

    우리가 겪은 것. 우리 원장에서 통지성으로 분류되는 항목은 21건이었다. 이런 항목은 기계가 상태를 알리려고 올린 것이다. 부트 판정이 안 된다거나, 첫 실행 관문이 감지됐다거나, 팀 편성이 일부만 끝났다거나, 컨텍스트 순환이 검증됐다는 식이다. 사람이 답을 해야 다음이 진행되는 구조로 두면, 이런 알림 하나하나가 전부 정지선이 된다.

    우리가 바꾼 방식. 통지성 항목은 근거를 적고 자동으로 종결하게 했다. 중요한 것은 종결 자체가 아니라 근거를 적는다는 쪽이다. 무엇을 보고 종결했는지가 원장에 남지 않으면, 나중에 그 판단이 옳았는지 확인할 방법이 없어진다. 조용히 넘어가는 것과 근거를 남기고 넘어가는 것은 다른 일이다.

    근거에 무엇을 적는가. 우리가 남기는 것은 세 가지다. 무엇을 보고 그렇게 판정했는지, 그 시점에 상태가 어땠는지, 그리고 왜 이 항목이 기록만 닫는 쪽이라고 봤는지다. 세 번째가 빠지면 나중에 그 판단을 다시 검토할 수 없다. 종결 자체는 한 줄이면 되지만 근거가 없으면 그 한 줄은 기록이 아니라 흔적일 뿐이다.

    종결한다는 것이 알리지 않는다는 뜻은 아니다. 우리는 통지성 항목의 알림 기록만 자동으로 닫고, 그 기록을 지우지는 않는다. 사람이 나중에 훑어볼 수 있게 남겨 두고, 같은 알림이 반복되면 그 반복 자체가 신호가 된다. 진행을 막지 않는 것과 보이지 않게 하는 것은 다른 일이고, 우리가 없앤 것은 앞쪽이다. 이 구분을 놓치면 통지성 자동화가 그냥 알림 끄기가 된다.

    ★자동 종결이 무엇을 하고 무엇을 하지 않는지 못박아 둔다. 우리 자동 종결은 알림 기록을 닫는 것이다. 그것은 실제 신뢰 화면이나 권한 화면에 승인 입력을 보내지 않고, 막혀 있던 실행을 풀지도 않는다. 이 구분이 중요한 이유는, 어떤 화면이 떴다는 것을 감지하는 일과 그 화면에 응답하는 일이 서로 다른 일이기 때문이다. 감지 기록을 자동으로 닫는 것은 우리가 하는 일이고, 화면에 대신 답하는 것은 우리가 하지 않는 일이다. 이 글의 다른 항목에서 첫 실행 신뢰 화면 이야기가 나오는데, 그 화면에 자동으로 답해도 된다는 뜻으로 읽지 않기 바란다.

    이것이 안전하다는 뜻은 아니다. 우리 환경에서 이 항목들이 기록만 닫는 성격이라고 판단해서 그렇게 그은 것이다. 같은 이름의 알림이라도 자기 환경에서 그 처리가 무언가를 실행하거나 권한을 열거나 차단을 푼다면 이 선은 그대로 쓸 수 없다.

    확인 방법. 자동으로 종결되는 항목이 있다면 그 기록에 근거가 함께 남는지 확인하라. 근거 없이 종결만 되어 있으면, 그것은 자동화가 아니라 그냥 안 보이게 만든 것이다.

    4. 되돌릴 수 없는 행위를 기계가 대신 누르면 정지 경계가 사라진다

    이 항목이 이 글에서 단정하는 유일한 대목이다.

    단정. 되돌릴 수 없는 행위는 사람 게이트로 남겨라. 우리가 그 목록으로 쓰는 것은 다음과 같다.

    
    발행 . 배포 . 과금 . 삭제 . 외부 발송
    

    왜 이것만 단정하는가. 앞의 항목들은 우리 환경에서의 선택이라 자기 환경에서 달라질 수 있다. 그런데 이 목록은 성격이 다르다. 이 행위들은 실행되고 나면 실행 전으로 돌아갈 수 없다. 잘못 눌렸을 때 고칠 기회가 없다는 뜻이고, 그래서 판단을 자동화의 대상으로 두지 않는 것이 우리 설계의 기준선이다. 우리 시스템에서 이 목록은 승인 요청의 대상이 아니라 무조건 멈추고 사람에게 묻는 대상이다.

    기계가 대신 누르면 무엇이 사라지는가. 게이트가 하는 일은 막는 것이 아니라 사람이 개입할 지점을 만드는 것이다. 그 지점을 기계가 통과시키면 게이트는 형태만 남고 기능은 없어진다. 그러면 문제가 생겼을 때 어디서 멈췄어야 하는지를 되짚을 자리도 함께 사라진다.

    정지 경계라는 말을 풀어 두면 이렇다. 자동화된 작업에는 사람이 개입하기로 미리 정해 둔 지점이 몇 군데 있다. 그 지점이 있으면 잘못된 방향으로 가더라도 거기서 한 번 멈춘다. 그 지점을 기계가 통과시키기 시작하면, 남는 것은 사람이 나중에 결과를 발견하는 것뿐이다. 되돌릴 수 있는 일이라면 발견한 뒤에 고치면 되지만, 되돌릴 수 없는 일은 발견해도 고칠 것이 없다. 그래서 우리는 이 목록만큼은 자동화의 대상으로 두지 않는다.

    요청자와 결재자를 나눈다. 우리는 행위 승인을 다룰 때 요청하는 쪽과 결재하는 쪽을 같은 주체로 두지 않는다. 일을 만든 쪽이 그 일의 허락도 스스로 내주면 게이트가 자기 자신을 통과시키는 구조가 되기 때문이다. 다만 시각 두 개로 이 구분을 확인했다고 말할 수는 없다. 시각은 순서와 만료를 보는 참고값이다.

    왜 그 구분이 중요한가. 일을 만든 쪽이 그 일의 허락도 스스로 내주면, 형식적으로는 승인 절차가 있지만 실질적으로는 아무도 검토하지 않은 것이 된다. 그런데 기록만 보면 승인을 거친 것처럼 보이기 때문에 나중에 이 상태를 알아채기 어렵다. 그래서 확인해야 하는 것은 시각이 아니라 세 가지다. 요청과 결정이 서로 연결돼 있는가, 승인한 사람이 누구이고 그 사람에게 그 권한이 있는가, 그리고 승인한 대상과 범위가 실제 실행된 것과 같은가. 같은 주체가 시간을 두고 요청하고 스스로 승인할 수도 있고, 서로 다른 주체가 기록 정밀도 안에서 같은 시각에 남을 수도 있다. 그래서 시각만으로는 갈리지 않는다. 이 셋을 확인할 수 없으면 통과라고 하지 말고 분리 여부 미확인으로 남겨라.

    확인 방법. 자기 설정에서 허용 목록을 훑어보고, 위 다섯 가지에 해당하는 동작이 자동 허용에 들어가 있는지 본다. 특히 명령 한 줄로 외부에 무언가를 내보내거나 지우는 동작이 포함돼 있는지를 본다. 들어가 있다면 그것이 의도한 것인지 다시 판단하기를 권한다.

    5. 무엇을 말하는 화면과 무엇을 요구하는 화면을 감시 장치가 구별하지 못했다

    이 항목도 우리가 직접 측정한 것이다. 두 층을 갈라 놓아도 남는 함정이다.

    증상. 승인 프롬프트를 자동으로 알아채는 장치를 두면, 그 장치가 가짜 프롬프트를 잡는 일이 생긴다.

    우리가 겪은 것. 우리 감시 장치는 화면에 나타난 문면을 보고 승인 프롬프트를 탐지한다. 그런데 그 화면에 승인 프롬프트를 설명하는 우리 문장이 떠 있으면, 장치는 그것도 프롬프트로 잡았다. 즉 보고서가 경보를 만들었다. 승인에 대해 쓴 글이 승인 요청으로 감지된 것이다.

    초보용으로 옮기면 이렇다. 문면으로만 판별하는 방식은 무엇을 말하는 화면무엇을 요구하는 화면을 구별하지 못한다. 사람에게는 너무 당연한 차이인데, 글자만 보는 쪽에서는 같은 글자다.

    독자에게도 같은 형태가 온다. 승인이나 권한에 대해 문서를 쓰거나, 화면을 캡처해 두거나, 오류 메시지를 그대로 붙여 놓은 파일을 열어 두면, 화면 글자를 보는 장치에게는 그것이 실제 요청과 같은 모양이다. 설명하려고 만든 것이 감지 대상이 되는 것이다.

    오류의 방향도 봐야 한다. 우리 경우에는 실제로 존재하지 않는 요청이 감지된 쪽이었다. 이 방향이 무해하다고는 말할 수 없다. 탐지 뒤에 자동 응답이 연결돼 있으면 그 응답이 무엇을 대상으로 무슨 일을 하는지에 따라 결과가 달라지기 때문이다. 기록을 남긴다는 것만으로 이 공백이 메워지지도 않는다. 반대 방향, 즉 실제 요청이 있는데 감지되지 않는 쪽도 있는데, 우리는 그 반대 방향을 세어 본 적이 없다. 그러니 이 항목에서 우리가 말할 수 있는 것은 한 방향의 오탐을 봤다는 것까지다.

    이것이 왜 두 층 이야기와 이어지는가. 두 층을 잘 갈라 놓아도, 그 갈라진 층에 항목을 넣어 주는 장치가 잘못 넣으면 분류는 소용이 없다. 우리 경우에는 존재하지 않는 요청이 통지성 쪽으로 들어왔다. 반대 방향으로 틀렸다면 더 곤란했을 것이다.

    확인 방법. 자동 감지 장치가 있다면 그 장치가 무엇을 근거로 판정하는지 확인하라. 화면 글자를 근거로 한다면 글자가 같기만 하면 걸린다는 뜻이다. 감지 기록을 훑어 실제 요청이 아닌 항목이 섞여 있는지 한 번 세어 보기를 권한다.

    6. 승인 창이 닫힌 뒤 도착한 결재를 쓸 수 없었다

    이 항목도 우리가 직접 측정한 것이다.

    증상. 승인은 났는데 일이 진행되지 않는다. 기록에는 요청이 남아 있고 결재도 있었는데 결과가 없다.

    우리가 겪은 것. 승인 요청에는 대기 창이 있다. 정해진 시간 안에 답이 오지 않으면 창이 닫힌다. 그런데 창이 닫힌 뒤에 결재가 도착한 경우가 있었고, 그 결재를 소비할 경로가 없었다. 그래서 그 항목들은 미결 상태로 남았다. 우리 기록에서 그렇게 남은 항목이 4건이다.

    초보용으로 옮기면 이렇다. 승인에는 시간 축이 있다. 허락할 것인지만 설계하고 언제까지를 설계하지 않으면, 늦게 도착한 허락이 갈 곳을 잃는다. 그리고 이 상태는 거절보다 알아채기 어렵다. 거절은 결과가 분명한데, 미결은 아무 일도 일어나지 않은 것과 화면에서 똑같아 보인다.

    느린 것과 멈춘 것을 가르는 법. 화면만 보면 둘이 같아 보인다. 우리가 쓰는 구분은 기록 쪽이다. 요청은 남아 있는데 그에 대응하는 결정 줄이 없고 대기 시간도 이미 지났다면, 그것은 느린 것이 아니라 멈춘 것이다. 이 판별이 가능하려면 요청과 결정이 각각 시각과 함께 남아 있어야 한다.

    그래서 우리가 시간 축에서 정해 두는 것. 창을 얼마나 열어 둘지, 창이 닫혔을 때 그 요청을 어떤 상태로 남길지, 그리고 닫힌 뒤에 도착한 답을 어떻게 처리할지 세 가지다. 우리는 앞의 두 가지는 정해 두고 있었고 세 번째를 정해 두지 않았다. 그 빈칸이 위의 미결 항목으로 나타났다. 그 빈칸을 어떻게 메울지는 아직 우리도 정하지 못했고, 정하지 못했다는 사실을 그대로 적어 둔다.

    확인 방법. 승인 기록에서 대기 시간 초과로 끝난 항목이 있는지 세어 보라. 그리고 그 항목들이 나중에 어떻게 처리됐는지 따라가 보라. 따라갈 경로가 없다면 그것이 이 문제다.

    7. 우리가 그은 경계와 그 이유

    여기까지가 우리가 겪은 것이고, 이 항목은 그것을 우리가 어떻게 정리했는지다. 다시 밝혀 두지만 이것이 안전한 설정이라는 뜻이 아니다. 우리가 어디에 선을 그었고 왜 거기에 그었는지를 적는 것이다. 자기 환경의 위험은 자기 것이고, 이 선을 그대로 옮겨 쓰는 것은 권하지 않는다.

    우리가 쓰는 판별 질문. 요청 하나를 놓고 이 처리가 기록만 닫는지를 먼저 묻는다. 답이 갈리는 지점에서 처리도 갈린다.

    
    기록만 닫는다              ->  근거를 적고 종결할 수 있다
    실행 . 권한 부여 . 차단 해제  ->  통지가 아니다
      기존 승인 범위 안인가
      대상은 무엇인가
      부수효과가 있는가
      복구 비용은 얼마인가
      넷 중 하나라도 확인 불가  ->  사람에게 보낸다
    발행 . 배포 . 과금 . 삭제 . 외부 발송  ->  사람 게이트
    

    기록만 닫는 쪽에 우리가 넣은 것. 기계가 올린 상태 통지다. 부트 판정 관련, 첫 실행 관문 감지, 컨텍스트 순환 검증, 팀 편성 통지 같은 항목이다. 이유는 이 항목들이 무언가를 하겠다고 요구하는 것이 아니라 무언가를 알리는 것이기 때문이다. 다만 종결할 때 근거를 함께 적는다.

    사람 게이트로 남긴 쪽. 발행, 배포, 과금, 삭제, 외부 발송이다. 이유는 4번 항목에 적은 그대로다. 실행 후에 실행 전으로 돌아갈 수 없기 때문이다. 그리고 그 목록 밖이라도 상태를 바꾸는 처리는 통지로 분류하지 않는다. 그때는 기존에 승인된 범위 안인지, 대상이 무엇인지, 부수효과가 있는지, 복구 비용이 얼마인지를 확인하고, 넷 중 하나라도 확인되지 않으면 사람에게 보낸다.

    경계에 걸치는 것은 사람에게 보낸다. 애매할 때 어느 쪽으로 기울일지를 미리 정해 두지 않으면 그때그때 편한 쪽으로 기울게 된다. 우리는 판단이 서지 않으면 사람에게 묻는 쪽으로 정했다. 그 선택의 대가는 속도이고, 우리는 그 대가를 지불하기로 한 것이다. 다르게 정할 수도 있고, 그것은 각자의 판단이다.

    판별 질문이 잘 안 통하는 자리도 있다. 기록만 닫는 것처럼 보이는데 실제로는 무언가가 함께 실행되는 경우다. 예를 들어 알림 하나를 닫는 처리가 그 김에 대기 중이던 작업을 다시 돌리게 되어 있으면, 그것은 기록을 닫은 것이 아니라 실행을 시킨 것이다. 우리는 이런 자리에서 처리가 실제로 무엇을 건드리는지 확인하고, 확인되지 않으면 사람에게 보낸다. 이것도 우리 선택이고 더 나은 방법이 있을 수 있다.

    우리가 하지 않은 것도 적어 둔다. 우리는 자동 승인의 비율을 얼마로 두는 것이 좋은지 측정한 적이 없다. 어떤 설정이 얼마나 위험한지도 등급으로 재 본 적이 없다. 그래서 이 글에는 몇 퍼센트를 열라거나 어떤 설정이 더 위험하다는 이야기가 없다. 세어 보지 않은 것은 쓰지 않는 것이 우리 규칙이다.

    처음 정할 때 우리가 쓴 순서. 우리는 허용 목록을 먼저 짜지 않았다. 며칠 동안 올라오는 요청을 그냥 받아 보면서 어떤 것들이 실제로 오는지를 봤고, 그다음에 각 항목에 그것이 기록만 닫는 처리인지를 물었고, 마지막에 그 답에 따라 목록을 만들었다. 목록을 먼저 만들면 실제로 오지도 않는 항목을 상상해서 넣게 되고, 정작 자주 오는 항목은 빠진다. 이 순서도 우리 방식이고 더 나은 순서가 있을 수 있다.

    확인 방법. 지금 자기 설정에서 자동 허용된 항목을 한 줄씩 읽으면서 이것이 기록만 닫는 처리인지를 묻는다. 무언가가 실행되거나 권한이 열리거나 막혀 있던 것이 풀리는 항목이 자동 허용에 들어가 있으면, 그 자리가 이 글이 말하는 지점이다.

    정리 — 확인할 것 다섯 가지

    첫째, 승인 요청을 하나의 덩어리로 보지 않는다. 전부 열거나 전부 닫는 두 선택만 남는 이유가 그것이다. 무엇을 알리는 항목과 무엇을 하겠다고 요구하는 항목은 다르게 다룰 수 있다.

    둘째, 가르는 질문은 이 처리가 기록만 닫는가다. 되돌릴 수 있는가는 기준이 되지 못한다 — 되돌릴 수 있는 일도 실제 행위이기 때문이다. 무언가가 실행되거나 권한이 열리거나 차단이 풀리면 통지로 분류하지 않고, 기존 승인 범위·대상·부수효과·복구 비용을 확인한다. 확인되지 않으면 사람에게 보낸다.

    셋째, 되돌릴 수 없는 행위는 사람 게이트로 남겨라. 발행, 배포, 과금, 삭제, 외부 발송이 우리가 쓰는 목록이다. 이것이 이 글에서 단정하는 유일한 항목이고, 근거는 실행 후에 되돌릴 수 없다는 성질 하나다.

    넷째, 자동 감지 장치는 무엇을 근거로 판정하는지 확인한다. 화면 글자로 판정하면 무엇을 말하는 화면과 무엇을 요구하는 화면을 구별하지 못한다. 우리 경우에는 승인을 설명한 문장이 승인 요청으로 잡혔다.

    다섯째, 승인에는 시간 축이 있다. 허락 여부만 설계하고 언제까지를 설계하지 않으면 늦게 도착한 결재가 갈 곳을 잃는다. 그 상태는 아무 일도 없는 것과 화면에서 똑같아 보이므로 기록을 세어 봐야 보인다.

    마지막으로 하나만 덧붙인다. 이 글은 어떤 설정이 안전하다고 말하지 않았다. 우리가 그은 선과 그 이유를 적었을 뿐이고, 그 선도 우리 환경에서 나온 것이다. 다만 되돌릴 수 없는 행위를 사람 손에 남겨 두라는 것 하나는 환경과 무관하게 권한다. 되돌릴 수 없다는 것은 설정의 문제가 아니라 그 행위의 성질이기 때문이다.

    원장에서 센 수와 그 한계

    우리가 직접 측정한 것 (2026-09-08 22:5x 시점 · 우리 한 팀의 기록)

    • 승인 원장 전수 92건과 그 유형 분포. 첫 실행 신뢰 관문 24, 부트 판정 불가 22, 통지성 승인 21, 행위 승인 12, 팀 편성 통지 7, 컨텍스트 순환 검증 6
    • 통지성 승인과 행위 승인이 이미 서로 다른 유형으로 갈려 기록되고 있었다는 것. 두 층 구분이 사후 해석이 아니라 운영 중에 이미 갈려 있던 것이라는 근거다
    • 건수가 가장 많은 항목이 승인 계열이 아니라 첫 실행 신뢰 관문이었다는 것. 우리 기록에는 그 반대로 적었다가 발행 전 기계 검사에서 걸러낸 자국이 남아 있다
    • 감시 장치가 승인 프롬프트를 설명하는 우리 문장을 프롬프트로 탐지한 것. 문면 기반 판별의 한계 사례다
    • 승인 대기 창이 닫힌 뒤 도착한 결재를 소비할 경로가 없어 미결로 남은 항목 4건
    • 행위 승인 기록에 요청과 결정이 각각 시각과 함께 남는다는 것. 다만 그 두 시각으로 요청자와 결재자가 분리됐다고 판정할 수는 없다 — 시각은 순서와 만료의 참고값이고, 분리 여부는 요청과 결정의 연결·승인자와 그 권한·승인 대상과 범위의 일치로 확인해야 한다

    이 글에서 우리 환경의 선택으로만 쓴 것 (일반 권고가 아니다)

    • 통지성 항목을 근거와 함께 자동 종결하는 처리. 우리 환경에서 그 항목들이 기록만 닫는 성격이라고 판단해서 그은 선이다. 그 종결은 알림 기록을 닫을 뿐이고 화면에 승인 입력을 보내지 않는다
    • 애매한 항목을 사람에게 보내는 규칙. 처리가 무엇을 건드리는지 확인되지 않으면 통지로 분류하지 않는다는 뜻이고, 속도를 대가로 지불하기로 한 우리 선택이다

    우리가 측정하지 않아 쓰지 않은 것

    • 어떤 설정이 얼마나 위험한지의 등급. 위험의 크기를 재 본 적이 없다
    • 자동 승인 비율의 적정값. 비율을 두고 실험해 본 적이 없다
    • 이 분포가 일반 사용자에게도 나타나는지 여부. 우리 한 팀의 원장만 셌다
    • 두 층을 가르지 않으면 반드시 무너지는지 여부. 우리는 두 실패 형태를 각각 관측했을 뿐이고 그것이 모든 설계에서 필연이라는 것은 측정하지 않았다
  • 터미널에 한글이 물음표로 나올 때 — 인코딩 문제 한 번에 잡기

    터미널에 한글이 물음표로 나올 때 — 인코딩 문제 한 번에 잡기

    터미널을 켜고 명령을 하나 실행했는데, 한글이 있어야 할 자리에 물음표만 줄지어 나온다. 처음 보면 글자가 부서졌다고 생각하게 된다. 그런데 물음표가 보일 때 볼 곳은 글자가 아니라 통로다. 그 글자를 담을 수 없는 통로로 글자를 보낸 것이 가능한 원인 중 하나다. 이번 사례에서는 전달 경로를 바꾸자 결과가 달라졌다. 그리고 다 고쳤는지 확인하는 방법은 눈이 아니라 대조다. 층별로 개수를 세는 검사는 뒤에서 자세히 설명하는데, 그것은 경보이지 판정이 아니다.

    이 글은 우리가 여러 대의 에이전트를 돌려 글을 만드는 작업에서 실제로 겪은 세 가지 기록에 기대고 있다. 하나는 한글이 전멸한 경로와 보존된 경로를 같은 자리에서 갈라 본 관측이고, 하나는 원인을 적어 둔 다음에도 같은 결함을 다시 밟은 기록이고, 하나는 글자 하나 때문에 도구가 통째로 멈춘 사고다. 각 항목마다 우리가 직접 측정한 것일반적인 설명을 문장 단위로 갈라 표시했다. 우리 관측은 우리가 본 환경의 사실이고, 모든 컴퓨터에서 똑같이 일어난다는 주장이 아니다.

    먼저 말을 몇 개 풀어 둔다. 인코딩은 글자를 숫자로 바꾸는 약속이다. 컴퓨터는 글자를 모르고 숫자만 다루므로, 어떤 숫자가 어떤 글자인지 약속을 정해 두고 그 약속으로 저장하고 읽는다. UTF-8은 세계의 글자를 폭넓게 담는 약속이고, cp949는 한국어 윈도우에서 오래 쓰인 약속인데 담을 수 있는 글자의 범위가 UTF-8보다 좁다. 파이프는 한 프로그램의 출력을 다른 프로그램의 입력으로 곧바로 잇는 통로를 말한다. 글리프는 화면이나 그림에 실제로 그려진 글자 모양을 말한다.

    같은 한글을 두 경로로 보냈을 때의 이번 관측 결과를 정리한 도식. 명령에 한글을 박아 파이프로 넘긴 경로에서는 머리글의 한글이 전멸했고, UTF-8 파일에 두고 프로그램이 직접 읽은 교정본에서는 한글이 보존됐다. 관측 1회이며 경로 간 우열을 측정한 것이 아니라는 한정을 함께 적었다. 보이지 않는 손상인 제어문자 항목과, 같은 함정을 세 번 밟은 경위, 그리고 층별로 세는 다섯 축을 담았다. 그 다섯 축은 판정이 아니라 경보라는 것, 0이 손상 부재의 증명이 아니라는 것, 문자가 있어도 손상 확정은 아니라는 것, 정상적인 의문부호를 손상으로 센 오탐이 우리 기록에 있다는 것, 판정은 언제나 기대 원문과의 대조로 내린다는 것을 그림 안에 함께 적었다.
    손상된 글자를 그리지 않고 통로를 그렸다. 이번 관측 1회의 결과이고 경로 간 우열을 측정한 것이 아니다. 다섯 축은 전부 경보이고 판정은 대조다 — 2026-09-08 우리 실측.

    물음표가 나온 자리

    1. 한글이 있어야 할 자리에 물음표만 있다

    증상. 명령을 실행했는데 결과에 한글이 보이지 않고 물음표만 나온다. 줄의 구조는 멀쩡하고 한글이 있던 자리만 물음표다. 우리가 본 사례의 모양은 이렇게 된다.

    
    === ??? ????? ===
    > ?? ?? ???
    

    위 블록은 어떤 특정 기록을 그대로 옮긴 것이 아니라 모양을 보인 것이다. 우리가 관측한 사례에서는 한글이 물음표로 바뀐 자리에 한글이 남지 않았고, 그래서 그 줄에 한글이 없었다. 다만 모든 손상이 이 모양이라는 뜻은 아니다. 일부 글자만 바뀌어 한글과 물음표가 섞여 남는 경우가 있을 수 있고, 우리는 그런 부분 손상을 측정하지 않았다.

    왜 그런가 (일반 설명). 글자를 저장하거나 내보낼 때는 어떤 약속으로 담을지 정해야 한다. 그 약속이 담을 수 없는 글자를 만나면 무슨 일이 일어나는지는 오류 처리 방식이 결정한다. 오류 처리 방식에 따라 중단되거나, 생략되거나, 다른 글자로 대체될 수 있고, 물음표 대체는 그중 하나다. 파이썬을 예로 들면 기본값은 중단이어서 대체하지 않고 오류를 내며 멈추는데, 이 글 4번 항목의 사고가 바로 그 경우다. 처리 방식의 종류는 파이썬 공식 문서의 오류 처리 방식(error handlers) 절에 정리돼 있다. 이것은 문서로 확인한 일반 설명이고, 우리가 직접 측정한 것은 아래 2번과 3번의 관측이다.

    여기서 두 가지가 따라온다. 첫째, 물음표는 되돌릴 수 없다. 원래 글자가 무엇이었는지의 정보가 그 자리에서 사라졌기 때문이다. 물음표를 한글로 되돌리는 도구를 찾는 것은 방향이 틀렸고, 원본을 다시 만들어야 한다. 둘째, 볼 곳은 글자가 아니라 통로다. 같은 글자를 어느 통로로 보내느냐에 따라 결과가 갈리는 것을 우리가 관측했다. 다만 우리는 그 통로의 어떤 설정이 원인이었는지를 코드로 짚어 확정하지는 못했으니, 통로 하나로 원인을 단정하지는 않는다.

    확인 방법. 영문과 숫자는 멀쩡한데 한글만 물음표라면 이 축을 의심한다. 좁은 약속도 영문과 숫자는 담을 수 있어서, 문제가 생기는 자리는 그 약속에 없는 글자 쪽이기 때문이다. 이것도 일반적인 설명이고, 의심의 근거이지 확정은 아니다. 이 축인지 가리는 더 나은 방법은 같은 글자를 서로 다른 통로로 보내 결과를 비교하는 것이고, 우리가 실제로 그렇게 갈라 본 기록이 다음 두 항목이다.

    2. 명령에 한글을 직접 박아 넘겼더니 한글이 전멸했다

    이 항목은 우리가 직접 측정한 것이다.

    증상. 프로그램에 한글이 든 문장을 넘겨 처리하게 했는데, 결과물의 한글이 전부 물음표로 나온다.

    우리가 측정한 것. 우리는 PowerShell의 here-string 안에 한글을 적어 두고, 그것을 파이프로 python3 -에 넘겨 처리하게 했다. 그렇게 만든 보고서의 머리글에서 한글이 전부 물음표가 됐다. 넘긴 통로는 명령줄과 표준 입력이었다.

    범위를 밝혀 둔다. 이것은 우리가 관측한 그 세션의 사실이다. PowerShell이 한글을 못 다룬다는 뜻이 아니고, 모든 환경에서 같은 결과가 난다는 주장도 아니다. 우리 환경의 어떤 조합에서 이 결과가 나왔다는 것까지가 우리가 아는 범위다. 원인이 되는 설정을 우리가 코드로 짚어 확인한 것은 아니다.

    초보용으로 옮기면 이렇다. 이 세션에서는 파이프로 전달한 머리글이 손상됐고, UTF-8 파일을 프로그램이 직접 읽은 교정본은 보존됐다. 그래서 우리는 한글을 파일에 두고 프로그램이 UTF-8로 읽게 하는 방식을 쓴다. 어느 경로가 더 잘 깨지는지를 우리가 잰 것은 아니다. 두 경로를 각각 한 번씩 관측했을 뿐이고, 그것으로 순위를 매기면 방금 밝힌 범위 제한을 스스로 깨는 셈이 된다.

    처방. 한글을 명령에 박아 넣지 말고 파일에 둔다. 그리고 프로그램이 그 파일을 UTF-8로 읽게 한다. 우리가 실제로 그렇게 바꿔 결과가 달라진 기록이 다음 항목이다.

    확인 방법. 결과물에서 한글이 사라졌는지 눈으로 훑는 것만으로는 부족하니 물음표의 개수를 함께 센다. 다만 개수는 경보일 뿐이고 그것만으로 손상 여부를 확정할 수 없다. 왜 그런지는 7번 항목에서 다룬다.

    3. 같은 한글을 UTF-8 파일에 두고 읽게 하니 그대로 보존됐다

    이 항목도 우리가 직접 측정한 것이다. 2번과 같은 세션에서 반대쪽 경로를 갈라 본 것이라 두 항목을 나란히 놓고 읽어야 한다.

    우리가 측정한 것. 같은 세션에서, 프로그램이 encoding="utf-8"로 파일을 읽어 만든 본문은 한글이 온전했다. 이어서 머리글도 UTF-8 파일로 만들어 두고 프로그램이 그 파일을 직접 읽어 인자 배열로 전달하게 바꿨더니, 교정본에서 한글이 보존됐다.

    정리하면 같은 세션에서 같은 성격의 글자가 두 통로로 갈라졌고 결과가 반대로 나왔다. 한쪽은 전멸이고 한쪽은 보존이다. 이 대비가 이 글에서 가장 값진 대목이라고 본다. 원인을 확정하지 못한 상태에서도 무엇을 바꾸면 되는지가 나오기 때문이다.

    처방. 한글은 파일에 두고, 프로그램이 그 파일을 UTF-8로 읽게 한다. 파이썬이라면 파일을 열 때 약속을 명시한다.

    
    open(path, encoding="utf-8")
    

    약속을 적지 않으면 프로그램은 그 컴퓨터의 기본 약속을 쓴다. 그 기본값이 무엇인지는 환경에 따라 다르고, 그래서 어제 되던 것이 다른 컴퓨터에서 안 되는 일이 생긴다. 약속을 문장 안에 적어 두면 적어도 그 컴퓨터의 기본값에 좌우되는 부분은 사라진다.

    이 글도 그 규칙으로 만들었다. 위 도식의 문구는 스크립트 안에 한글로 적어 넣지 않았다. 문구를 UTF-8 파일에 따로 두고, 그림을 그리는 프로그램이 그 파일을 UTF-8로 읽어 그렸다. 이 글의 2번과 6번이 바로 그 경로에서 나온 사고이므로, 이 글을 만드는 경로가 같은 사고를 밟으면 안 된다고 봤다.

    확인 방법. 같은 문장을 두 경로로 보내 결과를 비교한다. 한쪽만 깨지면 깨진 쪽이 통로 문제다. 양쪽 다 깨지면 더 앞단인 원본 파일도 함께 확인한다. 두 경로가 공유하는 뒷단에서 같은 원인이 작용했을 수도 있으니 원본 확인은 순서의 문제이지 결론이 아니다. 이 비교가 유용한 이유는 기대하는 원문을 우리가 알고 있기 때문이다. 원문을 아는 상태의 대조라야 판정이 되고, 이 점은 7번 항목에서 다시 다룬다.

    4. 물음표도 없이 도구가 그냥 멈췄다

    이 항목도 우리가 직접 측정한 것이다.

    증상. 물음표가 나오는 것이 아니라 프로그램이 오류를 내고 중단된다. 화면에는 이런 줄이 남는다.

    
    UnicodeEncodeError: cp949 codec can't encode character u2014
    

    우리가 측정한 것. 한국어 윈도우 콘솔에서 우리 진단 도구가 위 오류로 중단됐고, 원인이 된 글자는 줄표(em dash) 한 글자였다. 이 사건은 이 시리즈의 다른 글에서 이미 1차 자료로 다뤘으니 상세한 경위와 처방은 그쪽을 보기 바란다 — Claude Code를 윈도우에 설치한 뒤 처음 실행할 때 나는 오류들.

    초보용으로 옮기면 이렇다. 물음표만 문제가 아니다. 아예 죽는 경우도 있다. 그리고 그때는 프로그램이 고장났다고 읽히기 쉽다. 물음표는 결과물이 나오기는 하니 글자 문제로 보이는데, 중단은 아무 결과도 나오지 않기 때문이다. 둘은 같은 축의 두 얼굴이다. 담을 수 없는 글자를 만났을 때 중단할지, 생략할지, 다른 글자로 대체할지는 그 프로그램의 오류 처리 방식이 정한다.

    확인 방법. 오류 메시지에 codec이나 encode라는 낱말이 있으면 이 축을 먼저 의심한다. 낱말만으로 원인이 정해지는 것은 아니니, 메시지 전문과 어느 단계에서 멈췄는지를 함께 본다. 이 축이 맞다면 프로그램의 기능이 고장난 것이 아니라 글자를 내보내는 자리에서 막힌 것이므로, 볼 곳은 기능이 아니라 내보내는 통로다.

    5. 파일은 정상으로 보이는데 프로그램만 이상하게 동작했다

    이 항목도 우리가 직접 측정한 것이다. 그리고 이 축은 눈으로 알아차리기 어렵다.

    증상. 파일을 열어 눈으로 보면 아무 문제가 없다. 물음표도 없고 깨진 글자도 없다. 그런데 그 파일을 쓰는 프로그램만 이상하게 동작한다.

    제어문자가 무엇인가. 제어문자는 화면에 그려질 모양이 없는 글자다. 줄바꿈이나 탭처럼 자리를 옮기는 데 쓰이는 것도 있고, 옛 장치를 조작하기 위해 만들어진 것도 있다. 모양이 없으니 파일을 눈으로 봐도 있는지 없는지 알 수 없다. 이것이 일반 설명이다.

    우리가 측정한 것. 우리 운영 기록을 쓰는 중에, 문자열이 프로그램의 손을 거치는 과정에서 0x080x01 두 제어문자가 그 파일에 박혔다. 우리 기록에는 그 파일이 21,665바이트에서 21,667바이트로 바뀐 것이 남아 있다. 우리가 그것을 발견해 고쳤다.

    일반적인 문법 사실은 따로 적는다. 문자열 안의 역슬래시로 시작하는 조합을 프로그램이 하나의 제어문자로 바꿔 해석하는 문법이 여럿 있다. 이것은 널리 알려진 일반 사실이고, 위 문단의 관측과는 다른 층의 이야기다. 우리가 코드로 확인한 것은 위 문단의 관측이며, 어떤 문법이 그 변환을 일으켰는지를 우리가 코드로 짚어 확인한 것은 아니다.

    왜 눈으로 잡기 어려운가. 이유는 짧다. 제어문자는 눈으로 알아차리기 어렵다. 물음표는 보이지만 제어문자는 보이지 않는다. 물음표는 화면에 나오는 순간 누구나 이상하다고 느낀다. 제어문자는 파일을 열어 봐도 정상으로 보이므로, 사람은 파일이 아니라 프로그램을 의심하며 엉뚱한 곳을 고치게 된다.

    독자에게도 같은 위험이 있다. 웹 문서에서 복사해 붙여넣기를 하거나, 여러 도구를 거쳐 파일을 만들거나, 터미널에서 특수한 조합을 그대로 옮겨 쓰는 과정에서 모양 없는 글자가 섞여 들어올 수 있다.

    처방. 파일이 정상으로 보이는데 프로그램만 이상하게 동작하면 이 축을 의심하라. 그리고 눈으로 찾지 말고 바이트를 세라. 모양이 없는 글자는 눈으로 못 찾지만, 개수로는 정확히 잡힌다.

    확인 방법. 파일에서 줄바꿈·탭·복귀를 뺀 제어문자의 개수를 센다. 다만 이 축도 다른 축과 같은 성격이다. 예상하지 않은 문자가 있으면 경보이고, 판정은 원문·파일 형식과 대조해서 내린다. 제어문자가 원문에 원래 들어 있을 수도 있기 때문이다. 그리고 0은 손상이 없다는 뜻이 아니라 검사한 범위에서 나오지 않았다는 뜻이다. 우리는 이 사고 뒤에 그 계수를 검사에 정식으로 추가했다. 그 이유는 7번 항목에 적었다.

    6. 원인을 적어 뒀는데 다음 작업에서 같은 결함을 밟았다

    이 항목도 우리가 직접 측정한 것이다. 다만 여기에는 우리가 기록으로 아는 것우리가 지금 눈으로 본 것이 섞여 있어서, 그 둘을 갈라 적는다. 이 글의 주제가 바로 그 구분이기도 하다.

    우리 기록에 남아 있는 것. 앞선 글을 만들 때 쓴 스크립트를 그 다음 글에서 복사해 두 줄만 고쳐 썼다. 그런데 그 고치는 작업이 이 글 2번 항목의 경로를 그대로 지났고, 그 결과 소스 파일의 두 줄에 물음표가 연달아 박혔다. 그 아래로는 아무것도 막지 않았다. 물음표는 로컬 문서로, 원격에 올라간 글로, 그 글에 붙은 이미지의 대체 텍스트와 캡션으로 충실히 전파됐다.

    우리가 지금 눈으로 본 것. 이 글을 쓰면서 그 소스 파일을 다시 열어 그 두 줄을 확인했다. 지금 그 줄에는 물음표가 없다. 이미 고친 뒤의 파일을 보고 있는 것으로 읽는 것이 자연스럽지만, 사고 당시의 파일 상태를 우리가 이 자리에서 눈으로 확인한 것은 아니다. 그러니 위 문단은 기록에 근거한 서술이고, 이 문단이 우리가 지금 확인한 것이다. 둘을 섞어 적으면 확인하지 않은 것을 확인한 것처럼 쓰게 된다.

    축을 가리기 위해 반대쪽도 측정했다. 당시 이 문제를 그림 렌더나 전각 문자 쪽 문제로 보는 귀속이 있었다. 그래서 반대쪽을 쟀다. 같은 건의 이미지는 정상 렌더였다. 한글이 온전했고 문장부호도 공통 기준선에 맞게 그려져 있었다. 그림에 그려진 조각들의 기록도 정상이었고, 다른 글의 대체 텍스트와 캡션도 온전한 한글이었다. 즉 렌더에서 깨진 것은 하나도 없었다. 그 귀속은 이 측정으로 반증됐다. 깨진 자리는 그림을 그리는 단계가 아니라 소스에 글자를 넣는 단계였다.

    여기서 얻은 교훈이 이 글의 핵심 문장이다. 원인을 기록하는 것과 다음 작업이 그것을 안 밟는 것 사이에는 한 단이 더 있다. 그 단을 메우는 것은 경고문이 아니라 기계가 세는 검사다. 사람은 자기가 적어 둔 경고를 읽고도 그 위를 걸어간다. 읽는 것과 막는 것은 다른 일이다.

    같은 함정을 우리는 세 번 밟았다. 우리 기록에 남은 형태를 순서대로 적으면 이렇다. 첫째, 원인을 적어 둔 다음 작업에서 다시 밟았다. 이 항목의 사고다. 둘째, 그 함정을 경고하는 줄을 쓰다가 같은 경로로 5번 항목의 제어문자를 파일에 박았다. 셋째, 그 규칙을 적은 문장 자체에서 다시 밟았다. 이 글의 사실을 정리해 둔 우리 내부 문서에는 대체 문자를 0개로 유지하라는 규칙이 적혀 있는데, 그 규칙을 적은 문장 안에 대체 문자 한 글자가 박혀 있었다. 이 글을 쓰기 위해 그 문서를 기계로 검사하다가 찾아 고쳤다.

    뒤로 갈수록 강한 형태다. 마지막 것은 규칙이 자기 자신에게 적용되지 않았다는 뜻이므로, 경고문은 자기 자신을 지키지 못한다는 것을 이보다 짧게 보일 방법이 없다. 셋 모두 같은 통로에서 나왔다. 글자를 명령이나 소스에 직접 박아 넣는 경로다.

    확인 방법. 경고를 적었다고 해서 그 결함이 막힌 것이 아니다. 막혔는지 확인하려면 산출물을 기계로 세어야 한다. 다만 그 세는 검사에도 한계가 있고, 그 한계를 모르면 이번에는 검사를 경고문처럼 믿게 된다. 그래서 다음 항목은 검사와 그 한계를 함께 적는다.

    7. 층별로 세는 검사는 경보다 — 0은 손상이 없다는 증명이 아니다

    여기서부터가 이 글이 독자에게 주려는 도구다. 우리가 실제로 쓰는 검사이고, 요점은 층을 나눠 각각 센다는 것이다. 한 층만 보면 다른 층에 남은 손상을 놓친다. 그리고 이 절의 후반부는 그 검사가 무엇을 못 잡는지를 적는다. 그 부분이 검사 자체보다 중요하다.

    우리 검사가 실제로 낸 출력은 이렇다. 먼저 네 층이 모두 통과한 경우다.

    
    === 성공기준 4-b: 물음표 0 (네 층 각각) ===
      [PASS] ① 본문(원격 raw) 물음표 0개
      [PASS] ② alt 물음표 0개
      [PASS] ③ caption 물음표 0개
      [PASS] ④ PNG 안에 그려진 글자 물음표 0개
    

    본문만 보고 끝내면 안 되는 이유가 여기 있다. 이미지의 대체 텍스트와 캡션은 본문과 다른 자리에 저장되고, 그림 안에 그려진 글자는 아예 이미지 안에 들어 있다. 6번 항목에서 물음표가 본문과 이미지의 대체 텍스트, 캡션 세 곳으로 나란히 전파된 것을 우리가 기록으로 갖고 있다. 그때 그림에 그려진 글자 자체는 온전했다. 그래서 층을 나눈다.

    다음은 같은 검사의 다른 실행이다. 여기서 1개가 남았고, 그것이 왜 통과인지가 중요하다.

    
    === 물음표 네 층 각각 ===
      ① 본문(원격 raw)      물음표 1개
      ② alt                물음표 0개  [PASS]
      ③ caption            물음표 0개  [PASS]
      ④ PNG 안 그려진 글자   물음표 0개  [PASS] (조각 352개 대조)
      손상 지표: U+FFFD 0개 · 한글 인접 물음표 0개
    

    본문에 남은 물음표 1개는 손상이 아니었다. 우리가 인용한 영문 대화상자 문장 안에 있던 진짜 물음표였다. 영어 의문문의 끝에 물음표가 있는 것은 정상이다. 개수만 세는 검사는 그 정상적인 물음표를 손상으로 보고 실패를 냈다.

    그래서 판정 기준을 총 개수에서 손상 지표 두 개로 바꿨다. 하나는 U+FFFD가 0개인지이고, 다른 하나는 한글에 붙어 있는 물음표가 0개인지다. U+FFFD는 프로그램이 여기 있던 글자를 알아보지 못했다고 표시할 때 쓰는 대체 문자다. 이것도 있으면 경보이지 손상 확정은 아니다 — 인용문이나 의도적인 표기로 원문에 원래 들어 있을 수 있다. 한글 바로 옆의 물음표도 마찬가지로, 한글이 있던 자리가 바뀐 흔적일 수 있다는 신호다. 반대로 영문 문장 끝의 물음표는 한글에 붙어 있지 않으므로 걸리지 않는다.

    여기까지가 우리가 쓰는 검사다. 이제 그 검사가 무엇을 못 잡는지를 적는다.

    이 지표에는 한계가 둘 있다

    첫째, 못 잡는 경우가 있다. 이 글의 1번 항목이 설명한 대로, 한글이 물음표로 바뀌면 그 자리에 한글이 남지 않는다. 그러면 한글에 붙어 있는 물음표도 없어진다. 즉 손상이 심할수록, 한글이 전멸에 가까울수록 이 지표는 오히려 0이 된다. U+FFFD도 마찬가지로 0일 수 있다. 글자가 대체 문자가 아니라 물음표로 바뀌는 경로에서는 U+FFFD가 아예 나오지 않기 때문이다. 두 지표가 모두 0인데 손상일 수 있다 — 한글 전체가 물음표로 바뀌어도 두 지표는 0일 수 있다.

    둘째, 잘못 잡는 경우가 있다. 정상적인 한국어 의문문의 물음표도 한글에 붙어 있다. 우리 기록에 실제로 그 오탐이 있었다. 지난 글의 그림 기록을 다시 검사했을 때 물음표 1개가 걸려 실패로 나왔는데, 실물을 열어 보니 그 그림 제목에 쓰인 한국어 의문문의 정상적인 물음표였다. 틀린 것은 그림이 아니라 우리 계수 규칙이었다.

    정리하면 이 지표는 못 잡기도 하고 잘못 잡기도 한다. 그러니 정직한 사용법은 이렇다.

    층별 계수는 경보다. 걸리면 의심하고 들여다본다. 그러나 안 걸리는 것은 무죄 증명이 아니다. 검사가 조용하다고 해서 손상이 없다는 뜻이 아니라, 이 검사로는 아무것도 못 봤다는 뜻일 뿐이다.

    손상이 없다는 판정은 기대 원문과 각 층의 텍스트를 대조해야만 성립한다. 무엇이 있어야 하는지를 알아야 무엇이 없어졌는지 안다. 그래서 원문을 모르면 판정할 수 없다. 이것이 이 문제의 본질적인 난점이고, 편한 지표로 덮을 수 있는 것이 아니다. 3번 항목의 비교가 판정이 될 수 있었던 이유도 여기에 있다. 우리는 그때 기대하는 원문을 알고 있었다.

    실용적으로 옮기면 이렇게 된다. 중요한 글자를 어딘가로 보낼 때는 보내기 전에 원문을 따로 남겨 두라. 그러면 나중에 대조할 수 있다. 예를 들면 보내기 전에 정상적인 한글 문장을 UTF-8 파일로 저장해 두고, 그 파일을 다시 열어 글자가 그대로인지 확인한 뒤, 보낸 결과와 그 파일을 비교하는 식이다. 이 절차는 우리가 측정해 본 것이 아니라 위 관측에서 나온 권고다. 원문 없이 결과물만 놓고 손상 여부를 확정하려는 시도는, 지표가 아무리 정교해도 한계를 벗어나지 못한다.

    우리가 실제로 쓰는 다섯 축

    위 한계를 알고 쓰는 전제로, 지금 우리가 세는 축은 다섯 개다.

    
    1. 코드 블록 밖 물음표          0
    2. U+FFFD (대체 문자)           0
    3. 한글에 붙은 물음표           0
    4. 도식 3층 (alt/캡션/글리프)    0
    5. 제어문자 (개행/탭/복귀 제외)   0
    

    다섯 축 모두에 같은 규칙이 적용된다. 예상하지 않은 문자가 있으면 경보이고, 원문·파일 형식과 대조해서 판정한다. 그리고 0은 손상이 없다는 뜻이 아니라 검사한 범위에서 나오지 않았다는 뜻이다. 어느 축도 다른 축보다 높은 계급을 갖지 않는다.

    이 가운데 1번은 우리 집필 형식 규칙이지 손상을 가리는 방법이 아니다. 우리는 이 글에 손상 예시를 실어야 하는데 그 예시에 물음표가 들어가므로, 예시를 코드 블록 안에만 두기로 정하고 그 규칙을 지켰는지를 검사한 것이다. 독자의 파일에는 해당하지 않는 규칙이니 판별법으로 가져가지 말기 바란다.

    5번을 넣은 이유는 5번 항목의 사고다. 박힌 0x080x01은 물음표도 아니고 대체 문자도 아니며 한글 옆에 있지도 않았다. 즉 물음표 네 축을 전부 통과했을 것이다. 검사가 조용한데 파일은 손상돼 있는 상태였고, 그것이 축을 하나 늘린 이유다.

    예외 목록은 만들지 않았다. 정상적인 물음표가 걸릴 때 그것을 예외로 등록해 개수를 면제하는 방법이 있다. 그렇게 하면 검사에 구멍이 생기고, 그 구멍이 다음번에 진짜 손상을 통과시킬 수 있다. 우리는 대신 콘텐츠의 형식을 바꿨다. 손상 예시를 코드 블록 안에 두고 그 블록을 영문·기호 전용으로 썼으며, 도식에는 리터럴 물음표를 넣지 않고 통로를 그렸다. 다만 이것은 우리 검사가 조용해지도록 우리 쪽 형식을 맞춘 것이지, 그렇게 해서 손상이 없어졌다는 뜻은 아니다.

    확인 방법. 자기 글이나 산출물을 점검할 때 다음 순서로 본다. 첫째, U+FFFD를 찾는다. 걸리면 의심한다. 둘째, 한글 바로 옆의 물음표를 찾는다. 셋째, 본문 말고 이미지의 대체 텍스트와 캡션도 따로 본다. 넷째, 줄바꿈·탭·복귀를 뺀 제어문자의 개수를 센다. 그리고 다섯째, 가능하면 원문과 대조한다. 앞의 넷은 전부 경보이고, 판정은 다섯째에서 나온다.

    정리 — 확인할 것 다섯 가지

    첫째, 물음표가 보일 때 볼 곳은 글자가 아니라 통로다. 담을 수 없는 통로로 글자를 보낸 것이 가능한 원인 중 하나다. 이번 사례에서는 전달 경로를 바꾸자 결과가 달라졌다. 이미 물음표가 된 글자는 되돌릴 수 없으니 원본을 다시 만든다.

    둘째, 한글을 명령에 직접 박아 넘기는 대신 파일에 두고 프로그램이 UTF-8로 읽게 한다. 우리 관측에서 전멸과 보존을 가른 것이 이 차이였다. 경로 간 우열을 잰 것은 아니지만, 파일을 열 때 약속을 문장 안에 적어 두면 그 컴퓨터의 기본값에 좌우되지 않는다는 것은 분명하다.

    셋째, 물음표가 안 나오고 프로그램이 그냥 멈추는 것도 같은 축일 수 있다. 오류 메시지에 codec이나 encode가 있으면 기능보다 글자를 내보내는 통로를 먼저 본다. 낱말만으로 원인이 확정되지는 않는다.

    넷째, 파일은 정상으로 보이는데 프로그램만 이상하게 동작하면 제어문자를 의심한다. 제어문자는 눈으로 알아차리기 어렵다. 물음표는 보이지만 제어문자는 보이지 않는다. 눈으로 찾지 말고 줄바꿈·탭·복귀를 뺀 개수를 센다. 이 축도 경보이지 판정은 아니다.

    다섯째, 다 고쳤는지는 층을 나눠 세되 그 결과를 무죄 증명으로 읽지 않는다. 본문, 이미지의 대체 텍스트, 캡션, 그림에 그려진 글자를 각각 보고, 걸리면 의심한다. 그리고 손상이 없다고 말하려면 기대하는 원문과 대조한다. 한 줄로 줄이면 이렇다. 세는 것은 경보이고, 판정은 대조다.

    마지막으로 하나만 덧붙인다. 우리는 이 원인을 적어 두고도 같은 함정을 세 번 밟았고, 그중 하나는 그 함정을 경고하는 줄을 쓰다가, 다른 하나는 그 규칙을 적은 문장 자체에서 밟았다. 경고문을 잘 쓰는 것으로는 막히지 않는다. 막히는 것은 기계가 세는 검사를 산출물에 걸어 두는 쪽이다. 그리고 그 검사도 무엇을 못 잡는지 함께 적어 두어야 한다. 그러지 않으면 이번에는 검사가 경고문 자리를 대신 차지한다.

    우리가 잰 것과 일반 설명

    우리가 직접 측정한 것 (2026-09-08 및 그 앞선 작업 기록)

    • PowerShell의 here-string에 한글을 적어 파이프로 python3 -에 넘겼을 때 보고서 머리글의 한글이 전부 물음표가 된 것. 우리가 관측한 그 세션의 사실이며, 원인이 되는 설정을 코드로 짚어 확인한 것은 아니다
    • 같은 세션에서 프로그램이 encoding="utf-8"로 파일을 읽어 만든 본문의 한글이 온전했던 것, 그리고 머리글을 UTF-8 파일로 두고 직접 읽어 인자 배열로 전달한 교정본에서 한글이 보존된 것
    • 한국어 윈도우 콘솔에서 진단 도구가 UnicodeEncodeError: cp949 codec can't encode character u2014로 중단됐고 원인 글자가 줄표 한 글자였던 것. 상세는 이 시리즈의 다른 글에 있다
    • 앞선 글의 스크립트를 복사해 두 줄을 고치는 작업에서 소스 파일의 두 줄에 물음표가 연달아 박히고, 그것이 로컬 문서와 원격 글, 이미지의 대체 텍스트와 캡션으로 전파된 것. 이것은 우리 기록에 근거한 서술이다. 지금 그 파일의 그 두 줄에는 물음표가 없고, 사고 당시의 상태를 이 글을 쓰는 자리에서 눈으로 확인한 것은 아니다
    • 같은 건의 이미지가 정상 렌더였고 그림 조각 기록과 다른 글의 대체 텍스트·캡션도 온전한 한글이었던 것. 그림 렌더나 전각 문자 쪽 귀속은 이 측정으로 반증됐다
    • 우리 운영 기록을 쓰는 중에 0x080x01 두 제어문자가 파일에 박혔고 그 파일이 21,665바이트에서 21,667바이트로 바뀐 것, 그리고 그것을 발견해 고친 것
    • 이 글의 사실을 정리한 우리 내부 문서에서 대체 문자를 0개로 유지하라는 규칙을 적은 문장 안에 대체 문자 한 글자가 박혀 있던 것. 기계 검사로 찾아 고쳤다
    • 물음표 네 층 검사의 실제 출력 두 건, 그리고 본문에 남은 물음표 1개가 인용한 영문 대화상자의 진짜 물음표였던 것
    • 지난 글의 그림 기록을 다시 검사했을 때 걸린 물음표 1개가 그림 제목의 한국어 의문문 물음표였던 것. 틀린 것은 그림이 아니라 계수 규칙이었다

    이 글에서 일반 설명으로만 쓴 것 (우리가 코드로 확인한 것이 아니다)

    • 인코딩·UTF-8·cp949·파이프·글리프·제어문자의 개념 설명
    • 담을 수 없는 글자를 만났을 때 중단·생략·대체 중 무엇이 일어나는지는 오류 처리 방식이 정하고 물음표 대체는 그중 하나라는 것. 처리 방식의 종류는 파이썬 공식 문서의 오류 처리 방식(error handlers) 절에 있다
    • 좁은 약속도 영문·숫자는 담을 수 있어 문제가 생기는 자리는 그 약속에 없는 글자 쪽이라는 일반적인 설명
    • 문자열 안의 역슬래시 조합을 프로그램이 하나의 제어문자로 바꿔 해석하는 문법이 여럿 있다는 일반 사실. 우리 관측에 어떤 문법이 작용했는지를 코드로 짚어 확인한 것은 아니다

    우리가 측정하지 않아 일반화하지 않은 것

    • 경로 간 우열. 우리는 두 경로를 각각 한 번씩 관측했을 뿐이고, 어느 쪽이 더 자주 깨지는지를 재지 않았다
    • 물음표 손상과 중단 사고의 빈도 비교. 어느 쪽이 더 흔한지를 세지 않았다
    • 부분 손상의 모양. 우리가 본 사례는 한글이 남지 않은 형태였고, 일부 글자만 바뀌어 한글과 물음표가 섞이는 경우를 측정하지 않았다
    • 단일 원인. 통로를 바꾸면 결과가 달라진다는 것은 관측했지만, 그 통로의 어떤 설정이 원인인지는 코드로 확정하지 않았다

    이 글에 싣지 않은 것

    인터넷에 널리 돌아다니는 일반 처방이 몇 가지 있다. 콘솔의 코드페이지를 바꾸는 방법 같은 것들이다. 우리가 그 방법들을 직접 실행해 결과를 측정한 기록이 없어서 이 글에는 싣지 않았다. 우리가 실제로 실행해 결과를 본 처방만 위에 적었다.