본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

리뷰어가 응답 불가일 때

~13 min · review, reliability, detection, operations

Level 0젖은 흙
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

사용량 제한은 판정이 아니라 일정 사실이야

특정 리뷰어를 요청했는데 아무것도 안 돌아와. 할당량 거부, 비정상 종료, 영영 안 끝나는 회전 표시. 위험한 수는 그걸 리뷰 라운드로 기록하는 거야. 이제 로그가 결과물을 읽었다고 말하는데 안 읽었으니까. 다른 위험한 수는 파이프라인을 멈추는 거고. 응답 불가는 작업이 아니라 그 순간의 성질이거든.

답은 고리야. 순서가 있는 리뷰어 회전, 요청된 쪽에서 진입해서 정확히 한 바퀴. 매 도약마다 직전 리뷰어를 왜 건너뛰었는지가 기록돼. 고리가 리뷰어 없이 닫히면 거기가 사람이 정할 자리고. 그리고 그 결정은 대개 누군가 비공식적으로 빼놨던 리뷰어를 쓰는 거야. 그래서 고리는 그 자리에서 짜맞추는 게 아니라 적어둬야 해. 적어둔다는 게 전부 넣는다는 뜻은 아냐. 이름을 부르면 회전에 끼는 대신 혼자 돌아버리는 구성원은 일부러 빼고, 그 뺀 사실도 기록의 일부야.

자기 기만 없이 응답 가능 여부 재기

여기서부터 미묘해져. 뻔한 신호가 전부 서로 다른 방향으로 거짓말하거든.

바이트 수가 거짓말해. 어떤 실패는 실제로 오간 문자가 칠천 자를 넘는데 회전 표시 몇 프레임으로 그려졌어. 제어 문자랑 캐리지 리턴이지 답이 아니었고. 이스케이프 시퀀스를 벗기고 그려진 텍스트를 재.

종료 코드도 거짓말해. 다른 하나는 팔천 자를 뱉고 비정상 종료했고 꼬리에 할당량 메시지를 달고 있었어. 길고, 죽었지. 그러니 출력이 한도 패턴에 맞는 비정상 종료는 길이 상관없이 응답 불가로 쳐.

짧은 정상 종료는 답이 아냐. 거의 아무 말도 안 하고 0 으로 끝났다는 건 도구는 돌았고 리뷰는 안 나왔다는 뜻이야.

길고 정상 종료한 답은 절대 재검하지 않아. 무슨 단어가 들어 있든. 이건 반대 방향으로 하중을 받아. 사용량 제한 처리 코드에 대한 진짜 알찬 리뷰는 순진한 키워드 검사에 다 걸릴 거고, 그걸 죽이는 게 최악의 거짓 양성이니까.

일부러 안 잡히게 두는 모양

이게 못 잡는 실패가 하나 있어. 리뷰어가 표시 길이 바닥을 겨우 넘긴 평범한 서술을 돌려주고, 0 으로 끝나고, 한도 어휘를 안 쓰는 경우. 비정상 종료도 아니고, 할당량 메시지도 아니고, 길이 시험이 유죄 판결할 만큼 짧지도 않아. 이걸 잡겠다고 누가 제안한 기계적 탐지기는 전부 위의 거짓 양성을 다시 만들어.

그래서 세션 자신의 판단에 맡겨. 일부러 그렇게 하고, 그렇게 적어둬. 탐지 불가능한 경우를 계약에 이름 붙이는 이유는, 안 그러면 팀이 탐지기가 그것도 덮는다고 믿게 되기 때문이야.

탐지기는 기록된 실패 출력에서 조여, 상상에서 조이지 말고. 매 도약이 실제로 본 것의 꼬리를 기록해야 다음 사람이 진짜 표본에서 패턴을 늘려. 미리 상상해서 만든 패턴은 아무것도 안 맞고 조용히 실패하는데, 그게 아예 없는 것보다 나빠. 덮여 있다고 믿게 되니까.

Code

사람이 봤을 것을 기준으로 잰 응답 가능 여부·python
import re

ANSI = re.compile(r"\x1b\[[0-9;?]*[a-zA-Z]")
LIMIT = re.compile(r"usage limit|rate limit|quota|too many requests|"
                   r"429|403", re.I)
MIN_DISPLAYED = 400          # chars of RENDERED text


def displayed(raw: str) -> str:
    """What a person would have seen. A spinner sends thousands of
    characters and shows a few lines; measure what was shown."""
    text = ANSI.sub("", raw)
    # a carriage return rewrites the line - keep only the last write
    return "\n".join(seg.split("\r")[-1] for seg in text.split("\n"))


def unavailable(raw: str, exit_code: int) -> str | None:
    shown = displayed(raw)
    if exit_code != 0 and LIMIT.search(shown[-2000:]):
        return "limit-shaped crash"      # dead at ANY length
    if exit_code != 0 and looks_like_a_limit(shown):
        return "crash"      # non-zero ALONE is not enough:
                             # a crash with a real answer above it
                             # is still an answer
    if len(shown.strip()) < MIN_DISPLAYED:
        return "clean exit, no answer"   # ran, reviewed nothing
    return None                          # a long clean answer is
                                         # NEVER second-guessed -
                                         # it may be a real review
                                         # OF rate-limit code


# UNDETECTABLE ON PURPOSE: a few hundred chars of plain narration,
# exit 0, no limit vocabulary. Not a crash, not a quota message,
# not short enough to convict. Every mechanical rule proposed for
# it re-creates the false positive above, so it is left to the
# session's judgment - and that is written into the contract so
# nobody believes the detector covers it.

External links

Exercise

네 시스템에서 외부 의존 대상의 실패가 지금 결과로 기록되는 자리를 찾아봐. 두 경우의 로그가 어떻게 생겼을지 적어. 진짜로 빈 결과인 경우랑 의존 대상이 죽은 경우. 그리고 둘을 구별해주는 게 있는지 확인해. 없으면 구별해주는 항목을 넣고, 실패 경로가 요약 대신 받은 것의 원문 꼬리를 기록하게 만들어.
Hint
원문 꼬리는 지저분해서 다들 빼는 부분이고, 미래의 유지보수자가 탐지를 늘릴 수 있게 해주는 유일한 부분이야. "의존 대상 응답 불가"라고 적힌 로그 줄은 아무것도 안 가르쳐. 실제로 돌아온 것 이백 자가 붙은 같은 줄이 다음 수정을 짓는 재료고.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.