레거시 서비스 개선 회고
안녕하세요.
레거시 PHP 서비스 개선에 대한 회고입니다.
원래 작년 연말에 정리하려 했던건데 이런저런 핑계로 이제야 작성하게 되었네요.
중간에 귀찮아져서 GPT한테 시키다보니 문장이 조금 어색하더라도 양해 부탁드립니다...
이 회고는 레거시 서비스 → 현대화된 환경으로 조금씩 전환해 나간 과정을 정리한 기록이며,
더 좋은 방법이나 추가적인 개선점에 대한 의견을 얻기 위함으로 다양한 의견을 남겨주시면 감사하겠습니다.
먼저 간단히 서비스에 대해 설명하자면
프레임워크 없이 순수 PHP로 제작되었으며, 변경점 발생 시 FTP로 변경 파일을 직접 업로드하는 방식의 수동 배포를 진행해왔습니다.
물론 가장 좋은 해결책은 새로 만드는 것이겠지만, 언제나 그렇듯이 제한된 리소스로 인해 점진적인 개선이 불가피했습니다.
1. Git / Github (Actions) 도입
매주 배포 과정에만 4시간 이상이 소요될 정도로 수동 배포에 따른 업무 부담이 컸습니다.
서비스 특성상 평상시 트래픽은 많지 않았지만, 구독 기반 서비스라 안정적인 유지가 중요했습니다.
배포 시간을 절감하고, 에러 리스크를 낮추기 위해서는 자동화 파이프라인 구성이 시급하다고 판단했습니다.
- Key(env) 분리 & Ignore 처리
기존에는 소스 코드 내부에 API Key 등이 하드코딩되어 있었고, 이는 보안 이슈로 직결될 수 있었습니다.
.env 파일로 분리하여 관리하고, Git에서 Ignore 처리함으로써 보안 취약점을 줄였습니다.
- 자동 배포 파이프라인 구축
Git + Github Actions + AWS CodeDeploy를 연동하여 EC2 인스턴스로의 자동 배포를 설정했습니다.
개발자가 코드 Push → Pull Request → Merge 하면 자동으로 빌드 및 배포가 진행되어, 배포 비용을 크게 절감했습니다.
- 운영 환경 구분 및 브랜치 전략 도입
서버 환경을 Dev, Alpha, Prod로 구분하고, 브랜치 전략에 따라 배포 프로세스를 달리 적용했습니다.
운영 환경 분리로 인해 안정적인 테스트가 가능해졌고, 배포 리스크도 한층 감소했습니다.
2. 컨테이너 기반 개발 환경 설정
운영 서버와 다른 PHP 버전과 환경에서 개발해왔다는 것은 신선한 충격이었습니다.
이를 계기로 Docker를 도입해 개발 환경 자체를 컨테이너화하고, 나아가 immutable 배포 방향도 염두에 두었습니다.
- Docker, Docker-compose 파일 및 Readme 작성
로컬 환경에서 빠르게 컨테이너를 실행하고, 각 인원이 동일한 환경을 재현할 수 있도록 문서화했습니다.
- 웹서버 전환: Apache + mod_php → Nginx + PHP-FPM
prefork MPM(멀티 프로세스) 방식인 mod_php 대신 이벤트 드리븐인 PHP-FPM으로 전환해, 컨텍스트 스위칭 비용을 줄이고 성능을 높였습니다.
레거시 서비스에서도 큰 리스크 없이 손쉽고 효과적인 성능 개선을 기대할 수 있는 전환입니다.
3. VPC 구성 및 서브넷 분리
이미 EC2와 RDS를 사용 중이었지만, 보안 설정이 미흡하여 퍼블릭 액세스가 열려 있었습니다.
장기적으로 안정적인 운영을 위해서는 AWS의 권장 아키텍처(VPC, Subnet, Bastion 등)를 적용할 필요가 있었습니다.
- VPC 및 서브넷 구조화
public, private, data 용도의 서브넷을 가용영역 별로 분리하고, 필요한 게이트웨이와 라우팅을 설정했습니다.
- 퍼블릭 액세스 제거
EC2, RDS를 퍼블릭 서브넷에서 제거하고 프라이빗 서브넷으로 옮겼습니다.
외부 접속은 Bastion 서버 터널링을 통해서만 가능하도록 바꿔, 보안을 한층 강화했습니다.
4. Redis 도입
기존에는 세션 파일을 서버 내부에 저장했고, 스토리지 용량 제한으로 인해 세션 유지 시간이 4시간도 채 되지 않았습니다.
서버가 여러 대로 확장될 경우, 세션 동기화 문제도 큰 이슈였기 때문에 In-Memory DB를 도입할 필요가 있었습니다.
- 세션 저장소 이전
Redis를 사용해 세션을 중앙 관리함으로써, 서버간 세션 동기화와 I/O 병목 문제를 해소했습니다.
- 데이터 캐싱
쿼리 최적화를 하기 전, 상품 등 자주 읽지만 변경이 적은 데이터부터 Redis에 캐싱했습니다.
한 페이지당 최대 300번의 DB 커넥션이 발생하는 구조였기 때문에, 캐싱만으로도 상당한 성능 개선을 얻었습니다.
5. 이미지 이전 및 최적화
세션과 마찬가지로 이미지를 서버 스토리지에 저장하고 있어, 스케일아웃시 파일 동기화 문제가 발생할 우려가 있었습니다.
노출되는 이미지 사이즈에 비해 용량이 지나치게 큰 이미지들이 많았고, 캐싱 처리가 전혀 이루어지지 않아 트래픽 비용도 높았습니다.
- 이미지 이전 및 CDN 적용
서버 내부 디렉토리에 있던 이미지를 S3로 이전하고, CloudFront(CDN)를 적용해 전송 속도와 트래픽 비용을 최적화했습니다.
배포 시 이미지 파일 관리 이슈가 사라지고, 스케일아웃 시에도 독립적인 스토리지를 활용할 수 있게 되었습니다.
- Lambda를 통한 이미지 리사이징 및 최적화
S3 업로드 이벤트를 트리거로 Lambda 함수를 호출하여, 필요한 사이즈로 이미지 자동 리사이징 및 압축을 진행했습니다.
불필요하게 큰 원본 이미지를 직접 서빙하지 않아도 되므로, 트래픽 비용과 로드 시간이 크게 개선되었습니다.
6. 운영 Admin, Worker 서버 분리
백오피스의 통계 조회 로직이 비효율적으로 구현되어 있어, 관리자 몇 명이 접근하더라도 서버 리소스를 과도하게 점유했습니다.
특히 Excel 다운로드 시, 사용이 종료된 PHPExcel 라이브러리를 그대로 사용하고 있었고, 1만 건 이상의 데이터를 다운로드하면 서버가 다운될 정도로 부하가 컸습니다.
또한 Crontab으로 스케줄러를 돌리고 있어, 서버를 여러 대로 확장할 경우 중복 실행 혹은 실행 누락 문제가 발생할 가능성이 높았습니다.
무엇보다 구독 기반 서비스 특성상, 서버 다운 시 스케줄러가 멈추는 것은 치명적이었습니다.
- Admin(Server)와 Worker(Server) 분리
관리자 페이지, 통계 로직 등 비동기/배치 성격의 작업을 메인 웹서버와 분리해, 서비스 장애가 관리자 업무로 인해 번지지 않도록 했습니다.
필요 시 Worker 서버를 추가로 띄울 수 있도록 구조를 단순화해, 향후 수평 확장도 용이해졌습니다.
7. Excel 다운로드 및 통계 스케줄러 Lambda 이전
Excel 대량 다운로드와 통계 산출 로직은 CPU/메모리를 많이 소모해, 웹서버의 실시간 트래픽 처리에 지장을 줄 수 있었습니다.
Crontab 역시 서버 내부에서만 동작하다 보니, 장애 시 핵심 작업 중단으로 이어질 위험이 컸습니다.
- Excel 다운로드 Lambda로 이전
API Gateway를 통해 Lambda를 호출해 필요한 데이터를 가져온 뒤, S3에 Excel 파일을 업로드했습니다.
파일 생성이 완료되면 Slack으로 다운로드 링크를 전송하도록 하여, 사용자(관리자)가 서버 부하 없이 대용량 파일을 다운로드할 수 있게 했습니다.
- 통계 스케줄 Crontab → CloudWatch Canaries
통계 데이터를 주기적으로 생성하던 작업을 Lambda 함수 + CloudWatch Canaries(EventBridge)로 분리해, 서버 장애와 무관하게 정기 실행되도록 했습니다.
운영 관점에서도 GUI 기반으로 스케줄 등록/확인이 가능해져, 유지보수가 훨씬 용이해졌습니다.
8. 스케일 관리
트래픽 증가에 대비해 스케일아웃이 필요했습니다.
ECS나 EKS를 도입하는 방법도 있었지만, 팀 규모나 러닝 커브를 고려할 때 EC2 기반 오토 스케일링이 더 현실적이었습니다.
- 로드밸런서 + AMI + Auto Scaling Group
로드밸런서를 통해 트래픽을 분산하고, Auto Scaling Group으로 서버 인스턴스를 자동 증감시키는 구조를 마련했습니다.
- AWS CodeDeploy 기반 배포
배포용 인스턴스를 코드 업데이트한 뒤, 해당 인스턴스로 AMI를 생성했습니다.
이후 이 AMI를 시작 템플릿으로 사용해 ASG를 생성하고, Blue/Green 배포로 무중단 운영을 실현했습니다.
- 오버엔지니어링 최소화
ECS/EKS로 가면 러닝 커브가 커지고 관리 포인트가 늘어날 수 있으므로, 일단은 EC2 위주의 인프라로 안정적 확장에 집중했습니다.
9. Read DB 분리 및 쿼리 최적화
스케일아웃 전에는 트래픽 증가 시 EC2 인스턴스의 I/O가 병목이 되었지만,
스케일아웃 후에는 RDS 커넥션 이슈(동시 연결·쿼리 부하)가 새롭게 부각되었습니다.
페이지당 최대 300회 DB 커넥션이 발생할 정도로 쿼리가 비효율적이었고, Transaction을 사용하지 않아 성능이 저하되는 문제가 있었습니다.
- Read DB 분리
RDS Read Replica를 만들어, 조회 쿼리를 읽기 전용 DB로 분산했습니다.
메인 DB는 쓰기/트랜잭션 작업에 집중시키도록 구조를 재정비했습니다.
- 쿼리 정리 및 Transaction 도입
중복 쿼리를 최소화하고, 필요 시 Transaction으로 묶어 처리해 커넥션 남발을 줄였습니다.
- Slow Query 개선
AWS 성능개선 도우미를 통해 Slow Query를 식별하고 인덱스를 적절히 추가해 성능을 높였습니다.
- Analytics DB 별도 구축
Excel 다운로드, 통계 추출 등 배치·분석용 쿼리는 별도의 Read DB로 분리해 대용량 조회에도 운영 서버에 영향이 가지 않도록 조치했습니다.
10. 모니터링
서비스 안정성 확보와 장애 발생 시 신속한 대응을 위해, 로그·지표·에러를 실시간으로 모니터링할 필요가 있었습니다.
마케팅/운영 측면에서도 트래픽 추이와 사용자 행동을 파악하려면 분석 도구가 필요했습니다.
- AWS CloudWatch
서버(EC2, RDS, ALB 등) 성능 지표 및 로그를 모니터링하고, 지표 기반 알람을 설정했습니다.
- Sentry
애플리케이션 레벨의 예외 및 에러를 추적해, 문제 발생 시 스택 정보를 수집하고 알림을 받을 수 있게 했습니다.
- Google Analytics(GA)
웹 트래픽, 사용자 행동 패턴, 이벤트 클릭 등 프론트엔드 관점의 지표를 수집해, 마케팅 및 UX 개선에 참고했습니다.
페이지 로딩 시간, 이탈률 등 사용자 경험 관련 분석도 가능해졌습니다.