Chat SDK 로컬 캐싱 파헤쳐보기
채팅 애플리케이션을 개발하는 것은 복잡한 작업입니다. 특히, 다양한 고객 유스케이스를 고려해야 하는 SDK에서는 이런 도전이 더욱 어렵습니다.
오늘은 그 중에서도 Chat SDK의 로컬 캐싱 기능에 대해서 소개해드리려고 합니다. 로컬 캐싱은 서버로부터 받은 데이터를 클라이언트에 저장하고 사용하는 것을 의미하는데요. 이를 통해서 속도 개선, 오프라인 상황 지원, 불안정한 네트워크 상황에서의 안정적인 데이터 전달, 네트워크 자원 절약 등의 이점을 얻을 수 있습니다. 하지만 데이터 최신화, 연속성 보장 등 복잡한 구현 난이도를 가지고 있는데요. 센드버드에서는 이런 복잡한 작업들을 고객에게서 떠맡아, 최적의 솔루션을 제공하기 위해 끊임없이 노력해왔습니다.
이번 글에서는 저희가 로컬 캐싱의 어려운 과제들을 어떻게 해결해 왔는지 소개해 드리려고 합니다.
인터페이스
로컬 캐싱 기능을 제공하기 위해 가장 먼저 고민했던 것은 사용자 인터페이스였습니다. 사용자가 로컬 캐싱의 존재에 대해 크게 신경 쓰지 않으면서도, 그 기능의 이점을 누릴 수 있도록 만들고자 했습니다. 즉, 로컬 캐싱이 어떻게 작동하는지 고객이 알 필요가 없도록, 기능이 은연중에 동작하게끔 만드는 것이 목표였습니다.
그렇게 하기 위해서는, SDK에서 데이터 관리에 대한 주도권을 가져가야만 했습니다. 왜 데이터에 대한 주도권이 있어야 하는지에 대해서는 추후에 자세하게 설명드리겠습니다.
이 주도권을 위해서 저희는 ‘콜렉션'이라는 인터페이스를 새로 도입했습니다. SDK에서 데이터에 대해서 더 적극적인 결정을 내리기 위한 변화였고 이를 통해 고객의 구현 과정을 간소화 시킬 수 있었습니다.
The only UIKit you need.
콜렉션의 데이터 주도권
콜렉션이 데이터에 대한 주도권을 가져간다는 것이 어떤 의미인지 예시 시나리오를 통해 설명드리겠습니다.
예시 시나리오는 다음과 같습니다.
서버에는 1번부터 4번까지의 메시지가 존재
클라이언트가 1번부터 3번까지의 메시지 목록을 요청
서버에서 4번 메시지가 삭제됨
콜렉션을 사용하지 않았을 때의 시나리오
콜렉션을 사용하지 않는 상황입니다.
클라이언트가 1번부터 3번까지의 메시지 목록을 요청했습니다.
SDK에서는 서버에 마찬가지로 메시지 목록을 요청하고, 응답을 받아 그대로 유저에게 전달해 줍니다. SDK에서는 이를 따로 저장하지 않아서 고객이 어떤 메시지 목록을 가지고 있는지, 보여주고 있는지 알 수 없는 상태입니다.
서버에서 4번 메세지가 삭제되었습니다.
고객은 1번부터 3번 메시지만 보여주고 있으므로, 사실 4번 메시지가 삭제되었다는 이벤트는 필요하지 않은 이벤트입니다. UI에 아무런 영향을 끼지 않는 이벤트이니까요.
하지만 유저 화면은 SDK에서는 미지의 영역입니다. 어떤 메시지를 보여주고 있는지 알 수 있는 방법이 없기 때문에, 4번 메시지의 삭제 이벤트가 고객에게 필요한 이벤트인지 아닌지 알 수 없습니다.
따라서 SDK는 삭제 이벤트를 그대로 고객에게 전달해줍니다.
고객은 이 이벤트가 현재 UI와는 관련 없다는 사실을 확인하고 무시하는 과정을 거쳐야겠죠.
콜렉션을 사용했을 때의 시나리오
콜렉션을 사용하고 있는 상황입니다.
클라이언트가 1번부터 3번까지의 메시지 목록을 요청했습니다.
SDK에서는 서버에 메시지 목록을 요청하고, 응답을 받아 콜렉션에 반영합니다.
그리고 그 이후에, 콜렉션의 내용이 변경되었다고 고객에게 알려줍니다.
유저는 콜렉션 내용 그대로를 화면에 반영합니다. 따라서 UI에 어떤 메세지 목록이 반영되어 있는지 알 수 있습니다.
서버에서 4번 메세지가 삭제되었습니다.
콜렉션이 현재까지 받은 메시지 목록을 전부 가지고 있기에, 4번 메시지의 삭제 이벤트가 UI에 아무런 영향을 끼치지 않는다는 것을 알고 있습니다.
따라서 SDK는 삭제 이벤트를 고객에게 전달해주지 않습니다.
데이터 주도권 정리
이렇게 SDK가 콜렉션을 통해서 데이터 주도권을 가져가게 되었을때, 특정 이벤트를 고객에게 내보내지 않는 등 여러 결정을 적극적으로 할 수 있게 됩니다. 이는 SDK 내부적으로 여러 복잡한 작업들을 도맡아 수행한다는 것을 의미하므로, 고객입장에서는 구현이 쉬워지게 됩니다.
물론 이런 변화가 좋은 점만 있는 것은 아닙니다. SDK 내부적으로 많은 것을 결정하다 보니 특수한 유스케이스를 가진 고객은 불편할 수 있습니다. 이 경우에는 콜렉션 대신에 Chat SDK의 일반 인터페이스를 사용하는 것이 더 적합할 수 있습니다.
다음 항목들에서는 SDK에서 데이터 주도권을 이용해서 어떤 작업을 하고 있는지, 그 작업을 통해서 고객에게 어떤 이점을 가져다 주고 있는지 설명드리겠습니다.
Changelog
센드버드의 Chat SDK는 앱이 포어그라운드 상태일 때 서버로부터 실시간 이벤트를 수신합니다. 그러나 앱이 백그라운드 상태로 전환되면 실시간 이벤트 수신이 중단되고, 그 동안은 데이터를 최신 상태로 유지할 수 없습니다.
이를 해결하기 위한 것이 ‘Changelog’ 입니다. 'Changelog'는 이벤트 수신이 중단된 동안 업데이트 되거나 삭제된 메세지를 받을 수 있는 API 입니다. 앱이 포어그라운드 상태가 되었을 때 ‘Changelog’ 호출을 통해서 그 동안의 변경 사항을 반영할 수 있습니다.
데이터를 최신 상태로 유지하기 위해선 필수적인 기능이지만, 고객 입장에서는 ‘Changelog’의 필요성에 대해서 인지하는 것과, 포어그라운드 상태에 진입했을 때 호출해서 데이터에 반영해줘야 한다는 점에서 번거로울 수 있습니다.
하지만 콜렉션을 사용한다면, 내부적으로 changelog를 적절한 시점에 호출하여 데이터를 최신 상태로 유지합니다. 고객은 changelog에 대해서 아무런 신경을 쓰지 않아도 되는 것이죠.
데이터의 원천 고르기
데이터가 이미 로컬에 있는데도 서버에서 데이터를 중복으로 가져오는 것은 시간과 네트워크 자원 면에서 매우 비효율적입니다. 그러나 항상 로컬에서 데이터를 가져올 수는 없습니다. 그 이유는 메시지가 잘못된 순서로 표시될 수 있기 때문입니다.
예를 들어, 서버에는 1번부터 10번까지의 메시지가 있고, 로컬에는 1번, 2번, 9번, 10번 메시지만 저장되어 있다고 가정해봅시다. 이때 고객이 4개의 메시지를 요청했다면, 로컬에서만 데이터를 제공하게 되면 [1번, 2번, 9번, 10번] 이런 순서의 메시지 리스트를 받게 됩니다. 이렇게 되면 중간 메시지가 누락되어 고객에게 전달됩니다.
이런 문제를 해결하기 위해, 저희는 'chunk' 라는 개념을 도입했습니다. Chunk는 메시지들이 연속적임을 보장하는 구간을 나타냅니다. 만약 고객에게 chunk 안에서 메시지를 제공할 수 없는 상황이라면, 서버에서 메시지를 가져오게 됩니다.
아까 예시를 생각해보면, [9번, 10번] 메시지가 chunk에 있는 상황입니다. 그런데 chunk에 있는 메시지만으로는 4개의 메시지들 전달해 줄 수 없습니다. 따라서 서버에 추가적인 메시지를 요청하고 7번, 8번 메시지도 받아와서 고객에게 전달해주게 됩니다.
이는 고객이 직접 해결하기에 복잡하고 번거로운 부분입니다.
하지만! 역시 콜렉션을 사용한다면 ‘chunk’ 를 내부적으로 알아서 관리해주기 때문에 고객은 신경쓰지 않고, 데이터가 올바른 순서대로 받아서 사용할 수 있습니다. 상황이 맞아 로컬에서 데이터를 받아간다면 시간과 네트워크 자원도 아낄 수 있게 되죠.
Gap check
이전 주제에서 항상 로컬에서만 데이터를 가져오게 된다면 문제가 될 수도 있다고 말씀드렸습니다. 그렇지만 로컬에서만 데이터를 가져올 수 밖에 없는 경우도 있는데요. 바로 네트워크가 꺼져 있는 상태입니다.
이 경우는 서버에 요청을 할 수 없기 때문에 로컬에서 데이터를 가져와서 고객에게 전달해줍니다.
그렇다면 다시 인터넷이 연결되었을 때, 위에서 언급한 메시지의 불연속성 문제는 어떻게 해결 할 수 있을까요? 이를 해결하기 위해 저희가 도입한 기능이 ‘Gap Check’ 입니다.
`Gap Check`는 메시지 중간에 불연속적인 부분, 즉 Gap이 있나 없나를 서버에 있는 데이터과 비교하는 과정입니다. 그 과정에서 Gap이 확인된다면, 서버에서 메시지를 받아 메꿔주게 되고 그러면 다시 메시지의 연속성을 만족할 수 있게 됩니다.
지겨우실 수 있겠지만, 다시 한번 말씀드려보면 이는 고객이 직접 해결하기에 복잡하고 번거로운 부분입니다.
물론, 역시 콜렉션을 사용한다면 저희가 내부적으로 Gap check를 수행해서 적절하게 데이터를 전달해주기 때문에 고객은 이와 관련된 내용을 신경쓰지 않아도 됩니다.
마무리
Chat SDK의 로컬 캐싱 기능은 단순히 기능을 제공하는 것에 그치지 않고, 사용자가 최대한 많은 이점을 가져갈 수 있도록 설계되었습니다. 로컬 캐싱을 지원하면서도 데이터를 안정적으로 제공하기 위해, Changelog, Chunk, Gap check 등 복잡한 작업들을 내부적으로 처리하고 있습니다.
물론 사용자의 추가적인 작업을 요구하지 않으면서요!
이런 작업들은 고객이 복잡한 채팅 애플리케이션을 쉽게 구현하고 운영할 수 있도록 돕기 위함입니다. 앞으로도 저희는 이러한 개선을 계속하며, 고객의 편의를 위해 노력할 것입니다. SDK의 로컬 캐싱 기능을 사용해보시고, 그 편리함을 직접 느껴보세요!