본문으로 건너뛰기
버전: 3.3.1

부분 갱신 시스템의 도입

앞서 진행한 랭킹 캐시 구조 재설계를 통해 랭킹 조회 및 갱신 과정에서 발생하던 성능 문제를 상당 부분 개선할 수 있었습니다.

그러나 실제 운영 환경을 가정한 테스트 과정에서는 또 다른 병목이 존재한다는 사실을 확인할 수 있었습니다.

랭킹 데이터 자체는 빠르게 갱신할 수 있게 되었지만, 실시간 동기화 과정에서는 여전히 변경 여부와 관계없이 전체 랭킹 데이터를 전송하고 있었습니다.

특히 대부분의 갱신은 일부 사용자의 순위 변동에 불과했음에도 불구하고, 클라이언트는 매번 전체 데이터를 수신하고 비교해야 했으며, 모바일 환경에서는 불필요한 렌더링 비용 역시 함께 발생하고 있었습니다.

이에 따라 단순히 데이터를 빠르게 저장하는 것을 넘어, "변경된 데이터만 효율적으로 전달할 수 있는가?" 에 대한 고민이 필요하였고, 이를 해결하기 위해 Delta Sync 기반의 부분 갱신 시스템을 도입하게 되었습니다.



개요

앞서 진행한 캐시 구조 재설계를 통해 전국 랭킹 및 매장 랭킹 조회 과정에서 발생하던 성능 문제를 상당 부분 개선할 수 있었습니다.

그러나 실제 운영 환경을 가정한 동시 접속 테스트 과정에서는 또 다른 병목이 존재한다는 사실을 확인할 수 있었습니다.

당시 랭킹 시스템은 Firebase Snapshot을 통해 랭킹 변경 여부를 감지하고 있었습니다.

하지만 Snapshot 이벤트가 발생할 때마다 전체 랭킹 데이터를 다시 전송하는 구조를 사용하고 있었기 때문에, 실제 변경 범위와 관계없이 동일한 데이터가 반복적으로 전송되는 문제가 존재하였습니다.

실제로 대부분의 랭킹 갱신은 일부 사용자 또는 일부 순위의 변경에 불과했음에도 불구하고, 서버는 매번 전체 랭킹 데이터를 전송해야 했으며 클라이언트 역시 전체 데이터를 다시 비교하고 처리해야 했습니다.

이 과정에서 네트워크 전송량이 증가하였으며, 특히 모바일 환경에서는 불필요한 렌더링 비용 또한 함께 증가하고 있었습니다.

실제 운영 과정에서 사용자 점수 등록 이후 랭킹 갱신 알림이 표시되기까지의 시간을 측정한 결과, 일부 환경에서는 약 3~5초 수준의 지연이 발생하는 것을 확인할 수 있었습니다.

분석 결과 서버의 랭킹 계산 성능 자체보다는 전체 랭킹 데이터를 반복적으로 전송하고, 클라이언트가 이를 다시 비교 및 렌더링하는 과정에서 상당한 비용이 발생하고 있었습니다.

이에 따라 Firebase Snapshot을 데이터 조회 수단이 아닌 변경 감지 이벤트로 활용하도록 구조를 재정의하였고, 변경된 랭킹 데이터만 전달할 수 있는 부분 갱신 구조를 검토하게 되었습니다.

또한 변경 데이터의 순서를 보장하고 누락 상황을 복구하기 위해 Version 기반 동기화 방식을 함께 도입하였으며, 클라이언트는 현재 버전을 기준으로 Patch를 적용하고 버전 누락이 발생한 경우 전체 Snapshot을 다시 요청하도록 설계하였습니다.

본 문서에서는 전체 랭킹 동기화 방식의 한계와 Delta Sync 설계 과정, 그리고 Version 기반 부분 갱신 구조를 통해 개선할 수 있었던 데이터 동기화 방식을 정리합니다.


기존 동기화 구조

초기 랭킹 시스템은 Firebase Snapshot 이벤트를 통해 랭킹 변경 여부를 감지하고, 변경이 발생할 경우 최신 랭킹 데이터를 클라이언트에게 전달하는 구조를 사용하고 있었습니다.

당시에는 데이터 정합성과 구현의 단순성을 우선시하였기 때문에, 일부 순위만 변경되더라도 전체 랭킹 데이터를 다시 전송하는 방식을 선택하였습니다.

[1]

랭킹 변경 감지 → Firebase Snapshot

동기화 방식 → 전체 랭킹 데이터 전송

클라이언트 처리 → 전체 데이터 교체 및 재렌더링

이 방식은 구현이 단순하다는 장점이 있었습니다.

서버는 현재 시점의 랭킹 상태를 그대로 전달하기만 하면 되었으며, 클라이언트 역시 수신한 최신 데이터를 기존 데이터와 교체하는 방식으로 동작할 수 있었습니다.

또한 전체 랭킹 데이터를 기준으로 동기화를 수행하고 있었기 때문에, 이후 부분 갱신 구조에서 문제가 되었던 데이터 누락이나 순서 역전 현상 역시 크게 드러나지 않았습니다.

클라이언트는 수신한 최신 스냅샷으로 전체 데이터를 교체하기만 하면 되었기 때문에, 초기 개발 단계에서는 비교적 안정적으로 운영할 수 있었습니다.

그러나 실제 서비스 규모가 증가하면서 대부분의 랭킹 갱신이 일부 사용자 또는 일부 순위의 변경에 불과함에도 불구하고, 서버와 클라이언트는 매번 전체 랭킹 데이터를 기준으로 동기화를 수행하고 있었습니다.

결과적으로 변경 범위보다 훨씬 많은 데이터가 반복적으로 전송되기 시작하였으며, 이는 이후 Delta Sync 기반의 부분 갱신 구조를 검토하게 된 주요 원인이 되었습니다.


문제사항

초기 구조에서 가장 큰 문제는 실제 변경 범위와 관계없이 전체 랭킹 데이터를 반복적으로 전송하고 있었다는 점이었습니다.

당시에는 Firebase Snapshot 이벤트를 통해 랭킹 변경을 감지한 뒤, 변경이 발생할 경우 최신 랭킹 전체를 클라이언트에 전달하는 방식을 사용하고 있었습니다.

그러나 실제 운영 환경에서 발생하는 대부분의 갱신은 일부 사용자의 기록 등록이나 특정 순위의 변동에 불과하였습니다.

그럼에도 불구하고 서버는 매번 전체 랭킹 데이터를 생성하여 전송해야 했으며, 클라이언트 역시 수신한 전체 데이터를 기준으로 다시 비교 및 렌더링을 수행해야 했습니다.

[2]

실제 변경 범위 → 일부 사용자

전송 범위 → 전체 랭킹

클라이언트 처리 → 전체 비교 및 재렌더링

초기 사용자 수가 적은 환경에서는 큰 문제가 발생하지 않았지만, 서비스 규모가 증가하면서 이러한 비용은 점차 무시하기 어려운 수준으로 증가하기 시작하였습니다.

특히 전국 랭킹과 매장 랭킹이 동시에 운영되는 환경에서는 하나의 기록 변경이 여러 동기화 이벤트를 발생시킬 수 있었으며, 변경 범위에 비해 과도한 데이터 전송이 반복적으로 발생하였습니다.

또한 클라이언트 역시 대부분의 데이터가 변경되지 않았음에도 불구하고 전체 데이터를 다시 비교하고 렌더링해야 했기 때문에, 모바일 환경에서는 불필요한 UI 갱신 비용이 지속적으로 발생하였습니다.

실제 운영 과정에서는 랭킹 갱신 자체보다 변경되지 않은 데이터를 반복적으로 처리하는 과정에서 더 많은 비용이 발생하고 있었으며, 일부 환경에서는 사용자에게 랭킹 갱신 알림이 반영되기까지 수 초 이상의 지연이 발생하기도 하였습니다.

결과적으로 서버는 변경되지 않은 데이터를 반복적으로 전송하고 있었고, 클라이언트는 변경되지 않은 데이터를 반복적으로 처리하고 있는 상태였습니다.

이에 따라 전체 데이터를 동기화하는 방식에서 벗어나, 실제 변경된 데이터만 전달할 수 있는 Delta Sync 기반의 부분 갱신 구조를 검토하게 되었습니다.


Delta Sync 설계

전체 랭킹 데이터를 반복적으로 전송하는 구조의 한계를 확인한 이후, 실제 변경된 데이터만 전달하는 부분 갱신 구조를 검토하게 되었습니다.

초기에는 단순히 변경된 사용자 정보만 전송하는 방식 역시 고려하였습니다.

그러나 클라이언트가 현재 어떤 상태의 랭킹 데이터를 보유하고 있는지에 따라 동일한 변경 데이터라도 적용 결과가 달라질 수 있다는 문제가 존재하였습니다.

특히 실시간 환경에서는 네트워크 지연이나 재연결이 발생할 수 있으며, 이러한 상황에서는 일부 데이터만 전달하는 방식으로는 데이터 정합성을 보장하기 어려웠습니다.

이에 따라 서버는 랭킹 변경 정보를 독립적인 갱신 이벤트(Delta Event) 형태로 생성하고, 클라이언트는 해당 이벤트를 현재 랭킹 상태에 반영하는 구조를 설계하였습니다.

[3]

기존 구조 → 전체 랭킹 전송

변경 구조 → 변경 데이터만 전송

동기화 단위 → Delta Event

이를 통해 실제 변경 범위만 전달할 수 있게 되었으며, 이후 Version 기반 동기화 구조를 도입할 수 있는 기반 역시 함께 마련할 수 있었습니다.


Version 기반 동기화

부분 갱신 구조를 도입하면서 새로운 문제가 발생하였습니다.

기존 전체 동기화 방식에서는 최신 Snapshot으로 전체 데이터를 교체하기만 하면 되었기 때문에 크게 드러나지 않았지만, Delta Sync 환경에서는 이벤트 적용 순서가 데이터 정합성에 직접적인 영향을 주게 되었습니다.

실제 운영 환경에서는 네트워크 지연이나 재연결 과정에서 이전 이벤트가 뒤늦게 도착하는 경우가 발생할 수 있었으며, 이러한 상황에서는 오래된 데이터가 최신 데이터를 덮어쓰는 문제가 발생할 수 있었습니다.

이를 해결하기 위해 모든 Delta Event에 Version Number를 부여하도록 변경하였습니다.

[4]

Delta Event → Version 포함

클라이언트 → 현재 Version 보관

이전 Version 수신 시 → 폐기

클라이언트는 현재 보유한 Version보다 낮은 Version의 이벤트가 도착할 경우 해당 이벤트를 무시하도록 구현하였습니다.

이를 통해 이벤트 도착 순서와 관계없이 최신 상태를 유지할 수 있었으며, 부분 갱신 구조에서도 데이터 정합성을 확보할 수 있었습니다. 또한 Version은 단순히 오래된 이벤트를 폐기하기 위한 용도만이 아니라, 중간 버전이 누락된 상황을 감지하기 위한 수단으로도 활용되었습니다.

예를 들어 클라이언트가 Version 10 상태에서 Version 12 이벤트를 수신한 경우, Version 11이 누락되었다고 판단할 수 있습니다.

이러한 상황에서는 부분 갱신을 계속 적용하는 대신, 전체 Snapshot을 다시 요청하여 데이터를 재동기화하도록 설계하였습니다.

[5]

현재 Version → 10

수신 Version → 12

Version 11 누락 감지

→ Full Snapshot 재동기화


부분 갱신 적용

Version 기반 동기화 구조를 도입한 이후, 실제 랭킹 시스템에도 부분 갱신 방식을 적용하였습니다.

기존 구조에서는 랭킹 변경이 발생할 때마다 전체 랭킹 데이터를 다시 생성하여 전송하고 있었지만, 개선 이후에는 변경된 사용자와 순위 정보만 포함한 Delta Payload를 생성하도록 수정하였습니다.

클라이언트 역시 전체 데이터를 다시 교체하는 대신, 수신한 Delta Payload를 현재 보유 중인 랭킹 데이터에 반영하는 방식으로 변경하였습니다.

[6]

서버 → Delta Payload 생성

전송 데이터 → 변경된 랭킹 정보

클라이언트 → 부분 갱신 적용

이를 통해 대부분의 갱신 상황에서 전체 데이터 재전송이 불필요해졌으며, 실제 변경 범위만을 기준으로 동기화를 수행할 수 있게 되었습니다.

또한 변경 범위가 작은 경우에는 클라이언트가 처리해야 하는 데이터 양 역시 크게 감소하여 렌더링 비용을 줄일 수 있었습니다.


개선 결과

Delta Sync와 Version 기반 동기화 구조를 적용한 이후, 랭킹 시스템의 네트워크 사용량과 클라이언트 처리 비용을 크게 줄일 수 있었습니다.

기존 구조에서는 일부 사용자 순위만 변경되더라도 전체 랭킹 데이터를 반복적으로 전송하고 있었으나, 개선 이후에는 실제 변경된 데이터만 전송하도록 변경되었습니다.

또한 클라이언트 역시 전체 데이터를 비교하고 다시 렌더링하는 대신, 변경된 항목만 갱신하도록 수정되어 불필요한 UI 처리 비용을 줄일 수 있었습니다.

특히 모바일 환경에서 빈번하게 발생하던 랭킹 갱신 지연 현상이 감소하였으며, 사용자 입장에서 체감되는 실시간 반응성 역시 개선할 수 있었습니다.

결과적으로 캐시 구조 재설계를 통해 서버 측 성능을 개선한 데 이어, Delta Sync를 통해 동기화 과정에서 발생하던 병목 역시 완화할 수 있었습니다.

이를 통해 매장 수와 사용자 수가 증가한 이후에도 보다 안정적으로 실시간 랭킹 서비스를 운영할 수 있는 기반을 마련할 수 있었습니다.

각주

랭킹 변경 감지 → Firebase Snapshot

동기화 방식 → 전체 랭킹 데이터 전송

클라이언트 처리 → 전체 데이터 교체 및 재렌더링

실제 변경 범위 → 일부 사용자

전송 범위 → 전체 랭킹

클라이언트 처리 → 전체 비교 및 재렌더링

기존 구조 → 전체 랭킹 전송

변경 구조 → 변경 데이터만 전송

동기화 단위 → Delta Event

Delta Event → Version 포함

클라이언트 → 현재 Version 보관

이전 Version 수신 시 → 폐기

현재 Version → 10

수신 Version → 12

Version 11 누락 감지

→ Full Snapshot 재동기화

서버 → Delta Payload 생성

전송 데이터 → 변경된 랭킹 정보

클라이언트 → 부분 갱신 적용