[PonsWarp] 백프레셔는 기다림이 아니라 보호 장치였다
빠르게 보내면 좋은 전송이라고 생각했다. 채널이 받아줄 수 있는 만큼만 보내는 구조는 답답해 보였고, 네트워크가 열려 있는데 왜 일부러 속도를 줄이느냐는 질문도 스스로에게 던졌다. 벤치마크 화면의 송신 숫자는 중독성같은 파일을 더 짧은 시간에 보냈다고 말할 수 있으면, 그 숫자는 곧바로 설계가 맞다는 증거처럼 느껴졌다.
그 확신은 받는 쪽 브라우저가 먼저 무너지면서 깨졌다. 송신 로그는 끝났다고 찍히는데 수신 파일은 중간이 비거나, 완료 직전 화면이 멈추거나, 열리지 않는 결과물이 남았다. 그때부터 백프레셔는 기다림이 아니라 보호 장치로 보이기 시작했다. 느리게 만드는 장치가 아니라 세션이 끝까지 살아남게 만드는 장치였다. 이 글은 그 판단이 어떻게 바뀌었는지 적는다.

받는 쪽이 터지는 순간
전송을 받는 쪽은 네트워크 패킷만 받지 않는다. 조각을 조립하고, 디코딩하고, 디스크나 브라우저 저장소에 쓰고, 진행률과 미리보기 화면을 갱신한다. 송신 측이 네트워크 여유만 보고 밀어붙이면 수신 측 작업 큐가 먼저 찬다. 네트워크 계층의 성공과 애플리케이션 계층의 생존은 다른 문제다.
사용자는 그 장면을 버퍼 문제로 읽지 않는다. 진행률은 거의 찼는데 파일이 열리지 않으면 제품 전체가 거짓말을 한 것처럼 보인다. 작은 수신 과부하가 신뢰 전체로 번지는 이유가 여기에 있다. 전송 기능의 실패는 기능 목록의 한 칸이 아니라, 이 제품에 큰 파일을 맡기면 안 된다는 학습으로 남는다.
특히 모바일 수신에서 차이가 컸다. 배터리 절약 모드, 백그라운드 탭, 느린 저장소 쓰기, 열 제한 때문에 같은 무선망이라도 수신 처리량이 들쭉날쭉했다. 송신 측이 그 차이를 무시하면 빠른 구간에서 쌓인 압력이 한순간에 터진다. 데스크톱에서 보내고 모바일에서 받는 조합은 데모에서는 잘 보여도 실제 사용에서는 가장 먼저 백프레셔가 필요한 조합이었다.
수신이 바쁠 때 송신만 빠르게 도는 구조는 결국 재시도 폭풍을 부른다. 상위 로직이 타임아웃을 보고 같은 조각을 다시 넣고, 이미 바쁜 큐는 더 바빠진다. 빨라지려고 만든 경로가 더 불안정해지는 전형적인 되먹임이다. 이 되먹임을 끊는 장치가 바로 수신 압력 신호다.

처음에는 최대 처리량이 목표였다
초기 설계는 단순했다. 조각 크기를 키우고 병렬을 늘리고 송신 큐를 비우는 데 집중했다. 송신 완료 시각을 줄이는 일이 곧 제품 완성도처럼 보였다. 더 빠르게라는 목표는 반박하기 어려워서 회의에서도 반론이 잘 나오지 않았다.
문제는 보냈다의 주어가 송신 측이었다는 점이다. 수신 무결함과는 다른 사건이었다. 로그에는 송신 완료가 찍히고 사용자 화면에는 깨진 결과물이 남았다. 지표가 잘못된 성공을 학습하고 있었다. 개선 작업일수록 송신 속도는 올라가고 완료 후 손상 문의는 같이 늘 수 있는 구조였다.
병렬을 높일수록 완료 정의도 흐려졌다. 일부 조각이 앞서 나가고 일부는 늦게 도착하는 동안 진행률은 평균으로 미끄러졌다. 평균이 좋아 보여도 말단 구간에서 수신 쓰기가 밀리면 세션은 마지막에 죽는다. 사람은 마지막에 죽은 실패를 더 크게 기억한다.
그래서 나는 목표 함수를 바꾸기로 했다. 최대 송신 처리량이 아니라 세션 생존과 수신 무결함을 먼저 놓는 것. 이 전환이 있기 전에는 백프레셔를 넣어도 반쪽짜리 개선이 되기 쉽다. 목표가 송신에 남아 있으면 보호 장치는 거추장스러운 옵션으로 밀린다.

실패 신호가 남긴 반례
가장 선명했던 반례는 송신 완료와 수신 손상의 동시 발생이었다. 네트워크 계층만 보면 성공에 가깝다. 제품 계층에서 보면 실패다. 이 간극을 메우지 않으면 개발자는 전송은 됐다고 하고 사용자는 파일이 안 열린다고 한다. 같은 사건을 두고 팀이 서로 다른 언어로 싸운다.
두 번째 반례는 완료 직전 붕괴였다. 초반 진행은 매끄러운데 마지막 구간에서 수신 쓰기가 밀리며 세션이 죽었다. 평균 속도가 좋아도 말단 구간의 보호가 없으면 체감 품질은 바닥이다. 말단 구간이야말로 백프레셔가 가장 필요한 지점이다.
세 번째 반례는 조용한 드롭이었다. 수신이 감당하지 못한 조각을 그냥 버리고 넘어가면 겉으로는 흐름이 유지되는 듯 보여도 결과물은 손상된다. 드롭은 일시적으로 송신 대기열을 편하게 만들지만 무결성을 팔아 얻은 편안함이다. 파일 전송에서 그 거래는 성립하지 않는다.
네 번째 반례는 사용자 문장의 붕괴다. 보내는 중 하나만 있는 화면은 수신 대기를 설명하지 못한다. 사용자는 멈춘 건지 느린 건지 죽은 건지 구분하지 못한 채 새로고침을 누르고, 그 새로고침이 세션을 더 망가뜨린다. 보호 장치는 상태 문장 없이 완성되지 않는다.
경계를 다시 그은 방식
다시 그은 경계는 단순하다. 송신은 수신 확인 창 안에서만 움직인다. 수신 버퍼가 임계치에 닿으면 일시 중지를 프로토콜로 말하고 드롭으로 숨기지 않는다. 완료는 송신 종료가 아니라 수신 검증 이후에만 선언한다.
| 구성요소 | 책임 | 실패 시 문장 |
|---|---|---|
| 송신 | 확인 창 안 전송만 수행 | 잠시 속도를 조절하는 중 |
| 수신 버퍼 | 임계치에서 일시 중지 | 받는 중, 잠시 대기 |
| 조각 | 재전송 단위 고정 | 이어받는 지점 표시 |
| 완료 판정 | 수신 무결 확인 후 | 전송 완료 |
핵심은 수신 측에게 더 못 받겠다를 말할 권리를 준 것이다. 그 신호가 없으면 송신은 항상 낙관적으로 달린다. 낙관은 데모에서는 멋있고 실제 파일에서는 위험하다. 백프레셔는 그 낙관을 제품 언어로 교정하는 장치다.
상태 문장도 같이 바뀌어야 했다. 보내는 중 하나만 있으면 수신 대기를 설명할 수 없다. 받는 중, 잠시 대기, 이어받기 준비가 같은 상태 기계를 가리킬 때 사용자는 느린 이유를 버그가 아니라 보호로 읽을 수 있다. 문장이 먼저고 애니메이션은 나중이다.
구현·운영에 남긴 조건
백프레셔를 속도 제한으로만 보면 손해처럼 보인다. 보호 장치로 보면 의미가 바뀐다. 목적은 최고 속도가 아니라 세션이 끝까지 살아남는 것이다. 파일 전송에서 중요한 건 평균 속도보다 중간에 죽지 않는 곡선이다.
잠깐 느려져도 이어서 받을 수 있으면 사용자는 기다린다. 반대로 한 번 깨지면 다시 보내지 않는다. 그래서 전송 품질의 1차 지표를 송신 처리량이 아니라 완료율, 재개율, 손상율 쪽으로 옮기는 편이 맞다. 빠르게 죽은 전송은 느린 전송보다 못하다.
이 판단은 직접 전송 경로뿐 아니라 클라우드 보조 경로에도 같은 언어로 가야 한다. 어디를 타든 수신이 감당할 수 있는 속도가 공통 규칙이어야 모드 전환이 가능하다. 모드마다 압력 제어 문법이 다르면 전환 순간에 세션이 다시 죽는다.
보호 장치는 또한 팀의 토론 비용을 줄인다. 송신 완료와 수신 무결함을 다른 사건으로 부르기 시작하면, 개선안이 어떤 사건을 최적화하는지 질문이 선명해진다. 그 질문이 없는 최적화는 대개 송신 편향으로 미끄러진다.
운영 규칙은 짧게 적었다. 송신 완료와 수신 무결함은 다른 사건으로 기록한다. 확인 창 밖 전송은 성능 옵션이 아니라 버그로 취급한다. 버퍼 임계치는 일시 중지로 표현하고 조용한 드롭으로 숨기지 않는다. 완료 버튼과 완료 문장은 수신 검증 이후에만 연다. 재시도는 백프레셔 상태와 충돌하지 않게 설계한다.
측정 체계도 같이 바꾼다. 송신 속도만 올리는 변경이 수신 실패율을 키우는지 확인한다. 모바일 수신, 저사양 노트북 수신, 백그라운드 복귀 수신을 기본 시나리오에 넣는다. 실험실의 유선 환경만으로 전송 개선을 선언하지 않는다.
지원 문장도 같은 사전을 쓴다. 보내는 중, 받는 중, 잠시 대기, 이어받기, 확인 중, 완료. 이 여섯 문장이 내부 상태와 1대1로 맞지 않으면 보호 장치는 사용자에게 닿지 않는다. 규칙이 코드에만 있고 문장에 없으면 반쪽이다.
배포 전에는 반드시 압력 테스트를 넣는다. 수신 측 저장을 인위적으로 늦추고도 송신 측이 폭주하지 않는지, 일시 중지 신호가 오가는지, 재개 후 무결함이 유지되는지 본다. 이 테스트 없이 올린 빠른 전송은 결국 손상 문의로 돌아온다.
백프레셔를 강하게 걸면 왜 이렇게 느려요라는 질문이 돌아온다. 대기 이유를 숨기면 보호 장치는 버그로 보인다. 반대로 설명을 너무 기술적으로 하면 사용자는 무시한다. 받는 기기 속도에 맞추는 중 정도의 문장이 실용적이다.
제품 욕심도 긴장이다. 더 큰 조각, 더 많은 병렬, 더 화려한 진행률은 언제든 송신 편향을 부추긴다. 빠른 모드는 있어도 된다. 안전한 기본값이 먼저다. 기본 경로의 안전장치를 옵션 뒤로 숨기면 대부분의 사용자는 위험한 경로를 타게 된다.
팀 언어의 긴장도 있다. 전송 완료라는 말을 누가 어떤 기준으로 쓰는지 합의되지 않으면 지원팀과 개발팀과 사용자가 서로 다른 완료를 본다. 백프레셔 논의는 결국 완료 정의 논의다. 이 합의 없이 대시보드만 늘리면 숫자 전쟁만 남는다.
다음 전송 경로를 그릴 때 먼저 물을 질문은 얼마나 빠른가가 아니다. 수신이 바쁘다고 말할 수 있는가, 그 말이 송신을 실제로 멈추는가, 멈춘 동안 사용자 문장은 정직한가, 재개 후 무결함을 어떻게 증명하는가다.
이 네 질문에 답하지 못한 채 조각 크기와 병렬도만 조정하면 같은 회고를 다시 쓰게 된다. 백프레셔는 특정 함수 이름이 아니라 이 질문들에 대한 답의 집합이다.
팀 온보딩 때 이 질문을 먼저 공유하면 신규 기여자가 속도 패치를 기본값으로 넣기 전에 한 번 더 멈춘다. 그 한 번의 멈춤이 손상 문의 몇 건을 줄인다.
나는 이제 전송 시연에서 최고 속도 숫자보다 수신 지연을 넣어도 깨지지 않는 모습을 더 중요하게 본다. 그 시연이 가능해야 보호 장치가 제품에 실렸다고 말할 수 있다.
마치며
백프레셔는 전송을 느리게 만드는 장치가 아니다. 받는 쪽을 살려서 전송 전체를 살아남게 하는 장치다. 빠르게 끝나는 구조보다 실패해도 어디서 멈췄는지 설명 가능한 구조가 더 필요했다.
지금은 확인 창 밖 전송을 성능 튜닝이 아니라 결함으로 본다. 다음에 전송 경로를 다시 만지면 첫 그림에 백프레셔를 넣고 송신 완료와 수신 무결을 처음부터 분리해서 시작하겠다. 그 순서를 지키지 않으면 다시 같은 숫자 착각으로 돌아간다.