웹 개발 데일리 브리핑 — 2026-10-12
Soshy·

기준 시점: 2026-10-12 06:00 KST
조사 범위: 2026-10-11 06:00 ~ 2026-10-12 06:00 KST
추천 글 범위: 기준 시점 당시 최근 7일
1️⃣ 프론트엔드
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
2️⃣ 웹 플랫폼·브라우저
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
3️⃣ 백엔드·인프라
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
4️⃣ 개발 도구 및 보안
이번 조사 범위에서 포함할 만한 주요 신규 발표를 확인하지 못했습니다.
📚 추천 글
5 Best JavaScript Frameworks for SEO — 동일한 페이지를 8가지 렌더링 환경에서 비교한 실험
- 저자: Ali / Seotal Research
- 게시일: 2026-10-10
- 원문: https://www.seotal.com/blog/best-javascript-frameworks-for-seo
- 측정 데이터: https://www.seotal.com/data/best-javascript-frameworks-for-seo-summary.json
Astro, SvelteKit, Qwik, Next.js, Nuxt, React Router, Angular와 일반적인 React SPA를 대상으로 서버가 처음 반환하는 HTML과 브라우저에서 실행되는 JavaScript의 차이를 직접 측정한 글이다.
일반적인 프레임워크 비교 글과 다른 점은 기능 목록이나 개발자 선호도를 나열하는 데 그치지 않고, 동일한 콘텐츠를 여러 프레임워크로 구현해 실제 결과물을 비교했다는 것이다.
특히 SEO 점수와 검색 엔진이 실제로 접근할 수 있는 콘텐츠를 구분했다는 점이 흥미롭다.
동일한 페이지를 서로 다른 프레임워크로 구현한 이유
검색 엔진 최적화 측면에서 프레임워크를 비교할 때 흔히 발생하는 문제가 있다.
Astro로 만든 정적인 블로그와 Next.js로 만든 복잡한 전자상거래 사이트를 비교하면 실제 측정 결과는 프레임워크 자체보다 사이트의 구성에 더 크게 영향을 받을 수 있다.
이미지의 크기, 외부 분석 스크립트, 광고, 사용자가 설치한 라이브러리, 인터랙션 수 등이 모두 다르기 때문이다.
이 실험에서는 이러한 차이를 줄이기 위해 각 프레임워크에 동일한 콘텐츠를 구현했다.
테스트 페이지는 약 410단어 분량의 글이며 다음 요소를 포함한다.
- 페이지 제목과 메타 설명
- Canonical URL
- Article JSON-LD 구조화 데이터
- H1 제목과 본문 10개 문단
- 이미지 한 개
- 클릭할 수 있는 버튼 한 개
- 일반적인 링크 두 개
각 프레임워크의 공식적인 렌더링·빌드 기능을 사용해 정적 HTML을 생성했다.
반면 비교 대상으로 포함한 React SPA는 Vite로 빌드하고 클라이언트에서 JavaScript를 실행해야 콘텐츠가 나타나도록 구성했다.
모든 페이지는 동일한 로컬 서버에서 제공했으며 텍스트 응답에는 gzip 압축을 적용했다.
이후 JavaScript를 실행하지 않는 HTTP 요청으로 초기 HTML을 확인하고, Lighthouse 12.8.2를 이용해 모바일 성능을 측정했다.
Lighthouse 측정은 프레임워크마다 세 번 수행한 뒤 각 지표의 중앙값을 사용했다.
초기 HTML에 콘텐츠가 포함돼 있는지가 중요하다
가장 먼저 확인한 항목은 JavaScript를 실행하지 않았을 때 서버 응답에 실제 콘텐츠가 존재하는지였다.
서버 렌더링이나 정적 생성을 사용하는 일곱 프레임워크에서는 제목, 본문, Canonical URL, JSON-LD 등 주요 SEO 요소가 초기 HTML에 포함돼 있었다.
반면 일반적인 React SPA에서는 결과가 달랐다.
React SPA가 처음 반환한 HTML의 크기는 278바이트였다.
해당 HTML에는 실제 글의 제목이나 본문이 포함되지 않았다.
브라우저가 JavaScript를 실행한 뒤에야 사용자가 페이지 콘텐츠를 볼 수 있었다.
그런데 Lighthouse의 SEO 점수는 흥미롭게도 100점이었다.
Lighthouse는 JavaScript가 실행된 이후의 페이지를 검사할 수 있기 때문에 최종적으로 올바른 메타 정보와 콘텐츠가 존재하면 높은 점수가 나올 수 있다.
반면 JavaScript를 실행하지 않는 크롤러는 서버가 처음 전달한 빈 HTML만 확인한다.
따라서 Lighthouse에서 높은 SEO 점수를 얻었다는 사실만으로 모든 크롤러가 페이지의 콘텐츠를 읽을 수 있다고 판단할 수는 없다.
이 실험은 렌더링 이후의 DOM과 초기 HTTP 응답을 별도로 검증해야 하는 이유를 구체적으로 보여준다.
Google처럼 JavaScript 렌더링을 수행하는 검색 엔진도 있지만, 모든 크롤러가 같은 기능을 제공하는 것은 아니다.
특히 일부 AI 크롤러는 JavaScript를 실행하지 않거나 제한적으로 처리할 수 있어 초기 HTML에 실제 콘텐츠가 포함돼 있는지가 중요해진다.
프레임워크별 JavaScript 전송량 비교
연구진은 각 프레임워크가 브라우저에 전달하는 JavaScript 크기도 측정했다.
아래 수치는 Lighthouse의 전송량 정보와 HTML 내부의 스크립트를 합산한 값이다.
| 프레임워크 | JavaScript 전송량 | LCP | TBT |
|---|---|---|---|
| Astro | 0.1KB | 903ms | 7ms |
| Qwik | 28.1KB | 1,420ms | 0ms |
| SvelteKit | 37.1KB | 1,614ms | 40ms |
| Nuxt | 69.8KB | 1,543ms | 48ms |
| Angular | 76.9KB | 1,419ms | 179ms |
| React Router | 113.8KB | 1,729ms | 10ms |
| Next.js | 142.8KB | 1,368ms | 386ms |
| React SPA | 68.2KB | 1,409ms | 9ms |
LCP(Largest Contentful Paint)는 화면의 주요 콘텐츠가 표시되는 데 걸린 시간을 나타낸다.
TBT(Total Blocking Time)는 페이지 로딩 과정에서 메인 스레드가 긴 작업 때문에 차단된 시간을 측정하는 실험실 지표다.
여기서 가장 큰 차이를 보인 것은 Astro와 Next.js였다.
Astro는 테스트 페이지에 약 0.1KB의 JavaScript만 전달했다.
반면 Next.js는 약 143KB를 전달했다.
두 페이지 모두 서버가 반환하는 HTML에 필요한 콘텐츠를 포함하고 있었지만, 브라우저가 처리해야 하는 JavaScript의 양은 크게 달랐다.
이러한 차이는 렌더링 모델과 관련된다.
Astro는 기본적으로 컴포넌트를 HTML로 변환하고, 클라이언트에서 실제로 동작해야 하는 부분에만 JavaScript를 전달한다.
Next.js는 React 기반 애플리케이션의 hydration과 클라이언트 상호작용을 지원하기 위한 런타임을 포함한다.
테스트 페이지에는 버튼 하나만 존재했지만, 해당 상호작용을 지원하기 위해 추가적인 JavaScript가 필요했다.
그렇다고 Astro가 모든 웹 애플리케이션에서 Next.js보다 빠르다고 결론 내릴 수는 없다.
복잡한 사용자 상태, 실시간 데이터, 클라이언트 내비게이션이 많은 애플리케이션에서는 필요한 기능과 실행 비용이 달라지기 때문이다.
이 실험은 동일한 단순 콘텐츠 페이지를 구현했을 때 발생하는 기본 실행 비용을 비교한 것으로 이해하는 것이 적절하다.
실제 운영 사이트의 Core Web Vitals와 비교
연구진은 실험실 측정과 별도로 HTTP Archive의 2026년 8월 데이터를 이용해 실제 운영 사이트의 Core Web Vitals 통과 비율도 비교했다.
공개된 모바일 통과 비율은 다음과 같다.
| 프레임워크 | 모바일 Core Web Vitals 통과 비율 |
|---|---|
| Astro | 71.0% |
| Qwik | 62.5% |
| SvelteKit | 50.6% |
| Next.js | 35.1% |
| Nuxt | 28.6% |
Astro가 가장 높은 비율을 기록했고 Nuxt는 비교 대상 가운데 낮은 비율을 보였다.
그러나 이 수치는 앞서 진행한 실험과 성격이 다르다.
실험실 측정에서는 동일한 콘텐츠를 사용했지만, 실제 운영 사이트 데이터에는 서로 다른 목적과 규모의 웹사이트가 포함된다.
예를 들어 Astro는 콘텐츠 중심 사이트에서 많이 사용되고 Next.js는 복잡한 서비스에서도 널리 사용된다.
따라서 Core Web Vitals 통과 비율의 차이를 프레임워크 자체의 성능 차이로 직접 해석하면 안 된다.
외부 스크립트, 이미지 최적화, 클라이언트 기능의 복잡도와 개발팀의 구현 선택이 결과에 영향을 미친다.
Qwik의 경우 분석에 포함된 실제 모바일 사이트가 1,334개로 다른 주요 프레임워크보다 적다는 제한도 있다.
실험에서 주의할 점
이 연구는 SEO 전문 업체인 Seotal이 직접 수행한 자체 실험이다.
테스트 데이터와 측정 방법을 공개했다는 장점이 있지만, 독립적인 제3자가 결과를 재현해 검증한 연구는 아니다.
또한 각 프레임워크에서 테스트한 페이지는 한 개뿐이며 Lighthouse도 각각 세 번씩 실행했다.
따라서 절대적인 성능 순위를 결정하기에는 표본이 제한적이다.
측정 환경 역시 로컬 서버와 특정 컴퓨터에 한정돼 있어 CDN, 실제 네트워크 환경, 브라우저 캐시, 사용자 기기 성능에 따라 결과가 달라질 수 있다.
그럼에도 이 글은 웹사이트의 SEO를 평가할 때 Lighthouse 점수, 초기 HTML, JavaScript 실행 비용, 실제 사용자 성능을 서로 다른 지표로 구분해야 한다는 점을 실험으로 설명한다.
특히 CSR, SSR, SSG와 hydration의 차이를 단순한 개념 설명이 아니라 실제 생성된 HTML과 전송량으로 비교한다는 점에서 읽을 가치가 있다.
How to Design a Multi-Tenant SaaS Application
- 저자: Abhay Darji
- 게시일: 2026-10-09
- 원문: https://www.abhaydarji.com/blogs/how-to-design-multi-tenant-saas-application/
하나의 애플리케이션으로 여러 고객사의 서비스를 제공하는 멀티테넌트(Multi-Tenant) SaaS를 설계할 때 어떤 결정을 먼저 내려야 하는지를 설명한 글이다.
저자는 데이터 격리, 고객별 설정, 배포의 영향 범위, 장애 분석, 데이터 수명주기라는 문제를 중심으로 설계 선택과 변경 비용을 비교한다.
멀티테넌트 구조는 초기 구현보다 서비스가 성장한 이후의 유지보수에서 차이가 크게 드러난다.
고객이 한두 곳일 때는 고객별 조건문을 추가하거나 데이터를 직접 구분하는 방식으로도 기능을 제공할 수 있다.
하지만 고객 수가 늘어나면 같은 코드에서 서로 다른 설정과 데이터 접근 권한을 관리해야 한다.
이때 초기에 결정한 데이터 구조가 이후 확장과 보안에 큰 영향을 미친다.
테넌트 데이터를 격리하는 세 가지 방식
글에서는 먼저 데이터 저장 구조를 세 가지로 구분한다.
| 방식 | 구조 | 주요 특징 |
|---|---|---|
| 공유 데이터베이스·공유 스키마 | 모든 고객이 동일한 테이블을 사용하고 tenant_id로 구분 | 운영 비용이 낮지만 데이터 접근 범위 검증이 중요 |
| 공유 데이터베이스·개별 스키마 | 하나의 DB에서 고객마다 별도 스키마 사용 | 격리 수준이 높아지지만 마이그레이션 관리가 복잡 |
| 개별 데이터베이스 | 고객마다 별도 DB 또는 인스턴스 사용 | 강한 격리가 가능하지만 운영 비용과 관리 대상이 증가 |
저자는 특별한 계약상 요구가 없다면 공유 데이터베이스·공유 스키마 구조에서 시작하는 접근을 제안한다.
대신 특정 고객에게 더 강한 격리가 필요해졌을 때 별도 데이터베이스로 이동할 수 있도록 모든 데이터 접근 과정에 테넌트 식별자를 일관되게 전달하는 구조를 강조한다.
특히 테넌트 식별자의 출처가 중요하다.
사용자가 HTTP 요청 본문이나 Query Parameter에 임의로 입력한 tenant_id를 그대로 신뢰하면 다른 고객의 데이터에 접근할 수 있는 취약점으로 이어질 수 있다.
따라서 테넌트 정보는 서버가 검증한 인증 세션이나 신뢰할 수 있는 라우팅·도메인 설정에서 결정해야 한다.
데이터 접근 계층에서 격리를 강제하는 방법
공유 스키마 방식에서 가장 위험한 오류 가운데 하나는 데이터 조회 시 테넌트 조건을 누락하는 것이다.
예를 들어 주문 목록을 가져오는 API에서 tenant_id 조건이 빠지면 다른 고객의 주문까지 반환할 수 있다.
저자는 코드 리뷰만으로 이 문제를 해결하기보다 애플리케이션 구조상 테넌트 범위가 없는 쿼리를 만들기 어렵게 설계할 것을 제안한다.
예제에서는 인증된 테넌트 ID로 repository를 생성하고 해당 repository를 통해서만 데이터를 조회하도록 한다.
관계형 데이터베이스에서는 Row-Level Security(RLS)를 활용해 데이터베이스가 직접 접근 범위를 제한하는 방식도 소개한다.
다만 원문 코드에는 주의해야 할 문제가 하나 있다.
예제의 조회 함수는 테넌트 범위인 scope와 외부에서 전달받은 쿼리 객체 q를 다음 순서로 합친다.
{ ...scope, ...q }
JavaScript 객체에서는 나중에 펼친 객체의 속성이 앞선 값을 덮어쓸 수 있다.
따라서 q에 pk가 포함된다면 앞서 설정한 테넌트 키가 덮어써질 수 있다.
이는 원문이 강조하는 테넌트 격리 원칙과 충돌한다.
실제로 데이터 접근 범위를 강제하려면 허용된 검색 조건만 받아들이거나, 신뢰할 수 있는 테넌트 조건이 최종적으로 적용되도록 구성해야 한다.
중요한 데이터라면 애플리케이션 계층의 필터링 외에도 데이터베이스 권한이나 RLS로 격리를 검증하는 편이 안전하다.
이 사례는 보안 경계를 만든다는 설계 의도가 실제 코드에서 동일하게 보장되는지 별도로 확인해야 한다는 점을 보여준다.
고객별 기능을 조건문으로 관리하지 않는 이유
멀티테넌트 SaaS에서는 고객마다 서로 다른 요구사항이 발생한다.
어떤 고객은 별도의 가입 절차를 원하고, 다른 고객은 특정 관리 기능을 비활성화하고 싶어 할 수 있다.
가장 단순한 구현은 고객 ID를 기준으로 분기하는 것이다.
그러나 tenant === 'acme'처럼 특정 고객 이름을 코드에 직접 포함하면 새로운 고객을 추가할 때마다 코드 수정과 배포가 필요해진다.
저자는 이런 분기를 기능의 의미를 나타내는 설정값으로 바꾸는 방식을 소개한다.
예를 들어 특정 고객의 이름을 확인하는 대신 config.enrolment.requiresEmployeeId 같은 설정을 사용한다.
설정은 플랫폼 기본값과 고객별 Override를 조합해 결정한다.
이렇게 구성하면 새로운 고객을 추가할 때 공통 애플리케이션 코드를 수정하지 않고 설정만 변경할 수 있다.
하지만 모든 차이를 설정으로 만드는 것도 좋은 선택은 아니다.
설정 하나가 늘어날 때마다 테스트해야 하는 동작의 조합도 증가하기 때문이다.
저자는 실제로 여러 고객이 공유할 가능성이 있는 기능은 공통 설정으로 관리하되, 극히 제한적인 고객 요구사항은 별도의 확장 지점으로 처리하는 선택을 제안한다.
배포 장애의 영향 범위를 줄이는 설계
데이터 격리만큼 중요한 문제는 운영 중 발생하는 장애의 영향 범위다.
멀티테넌트 환경에서 여러 고객이 동일한 서버와 데이터베이스를 공유하면 특정 고객의 작업이 전체 서비스에 영향을 줄 수 있다.
예를 들어 고객 한 곳에서 대량의 데이터 가져오기를 실행하면서 데이터베이스 연결이나 작업자 자원을 대부분 사용한다면 다른 고객의 일반적인 요청까지 느려질 수 있다.
이를 방지하기 위해 글에서는 다음과 같은 운영 구조를 제안한다.
- 고객별 요청량 제한
- 백그라운드 작업의 동시 실행 수 제한
- 일부 고객을 대상으로 먼저 배포하는 점진적 배포
- 특정 고객의 작업만 일시 중지할 수 있는 제어 기능
로그와 모니터링 데이터에도 테넌트 정보를 포함하도록 설계한다.
장애가 발생했을 때 먼저 확인해야 할 질문은 문제가 모든 고객에게 발생하는지, 특정 고객에게만 발생하는지이기 때문이다.
로그에 tenantId와 traceId가 포함돼 있으면 동일한 오류를 고객별로 구분할 수 있다.
반면 이러한 정보가 없다면 장애가 발생한 뒤에야 요청 로그와 데이터베이스 기록을 다시 연결해야 한다.
데이터 수명주기까지 고려해야 한다
서비스를 사용하는 고객은 언젠가 데이터를 내보내거나 서비스 이용을 종료할 수 있다.
이때 특정 고객의 데이터만 정확하게 추출하고 삭제할 수 있어야 한다.
초기 데이터 모델에서 테넌트 식별자가 일관되게 관리되지 않았다면 고객 한 곳의 데이터를 삭제하는 작업이 복잡해질 수 있다.
데이터뿐 아니라 첨부파일, 백그라운드 작업, 로그, 백업에 남아 있는 정보를 어떻게 처리할지도 고려해야 한다.
다만 이 글은 실제 운영 시스템의 성능 측정이나 장애 감소 수치를 제공하지 않는다.
또한 앞서 살펴본 데이터 접근 코드처럼 제시된 예제가 모든 보안 조건을 완벽하게 구현했다고 볼 수는 없다.
따라서 예제 코드를 그대로 가져다 사용하기보다 멀티테넌트 설계에서 무엇을 시스템 차원에서 강제해야 하고, 무엇을 운영 정책으로 관리해야 하는지를 이해하는 자료로 읽는 것이 적절하다.
Pacing Bulk Async Jobs Under an LLM Rate Limit
- 저자: Jo4 Team
- 게시일: 2026-10-09
- 원문: https://jo4.io/blog/pacing-bulk-async-llm-jobs/
외부 LLM API를 호출하는 대량의 비동기 작업에서 429 Rate Limit 오류가 반복적으로 발생할 때 재시도 횟수를 늘리는 대신 작업을 시작하는 속도 자체를 조절하는 방법을 설명한 글이다.
Java 기반 서버 애플리케이션에서 발생한 문제와 이를 해결하기 위해 사용한 스케줄링 코드를 직접 소개한다.
AI 기능에 한정되지 않고 외부 API를 대량으로 호출하는 백엔드 작업에도 적용할 수 있는 내용이다.
비동기 작업을 한꺼번에 실행할 때 발생하는 문제
관리자 페이지에서 전체 데이터를 다시 처리하는 기능을 생각해 보자.
관리자가 버튼을 누르면 서버는 수백 개의 데이터 항목을 조회하고 각 항목에 대해 AI API를 호출해야 한다.
가장 단순한 구현은 모든 항목을 비동기 작업으로 등록하는 것이다.
하지만 이 방식은 외부 API의 처리량 제한을 고려하지 않는다.
예를 들어 동시에 500개의 작업을 등록했다고 가정하자.
실제 실행에 사용하는 Thread Pool의 크기가 10이라면 처음에는 10개의 작업이 실행되고 나머지 작업은 대기한다.
그러나 먼저 실행된 작업들이 비슷한 시점에 완료되면 새로운 작업도 거의 동시에 실행된다.
결과적으로 외부 API에는 짧은 시간 안에 다수의 요청이 몰릴 수 있다.
이때 API 제공업체가 허용한 요청량을 초과하면 HTTP 429 응답이 발생한다.
애플리케이션이 Exponential Backoff를 사용하더라도 문제가 완전히 해결되지는 않는다.
오히려 여러 작업이 비슷한 시점에 실패하고 비슷한 간격으로 재시도하면서 다시 요청이 집중될 수 있다.
저자는 실제로 대량의 비동기 작업을 실행했을 때 이러한 문제가 발생했다고 설명한다.
요청 수보다 토큰 사용량이 실제 제한일 수 있다
LLM API에서는 Rate Limit을 단순한 요청 수로만 계산하지 않는 경우가 많다.
요청 100개가 각각 100토큰을 사용한다면 전체 사용량은 10,000토큰이다.
반면 요청 10개가 각각 10,000토큰을 사용한다면 전체 사용량은 100,000토큰이다.
따라서 초당 요청 수가 낮더라도 각 요청에서 많은 토큰을 사용하면 제공업체의 TPM(Tokens Per Minute) 제한을 초과할 수 있다.
저자는 이를 해결하려면 먼저 현재 API 계정에 적용된 실제 제한과 요청당 평균 토큰 사용량을 확인해야 한다고 설명한다.
단순히 비동기 실행 스레드 수를 줄이는 것만으로는 외부 API의 토큰 제한을 정확하게 관리하기 어렵다.
작업을 시작하는 시점을 분산하는 방식
저자가 선택한 방법은 각 작업을 즉시 비동기 실행 환경으로 전달하지 않고 작업을 시작하는 시점 사이에 일정한 간격을 두는 것이다.
구현에는 Java의 ScheduledExecutorService를 사용한다.
핵심 구조는 단순하다.
작업 목록의 첫 번째 항목은 즉시 실행하고, 두 번째는 1.5초 뒤, 세 번째는 3초 뒤, 네 번째는 4.5초 뒤에 실행하도록 등록한다.
작업 번호를 i라고 하면 실행 지연 시간은 다음과 같다.
i × PACING_MS
원문에서는 PACING_MS를 1,500ms로 설정했다.
중요한 점은 스케줄러가 실제 LLM API 호출이 끝날 때까지 기다리는 방식이 아니라는 것이다.
스케줄러는 정해진 시점에 기존 비동기 작업을 시작하도록 요청한 뒤 다음 작업을 기다린다.
즉 스케줄러가 작업의 실행 간격을 조절하고 기존 비동기 실행 환경은 실제 작업을 처리하는 역할을 맡는다.
이렇게 두 책임을 분리하면 대량의 백그라운드 작업 때문에 일반적인 사용자 요청의 처리량이 감소하는 문제를 줄일 수 있다.
Thread Pool 크기를 줄이는 것과의 차이
비슷한 문제를 해결하기 위해 Thread Pool의 크기를 줄일 수도 있다.
예를 들어 동시에 실행할 수 있는 스레드를 10개에서 2개로 줄이면 한꺼번에 실행되는 요청 수도 감소한다.
하지만 비동기 실행 환경이 다른 기능과 공유되고 있다면 문제가 발생한다.
사용자가 직접 실행한 작업이나 실시간 API 요청도 동일한 Pool을 사용한다면, 관리자용 대량 작업을 제한하기 위해 전체 애플리케이션의 동시 처리량을 줄이는 결과가 된다.
저자는 따라서 비동기 실행 환경 전체를 제한하는 대신 Rate Limit이 필요한 작업의 등록 속도만 제한하는 접근을 선택했다.
이 방식은 다른 작업의 동시 실행 능력을 유지하면서 특정 대량 작업의 요청 패턴을 조절할 수 있다는 장점이 있다.
재시도는 완전히 제거하지 않는다
작업을 일정한 간격으로 실행한다고 해서 429 오류가 절대 발생하지 않는 것은 아니다.
다른 애플리케이션이나 서버 인스턴스가 동일한 API Key를 사용하고 있을 수 있기 때문이다.
또한 LLM 요청마다 사용하는 토큰 수가 달라질 수 있어 고정된 요청 간격만으로 토큰 사용량을 정확하게 제어하기 어렵다.
따라서 저자는 Pacing과 Retry를 함께 사용한다.
Pacing은 애플리케이션 스스로 짧은 시간에 과도한 요청을 발생시키지 않도록 제한한다.
Retry와 Backoff는 예상하지 못한 Rate Limit 오류를 처리한다.
두 방식은 서로 대체 관계가 아니라 다른 문제를 해결한다.
프로세스가 종료되면 예약된 작업이 사라진다
이번 구현의 가장 큰 제한은 스케줄러가 메모리 안에서만 작업을 관리한다는 점이다.
관리자가 500개의 작업을 등록했는데 서버가 200번째 작업을 실행하기 전에 재시작되면 아직 실행되지 않은 작업은 사라질 수 있다.
원문에서는 이런 상황을 허용한다.
해당 기능이 언제든 다시 실행할 수 있는 관리 작업이기 때문이다.
반대로 결제, 주문 처리, 중요한 데이터 변경처럼 반드시 완료돼야 하는 작업에는 같은 접근을 그대로 적용하기 어렵다.
이러한 경우에는 메시지 큐나 데이터베이스 기반 작업 테이블을 사용해 작업의 진행 상태를 저장하고, 서버가 재시작되더라도 미완료 작업을 다시 처리할 수 있도록 구성해야 한다.
또한 원문의 구현은 하나의 애플리케이션 프로세스 안에서 요청 간격을 조절하는 방식이다.
여러 서버 인스턴스가 동시에 실행된다면 각 인스턴스가 독립적으로 요청을 보내기 때문에 전체 API 사용량은 여전히 제한을 초과할 수 있다.
여러 인스턴스가 하나의 API Key와 Rate Limit을 공유한다면 Redis 기반 Rate Limiter나 중앙 집중식 작업 큐처럼 전체 요청량을 함께 조정하는 구조가 필요할 수 있다.
글에는 개선 전후의 429 발생률이나 전체 처리 시간에 대한 정량적인 benchmark는 제공되지 않는다.
따라서 실제 성능 향상 폭을 판단하기보다는 Rate Limit이 존재하는 외부 API에 대량 요청을 보낼 때 실행 속도와 동시성을 왜 구분해서 관리해야 하는지를 이해하는 사례로 보는 것이 적절하다.
특히 AI API를 사용하는 백엔드에서 모든 작업을 무조건 병렬로 실행하거나, 오류가 발생한 뒤 재시도 횟수만 늘리는 접근의 한계를 구체적인 코드와 운영 상황으로 설명한다.