Firestore 사용시 시간정보 저장 및 쿼리 방법 (ServerTimeStamp/NTP)

정신을 차리고 보니 시스템 밀리초를 Firestore 데이터베이스에 그대로 저장해버리는 이상한 짓을 해버렸다. 문서 작성 시각, 수정 시각 등을 각 사용자 디바이스의 밀리초로 지정했더니 뒤죽박죽 난리도 아니다. 운영하는 앱이 게시판 형식의 서비스가 아니라 실시간 채팅 서비스나 게임이였다면 상상도 하기가 싫다.

System.currentTimeMillis()

보통 현재 시간을 가져올 때 이런 식으로 시스템 시간을 가져온다. 물론 백엔드 서버 개발을 할 때는 시스템 시간을 가져와서 데이터베이스에 그대로 저장해도 무방하지만, 파이어베이스의 데이터베이스들과 같이 사용자 개개인의 모바일 기기에서 데이터베이스 insert를 함께 하는 경우가 종종 생길때는 문제가 발생할 수 있다. 일반적인 경우에는 문제가 없겠지만, 장기적으로 문제를 야기한다. 바로 기기간의 밀리초 타임이 정확하지 않다는 점.

currentTimeMillis 함수를 통해 가져오는 밀리초타임은 1970년 1월 1일 부터 경과된 시간을 밀리초로 나타낸다. 기본적으로 UTC를 기준으로 계산되는 것이라, 각 휴대기기의 타임존에 따라서 이 수치가 차이가 나는것은 아니다.

val millis = System.currentTimeMillis()
val seconds = (millis / 1000).toInt() % 60
val minutes = (millis / (1000 * 60) % 60).toInt()
val hours = (millis / (1000 * 60 * 60) % 24).toInt()

val text = String.format("%d시 %d분 %d초", hours, minutes, seconds)
// 9시 38분 54초

그 말인즉슨 위의 코드를 실행해보았을 때, 현재 시각보다 9시간이 감소된 시간을 출력한다. (우리나라 시간대가 UTC 기준으로 +9 이기 때문, 실제 실행 시각은 18시 38분).

그러나, 가장 큰 문제는 해당 시간대는 전적으로 각 디바이스에 의존한다는 점. (실제로 설정에 들어가서 시스템 시간을 변경하면 변경된 시각으로 밀리초가 바뀐다.)

물론 거의 모든 디바이스가 서버에서 시간을 동기화하고 있기 때문에, 크게 차이나지 않는 비슷한 시간들을 가진다. 그러나 마지막 부팅 시기, 마지막 업데이트 시기에 따라서 기기별로 편차가 존재하는것은 어쩔 수가 없다.

Firestore에서 해당 부분 해결하기

Firestore 특성상 특정 작업에서 기기에서 데이터베이스에 직접 쓰기를 요청하는 일은 꽤나 빈번하다. 이에 Firestore 에서 각 값을 업데이트 하는 작업에 한해서, 데이터 쓰기를 수행할 때 각 필드 값을 서버 시간으로 지정할 수 있도록 하는 FieldValue를 제공하고 있다. 사용은 다음 처럼 한다.

val time = hashMapOf(
    "time" to FieldValue.serverTimestamp()
)
FirebaseFirestore.getInstance().collection("test").document("time").set(time)

다음과 같이 각 값에 제공되는 serverTimestamp를 사용한 결과, timestamp 타입으로 현재 서버 시각이 입력된다.

혹은 데이터를 업데이트하거나 추가할 때 POJO를 이용하고 있었다면, 다음과 같이 ServerTimestamp 어노테이션을 사용해서 값이 null 일 경우 자동으로 서버 시각이 적용되도록 할 수 있다. 레퍼런스

@ServerTimestamp
private Timestamp applicationTimestamp;

@ServerTimestamp
private Date applicationDate;

다만, 이는 값을 업데이트 할 때만 사용할 수 있으며, 일반 쿼리에서는 값을 사용할 수 없다. (실제로 서버 타임스탬프 FieldValue로 쿼리를 할 경우, 쓰기 작업에서만 사용가능하다고 에러를 낸다.) 때문에 정확한 시간 쿼리를 원하는 경우에는 클라우드 펑션이나 외부서버에서 중계 API 를 따로 두어야 할 것 같다. 또 한가지 방법으로는 실제 시간을 NTP 서버에서 받아와서 적용하는 방법이 있다.

NTP 서버에서의 시간 불러오기는 다음 라이브러리를 이용해서 쉽게 구현할 수 있다. “TrueTime” 이름부터 아주 정확한 시간?을 제공해줄 것 같은 느낌이 든다. time.google.com, time.apple.com 등 유수의 NTP 서버에서 시간을 직접 받아와서 동기화 할 수 있다.

https://github.com/instacart/truetime-android

해당 라이브러리 위키를 따라서 라이브러리를 성공적으로 초기화 한 뒤 다음과 같이 쿼리문을 작성할 수 있다

firebaseFirestore.collection("name of collection")
    .whereGreaterThan("name of parameter", TrueTime.now())
    .get()

이렇게 응급대처를 할 수는 있을것 같다. 하지만 개인적인 소견으로는 클라이언트 단에 시간 관리를 직접적으로 맡기는것은 좋지 못한 방법이라는 생각이 든다. 더 좋은 방법이 있다면 코멘트 부탁드린다.

그러나 꼭 데이터베이스 쿼리 용도를 떠나서 디바이스 마다 상이할 수 있는 시간 편차를 어느정도 극복하게 해주는 해당 방법은 여러 상황에서 유용하게 쓸 수 있다고 생각한다.


Leave a Reply

Your email address will not be published. Required fields are marked *