Community코딩 & 개발github.com

george-andrikopoulos/domain-low-latency

저지연 시스템 설계 및 측정을 위한 엔지니어링 규율입니다. 두 번째 패스에서 설계 공격, 측정은 해당 체제와 일치해야 하며, 무제한 큐는 과부하 동작이 없고, 구조는 입증된 체제를 유지해야 하며, 할당된 메모리는 상주하지 않고, 요구 사항은 해당 계층에서 충족하며, 핫 경로에 할당 금지, 조정된 생략 없음, 핫 라인의 거짓 공유 없음, 경합 경로의 재시도 루프 없음, 게시 창 내 스톨 없음, 바운드 경로의 시스템 콜 없음 등을 다룹니다.

domain-low-latency란 무엇인가요?

이 스킬은 저지연 시스템 개발을 위한 엔지니어링 규칙 모음입니다. Rust와 Java 환경을 주요 대상으로 하며, JMH(Aleksey Shipilev)와 같은 기성 마이크로벤치마킹 도구를 참고하여 작성되었습니다. 주요 규칙으로는 측정 결과가 실행 체제(워밍업 vs 콜드)를 명시하고 프로덕션 환경과 일치해야 한다는 점, 무제한 큐는 대기 시간을 폭발적으로 증가시키므로 반드시 바운드와 해당 동작을 정의해야 한다는 점, 데이터 구조는 설계 시 가정한 동시성 체제(예: MPSC)를 벗어나면 오작동할 수 있으므로 타입 시스템이나 어설션으로 이를 강제해야 한다는 점, 할당된 메모리는 실제로 사용(터치)되기 전까지 물리적 페이지를 확보하지 않으므로 핫 경로에서는 사전 터치 및 고정이 필요하다는 점, 요구 사항은 해당 추상화 계층(머신 레벨, OS 레벨, 라이브러리 레벨)에서 해결해야 한다는 점 등을 포함합니다. 또한 핫 경로 할당 금지, 조정된 생략(coordinated omission) 방지, 핫 라인 거짓 공유 방지, 경합 경로 재시도 루프 방지, 게시 창 내 스톨 방지, 바운드 경로 시스템 콜 방지 등의 하위 규칙도 포함되어 있으며, 각 규칙마다 실패 모드 확인을 위한 질문을 제공합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/george-andrikopoulos/relearn/tree/HEAD/claude-pack/skills/domain-low-latency

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

도메인: 저지연 규칙

측정값은 웜 또는 콜드 상태로 실행되었는지 명시하고, 프로덕션 환경과 일치해야 함 [R:측정값은-보고되는-체제와-일치해야-함]

Rust와 Java 사용자를 대상으로 작성됨.

JMH와 그 샘플(Aleksey Shipilev)에서 작성되었으며, 그 존재는 JVM에서 수제 마이크로벤치마크가 기본적으로 잘못되었다는 주장의 근거입니다.

측정이 실행된 체제를 명시하고, 그 체제가 프로덕션에서 실행되는 체제와 일치하는지 확인하십시오. 여기에는 두 가지 오류가 있으며, 그중 하나만 알려져 있습니다.

프로덕션이 웜인데 콜드를 측정하는 경우. 첫 번째 실행은 인터프리터 모드 또는 콜드 명령어 캐시에서 실행되고, 훈련되지 않은 분기 예측기, 채워지지 않은 페이지 테이블, 그리고 관리형 런타임에서는 컴파일된 코드가 전혀 없는 상태에서 실행됩니다. 그 수치는 안정 상태보다 1~2자리수 더 나쁠 수 있으며, 작업 자체보다는 워밍업을 설명합니다. JVM에서 에스컬레이션은 단계적으로 이루어집니다(인터프리터, C1, C2, 오래 실행되는 루프에 대한 OSR(On-Stack Replacement) 사용). 수천 번의 반복이 필요하므로 수제 타이밍 루프는 기본적으로 잘못되고, 그래서 벤치마킹 하네스가 존재합니다.

프로덕션이 콜드인데 웜을 측정하는 경우. 이는 엄격해 보이기 때문에 주목받지 못하는 절반입니다. 1분에 한 번 실행되는 경로는 절대 웜이 아닙니다. 시장 개장 버스트는 콜드 코드로 도착합니다. 오류 핸들러는 가장 최악의 순간에 하루 중 처음으로 실행됩니다. 이 모든 경우에 대해 웜 수치는 어떤 사용자도 경험하지 못할 숫자이며, 신중히 폐기된 워밍업이 바로 프로덕션 동작입니다. 그곳에서 워밍업을 폐기하는 것은 위생 처리가 아닙니다. 측정 자체를 삭제하는 것입니다.

따라서 규칙은 절차가 아니라 범위 일치입니다: 어떤 체제를 측정했는지 말하고, 프로덕션이 어떤 체제에 있는지 말하고, 둘이 동일하다는 것을 보여주십시오. 프로덕션이 진정으로 둘 다인 경우(안정적인 스트림과 콜드 버스트)는 두 개의 측정값과 두 개의 보고된 숫자로 표시해야 하며, 하나로 평균 내어 무의미하게 만들어서는 안 됩니다.

이는 집합이 아닌 시간에 적용된 [R:측정은-주장이지-부분집합이-아님]입니다: 워밍업 샘플은 부분집합이며, 이를 폐기하는 것은 범위 결정이고, 반사적으로 내려진 범위 결정은 측정되었다는 권위를 가지고 잘못된 숫자를 만들어냅니다.

실패 모드 확인: 프로덕션 경로는 중요한 순간에 웜인가 콜드인가, 그리고 나는 무엇을 측정했는가? 벤치마크가 처음 N번의 반복을 폐기했다면, 프로덕션에 그 반복에 해당하는 것이 무엇인지 말하십시오. 해당하는 것이 없다면, 폐기된 부분이 바로 답이었습니다.

의도적으로 분리된 두 개의 이웃 규칙. [R:조정된-생략-금지]는 계측이 중요한 이벤트 동안 샘플을 수집하지 못하는 것입니다. 여기는 계측이 잘못된 체제에서 샘플을 수집하는 경우입니다. 그리고 [R:추상화가-컴파일되어-사라졌는지-확인]는 최적화 프로그램이 벤치마크가 측정하던 작업을 제거한 경우를 다룹니다(아무것도 없는 웜 측정).

바운드 없는 큐는 과부하를 거부 대신 대기 시간으로 변환함 [R:바운드-없는-큐는-과부하-동작이-없음]

Rust와 Java 사용자를 대상으로 작성됨.

리틀의 법칙(J. D. C. Little, 1961)에서 작성됨: L = λW, 따라서 거부하지 않는 큐의 경우 도착률이 서비스율을 초과하면 대기 시간이 제한 없이 증가함.

소비가 생산을 따라잡지 못할 때 큐가 무엇을 할지 결정하십시오. 큐가 계속 커지는 것은 결정이 아닙니다.

큐 깊이는 대기 시간입니다. 이는 리틀의 법칙이며, 휴리스틱이 아닙니다: 도착률 λ, 평균 큐 길이 L일 때 평균 대기 시간은 L/λ입니다. 따라서 바운드 없는 L은 바운드 없는 대기 시간입니다. 따라서 용량 제한이 없는 큐는 대기 시간 제한이 없으며, 해당 큐의 상위 어딘가에 명시된 대기 시간 요구 사항은 어떤 것도 보장하지 않습니다.

이 실패는 진단이 늦어지는 특징적인 형태를 가집니다. 생산자는 절대 블로킹되지 않으므로 상위에서는 문제를 보고하지 않습니다. 처리량은 모든 항목이 결국 처리되므로 올바르게 보입니다. 유일한 증상은 대기 시간이 단조롭게 증가한다는 것입니다. 이것이 눈에 보일 즈음에는 백로그가 아무도 원하지 않는 작업을 수행하고 있습니다. 소비된 모든 항목은 이미 오래되었으므로, 용량은 질문의 순간이 지나간 질문에 대한 답변을 전달하는 데 사용되고 있습니다. 이것이 부하가 정상으로 돌아와도 시스템이 회복되지 않는 이유입니다. 먼저 따라잡지 못했던 동일한 속도로 백로그를 비워야 합니다.

따라서 큐를 바인딩하고, 바운드에서의 동작을 선택하십시오. 네 가지가 정당하며, 선택은 기술적 결정이 아닌 도메인 결정입니다:

  • 생산자 블로킹. 진정한 역압력(backpressure). 가장 느린 소비자가 모든 사람의 속도를 설정하며, 압력은 실제로 이를 덜어낼 수 있는 사람에게 전파됩니다. 생산자가 중단되어서는 안 되는 체제에서는 잘못된 선택입니다. 이는 [R:게시-창-내-스톨-금지]가 다루는 체제입니다.
  • 가장 오래된 항목 드롭. 시장 데이터, 센서 판독값, 텔레메트리에 적합합니다: 오래된 틱은 가치가 없으며, 가장 새로운 것만이 가치 있습니다.
  • 가장 최신 항목 드롭. 백로그가 공정한 큐이고 늦게 도착한 항목이 어떤 약속도 받지 못한 경우에 적합합니다.
  • 거부하고, 그 사실을 알림. 주문 입력 및 호출자가 결과를 반드시 알아야 하는 모든 것에 적합합니다. 조용히 드롭된 주문은 거부된 주문보다 호출자가 호출에 대해 가진 신뢰의 전체 폭만큼 더 나쁩니다.

선택하지 않으면 누구도 의도적으로 선택하지 않을 다섯 번째 동작이 선택됩니다: 모든 것을 흡수하고, 모든 항목의 대기 시간을 동일하게 저하시키며, 결국 메모리 고갈로 실패합니다. 또는 관리형 런타임의 경우, 백로그 자체로 인한 수집 일시 중지(GC pause)가 발생하여 소비자를 더욱 느리게 만들고, 이는 잘못된 부호의 피드백 루프에 가장 가까운 것입니다.

바운드 없는 구조는 바운드가 진정으로 다른 곳에 있는 경우(생산자가 상위에서 속도 제한되는 큐, 또는 소비자가 가능한 모든 도착률보다 확실히 빠른 큐) 올바른 선택입니다. 코드에서 큐 옆에 그 이유를 명시하십시오. 이유가 명시된 바운드 없는 큐는 결정입니다. 이유 없이 같은 형태를 가진 것은 결정의 부재입니다.

실패 모드 확인: 소비가 생산을 10초 동안 따라잡지 못하면, 이 큐는 무엇을 하고 누가 알게 되는가? 답이 "커진다"와 "아무도 모른다"라면, 여기에는 과부하 동작이 없고, 단지 과부하 증상이 기다리고 있을 뿐입니다.

구조는 증명이 가정한 동시성 체제 하에서만 올바름 [R:구조는-입증된-체제를-유지함]

Rust와 Java 사용자를 대상으로 작성됨.

Dmitry Vyukov, Intrusive MPSC node-based queue, 1024cores에서 작성됨. 여기서 체제는 알고리즘의 이름에 명시되고 코드에는 명시되지 않음.

구조의 정확성 주장에서 체제를 읽어내고, 코드에서 이를 위반하는 것을 방지하는 메커니즘을 명명하십시오. "문서가 단일 소비자라고 말한다"는 메커니즘이 아닙니다.

MPSC는 다중 생산자, 단일 소비자를 의미하며, 여기서 '단일'은 설명이 아닌 전제 조건입니다. Vyukov의 mpscq_pop은 self->tail을 읽고, 그것부터 탐색하며, 다시 씁니다. 이는 동기화되지 않은 일반 로드 및 저장입니다. 정확히 하나의 스레드가 이를 실행한다고 가정했기 때문입니다. 두 소비자가 여기서 인터리브되면 둘 다 동일한 노드를 반환하거나, 하나의 노드를 완전히 잃을 수 있습니다. 어느 결과도 아무것도 발생시키지 않습니다. 어설션, 경합할 잠금, 오류 경로가 없습니다. 큐는 그럴듯한 값을 반환하고 프로그램은 중복되거나 사라진 항목으로 계속 실행됩니다.

이 침묵이 전체 문제이며, 부하에 의해 더욱 악화됩니다. 두 소비자가 수백 번의 작업을 실행할 때 중요한 윈도우 내에서 인터리브되지 않을 수 있습니다. 테스트 스위트는 통과하고, 스테이징 소크 테스트도 통과합니다. 그리고 인터리빙은 프로덕션 트래픽과 함께 발생합니다. 재현할 수 없는 조건에서만 나타나는 결함은 외부에서 완전히 다른 곳의 결함과 구별할 수 없습니다.

따라서 언어가 허용하는 경우 체제를 타입에 넣으십시오. 정확히 하나의 소비자 핸들을 생성 시점에 내어주고, Clone이나 Sync를 구현하지 않게 하며, pop에 &mut self를 사용하여 대여 검사기가 독점성을 강제하게 하십시오. 그러면 "두 소비자"는 찾아야 할 버그가 아니라 컴파일되지 않는 프로그램입니다. 이는 [R:불법-상태-표현-불가-만들기]이며, 체제가 구조의 수명 주기에 따라 변하는 경우에는 [R:프로토콜을-위한-타입스테이트]입니다. 이는 전체 도메인에서 해당 규율의 가장 영향력 있는 사례입니다. 대안적인 제어는 헤더 주석의 한 문장이고, 방지하는 실패는 원인을 특정할 수 없기 때문입니다.

언어가 이를 지원하지 않는 경우(Java는 그러한 핸들이 없음), 제어는 실제로 실행되는 무언가여야 합니다: 첫 번째 사용 시 소유 스레드를 기록하고 디버그 빌드의 모든 후속 호출에서 이를 어설션하거나, 모든 소비를 불변 조건이 명시된 유일한 위치인 단일 객체를 통해 라우팅하십시오. 주석은 제어가 아닙니다([R:보장은-독자가-필요함]).

이 원칙은 큐보다 더 넓으며, 다른 구성원도 같은 방식으로 실패합니다. 두 개의 생산자가 있는 SPSC 링. 재진입 불가로 문서화되어 있지만 시그널 핸들러나 콜백에서 진입하는 구조. 세 번의 리팩토링 후에 그것을 담고 있던 필드가 공유되면서 두 스레드에서 접근되는 HashMap. 변이를 가로질러 유지되는 반복자. 각 경우에 체제는 정확성 주장의 가설이었으며, 아티팩트 내에는 그것을 기록하는 것이 없습니다.

실패 모드 확인: 이 구조의 정확성 주장은 어떤 체제를 가정하며, 지금 그 체제를 위반하면 무엇이 실패할까? 정직한 답이 "이름이 그렇게 말한다"나 "우리는 조심한다"라면, 해당 구조는 보호되지 않으며 다음에 이를 건드리는 사람은 알지 못할 것입니다.

한 번도 접근되지 않은 메모리는 아직 메모리가 아님 [R:할당된-것은-상주하지-않음]

Rust와 Java 사용자를 대상으로 작성됨.

Ulrich Drepper, "What Every Programmer Should Know About Memory" (2007)에서 다운로드됨. 요구 페이징(demand paging)과 최초 접근 비용에 대해 설명됨.

사전 할당은 준비가 아닙니다. 성공적인 할당은 주소 공간을 확보합니다. 페이지는 최초 접근 시 도착하며, 그 접근 자체가 비용입니다.

malloc과 mmap은 가상 주소를 반환합니다. 물리 페이지는 지연 공급됩니다: 각 페이지에 대한 첫 번째 쓰기는 커널로 트랩(trap)되어, 프레임을 찾고, 0으로 채운 후(이전 테넌트의 데이터가 유출되어서는 안 되므로), 매핑합니다. 따라서 시작 시 할당되고 핫 경로에서 처음 쓰여지는 1GB 버퍼는 약 25만 건의 페이지 폴트를 포함하며, 페이지당 하나씩, 첫 번째 순회에 걸쳐 분산되어 발생합니다. 해당 경로의 모든 할당 없음(no-allocation) 어설션은 충족되지만, 그 각각은 잘못된 것을 측정하고 있습니다: 할당자를 호출한 것은 아무것도 없습니다.

상주 상태는 일단 달성되면 영구적이지도 않습니다. 페이지는 메모리 압박이나 스왑 하에 회수될 수 있으므로, 시작 시 버퍼를 접근한 후 유휴 상태로 남아 있는 장기 실행 프로세스는 일주일 동안 폴트가 없던 경로에서 다시 폴트를 일으킬 수 있습니다. 이것이 해결책에 두 가지 절반이 있고, 첫 번째 절반만 수행하는 것이 흔하고 값비싼 실수인 이유입니다.

따라서, 데드라인이 있는 경로의 모든 항목에 대해: 시작 시, 실제 작업이 도착하기 전에 경로가 접근할 모든 페이지에 쓰기를 수행하십시오. 접근은 쓰기여야 합니다. 접근되지 않은 페이지의 읽기는 공유 제로 페이지에 의해 제공될 수 있으며 아무것도 증명하지 않기 때문입니다. 그런 다음 고정(pinning)하십시오: mlockall(MCL_CURRENT | MCL_FUTURE), 그리고 박스에서 스왑을 비활성화하십시오. JVM에서 -XX:+AlwaysPreTouch는 힙에 대해 첫 번째 절반을 수행하며, 시작 시간이 느려지는 대신 비용이 바운드가 있는 경로에서 제거되기 때문에 그만한 가치가 있습니다.

거대 페이지는 양방향으로 산술을 변경하며, 기본값보다는 자체 결정이 필요합니다. 더 적고 더 큰 페이지는 더 적은 폴트와 TLB 압력을 의미합니다. 각 폴트는 더 크며, 투명한 거대 페이지(THP)는 정확히 잘못된 순간에 스레드를 밀리초 동안 멈출 수 있는 조각 모음(defragmentation) 경로를 가져옵니다. 부팅 시 예약된 명시적 거대 페이지는 이를 피합니다. THP를 always로 설정하고 희망하는 것은 이 도메인이 계속해서 명명해야 하는 형태입니다.

설정을 수행하는 스레드가 아닌, 메모리를 소유할 스레드에서 접근하십시오. 멀티 소켓 머신에서 첫 번째 쓰기는 배치(placement)도 결정합니다: 페이지는 폴트를 일으킨 스레드의 노드에 할당됩니다. 따라서 이 규칙을 만족시키는 명백한 방법(초기화 스레드에서 모든 버퍼를 탐색하는 하나의 루프)은 모든 페이지를 해당 스레드의 노드에 배치하게 됩니다. 그리고 다른 소켓에 고정된 워커는 모든 데이터를 인터커넥트를 통해 대략 로컬 지연 시간의 두 배로 읽어야 합니다. 이제 경로는 폴트가 없지만 완전히 원격입니다: 더 빠른 첫 번째 메시지로 얻은 더 느린 안정 상태입니다. 먼저 소유 스레드를 고정한 다음, 각 스레드가 자신이 사용할 메모리에 접근하게 하십시오.

실패 모드 확인: 이 경로가 접근할 모든 페이지가, 그것을 읽을 스레드에 의해 쓰여졌으며, 회수되는 것을 막는 것은 무엇인가? 세 가지 질문입니다. 첫 번째 질문에만 답하면 유휴 상태가 될 때까지 빠르다가 느려지는 시스템이 남고, 처음 두 질문에만 답하면 단일 소켓에서 빠른 시스템이 남습니다.

[R:핫-경로-할당-금지]는 형제 규칙이며 어느 것도 다른 것을 대체하지 않습니다. 전자는 할당자 진입에 관한 것이고, 후자는 할당자가 이미 반환한 메모리에 관한 것입니다. [R:요구-사항을-해당-계층에서-답변]은 해결책이 모두 메모리 정책, 잠금 또는 부트 파라미터인 이유입니다. 컨테이너나 크레이트의 선택은 여기에 도달하지 못합니다. [R:게시-창-내-스톨-금지]는 가장 크게 영향을 미치는 곳입니다. 라이터(writer)의 두 저장 사이에 발생한 페이지 폴트는 그 전체 지속 시간 동안 창을 열어 놓기 때문입니다.

요구 사항을 해당 계층에서 답변하라 [R:요구-사항을-해당-계층에서-답변]

Rust와 Java 사용자를 대상으로 작성됨.

무엇을 선택하기 전에 요구 사항이 어떤 계층에 있는지 명시하고, 그곳에서 충족하십시오.

머신에 관한 요구 사항(격리된 코어를 방해하지 않음, 전송 경로에서 페이지 폴트를 일으키지 않음, 실시간 스레드가 경합하는 잠금을 획득하지 않음)은 더 빠른 크레이트, 더 좋은 평판의 전송, 또는 비동기 API를 선택함으로써 답변되지 않습니다. 이들은 라이브러리 수준의 결정입니다. 올바를 수 있고 도움이 될 수도 있지만, 시스템 수준의 제약 조건을 보유하지는 않습니다. 이를 마치 그런 것처럼 취급하면, 열린 질문이 그 뒤에 아무것도 없는 닫힌 질문으로 변환됩니다.

실패는 잘못된 선택이 아니라 잘못된 장부 항목입니다. 충족된 것으로 표시된 제약 조건은 더 이상 검사되지 않습니다. 열린 제약 조건은 여전히 검사됩니다.

[truncated for translation, 원본이 잘렸습니다]

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

george-andrikopoulos/domain-java

Engineering discipline for working in java. Covers: a speculated path deoptimises when the input changes; a view is not a copy; a wrapper type is not free here; close what you open; design for inheritance or forbid it; equality is one contract; exceptions name what failed; identity is not equality for boxes; no reference to internals escapes; null is not a value; publish safely or not at all; seal the alternatives; serializable is a second constructor

george-andrikopoulos/domain-rust

Engineering discipline for working in rust. Covers: design types first; borrow in signatures; module visibility is deliberate; must use on consequential returns; newtype liberally; parse wide then range check; seal closed trait sets; typestate builder for required fields; typestate for protocols; verify the abstraction compiled away; async all the way; errors name what failed; justify every clone; no let underscore on a local; parse dont validate; private fields only; xplat fixtures; no...

george-andrikopoulos/global

Engineering discipline that applies to every project and language. Covers: guarantee needs a reader; verdict survives the channel; check the claim you inherit; doc currency; measure the claim not a subset; no silent spend; prefer by construction; repair the lying artefact; signal needs a consequence; verify through production path; detector excludes own definitions; an ambiguous write target is a question; anchor outside the checked partys reach; delegate a fan out; diff against the known...

관련 스킬