드라이버 팀의 주요 임무는 대상 프로그래밍 언어 및 Neo4j 배포 토폴로지에 관계없이 관용적이고 일관된 개발자 경험을 제공하는 것이에요.
일관성은 다양한 드라이버가 노출하는 개념과 API뿐만 아니라 작동 방식에서도 나타나죠.
이를 보장하는 주요 방법은 공유 승인 테스트 제품군을 이용하는 것인데, 바로 TestKit이에요.
그러던 중, 제 동료인 Rouven이 TestKit에 날짜 및 시간 관련 테스트를 도입하는 작업을 하다가 이상한 점을 발견했어요.
Neo4j 역사 및 Bolt 프로토콜 101
문제의 핵심을 살펴보기 전에 Bolt 프로토콜이 어떤 목적으로 사용되는지 간단하게 요약해 볼게요.
프로토콜에 이미 익숙하시다면 이 섹션과 다음 섹션은 건너뛰셔도 괜찮아요.
역사적으로 Neo4j는 JVM 전용 임베디드 데이터베이스로 시작했답니다.
실제로 동일한 환경(공유 힙, 공유 가비지 수집 주기 등)을 공유하면서 애플리케이션과 같은 위치에서만 실행되는 방식이었죠.
Neo4j 1.0(약 2010년) 즈음에 REST API가 출시되면서 Neo4j는 모두가 알고 좋아하는 기본 포트 7474를 사용하여 독립형 서버로 배포될 수 있게 되었어요.
클러스터링도 곧 등장했죠.
몇 년 후(약 2015 ~ 2016년 초), Neo4j 3.0이 나왔고, 완전히 새로운 바이너리 애플리케이션 프로토콜이 등장했는데, 바로 기본 포트 7687을 사용하는 Bolt 프로토콜이에요. Bolt 프로토콜이 탄생하면서 Bolt 서버 구성 요소와 이를 구현하는 Java, Python, JavaScript용 공식 드라이버 세트도 함께 나왔죠(.NET 및 Go는 나중에 출시되었고요).
간단히 말해서 Bolt 프로토콜은 클라이언트와 서버가 특정 Bolt 메시지를 통해 상호 작용하는 방식을 지정하고, PackStream 형식으로 데이터를 교환하는 방식이에요.
더 자세한 내용은 에서 확인하실 수 있어요.
날짜시간* 구조
이러한 데이터 구조 중에서 DateTime과 DateTimeZoneId를 찾아볼 수 있어요.
둘 다 초와 나노초 단위로 정의된 지역화된 시점을 인코딩하죠.
이 데이터들은 다음과 같은 곳에서 나타날 수 있어요.
- Cypher 쿼리 매개변수
- 시간적 기능 중 하나를 호출할 때의 Cypher 쿼리 결과
- 반환된 Node 및/또는 Relationship의 날짜/시간 속성
특정 시점을 현지화하는 방식에 따라 달라지는데요.
DateTime은 UTC로부터의 오프셋을 초 단위로 지정해요.DateTimeZoneId는 시간대 이름으로 현지화를 지정하고요.
어떻게 작동하는지 설명해 드릴게요.
1970–01–01T02:15:00.000000042+01:00을 DateTime으로 예시를 들어볼게요.
해당 UTC 시간은 1970–01–01T01:15:00.000000042Z (Z는 UTC를 나타내요)가 되겠죠.
이후 경과된 초 수는 유닉스 에포크 1시간 15분, 즉 4,500초에요.
오프셋은 1시간, 즉 3,600초이고요.
현지화된 초 수는 4500+3600, 즉 8100초가 돼요.
결과 DateTime은 따라서 다음과 같아요.
{
seconds: 8100
nanoseconds: 42,
tz_offset_seconds: 3600
}
똑같이 해볼까요? DateTimeZoneId와 1970–01–01T02:15:00.000000042[Europe/Paris]를 예로 들어볼게요.
이 시점에서 이 시간대의 UTC offset은 +1시간이에요.
따라서 UTC 시간은 1970–01–01T01:15:00.000000042Z가 되죠.
여기서 위와 동일한 계산이 이루어지고 결과 DateTimeZoneId는 다음과 같아요.
{
seconds: 8100
nanoseconds: 42,
tz_id: “Europe/Paris”
}
스웨덴으로 돌아가기
Rouven은 1980–09–28T02:30:00[Europe/Stockholm], 즉 유럽/스톡홀름 시간대로 1980년 9월 28일 오전 02시 30분에 문제를 발견했어요.
다음 Cypher 쿼리를 실행해서 직접 확인해 볼 수 있어요. RETURN datetime ("1980–09–28T02:30:00[Europe/Stockholm]")를 실행하고 결과를 확인해 보세요.
1980년 9월 28일에 스웨덴에서 무슨 일이 있었던 걸까요?
(스웨덴은 이 분야에서 특별한 위치를 차지하고 있다는 점을 언급해야겠네요. Jon Skeet이 유명하게 지적했듯이요.)
제가 오늘 (빛을) 구하러 왔어요!
정답은 '네'입니다!
1980년에 스웨덴은 서머타임, 즉 일광 절약 시간제(DST)를 시행하기 시작했어요.
DST는 일년 중 특정 시기에 시계를 이동시켜 깨어 있는 시간을 일광 시간에 맞추는 방식이에요.
겨울에는 보통 시계를 한 시간 앞으로 당겨요.
예를 들어, 어떤 나라가 오전 2시에 시간 이동을 하기로 결정했다면, 오전 1시 59분 59초 후에 시계는 오전 3시로 이동하는 거죠.
여름에는 일반적으로 시계를 1시간 뒤로 설정해서 시간이 중복되는 현상이 발생해요.
같은 예로, 오전 2시 59분 59초 후에는 시계가 오전 2시로 다시 설정되는 거죠. 다른 UTC offset으로 두 번 발생하게 돼요.
DateTimeZoneId의 모호함
1980–09–28T02:30:00[Europe/Stockholm]을 Bolt 프로토콜에 표현된 DateTimeZoneId로 변환해 볼게요.
가장 먼저 해당 UTC 시간을 알아내야 해요.
해당 날짜에 시계는 처음 발생한 오전 2시 59분 59초 이후에 오전 2시로 다시 설정되었어요.
따라서 UTC offset이 다른 두 개의 오전 2시 30분이 존재했던 거죠.
이 시간은 중복을 나타내기 때문에 어떤 offset을 사용해야 할지 알 수 없어요.
설상가상으로 대부분의 언어는 이 날짜/시간을 자동으로 해결해 버려요.
다음 Go 프로그램을 제 컴퓨터에서 실행하면 1시간의 offset이 출력될 거예요.
그러면 다음이 출력되죠: The offset is 3600s.
Go API의 time.Date 문서를 보면 다음과 같이 명시되어 있어요:
Date는 전환과 관련된 두 영역 중 하나에서 올바른 시간을 반환하지만 어느 영역인지 보장하지는 않습니다.
다음 Python 프로그램은 프로그래머가 지역화 프로세스를 좀 더 제어할 수 있음을 보여줘요 (두 번째 localize 호출과 해당 파라미터 is_dst에 주목하세요):
전반적으로 모호성은 여전히 남아있고, 앞으로 버그가 발생할 가능성이 있다는 거죠.
수정이 필요한 시점
이 문제의 근본 원인은 DateTimeZoneId의 seconds 필드에 때로는 해결할 수 없는 offset이 포함되어 있다는 점이에요.
모호성을 해결하는 한 가지 방법은 DateTimeZoneId의 seconds 필드를 UTC 시간으로 인코딩하는 거예요 (DateTime의 seconds 필드도 일관성을 위해 UTC로 인코딩하는 게 좋겠죠).
UTC 시간은 단조롭기 때문에 (계속 증가하니까) 모호할 일이 없어요.
유럽/스톡홀름 시간대의 끔찍했던 1980년 9월 28일 오전 2시 30분으로 다시 돌아가서, UTCDateTimeZoneId (UTC로 인코딩된 대체 DateTimeZoneId)의 seconds 필드는 다음 중 하나가 될 거예요.
- 338949000초, 즉
1980–09–28T02:30:00+02:00 - 아니면 338952600초, 즉
1980–09–28T02:30:00+01:00
더 이상 모호함은 없죠! 문제가 해결된 거예요!
수정 일정
UTC 인식 구조는 Neo4j 5 릴리스부터 사용할 수 있어요.
드라이버가 요청하고 서버가 요청을 수락하는 경우, 4.4.12 이후의 모든 Neo4j 4.4 릴리스에서도 사용할 수 있어요.
이렇게 하면 최신 서버에 연결하는 구형 드라이버가 기존 날짜/시간 구조와 계속 작동하므로 호환성 문제가 발생하지 않아요.
- Bolt Protocol
- Neo4j 드라이버
- UTC
에이치시스템즈의 LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.
'Neo4j' 카테고리의 다른 글
| 그래프로 자연어 검색, 속도를 더하다 (0) | 2026.09.03 |
|---|---|
| 2020년 Neo4j, 가장 핫했던 9가지 뉴스 & 발표 총정리! (0) | 2026.09.02 |
| 임시 Neo4j 데이터베이스를 위한 라이브러리 (0) | 2026.09.01 |
| Cypher Map Projection 완벽 가이드 (0) | 2026.09.01 |
| Neo4j Enterprise Edition을 위한 Production급 PrivateLink 설정 완벽 가이드 (0) | 2026.08.31 |
