
웹 스크래핑 서비스를 운영하다 보면 단순한 정적 데이터 수집을 넘어서 로그인이 필요한 사이트의 데이터를 수집해야 하는 경우가 많다. 이때 여러 API 요청이 하나의 로그인 세션을 공유해야 하는 상황이 발생한다.
예를 들어 다음과 같은 시나리오가 있다
단일 서버 환경에서는 이런 요구사항이 문제없이 동작했지만, 비즈니스 확장으로 인해 서버를 스케일 아웃하면서 새로운 도전과제가 생겼다.
서버가 여러 대로 증가하면, 어떻게 특정 브라우저 세션에 대한 후속 요청이 해당 브라우저가 실행 중인 서버로 정확히 라우팅될 수 있는가라는 근본적인 문제가 발생한다.
단일 서버 환경: 모든 요청이 동일한 서버로 들어오므로 메모리에 관리되는 브라우저 세션에 접근 가능하다.

멀티 서버 환경: 로드 밸런서에 의해 요청이 여러 서버로 분산되는데, 후속 요청이 다른 서버로 라우팅되면 기존 로그인 세션을 찾을 수 없다.

쉽게 말해, "특정 사용자의 요청은 항상 같은 서버로 가야 한다"는 것에 대한 문제다.
이런 상황은 서버가 사용자별 정보를 메모리에 저장하고 있을 때 발생한다.
결국, 단일 서버로 사용할때는 문제가 없었으나, 고객사를 늘려야 하는 상황에 추가적으로 문제가 발생했다.
이 문제의 핵심은 각 서버가 브라우저 인스턴스의 상태를 로컬 메모리에서 관리하기 때문이다.
일반적인 웹 애플리케이션에서는 이런 문제가 발생하지 않는다.
데이터베이스 기반 애플리케이션의 경우에는 서버가 상태를 가지지 않는다.

하지만 브라우저 세션의 경우, 로그인 상태, 쿠키, 세션 스토리지 등의 정보가 브라우저 인스턴스 자체에 저장되어 있어 외부로 추상화하기 어렵다.
이 문제를 해결하기 위해 여러가지 접근 방법을 시도해보았다.
DB 패턴을 모방하여 브라우저 관리를 중앙집중화하려고 시도했다. Playwright 서버를 별도로 두고 각 Scraper 서버가 이에 접근하는 구조를 고려했다.

시도 결과 Playwright 서버를 분리하는 것 자체는 좋은 아이디어였다. 브라우저를 실행하는 무거운 작업을 별도 서버에서 처리하니까 스크래핑 서버들의 부담이 크게 줄어들었기 때문이다.
하지만 핵심 문제인 "브라우저 공유"는 해결되지 않았다. Playwright에서 브라우저에 접근하는 방법은 크게 두 가지가 있다:
결국 CDP 방식은 관리 포인트가 너무 많아져서 포기하게 되었다.
비록 브라우저 공유 문제는 해결하지 못했지만, 내부 회의를 통해 Playwright 서버를 분리하는 것 자체는 유지하기로 결정했다. 당장의 문제 해결과는 무관하지만, 장기적으로 보면 다음과 같은 이유로 좋은 판단이었다:
결국 각 서버가 독립적으로 브라우저를 관리하되, 네트워크 리소스는 절약하는 하이브리드 구조를 채택했다.

이 아키텍처의 특징은 다음과 같다.
하지만 아직, 문제를 해결한 것은 아니었다. 여전히 서버 확장 시 사용자의 요청이 1차 요청의 서버가 아닌 다른 서버로 라우팅 되면 브라우저를 찾을 수 없다.
이제 핵심 문제를 해결할 차례다. 이 문제를 해결하는 방법은, 요청을 받았을 때 해당 브라우저 세션이 존재하는 서버로 정확히 라우팅하는 것이다.

전체 AWS 기반 구조에서 다음과 같은 컴포넌트들을 활용한다
세션 생성 시 (1차 요청): 새로운 세션이 생성되면 해당 세션 ID와 서버 정보를 ElastiCache에 저장한다. 이때 TTL을 설정하여 세션의 수명을 관리한다.
세션 기반 라우팅 (2차 이후 요청): Lambda 라우터가 요청에서 세션 ID를 추출하고, ElastiCache에서 해당 세션이 존재하는 서버 정보를 조회한 후 적절한 서버로 라우팅한다.
장점:
잠재적 한계점과 대응방안
이번 프로젝트를 통해 상태를 가진 서비스의 수평 확장이 얼마나 복잡한 문제인지 직접 경험할 수 있었다.
Stateful vs Stateless의 실제 영향
이론적으로만 알고 있던 개념이 실제 비즈니스 요구사항과 충돌할 때의 복잡성을 이해하게 되었다
세션 친화성 패턴의 필요성
브라우저 세션처럼 외부로 추상화하기 어려운 상태의 경우, 지능형 라우팅이 현실적 해결책이 된다
트레이드오프 분석의 중요성
각 아키텍처 결정이 가져오는 장단점을 명확히 파악하고 비즈니스 요구사항에 맞는 선택을 해야 한다
이 문제는 웹 스크래핑뿐만 아니라 다음과 같은 상황에서도 동일하게 적용될 수 있다:
핵심은 상태의 생명주기를 명확히 정의하고, 적절한 라우팅 전략을 통해 세션 일관성을 보장하는 것이다.
서버를 확장하는데 있어서 서버를 Stateful하게 두면 안 되는 이유는 알고 있었지만, 몇몇 경우에는 서버가 Stateful하게 될 수밖에 없다는 것을 깨달았다. 이번 경우가 딱 그런 경우였는데, 브라우저 세션이 하나로 묶여서 여러 서버가 사용할 수 있는 형태가 아니다 보니 이런 문제가 발생했다.
이렇게 서버가 늘어남에 따라서 각 서버별로 처리해야 할 것들이 있는 경우에는 요청을 먼저 보고 각 서버에 할당할 수 있는 라우터의 필요성을 알게 되었다.
이러한 라우터 사용 구조는 상태가 있는 여러 환경에서 유용할 것 같아서 블로그 글을 써 보았다 ^^