(예고 : 결론부터 말하자면 Query DSL에서 LocalDate는 아무 문제 없습니다. ^^ )
역시 오늘도 에러가 터졌습니다. 그냥 지나가는 날이 없는 것 같습니다. ㅎㅎ
예예~~
1️⃣ 증상
현재 저희서비스에서는 뉴스레터 보관함은 수 많은 필터가 존재하기에 JPQL로 작성하면 엄청 복잡해지기에 해당 로직은 query dsl을 사용해서 구현했습니다.
여러가지 필터, 읽은 아티클 숨김, 검색등을 한 번에 처리하기에는 query dsl이 좋다고 생각되어 사용하였습니다.
하지만 최근에 문제가 발생하였습니다.
8일전?? 9일전???
왜 그러지 where문이 동작을 안하나???? LocalDate가 이상한가??
혹시 프론트가 표기를 잘 못했나 ?? 일단 서버 로그를 먼저 확인해보았는데 아뿔사!
해당 오류를 발견한 날인 7월 7일이기에 6월 28일 아티클은 포함되어있으면 안되는데 포함되어있었습니다.
그렇습니다. 이건 백엔드 잘못입니다.
2️⃣ 원인 찾아보기
그럼 과연 뭐가 문제일까요??
LocalDate currentDate = LocalDate.now();
LocalDate sevenDaysAgo = currentDate.minusDays(7);
private List<ArticleDTO> getArticles(BooleanExpression predicate, Pageable pageable, String userEmail) {
JPAQuery<ArticleDTO> articles = queryFactory
.select(new QArticleDTO(this.article, readBox.readPercentage.coalesce(0), newsletter))
.from(this.article)
.leftJoin(readBox).on(this.article.id.eq(readBox.articleId).and(readBox.userEmail.eq(userEmail)))
.join(newsletter).on(this.article.newsletterEmail.eq(newsletter.email))
.where(predicate)
.where(article.receivedAt.between(sevenDaysAgo, currentDate))
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
applySorting(articles, pageable);
return articles.fetch();
}
현재 아티클을 가져오는 로직은 위와 같습니다.
여기서 ` .where(article.receivedAt.between(sixDaysAgo, currentDate))` 해당 로직이 최근 7일 아티클만 보여주도록 도와주는 로직입니다.
로직은 처음 만들때 테스트를 했을 때 잘되었기 때문에 혹시 서버와 DB 시간이 맞지않을까? 하는 마음에 확인해 보았습니다.
Local time 이 제대로 설정되어있었습니다.
그렇다면 DB는?
DB도 역시 정상적으로 설정되어있었습니다.
아그럼 뭔가 쿼리쪽문제이구나 싶었습니다.
그런데 로컬에서 돌려보니 정확히 7월 1일까지만 가져왔습니다. ?? (이 당시에는 -6일만 하게 설정되어있었습니다.)
뭐지?
1. 프론트쪽에 문제없음
2. 서버와 DB의 타임존은 일치하고
3. 로컬에서 테스트 해봤는데 정상적으로 동작
4. 서버에서는 제대로 동작을 안 한다???
아맞다. 도커 !
(사실 타임존은 큰 문제가 되지 않습니다. 저 에러를 해결할 당시 시간이 조금 흘렀더니 10일전 아티클까지 보여졌습니다. 즉 타임존 문제라면 하루 까지 차이가 날 수 있지만 의도한 일수랑 3일이 차이났기 때문에 전혀 다른 문제였습니다. 그렇다고 where문이 동작하지 않은 것은 아닙니다. 11일전 아티클은 잘 필터링 해줬기 때문입니다. )
확인해보니 도커는 UTS로 설정되어있었습니다.
아래 명령어를 입력하면 서울로 타임존을 변경할 수 있습니다.
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
echo "Asia/Seoul" > /etc/timezone
하지만 이렇게만 하면 코드가 배포되면 도커가 배포된 코드의 이미지로 다시 실행되기 때문에 타임존이 초기화됩니다.
따라서 아예 시작할 때 설정해주면 됩니다.
시작할 때 즉 도커를 빌드 할 때 타임존을 설정해주기 위해 Dockerfile에 아래처럼 작성해줍니다.
# 필요한 패키지 설치 (tzdata)
RUN apk add --no-cache tzdata
# 시간대 설정
ENV TZ=Asia/Seoul
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
이렇게 설정하면 아래와 같이 정상적으로 우리가 원하는 타임존이 설정됩니다.
3️⃣ 이상한 해결
이렇게 설정하고 재 배포를 했는데 신기하게 정상적으로 기능이 돌아왔습니다. ????????
사실 로컬타임 설정해주고 쿼리 바인딩하고 log로 찍어서 currentDate, sevenDaysAgo가 어떤 값을 출력하는 지 볼려고 했는데 재배포만 했을 뿐인데 해결인 된다고???
오늘이 7월 8일이니(하루지남) 7일 전인 7월 1일 정확히 바인딩되고 실제로 아래와 같이 정상적으로 동작하게 되었습니다.
결국 원인은 코드문제는 아니었고 사실 타임존도 크게 문제가 된건 아니였습니다.
뭔가 서버가 재시작되니까 해결된 것으로 볼때 서버가 지속될 때 날짜가 밀리는 현상이나 고정되는 현상? 같은게 있는것 같습니다
그래서 로그처리를 해놓고 몇일 지켜보기로 했습니다.
과연 무엇이 진짜 원인이었을지 끝까지 찾아보겠습니다.
📌 참고
query dsl을 사용할 때 LocalDate 쪽에 문제가 있나싶어서 찾아봤는데 이 부분은 잘 해결된 것 같았습니다.
https://github.com/querydsl/querydsl/issues/3001
+ 7월 9일 오전 6시 30분
4️⃣ 해결
글을 쓰고 하루가 지났습니다.
아침에 일어나서 아티클 보관함에 가보니 8일전 아티클이 남아있었다 ??!!!
바로 서버 들어가서 localDate 로그를 보았습니다.
오늘은 7월 9일 6시 32분 인데 7월 8일로 업데이트가 되지 않고 고정되어있었습니다.
아 그럼 LocalDate가 업데이트가 안되는 구나 왜 업데이트가 안될까???
코드를 보니 응??
.... 바보짓을 했습니다.
원인을 확실히 알고 보니 정확하게 보였습니다.
아무 생각없이 localDate를 여러 메서드에서 사용하니까 인스턴스 변수에 선언했기에 서버가 처음 시작할 때 초기화 되고 인스턴스가 파괴 될 때 까지(스프링 애플리케이션에서는 빈[Bean]으로 관리되는 클래스의 인스턴스 변수는 일반적으로 서버가 종료될 때까지 초기화된 상태로 유지됩니다) 즉 서버가 종료될 때 까지 유지되기에 인스턴스 변수로 사용하지 말고 메서드로 만들어서 호출할 때마다 LocalDate가 갱신되도록 했어야 했습니다.
아 바보 같은 실수를 하다니 등잔밑이 어둡다더니 인스턴스 변수가 선언된 곳은 안 보고 쿼리가 왜 안되지 querydsl이 잘 못되었나? 내가 사용하는 버전이 문제가 있나? localDate는 아직 완벽하게 지원하지 않나?(실제로 2022년에 이슈에올라옴) 등등 너무 해당 코드가 구현된 메서드에만 집중했던 게 패착이었습니다.
다음부터는 조금 더 넓게 봐야겠다고 생각했습니다.
현재는 인스턴스 변수는 제거하고 private static 메서드를 만들어서 해당 메서드를 호출할 때마다 date가 갱신되도록 하고 있습니다.
'서비스 운영 일지 > Attraction' 카테고리의 다른 글
여러분의 프로젝트의 종속성은 안녕하신가요? (0) | 2024.08.02 |
---|