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

상시 지출은 하나, 불변식에 이름 붙여서

~11 min · cost, invariants, scheduling, product-boundary

Level 0미분류
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

배경 지출은 어디서 오나

아무도 안 읽을 기사에 토큰을 태우기로 결정하는 사람은 없어. 합리적인 기능 하나씩 도착하지. 열자마자 뜨게 전부 미리 요약해두자. 가끔 필요하니까 미리 번역해두자. 순위를 잘 매기게 기사마다 모형으로 점수를 매기자. 제안 하나하나가 따로 놓고 보면 변호 가능하고, 하나하나가 기사당 비용에 하루 수천 건의 도착률을 곱하고, 총액은 어떤 리뷰도 아닌 청구 기간 끝에 나타나.

구조적 방어는 예산 경보가 아냐. 경보는 사후에 알려주지. 불변식으로 적힌 규칙이야. 과금되는 작업은 사람이 요청할 때 일어나고, 예외는 고정되고 열거돼 있다.

예외는 발견하지 말고 열거해

일간 브리핑은 예약된 생성 하나 값어치가 있어. 그건 진짜 예외고 규칙이 그걸 인정해야 해. 중요한 건 그게 예외로 적혀 있다는 거야. 항목 하나, 이름 붙여서, 이유를 달아서. 끝없는 집합의 첫 번째가 되는 대신.

열거된 예외는 앞으로 모든 제안에 물을 질문을 줘. 이게 두 번째 상시 지출을 더하나? 그 질문은 설계 논의에서 답할 수 있고, 결정이 내려져야 할 자리가 거기야. 그게 없으면 추가마다 자기 장점으로 판단되고, 개별적으로 합리적인 추가는 전부 통과해.

예약된 것도 제한해

허가된 지출도 가드가 필요해. 예약 작업이랑 수동 트리거가 둘 다 발동할 수 있으니까. 수동 새로고침은 사람이 요청하는 거니까 규칙상 허용돼. 근데 둘이 겹쳐서 하루에 생성 둘을 낼 수 있지.

잠금이 답이고, 경합 시 동작이 설계 결정이야. 줄 세우는 게 아니라 거절해야 해. 줄 세우면 이중 트리거가 조금 뒤의 순차 지출 둘이 되고, 그건 가드가 막으려던 결과 그 자체거든. 거절하고 이미 쓰이고 있는 걸 돌려주는 게 트리거 하나가 청구 둘이 되는 걸 막아줘. 근데 그게 뭘 사는지는 조심해서 봐. 브리핑 둘이 동시에 쓰이는 걸 막지, 같은 날 두 번째 브리핑을 막진 않아. 수동 새로고침은 사람이 요청하는 거라 규칙이 허용하거든. 상시고 하나뿐인 지출은 예약된 쪽이야.

모든 토큰을 탭에 귀속시켜

규칙의 실용적 형태는 어떤 지출에든 적용할 수 있는 귀속 시험이야. 그걸 일으킨 사람 행동을 가리켜봐. 기사 열기, 대기열 버튼 누르기, 번역 탭하기, 질문하기. 하나하나가 탭이고, 탭이 곧 허가야. 일간 브리핑은 대신 불변식을 가리키고, 그래서 불변식이 그걸 지명해야 하는 거고.

둘 중 아무것도 못 가리키는 지출은 출력이 아무리 쓸모 있어도 배경 소각이야. 그게 시험 전부고, 적용하는 데 십 초 걸려.

상시 비용은 열거하고, 절대 부작용으로 도착하게 두지 마. 이름 붙은 예외 하나짜리 규칙은 강제 가능해. 다음 제안이 두 번째가 되겠다고 논증해야 하니까. 안 적힌 예외를 가진 규칙은 강제력이 없어. 어느 추가가 선을 넘었는지 아무도 못 짚으니까.

Code

예약된 유일한 지출, 줄 세우지 않고 거절하는 잠금으로 지킴·python
_brief_lock = threading.Lock()      # a LOCK, not a queue


async def generate_brief(kind: str = "manual") -> dict:
    """The scheduled spend, plus the manual refreshes a person asks for.

    Both the scheduler and a tapped refresh reach this, and they live
    in different event loops -- hence a threading lock rather than an
    asyncio one, which would protect nothing across loops.
    """
    # DECLINE on contention, never queue: queuing turns a double
    # trigger into two sequential spends slightly later, which is
    # exactly what this guard exists to prevent. Hand back what is
    # already being written toward instead.
    if not _brief_lock.acquire(blocking=False):
        return {**_latest_brief(), "already_running": True}
    try:
        return await _generate_brief(kind)
    finally:
        _brief_lock.release()


# Be precise about what this guarantees. It prevents two briefs being
# written AT ONCE. It does NOT prevent a second brief today -- a
# manual refresh is a person asking, and the rule allows that by
# design. The standing spend is the SCHEDULED lane, and that one is
# singular. Conflating 'one a day' with 'one at a time' would be
# claiming a guard the code does not implement.

External links

Exercise

네 시스템이 과금되는 돈을 쓰는 자리를 전부 나열해봐. 모형 호출, 유료 API, 컴퓨트 작업. 각각에 대해 그걸 허가하는 사람 행동을 지명해. 하나도 못 가리키는 건 상시 비용이야. 그러고 나서 네 상시 비용이 설계 리뷰가 마주칠 만한 데 어디든 적혀 있는지 확인해.
Hint
귀속에 저항하는 건 보통 '미리', '워밍', '배경', '싱크' 같은 말로 설명돼. 그 접두사는 요청을 예상하고 하는 작업이라는 표시고, 요청이 영영 안 올 수도 있다는 뜻이야. 그리고 출력이 나중 탭에 재사용 안 되면 그 예상이 비용 전부였던 거고.

Progress

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

댓글 0

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

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