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

볼트 하나에 요청 하나

~14 min · batching, latency, performance, measurement

Level 0흩어진 노트
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

맞는데, 느려

지난 레슨의 검증 읽기 규칙은 노트 단위로 정확해. 이미 사본을 든 맥에선 싸기도 해. 매니페스트가 넘어오고, 해시가 다 맞고, 그 외엔 아무것도 안 움직여. 문제는 사본이 없는 맥이야. 새 상태 폴더, 특정 볼트를 한 번도 안 읽은 기계, 프로세스 사이에 아무것도 못 보관하는 샌드박스. 그런 맥에선 "노트 단위"가 곧 노트마다 네트워크 요청 하나라는 뜻이야.

2026-11-10, 같은 집 안(Thunderbolt 링크)에서 워크벤치가 라이브 저장소를 대상으로 쟀어. 로컬 사본 없이 모든 볼트의 모든 노트를 읽는 데 요청 495번, 5.5초가 걸렸어. 그중 노트 하나짜리 요청 480번이 4.4초. 사본 없이 정체성 로드만 하면 문서 67개에 요청 70번, 0.84초. 집 안에선 괜찮아. 호텔 링크에선 괴로워. 요청 하나하나가 왕복이니까.

노트 말고 볼트를 달라고 해

Coop todo 1110-41이 저장소 공유에 새 질문 하나를 붙였어. notes?vault=야. 볼트 하나의 노트를 전부, 혹은 이름 붙인 목록만(paths, JSON 목록, 요청당 이름 몇천 바이트) 돌려줘. 답에 든 노트는 여전히 하나하나 리더의 매니페스트와 대조하고, 맞는 것만 보관해. 바뀐 건 질문 횟수야. 리더는 얼마나 빠졌는지를 보고 방식을 골라.

  • 빠진 노트가 넷 이하: 예전처럼 하나씩 읽어. 누가 방금 쓴 노트 하나에 배치까지 쓸 필요는 없어.
  • 넷 넘게 빠짐: 빠진 노트만 정확히 이름 대서 배치로 물어. 다른 데서 쓴 노트 몇 개 때문에 느린 링크로 볼트 전체를 끌고 오는 일은 없어.
  • 볼트 절반 이상 빠짐: 답 하나로 전체를 달라고 해. 그 답은 같은 볼트 버전을 읽는 모든 리더에게 똑같으니까, 리더 쪽 Delta가 그 맥의 다음 리더를 위해 들고 있어.

사본을 보관 못 하는 리더(읽기 전용 샌드박스)는 가져온 걸 프로세스가 살아 있는 동안 들고 있어. 정본 맥의 공유가 아직 새 질문을 모르면, 그 맥에선 예전처럼 노트를 하나씩 읽어. 그래서 롤아웃 중에도 옛 버전과 새 버전이 함께 돌 수 있어.

이후의 숫자

같은 맥, 같은 저장소, 같은 날.

  • 사본 없이 모든 볼트의 모든 노트: 전엔 요청 495번 5.5초, 후엔 요청 33번 1.1초(배치 요청 16번이 0.48초).
  • 사본 없이 정체성 로드: 전엔 요청 70번 0.84초, 후엔 요청 5번 0.20초.
  • 사본 있는 정체성 로드: 요청 3번, 전 0.09초, 후 0.04초.
  • 같은 프로세스의 두 번째 실행: 양쪽 다 요청 0번.

저장소는 첫 측정 때 볼트 14개에 노트 480개(3.75 MB), 두 번째엔 16개에 496개를 들고 있었어. 정직하게 밝힐 비용이 하나 있어. 배치 답은 바이트를 base64로 실어서, 노트 3.8 MB가 선로 위에선 5.1 MB가 돼. 빈칸도 하나 있어. 집 밖에선 안 쟀어. 아키텍처 문서에도 그대로 그렇게 적혀 있고, 내세울 수 있는 주장은 정성적인 것 하나뿐이야. 요청을 하나 아낄 때마다 네가 쓰는 링크의 왕복 하나를 아낀다는 것.

옛 숫자는 어디서 왔나

앞선 리뷰는 한 번 돌 때마다 노트 하나짜리 요청이 2,123번 나간다고 보고했어. 알고 보니 은퇴한 폴더 구현이 자기 공유를 통해 사본을 노트 하나하나 증명하던 거였어(이사 전 그 공유 로그엔 그런 요청이 174,177번). 새 저장소의 리더는 보관한 사본을 노트마다 묻지 않고 매니페스트로 검증하니까, 이사 뒤로 노트 하나짜리 요청은 사본 없는 맥에서만 나왔어. 바로 이 레슨이 고친 경우지. 최적화하기 전에 재 보고, 잰 대상이 지금도 돌고 있는 바로 그건지 확인해.

Code

빠진 걸 어떻게 읽을지 고르기 (기준치 스케치)·python
def fetch_missing(vault, manifest, kept, share):
    missing = [uri for uri, sha in manifest.items()
               if uri not in kept or sha256(kept[uri]) != sha]   # an empty note is a note
    if not missing:
        return {}
    if len(missing) <= 4:                                  # a note or two: one by one
        got = {uri: share.note(uri) for uri in missing}
    elif len(missing) * 2 >= len(manifest):                # half or more: the whole vault, once
        got = share.notes(vault=vault)
    else:                                                  # some: name exactly what is lacking
        got = {}
        for chunk in chunks(missing, by_bytes=4000):
            got.update(share.notes(vault=vault, paths=chunk))
    return {uri: data for uri, data in got.items()
            if uri in missing and sha256(data) == manifest[uri]}   # keep only what verifies
워크벤치, 같은 집, 2026-11-10 측정·text
read                                         before                  after
-------------------------------------------  ----------------------  -----------------------
every note of every vault, no copy here      495 requests, 5.5 s     33 requests, 1.1 s
identity load (67 documents), no copy        70 requests, 0.84 s     5 requests, 0.20 s
identity load, copy in place                 3 requests, 0.09 s      3 requests, 0.04 s
a second pass in the same process            0 requests              0 requests

not measured: away from home (each saved request = one round trip of that link)

External links

Exercise

작은 항목 여럿을 한 번에 하나씩 가져오는 클라이언트를 골라. 콜드(캐시 없음)일 때와 웜일 때를 재. 이름 붙인 목록이나 전부를 돌려주는 배치 엔드포인트를 더해. 빠진 항목 수를 보고 하나씩·이름 붙인 배치·전부 중에 고르고, 매니페스트와 맞는 항목만 보관해. 다시 재고, 어느 링크는 쟀고 어느 링크는 안 쟀는지 적어.
Hint
기준치는 함수 하나에 모아 두고, 숫자마다 이유를 주석으로 달아. 그리고 닿을 수 있는 가장 느린 링크에서 재. 하나씩 가져오기와 배치의 차이는 왕복 시간이 1밀리초 늘 때마다 커져.

Progress

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

댓글 0

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

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