1. 기술 선택 배경: 주어진 환경(Ag-Grid)의 극대화와 TypeScript의 필요성
프로젝트의 가장 큰 과제 중 하나는 수만 건 이상의 데이터를 사용자가 빠르고 직관적으로 조회할 수 있는 화면을 제공하는 것이었습니다. 특히 단순 조회 수준이 아니라 엑셀과 유사한 사용자 경험을 제공해야 했기 때문에 요구사항의 난이도가 상당히 높았습니다.
사용자는 다음과 같은 기능을 자연스럽게 기대하고 있었습니다.
● 실시간 검색 및 필터링
● 컬럼 정렬 및 고정
● 다중 선택
● 스크롤 최적화 및 대량 데이터 렌더링
● 엑셀 다운로드
제가 프로젝트에 투입되었을 때, 이러한 대규모 데이터 처리에 특화된 Ag-Grid 기반 React는 이미 핵심 컴포넌트로 도입되어 있는 상태였습니다. 일반적인 HTML Table로는 브라우저 DOM 과부하로 인해 감당할 수 없는 수준의 데이터였기 때문에, 가상 스크롤(Virtual Scrolling)을 기본 지원하는 Ag-Grid의 도입은 필수불가결한 선택이었을 것입니다.
저의 주된 과제는 이미 도입된 Ag-Grid의 성능을 한계까지 끌어올리고, 요구사항에 맞게 커스터마이징하는 것이었습니다. Ag-Grid가 제공하는 서버 사이드 Row Model, 다중 정렬, 커스텀 Cell Renderer 등을 활용해 수만 건의 데이터도 부드럽게 처리할 수 있도록 최적화해 나갔습니다.
추가적으로 프로젝트에서는 TypeScript를 필수적으로 적용하였습니다. 서비스 규모가 커질수록 API 응답 구조와 상태 관리 구조가 점점 복잡해졌기 때문입니다. JavaScript만 사용하는 경우 런타임에서만 오류를 발견하는 상황이 자주 발생하였으나, API 응답 구조를 interface 기반으로 명확하게 정의함으로써 이를 해결하였습니다.
● API Response Interface
● Grid Row Model / Domain Type
● State Type / Event Payload Type
그 결과 코드만 보더라도 데이터 구조를 즉시 파악할 수 있었고, 협업 과정에서도 커뮤니케이션 비용을 크게 줄일 수 있었습니다.
2. 아키텍처 설계와 계층 분리: 두 번의 프로젝트 경험
프로젝트가 점점 커지면서 단순 기능 구현보다 유지보수성과 구조 안정성이 훨씬 중요해지기 시작하였습니다. 저는 시기가 다른 두 번의 프로젝트를 거치며, 프론트엔드 아키텍처가 개발 생산성과 유지보수에 미치는 영향을 직접 체감할 수 있었습니다.
첫 번째로 경험했던 프로젝트는 View-Stub-State 구조를 채택하고 있었습니다. 이 구조에서는 기능 단위로 폴더를 분리하고 각 계층 역할을 명확히 나누었습니다.
● View: 순수 UI 렌더링 담당
● Stub: API 통신과 도메인 비즈니스 로직 담당
● State: 전역 상태 및 비즈니스 상태 관리
이러한 구조의 가장 큰 장점은 “프로젝트 흐름을 빠르게 이해할 수 있다”는 점이었습니다. 실제로 제가 신규 개발자로 프로젝트에 처음 투입되었을 때, 이 명확한 계층 분리 덕분에 기능별 구조를 쉽게 파악하고 온보딩 시간을 크게 단축할 수 있었습니다. 기능 단위 독립성이 높아 유지보수성과 확장성 역시 뛰어났습니다.
그 후 투입된 두 번째 프로젝트에서는 Container-Presenter 패턴을 사용하고 있었습니다. 이 패턴은 UI 렌더링(Presenter)과 비즈니스 로직(Container)을 분리하는 대표적인 구조입니다.
이 구조는 관심사 분리(Separation of Concerns)가 명확하다는 장점이 있었습니다. Presenter를 순수 UI 컴포넌트로 유지할 수 있어 테스트와 재사용성이 좋았습니다. 하지만 서비스 규모가 더 커지고 복잡해지면서 기존 View-Stub-State 구조와 비교되는 한계점들이 나타나기 시작하였습니다.
● 폴더 구조 복잡도 증가
● 상태 관리의 분산
● 도메인 로직 중복 및 API 구조 관리의 어려움
결과적으로 두 가지 아키텍처를 모두 겪어보며, 장기적으로 운영되는 대규모 프로젝트일수록 도메인 로직과 상태를 더욱 세밀하게 격리하는 View-Stub-State 형태의 기능 단위 모듈화가 더 유리할 수 있다는 인사이트를 얻게 되었습니다.
3. 대규모 데이터 처리 최적화 경험
대규모 데이터 처리는 단순히 화면에 데이터를 출력하는 것 이상의 문제였습니다. 특히 이미 도입되어 있던 Ag-Grid 환경에서 렌더링 성능과 상태 관리 최적화가 매우 중요하였습니다.
초기에는 데이터가 변경될 때마다 전체 Grid가 다시 렌더링되어 스크롤 지연, 브라우저 메모리 증가 등의 현상이 발생하였습니다. 이를 해결하기 위해 여러 최적화 전략을 적용하였습니다.
가장 먼저 적용한 것은 React.memo 기반 렌더링 최적화였습니다. Cell Renderer와 Row Component를 메모이제이션하여 불필요한 재렌더링을 줄였습니다. 또한 useMemo와 useCallback을 적극적으로 활용하였습니다. 특히 Ag-Grid에서는 컬럼 정의(Column Definition) 객체가 자주 재생성되면 Grid 전체가 다시 초기화되는 문제가 있었기에, 이를 useMemo로 단단히 고정하였습니다.
더불어 네트워크 부하와 초기 로딩 시간을 줄이기 위해 서버 사이드 Pagination 구조를 고도화하였습니다. 검색과 정렬 로직을 클라이언트가 아닌 서버에서 처리하도록 이관하면서 브라우저 부담을 크게 줄였고, 실제 운영 환경에서도 수만 건 이상의 데이터를 안정적으로 처리하는 구조를 완성하였습니다.
4. 문제 해결 경험: 데이터 정합성과 비동기 이벤트 처리
프로젝트에서 가장 고민이 많았던 부분 중 하나는 알림 서비스의 데이터 정합성 문제였습니다. 하나의 메서드 안에서 DB 알림 기록 저장과 외부 API 메시지 발송이 동시에 수행되고 있었습니다.
문제는 외부 API 호출 이후 예외가 발생하여 DB 트랜잭션이 롤백될 때 발생하였습니다. 사용자는 알림을 받았지만 시스템에는 기록이 남지 않는 심각한 정합성 오류가 생기는 것입니다. 외부 API 호출은 본질적으로 DB 트랜잭션과 동일한 원자성을 보장할 수 없었습니다.
결국 프로젝트에서는 이벤트 기반 비동기 처리 구조를 도입하였습니다. 핵심 아이디어는 “DB 커밋 이후에만 외부 작업을 수행한다”는 것이었습니다.
1. 메인 비즈니스 로직 및 DB 저장 수행
2. DB 트랜잭션 Commit 완료 시점에 이벤트 발행
3. 별도의 이벤트 핸들러가 메시지 발송을 독립적으로 처리(메인 트랜잭션과 분리)
이 구조를 통해 DB 저장이 실패하면 메시지도 발송되지 않게 되었고, 외부 API가 실패하더라도 메인 비즈니스 로직에는 영향을 주지 않도록 완벽히 격리할 수 있었습니다.
5. 협업과 유지보수 관점에서의 개선 효과
프론트엔드 프로젝트는 시간이 지날수록 컴포넌트와 상태 관리 구조가 빠르게 복잡해집니다. 두 번의 대형 프로젝트를 거치며 제가 얻은 가장 큰 수확은, 초기 아키텍처 설계와 타입 정의가 동료들과의 협업에 얼마나 큰 영향을 미치는지 체감한 것이었습니다.
TypeScript를 통해 API Response를 공통 타입으로 관리하면서 백엔드와의 데이터 구조 이해가 훨씬 직관적으로 변하였습니다. 또한, View-Stub-State와 같은 명확한 계층 분리 아키텍처를 경험하면서, 특정 기능 수정 시 사이드 이펙트를 최소화하는 방법을 몸소 익혔습니다.
코드 리뷰 품질도 자연스럽게 올라갔습니다. 구조가 통일되다 보니 구현 방식 자체를 해석하는 데 드는 시간이 줄어들고, 비즈니스 로직 자체에 집중하는 리뷰가 가능해졌습니다.
6. 마무리
이번 프로젝트들은 단순히 React 화면을 구현하는 것을 넘어, 대규모 데이터 환경에서의 아키텍처 설계와 운영 안정성이 얼마나 중요한지 깊이 체감할 수 있는 시간이었습니다.
Ag-Grid라는 훌륭한 도구가 이미 주어져 있었지만, 이를 프로젝트의 요구사항에 맞게 튜닝하고 최적화하는 과정은 결코 단순하지 않았습니다. 또한 서로 다른 두 가지 프론트엔드 아키텍처를 연달아 경험하며, 팀의 규모와 서비스 특성에 맞는 구조 설계가 필수적이라는 점을 배웠습니다.
결과적으로 이 경험들은 단순한 기능 구현 이상의 의미를 가졌으며, 앞으로 대규모 React 기반 시스템을 설계하고 트러블슈팅할 때 흔들리지 않는 튼튼한 기준점이 될 것입니다.
LEE DAVID