1️⃣  발단

Imgenie 서비스에 mongo db를 달아주고 있던 중, 테스트 코드에서 아래와 같은 오류가 발생했다.

이 오류는 아래 함수에서 발생한 것인데, 저장 전의 auth 객체와 와 실제 db에 저장된 found 객체가 다르다는 것이였다.

def test_find_by_refresh_token(self):
    user = self.__user()
    auth = self.__auth(user, "123")

    found = self.auth_repository.find_by_refresh_token(auth.refresh_token)
    assert auth == found ## 여기서 발생!

 

2️⃣ 이유

파이썬 datetime의 마이크로초의 값이 다른것이 문제인데 found객체의 마이크초 일부가 없는 걸 보니 뭔가 mongo db에서 저장되는 형식이 datetime과 다른것 같다.

(693360 vs 693000)

 

그리고 mongodb 공식문서에서 다음 글을 찾았다.

BSON Date is a 64-bit integer that represents the number of milliseconds since the Unix epoch (Jan 1, 1970). 
BSON Date는 Unix epoch 시간부터의 밀리초를 표현하는 64비트의 정수값입니다.
> https://www.mongodb.com/docs/v4.4/reference/bson-types/#date

 

파이썬의 datetime은 마이크로초까지 표현하기 때문인 것 같다.

밀리초는 10^-3까지 마이크로초는 10^-6까지 표현한다

 

693360에서 마지막 360이 날라간 이유도 이것 때문인 것으로 결론지었다.

3️⃣ 해결

원래는 datetime.utcnow를 created_at의 디폴트로 쓰고 있었는데

이를 초기에 microsecond 중 뒤 3글자를 round해서 해결했다.

def utcnow():
    now = datetime.utcnow()
    return now.replace(microsecond=round(now.microsecond, -3))


class AuthDocument(Document):
	...
    created_at = DateTimeField(default=utcnow)

 

좀 더 나은 방법도 있다.

믹스인을 쓰는 방식인데, 이게 유지보수를 생각하면 편한것 같다.

"""
date.py
"""

def utcnow():
    now = datetime.utcnow()
    return now.replace(microsecond=round(now.microsecond, -3))


class CreatedAtMixin:
    created_at = DateTimeField(default=utcnow)
    meta = {"abstract": True}
    
"""
auth.py
"""
   
class AuthDocument(CreatedAtMixin, Document):
    user = ReferenceField(UserDocument, required=True, unique=True)
    refresh_token = StringField(required=True)

    meta = {"indexes": [{"fields": ["created_at"], "expireAfterSeconds": int(config.refresh_token_exp_period.total_seconds())}]}

    def to_dto(self) -> Auth:
        return Auth(id=str(self.id), user=self.user.to_dto(), refresh_token=self.refresh_token, created_at=self.created_at)
2jun0