PHP 동시 요청 세션 블로킹 현상 원인 분석과 단계별 해결 체크리스트
동일 사용자가 보낸 비동기 AJAX 요청이 순차적으로 지연되는 PHP 세션 잠금 현상의 원인을 짚어보고, session_write_close와 읽기 전용 옵션을 활용한 실무 점검 체크리스트를 정리합니다.
점검 목적: 동시 요청 병목을 유발하는 세션 잠금 메커니즘의 이해
웹 애플리케이션에서 사용자가 페이지를 조회하는 동안 백엔드로 대용량 파일 다운로드, 외부 API 연동, 실시간 알림 조회를 비동기(AJAX)로 동시에 호출하는 환경을 가정해 볼 수 있습니다. 프론트엔드에서는 분명 비동기로 병렬 요청을 보냈음에도 불구하고, 브라우저 개발자 도구의 네트워크 탭에서 두 번째 요청이 첫 번째 요청이 끝날 때까지 대기(TTFB 지연) 상태에 머무는 현상이 자주 발생합니다. 이 문제는 서버 사양 부족이 아니라 PHP의 기본 세션 처리 메커니즘에서 기인하는 경우가 많습니다.
PHP는 파일 기반의 기본 세션 핸들러를 사용할 때 세션 데이터의 무결성을 보호하기 위해 배타적 잠금(Exclusive File Lock)을 겁니다. session_start()가 실행되는 순간 해당 세션 파일에 락이 걸리며, 스크립트 실행이 완전히 종료되거나 명시적으로 세션을 닫기 전까지 동일한 세션 ID를 공유하는 다른 요청은 session_start() 단계에서 무조건 대기합니다. 따라서 동시 요청 병목을 해결하기 위해서는 세션 생명주기와 잠금 유지 시간을 정밀하게 점검하는 체크리스트가 반드시 필요합니다.

항목별 판단 기준: 세션 블로킹 발생 여부를 진단하는 4대 체크포인트
세션 블로킹을 진단하기 위해서는 백엔드 코드와 인프라 구성을 네 가지 기준으로 나누어 점검해야 합니다. 첫째, 긴 실행 시간을 갖는 스크립트에서 session_start()를 호출한 뒤 세션을 즉시 닫지 않고 유지하고 있는지 확인합니다. 외부 API 통신이나 대량 데이터 쿼리처럼 수초 이상 걸리는 로직 앞에 세션이 열려 있다면 100% 블로킹이 발생합니다.
둘째, 단순 데이터 조회가 목적인 요청에 세션 쓰기 권한이 불필요하게 유지되는지 점검합니다. 로그인 사용자 ID나 권한 정보만 읽어오면 되는 API에서도 일반 session_start()를 호출하면 불필요한 배타적 잠금이 발생합니다. 셋째, 세션 저장소로 Redis나 Memcached를 사용할 때 락 설정(redis.session.locking) 옵션이 활성화되어 동일한 대기열이 발생하는지 확인합니다. 넷째, 정적 에셋이나 무상태(Stateless) 엔드포인트에서 공통 부트스트랩 파일에 의해 세션이 자동 시작되는지(session.auto_start) 점검해야 합니다.

정상·오류 예시: 블로킹 유발 패턴과 조기 세션 해제 코드 비교
오류를 유발하는 대표적인 패턴은 긴 작업을 수행하기 전에 세션을 닫지 않는 코드입니다. 예를 들어 컨트롤러 상단에서 session_start()를 호출하여 $_SESSION['user_id'] 값을 읽은 뒤, sleep(5)나 외부 결제사 API 통신처럼 시간이 걸리는 로직을 그대로 통과하면 해당 5초 동안 동일 세션의 모든 요청이 완전히 멈춥니다. 스크립트 종료 시점까지 세션 파일 락이 유지되기 때문입니다.
반면 정상적인 구현은 세션에서 필요한 값을 읽거나 쓴 직후 session_write_close()를 명시적으로 호출하는 형태입니다. PHP 7.0 이상 환경에서는 세션 데이터를 수정할 필요가 없을 때 session_start(['read_and_close' => true]) 옵션을 지정하여 세션을 여는 즉시 잠금을 해제하도록 작성할 수 있습니다. 이렇게 분기 처리하면 세션 값을 정상적으로 읽어오면서도 후속 동시 요청이 지연 없이 병렬로 실행됩니다.

수정 우선순위: 세션 락 완화를 위한 단계별 리팩터링 절차
세션 블로킹 문제를 해결할 때는 서비스 영향도가 적은 작업부터 순차적으로 적용하는 것이 안전합니다. 1순위는 실행 시간이 1초 이상 소요되는 장기 실행 엔드포인트(파일 내보내기, 리포트 생성, 외부 연동) 내부에서 session_start() 직후 session_write_close()를 호출하도록 코드를 수정하는 것입니다. 이 조치만으로도 사용자가 체감하는 화면 멈춤 현상을 즉시 제거할 수 있습니다.
2순위는 읽기 전용 REST API 엔드포인트에 read_and_close 옵션을 적용하거나 프레임워크 수준에서 세션 미들웨어를 분리하는 작업입니다. 세션 수정이 일어나는 로그인, 정보 수정, 장바구니 담기 등의 엔드포인트에만 쓰기 잠금을 허용하고 일반 조회 API는 세션 잠금 없이 동작하도록 분리합니다. 3순위는 세션 저장소를 Redis와 같은 분산 캐시로 이전하면서 락 타임아웃과 재시도 주기를 서비스 트래픽 특성에 맞게 튜닝하는 작업입니다.

최종 확인: 네트워크 워터폴 분석과 동시성 검증 절차
수정 작업이 완료되었다면 브라우저와 부하 테스트 도구를 통해 실제 동시 처리가 정상 동작하는지 확인해야 합니다. 먼저 브라우저 개발자 도구의 네트워크(Network) 탭을 연 상태에서 3초 이상 걸리는 API와 0.1초 만에 끝나는 조회 API를 동시에 호출하는 테스트 페이지를 실행합니다. 수정 전에는 두 번째 요청의 워터폴(Waterfall) 막대그래프에서 Waiting for server response(TTFB)가 3초 이상 밀려났지만, 수정 후에는 두 요청이 동일한 시점에 출발하여 각자의 처리 시간대로 병렬 완료되는지 확인합니다.
추가로 세션 쓰기 작업이 필요한 동시 요청 상황에서 데이터 유실이 발생하지 않는지 검증해야 합니다. 서로 다른 요청이 동시에 세션 배열의 다른 키를 갱신할 때 session_write_close() 시점에 따른 덮어쓰기 레이스 컨디션이 없는지 세션 파일 또는 Redis 키의 최종 저장 상태를 덤프하여 정합성을 최종 확정합니다.