한 화면에 모으려다 생긴 21초
흩어져 있던 SNS 현황을 한 화면에서 보고 싶었습니다. 인스타그램 4개 계정은 이미 만들어 둔 인사이트 수집 코드를 그대로 가져다 쓰고, Threads 쪽은 현재 상태를 실시간으로 붙였습니다. 접속은 따로 프록시를 두지 않고 tailnet IP로 직접 붙는 방식이고, 포트 8892에 띄운 파이썬 파일 하나입니다.
문제는 데이터를 처음부터 모으는 콜드 로드에 약 21초가 걸린다는 점이었습니다. 계정 여러 개를 순서대로 돌며 외부 API를 부르니, 이 시간을 제 코드에서 크게 줄일 방법은 마땅치 않았습니다.
그래서 질문은 수집을 빠르게 만드는 쪽이 아니라 다른 쪽으로 바뀌었습니다. 당신의 대시보드는 요청이 들어온 그 순간에 데이터를 모으기 시작하나요, 아니면 이미 모아둔 것을 보여주기만 하나요?
함정은 21초가 아니라 그 21초를 누가 기다리느냐
가장 단순하게 짜면 요청이 오고, 수집하고, 그린 다음 응답합니다. 이러면 브라우저를 연 사람이 21초를 그대로 기다립니다.
여기에 흔히 하는 대로 동시 수집을 막으려고 락을 걸면 상황이 더 나빠집니다. 수집 전체를 락 안에 넣으면 두 번째 요청은 첫 번째 수집이 끝날 때까지 락 앞에서 기다리게 됩니다. 캐시를 읽기만 하면 되는 요청도 같은 락을 잡으려 한다면, 갱신 중에는 읽기까지 막힙니다. 결국 화면 하나를 보려는 모든 요청이 가장 느린 외부 호출에 묶입니다.
수집 시간 자체가 외부 API 몫이라, 여기에는 값싼 수정이 없었습니다. 남은 선택지는 이 시간을 요청 경로 밖으로 빼는 것뿐이었습니다.
당신이라면 수집을 빠르게 만드는 데 먼저 시간을 쓰겠습니까, 아니면 아무도 그 시간을 기다리지 않게 구조를 바꾸겠습니까?
바꾼 구조
커밋에 남긴 원칙은 네 가지입니다.
- 수집은 락 밖에서 합니다. 21초 걸리는 작업 동안에는 아무 락도 잡지 않습니다. 락은 다 모은 결과를 캐시에 넣는 짧은 순간에만 씁니다.
- 요청은 캐시만 읽고, 막히지 않습니다. 요청 처리 코드는 수집 함수를 부르지 않습니다. 지금 캐시에 있는 것을 바로 돌려줍니다.
- 캐시가 비어 있으면 로딩 페이지를 즉시 보여줍니다. 서버를 막 띄운 직후처럼 아직 아무것도 모으지 못했을 때도 요청이 21초를 붙잡고 있지 않습니다.
- warmer 스레드가 백그라운드에서 다시 채웁니다. 갱신은 요청이 아니라 별도 스레드가 맡습니다.
이렇게 하면 콜드 로드는 여전히 약 21초 걸리지만, 그 시간이 응답 시간에 더해지지는 않습니다. 커밋 메시지에 '무영향화'라고 적은 것도 그런 뜻입니다. 빨라진 것이 아니라, 기다리는 사람이 없어졌습니다.
자가진단 체크리스트
- 요청 핸들러 안에서 외부 API를 호출하는 수집 함수를 직접 부르고 있지는 않은가요?
- 느린 작업 전체를 감싸는 락이 있고, 캐시를 읽기만 하는 요청도 그 락을 기다리게 되어 있지는 않은가요?
- 캐시가 비어 있을 때 요청이 수집이 끝나기를 기다리나요, 아니면 바로 응답하나요?
솔직한 부분
이 구조로 바꾼 뒤의 응답 시간을 따로 재서 기록해 두지는 않았습니다. 21초라는 숫자도 콜드 로드 한 경우의 대략적인 값이고, 계정 수나 API 상태에 따라 얼마나 달라지는지는 모릅니다. 대가도 분명히 있습니다. 서버를 막 띄운 직후 첫 방문자는 데이터 대신 로딩 페이지를 보게 되고, warmer가 갱신하는 동안에는 직전에 모아둔 데이터가 보입니다. 혼자 보는 대시보드라 이 정도면 괜찮다고 판단했지만, 실시간성이 중요한 화면이라면 같은 결론이 나오지 않을 수 있습니다.