MySQL timestamp 와 Y2K38 Problem

Hyunho Jung
finda 기술 블로그
33 min readDec 27, 2022

Motivation

안녕하세요 핀다에서 DBA 로 근무하고 있는 정현호 라고 합니다.

오늘은 날짜와 시간에 관련된 여러가지 이야기를 해보려고 해요.

그리니치 천문대(Royal Observatory Greenwich)

먼저 올해 유행 또는 트렌드를 잠깐 얘기를 해볼게요!

작년에 이어서 올해에도 계속된 트렌드가 있다면 그것은 밀레니엄, Y2K 일 거라고 생각해요.

그중 패션계에서 주목하고 있는 트렌드는 바로 Y2K 패션으로 1990년대 말부터 2000년대를 일컫는 일명 Y2K 패션으로 부르는, 그 시절 패션이 작년에 이어 올해 한 해도 주요 트렌드였어요.

출처: 뉴진스 공식인스타그램(@newjeans_official) / 공식 트위터(@newjeans_ador)

유행과 트랜드는 돌고 돈다는 그 말처럼, 2022년인 재해석된 2000년대의 복고 또는 레트로 감성의 컨셉 Y2K 패션이 올해에도 많은 주목을 받았어요.

패션계 이외에도 방송에도 90~2000년대를 회상하게 만드는 드라마도 많은 인기를 끌었었는데요

공식 포스터

두 드라마 모두 90년대~2000년대 그 시절 실제 있었던 일들이 드라마 스토리에 담겨 있어요.

드라마 “스물다섯 스물하나”에서는 Y2K 버그(밀레니엄 버그)에 대한 내용이 하나의 에피소드로 나오면서 “지구가 멸망할지도 모른다고요”라고 걱정을 하는 부분이 있었고, 드라마 “재벌집 막내아들’에서도 “노스트라다무스가 예언한 1999년 지구 종말론 바로 이거였어요, Y2K로 인한 핵폭발!”이라고 그때 당시의 상황을 소개하며, 이 소재를 통해서 주인공이 Y2K 무제한 보상제라는 많은 금액의 광고 효과를 거뒀다는 내용이 드라마에서 다뤄지고 있습니다.

이처럼 올 한 해에는 세기말 Y2K에 대한 내용이 다양한 분야에서 다뤄졌고, 새해가 얼마 남지 않은 연말이 다가오다 보니 그때의 기억으로 세기말에 IT 와 세계를 떠들썩하게 했던 Y2K Problem(밀레니엄 버그)과 그 이후의 날짜와 시간에 이슈 대한 내용으로 정리해 보면 좋겠다 생각되어서 글을 작성하게 되었습니다.

Y2K

이번 글의 주제는 Y2K38 Problem 이라고 불리는 2038년도의 날짜 이슈인데요.

Y2K38을 얘기하기 전에, 우리가 익히 들어서 알고 있는 Y2K 이슈부터 살펴보도록 할게요.

밀레니엄 버그(Y2K)는 1999년 들어서 한국은 물론 전 세계를 불안에 사로잡았고, 한 해에 방송사의 주요 뉴스로 빠지지 않은 단골 소재였어요.

출처: 14f 유튜브 채널

2000년 문제 또는 밀레니엄 버그라고 불리는 이 문제는 1999년 12월 31일에서 2000년 1월 1일로 넘어가면서 날짜와 시간을 다루는 과정에서 오류가 발생되는 문제로 오래전에 컴퓨터 설계 당시에 오류입니다.

컴퓨터 메모리의 가격이 매우 고가에 첨단 부품이었던 60년대에는 기술적, 비용적 문제로 서기 연호를 2자리수로 만 인식과 처리하도록 설계하게 되었어요.

즉, 1965년을 65라고 만 저장하고 사용해왔던 것이었죠.

연도 표기에 관한 용어는 RTC(Real Time Clock·리얼타임시계)라고 하고 RTC 에서는 2자리수만 표기되도록 설계되었어요.

이러한 이유로 두 자리를 인식과 사용하는 시스템 설계가 되었고 처음에는 연도를 두 자릿수만 사용해도 컴퓨터가 연도를 인식하고 사용하는데 아무런 문제는 없었어요.

그러나 두 자릿수로 만 표기할 경우 몇 십 년 뒤에 2000년이 되면, 2000년과 1900년을 구분하지 못하게 되는 문제가 발생할 것이라고는 예상하지 못했던 것이었죠.

그래서 이 문제는 2000년 되기 몇 해부터 많은 이슈가 되었어요 밀레니엄 버그라고 불리는 Y2K에 대응하기 위해서 막대한 비용과 인력을 투자하여, 프로그램의 버그를 수정하고 대응하였어요.

하드웨어 측면에서는 486 정도 되는 컴퓨터에서 사용되는 바이오스부터는 이 문제를 해결해서 제작되거나, 486 메인보드부터는 BIOS 업데이트가 가능해서 바이오스 업데이트로도 해결할 수 있었어요.

486 이전의 386시스템이나 1970년대부터 사용된 메인 프레임 또는 예전 전산 시스템이 주요한 문제의 대상이었고, 그런 전산 시스템에서 구동 중인 프로그램도 연호를 2자리를 사용하고 있었던 것이었죠.

하드웨어에서 수정 가능하다면 수정을 하였고, 프로그램의 수정이 필요하다면 프로그램 수정을 진행하였어요.

그래서 2000년 되기 수년 전부터 예전에 작성된 코볼 프로그램에서 이 문제에 대응하기 위해서 그때 당시에도 많이 은퇴하여 찾기 힘든 코볼 프로그래머를 찾는 것이 아주 힘들었다고 해요.

일부 프로그램은 윈도우윙(Windowing)이라는 임시방편으로 00~20까지의 연도 숫자를 2000 ~ 2020년으로 인식하도록 코드를 수정하는 형태로 대응하기도 했어요

이런 윈도우윙 임시방편으로 대응한 프로그램이나 시스템은 다시 2020년 되어서 Y2K20 버그에 의해서 다시 문제가 발생된 경우도 있었어요.

설마! 2020년까지 그 프로그램이나 코드, 시스템이 사용될 것이라고 예측하지 못하였던 것이었죠.

다만 Y2K20으로 인한 문제된 비중이 크지 않고 하다 보니 밀레니엄 버그 때보다는 덜 이슈화가 되고 조용히 넘어가게 되었던 것이고요.

그럼 2020년을 넘어서 이제는 날짜나 시간에 관한 문제가 없을까요? 다 해결된 것일까요?!

가까운 시일 내는 아니지만, 여전히 끝은 아니었습니다.

2038년이 되면 Y2K38 Problem(Year 2038 버그) 이슈가 아직 남아 있었기 때문이죠.

Y2K38

이제 이 글의 본격적인 주제인 Y2K38(Year 2038 Problem)에 대해서 얘기를 해보려고 해요.

Y2K38은 2038년에 발생되는 날짜 시간과 관련된 문제로 Year 2038 Problem 또는 Unix Millennium bug라고 하며, 줄여서 Y2K38이라고 불리고 있어요.

앞에서 날짜와 시간과 관련된 문제가 발생되었던 Y2K 와 Y2K20 이슈를 살펴보았어요.

초기 설계 시의 어쩔 수 없는 여러 이유에 따른 오류였고, Y2K38 역시도 이와 유사하다고 할 수 있어요.

유닉스 시간(Unix Timestamp)은 32비트 정수형을 사용해서 날짜와 시간을 표현을 해요.

그래서 날짜/시간은 32 비트 정수형 사용 가능한 허용 범위에서만 표현할 수 있습니다.

표현 가능 범위의 제한에 의해서 유닉스 시간(Unix Timestamp)은 시간이 2038년 1월 19일 3시 14분 7초를 지나게 되면 처음 값(초기값)인 0으로 되면서 1970년 1월 1일 0분 0초로 돌아가는 오류가 발생하게 되며 이러한 문제 현상을 Y2K38 Problem이라고 합니다.

이 이슈는 유닉스 타임스탬프를 사용하는 모든 곳에서의 공통 사항이에요.

유닉스(Unix) 운영체제에서 채용한 Timestamp는 유닉스 시간(Unix time , Unix timestamp, POSIX Time) 또는 Epoch 시간이라고 불립니다.

그리니치 평균시를 기반으로 한 협정 세계시(協定 世界時, UTC) 기준 1970년 1월 1일 자정을 기준으로 해서 경과 시간을 초(second)로 환산하여 정수로 표현해요.

32비트 크기의 정수형으로 시간을 나타내는 변수를 사용하고 초당 1씩 증가합니다.

그로 인해서 시간 값이 32비트의 최대 허용 범위인 2,147,483,647까지 증가하게 되면 더 이상 증가할 수 없기 때문에 이 과정에서 오버플로우가 발생되면서 해당 변수의 가장 최솟값으로 돌아가게 됩니다.

1970년 1월 1일 자정에서 시작해서 2,147,483,647까지 증가된 날짜가 2038년 1월 19일 3시 14분 07초입니다.

그래서 2038년 1월 19일 3시 14분 07초가 지나게 되면 유닉스 시간의 처음 값으로 돌아가게 되고 그에 따라서 각종 계산의 오류나, 프로그램 수행 동작의 문제 및 오류 등 여러 문제점을 야기할 수 있게 되는 것이에요.

OS(시스템), 응용 프로그램, 데이터베이스, 32비트 시간 표현을 사용하는 데이터 구조, 32비트 시간 필드를 사용하는 바이너리형 파일 또는 파일 시스템 등 여러 곳에서 Unix Time 은 사용되고 있고 이 이슈가 해결되었는지, 해결되었다면 해결된 시점이나 버전 등에 대한 정보는 각 구성 요소마다 차이가 있습니다.

지금 사용 중인 시스템, 데이터베이스, timestamp 기능 등에서 해결되었는지 확인하는 방법은 간단해요.

UTC 기준으로 2038년 1월 19일 3시 14분 07초 이상의 시간이 사용되는지를 확인해보면 알 수 있습니다.

예를 들어 예전 커널 버전(5.6 이하)의 Linux 32-bit에서는 아래와 같이 문제가 발생하는지 간단하게 테스트해 볼 수 있어요.

$ uname -a
Linux testsrv 2.6.9-34.ELsmp #1 SMP Wed Mar 8 00:27:03 CST 2006 i686 i686 i386 GNU/Linux

$ echo $TZ
UTC

$ date -u -d "2038-01-19 03:14:06" +"%Y-%m-%d %H:%M:%S"
2038-01-19 03:14:06

$ date -u -d "2038-01-19 03:14:07" +"%Y-%m-%d %H:%M:%S"
2038-01-19 03:14:07

$ date -u -d "2038-01-19 03:14:08" +"%Y-%m-%d %H:%M:%S"
date: invalid date `2038-01-19 03:14:08'

문제가 해결되지 않은 시스템이나 함수 등에서는 위와 같은 테스트처럼 문제가 발생되거나 1970년도 1월 1일로 시간이 돌아가게 됩니다.

같은 방식으로 최근의 64-bit Linux에서 수행하면 아래와 같이 정상적으로 시간이 출력 되는 것을 확인할 수 있어요.


$ uname -a
Linux testsrv 5.10.109-104.500.x86_64 #1 SMP Wed Apr 13 20:31:43 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ echo $TZ
UTC
$ date
2022. 11. 28. (월) 18:35:49 UTC

$ date -u -d "2038-01-19 03:14:06" +"%Y-%m-%d %H:%M:%S"
2038-01-19 03:14:06

$ date -u -d "2038-01-19 03:14:07" +"%Y-%m-%d %H:%M:%S"
2038-01-19 03:14:07

$ date -u -d "2038-01-19 03:14:08" +"%Y-%m-%d %H:%M:%S"
2038-01-19 03:14:08

$ date -u -d "2038-01-19 03:14:09" +"%Y-%m-%d %H:%M:%S"
2038-01-19 03:14:09

마찬가지로 작성된 프로그램에서도 동일한 에러가 발생하는 것을 확인할 수 있는데요

간단하게 아래와 같이 작성을 하고

#include <stdio.h>
#include <time.h>

int main(void) {
time_t x;

x = 2147483647;
printf("%s\n", ctime(&x));
x += 1;
printf("%s\n", ctime(&x));
x += 1;
printf("%s\n", ctime(&x));

return 0;
}

실행을 하면, 마찬가지로 07초를 넘어서는 순간 시간의 문제가 발생이 되는 걸 확인할 수 있어요.

$ uname -a
Linux testsrv 2.6.9-34.ELsmp #1 SMP Wed Mar 8 00:27:03 CST 2006 i686 i686 i386 GNU/Linux

$ gcc -o test_g_lib_y2k38 test_g_lib_y2k38.c && ./test_g_lib_y2k38
Tue Jan 19 03:14:07 2038

Fri Dec 13 20:45:52 1901

Fri Dec 13 20:45:53 1901

최근 버전의 64-bit에서 동일하게 수행하면 역시 문제없이 정상적으로 날짜 출력이 되는 것을 확인할 수 있습니다.

$ uname -a
Linux testsrv 5.10.109-104.500.x86_64 #1 SMP Wed Apr 13 20:31:43 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

$ gcc -o test_g_lib_y2k38 test_g_lib_y2k38.c && ./test_g_lib_y2k38

Tue Jan 19 03:14:07 2038

Tue Jan 19 03:14:08 2038

Tue Jan 19 03:14:09 2038

32-bit 플랫폼의 자료형에서 long 이 8 byte(64bit) 가 아닌 4 byte(32bit)이고, 그래서 이 문제를 해결하는데 보통은 64-bit 아키텍처를 사용하면서 여러 방법으로 문제를 수정하고 있습니다.

다만 32-bit(i386) 시스템을 교체하기 어렵거나, 계속 32-bit 사용에 대한 요구사항이 있다 보니, 2020년 1월 9일에 Linux Kernel Mailing 을 통해서 Kernel 5.6 에서부터 32-bit에 대한 Y2K32의 문제 해결이 포함됨을 announce 하게 되었어요. (다만 주의할 점이 있으니 원문 메일링 내용을 참조)

MySQL 의 Timestamp와 Y2K38

그럼 이제 이 글에서 확인해보려는 MySQL의 timestamp에 대해서 얘기해 보려고 해요.

먼저 MySQL에서 날짜 및 시간 타입을 다루는 timestamp와 datetime의 각각의 차이점을 잠깐 살펴보겠습니다.

TimeStamp와 Datetime 차이

시간대(timezone) 지원 여부

timestamp 와 datetime 은 사실 거의 동일한 특성을 가지고 있으며, timestamp 는 UTC 로 저장되고 datetime은 그렇지 않다는 점이 큰 차이점이에요.

그에 따라서 timestamp는 DB의 time_zone 시스템 변수의 영향을 받으며 아래와 같이 간단하게 내용을 확인해 볼 수 있어요.

mysql> create table tb_dt_ts_test
(id int auto_increment primary key,
col_dt datetime, col_ts timestamp );

mysql> insert into tb_dt_ts_test(col_dt,col_ts)
values (now(),now());

mysql> select @@global.time_zone, @@session.time_zone,@@system_time_zone;
+-------------------+----------------------+--------------------+
| @@global.time_zone | @@session.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM | SYSTEM | UTC |
+--------------------+---------------------+--------------------+

mysql> select * from tb_dt_ts_test;
+----+---------------------+----------------------+
| id | col_dt | col_ts |
+----+---------------------+----------------------+
| 1 | 2022-11-30 21:38:54 | 2022-11-30 21:38:54 |
+----+---------------------+----------------------+


mysql> set session time_zone = "Asia/Seoul";
mysql> select * from tb_dt_ts_test;
+-----+--------------------+----------------------+
| id | col_dt | col_ts |
+----+---------------------+----------------------+
| 1 | 2022-11-30 21:38:54 | 2022-12-01 06:38:54 |
+----+---------------------+----------------------+

참고) 설치형 MySQL의 경우 타임존 테이블에 타임존 테이블 정보를 로드(Load) 해야 time_zone = ‘UTC’ 이 가능해요.

자세한 사항은 문서를 확인해 주세요.

저장 공간

저장 공간의 경우 큰 차이는 아니지만 아래와 같이 1 바이트 차이가 있어요.

Datetime : 5 bytes + fractional seconds storage
Timestamp : 4 bytes + fractional seconds storage

날짜 지원 범위

datetime 은 ‘YYYY-MM-DD hh:mm:ss’ 형식으로 표현하고 있으며 아주 큰 범위의 날짜 범위를 지원하고 있고 범위는 ‘1000–01–01 00:00:00’ ~ ‘9999–12–31 23:59:59’ 입니다.

timestamp 은 이름에서 유추할 수 있는 것처럼 UNIX Time(Timestamp) 와 동일해요.

그리니치 평균시를 기반으로 한 협정 세계시(協定 世界時, UTC) 기준 1970년 1월 1일 자정을 기준으로 해서 경과 시간을 초(second)로 환산하여 정수로 표현하는 것이에요.

Unix Time 과 동일하게 기본적으로 4 바이트 정수형 변수로 구현과 시간을 표현하고 있고, MySQL timestamp 는 우리가 읽은 수 있는 날짜 형식으로 값을 표현을 해요.

Unix Timestamp 와 MySQL timestamp 모두 같은 방식이기 때문에 2038년 1월 19일 3시 14분 07초까지만 표현이 가능해요.

MySQL의 UNIX_TIMESTAMP 함수를 이용하면 UNIX 형식으로 표현합니다.

mysql> select unix_timestamp ('2022-12-25 09:45:00') as 'unixt_imestamp'; 
+-----------------+
| unixt_imestamp |
+-----------------+
| 1671929100 |
+-----------------+

FROM_UNIXTIME 함수를 이용하면 Unix timestamp 값을 사람이 읽을 수 있는 형식으로 날짜 및 시간 값을 표현합니다.

mysql> select from_unixtime (1671929100) as 'mysql_timestamp';
+---------------------+
| mysql_timestamp |
+---------------------+
| 2022-12-25 09:45:00 |
+---------------------+

MySQL 에서의 Y2K38

위의 내용에서 확인할 수 있는 것처럼 MySQL에서 사용되는 timestamp 역시도 Y2K38 문제를 피해 갈 수는 없었고, 동일하게 문제가 발생하게 돼요.

MySQL에서 문제가 발생하는지는 아래와 같이 간단하게 확인할 수 있어요.

테스트를 위해서 사용한 버전은 MySQL 8.0.27 버전이고 OS(system)의 time_zone 은 UTC이고 MySQL의 time_zone 은 SYSTEM입니다.

mysql> select @@version;
+------------+
| @@version |
+------------+
| 8.0.27 |
+------------+

mysql> select @@global.time_zone, @@session.time_zone,@@system_time_zone;
+--------------------+--------------------------+---------------+
| @@global.time_zone | @@session.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM | SYSTEM | UTC |
+--------------------+---------------------+--------------------+

2038–01–19 03:14:07 날짜와 시간 값을 Unix time으로 변환을 하면 2147483647으로 4 bytes 정수형의 마지막 값이 출력이 돼요.

mysql> select unix_timestamp('2038-01-19 03:14:07')  as 'unixt_imestamp';
+-----------------+
| unixt_imestamp |
+-----------------+
| 2147483647 |
+-----------------+

그 다음 1초 뒤의 시간인 08초를 입력하면 integer overflow 가 발생되어 0으로 되돌아가는 것을 확인할 수 있어요.

mysql> select unix_timestamp('2038-01-19 03:14:08')  as 'unixt_imestamp';
+-----------------+
| unixt_imestamp |
+-----------------+
| 0 |
+-----------------+

overflow 설명

여기서 잠깐 overflow에 대해서 간략히 설명을 하면,

컴퓨터의 정수 연산의 계산 결과가 허용 범위를 초과할 때 발생하는 오류” 를 의미해요

4byte int 형의 경우 음수 와 양수를 모두 가질 수 있는 signed 형의 경우 값의 범위는 -2,147,483,648 ~ 2,147,483,647 이고,

양수만 처리할 수 있는 unsigned 형은 0 ~ 4,294,967,295 으로 양의 값에 대해서 더 넓은 값을 처리할 수 있어요

이와 같이 사용 가능한 값의 범위는 값을 담을 수 있는 크기에 따라서 정해져 있어요

최댓값의 범위를 넘어서게 되면 최솟값으로 되돌아가게 되고, 이를 overflow 라고 하고, 그림으로 보면 아래와 같이 표현할 수 있어요.

양수만 처리하는 unsigned 의 경우 최댓값을 넘어서면 –(음수)가 아닌 0 이 됩니다.

이것은 마치 잠을 잘 때 양 1마리, 2마리 세는 것처럼 반복이 된다고 아래 그림처럼 이해하면 쉬울 것 같아요

출처 : 나무위키 참조, 원본 : xkcd 571화 ‘Can’t Sleep’

이렇게 다시 되돌아가는 이유로 컴퓨터가 실제로 사용하는 이진수(binary number) 와 연관 되어있고, 특히 양의 값과 음의 값 처리에 대한 부분과 연관되어 있습니다.

2진수에서 가장 왼쪽의 1비트는 수에 영향을 가장 크게 미치는 비트인 중요 비트로 MSB(Most Significant Bit) 라고 부르는데요, 해당 비트는 부호(+/-)비트로 사용 되며 0일때는 양수, 1일때는 음수를 나타내게 돼요

4byte int 양수의 맨 마지막 값인 2,147,483,647를 2진수로 변환하면 아래와 같습니다.

01111111111111111111111111111111
(4byte=32bit=32자리)

여기에 1을 더 하게 되면 부호 비트가 0에서 1로 변하게 되고 아래와 같이 값이 바뀌게 됩니다.

10000000000000000000000000000000

위의 이진수는 10진수로 변환하면 -2,147,483,648 이 되게 됩니다.

2의 보수 표현법 따라 수의 부호(+/-) 를 변경하기 위한 계산은 비트를 반전시킨 다음 1을 더하면 되는데요

-2,147,483,648 값의 이진수인 10000000000000000000000000000000 값을 비트 반전을 시키게 되면

01111111111111111111111111111111 이 되고

여기에 다시 1을 더하면

10000000000000000000000000000000 이 다시 계산되게 됩니다.

즉, 원래 값이 되는 것이지요

그래서 2진수에서는 음수를 한개 더 표현할 수 있으며, 그에 따라 4byte int 에서의 사용 가능한 수의 범위는 signed 기준 -2,147,483,648 ~ 2,147,483,647 이 되는 것입니다.

다시 MySQL로 돌아와서!

2038–01–19 03:14:08 날짜와 시간 값을 Unix time으로 변경한 다음에 다시 MySQL Timestamp로 변환을 해보면 아래 결과처럼 시작 날짜 시간인 1970–01–01 00:00:00 으로 시간이 되돌아간 것을 확인할 수 있습니다.

mysql> select from_unixtime(unix_timestamp('2038-01-19 03:14:08')) 'mysql_timestamp';
+---------------------+
| mysql_timestamp |
+---------------------+
| 1970-01-01 00:00:00 |
+---------------------+

Y2K38 에 대해서 처음 설명한 내용과 같이 OS 시스템이나 애플리케이션 별로 수정하고 개선한 시점과 방식이 조금씩 다른데요.

그럼 MySQL 어떻게 수정이나 대응하였을까요?

MySQL 에서 개선된 사항

DBMS마다 개선사항 반영 여부의 차이가 있으며, MySQL 은 다행히 올해(2022년) 초에 릴리스된 8.0.28 버전에서 Y2K38 개선 사항이 포함되었습니다.

그래서 MySQL 버전 8.0.28에서 테스트해보면 ‘2038–01–19 03:14:07’ 이후로도 날짜 시간이 표시되는 것을 확인할 수 있어요.

mysql> select @@version;
+------------+
| @@version |
+------------+
| 8.0.28 |
+------------+

mysql> select @@global.time_zone, @@session.time_zone,@@system_time_zone;
+--------------------+--------------------------+---------------+
| @@global.time_zone | @@session.time_zone | @@system_time_zone |
+--------------------+---------------------+--------------------+
| SYSTEM | SYSTEM | UTC |
+--------------------+---------------------+--------------------+


mysql> select unix_timestamp('2038-01-19 03:14:07') as 'unixt_imestamp';
+-----------------+
| unixt_imestamp |
+-----------------+
| 2147483647 |
+-----------------+

mysql> select unix_timestamp('2038-01-19 03:14:08') as 'unixt_imestamp';
+-----------------+
| unixt_imestamp |
+-----------------+
| 2147483648 |
+-----------------+

mysql> select unix_timestamp('2038-01-19 03:14:09') as 'unixt_imestamp';
+-----------------+
| unixt_imestamp |
+-----------------+
| 2147483649 |
+-----------------+

MySQL 8.0.27 와 달리 8.0.28 버전에서는 ‘2038–01–19 03:14:08’ 가 돼도 문제가 발생하지 않게 되었어요!

그럼 더 자세하게 어떻게 개선점이 반영되었는지 Source Code를 통해서 조금 더 자세하게 살펴볼게요.

8.0.28 버전에서는 MySQL의 시간과 관련된 헤더 파일인 my_time.h 파일에서 새로운 헤더 파일 my_time_t.h 을 참조하는 include 구문이 추가되었어요.

#include "my_time_t.h"

그래서 먼저 새로 추가된 my_time_t.h 파일 간략하게 살펴보도록 할게요.

#ifdef __cplusplus
using my_time_t = int64_t;
#else
typedef my_time_t int64_t;
#endif

struct my_timeval {
int64_t m_tv_sec;
int64_t m_tv_usec;
};

새로운 구조체 my_timeval 을 선언한 것을 확인할 수 있으며, 멤버변수의 타입은 int64_t 으로 64-bit 플랫폼에서 long long int 형으로 즉, 64-bit=8 bytes 인 long 정수형 변수로 선언되어 있습니다.

my_time_t 역시도 64-bit=8 bytes 인 long 정수형 변수로 선언되어 있는 것을 확인할 수 있습니다.

이제 주요 변경 사항인 my_time.h 헤더파일을 살펴보도록 할게요.

constexpr const bool HAVE_64_BITS_TIME_T = sizeof(time_t) == sizeof(my_time_t);

sizeof(time_t) == sizeof(my_time_t) 같은 지 여부에 따라서 해당 결과를 참/거짓(True/False) 값을 담는 boolean 타입으로 HAVE_64_BITS_TIME_T 선언하였고, 2개의 변수의 비교 결과를 변수에 대입하게 됩니다.

my_time_t는 새로 추가된 헤더파일 my_time_t.h에서 64-bit=8 bytes 로 선언되었고, time_t는 64비트 플랫폼에서 64-bit=8 bytes으로 사용됩니다.

그래서 둘 다 8 bytes가 맞는다면 TRUE 가 반환하게 됩니다.

2개 변수의 사이즈가 얼마인지, 2개가 같은지는 아래와 같이 간단하게 테스트해볼 수 있는데요

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/time.h>
#include "my_time_t.h"


int main(void)
{
printf("time_t: %dbyte\n",sizeof(time_t));
printf("my_time_t: %dbyte\n",sizeof(my_time_t));

if ( sizeof(time_t) == sizeof(my_time_t) )
printf("true\n");
else
printf("false\n");
return 0;
}

실행해서 테스트 해보면 둘다 8 byte로 같다는 것을 알 수 있습니다.

$ g++ -o sizeof_time_diff sizeof_time_diff.cc && ./sizeof_time_diff
time_t: 8byte
my_time_t: 8byte

true

2개 변수가 같은 8 byte임으로 HAVE_64_BITS_TIME_T 는 TRUE 가 되게 돼요.

그 다음에는 아래의 삼항 연산자 부분을 살펴볼게요.

constexpr const my_time_t MYTIME_MAX_VALUE =
HAVE_64_BITS_TIME_T ? 32536771199
: std::numeric_limits<std::int32_t>::max();

HAVE_64_BITS_TIME_T 는 TRUE이기 때문에 MYTIME_MAX_VALUE 변수에는 32536771199 이 대입되게 되며, 해당 변수의 데이터 타입은 my_time_t 즉, 8 bytes long형이 되게 돼요.

그 다음은 inline함수 is_time_t_valid_for_timestamp 에 대해서 8.0.27 버전과 8.0.28 버전을 비교해서 살펴보도록 할게요

## MySQL 8.0.27
inline bool is_time_t_valid_for_timestamp(time_t x) {
return x <= TIMESTAMP_MAX_VALUE && x >= TIMESTAMP_MIN_VALUE;
}


## MySQL 8.0.28
inline bool is_time_t_valid_for_timestamp(time_t x) {
return (static_cast<int64_t>(x) <= static_cast<int64_t>(MYTIME_MAX_VALUE) &&
x >= MYTIME_MIN_VALUE);
}

boolean 타입(true/false)으로 생성된 inline 함수 is_time_t_valid_for_timestamp 은 timestamp 값에 대한 유효성 체크에 대해서 8.0.27 버전에서는 TIMESTAMP_MAX_VALUE 와 TIMESTAMP_MIN_VALUE 변수를 사용했습니다.

8.0.28 버전 부터는 MYTIME_MAX_VALUE 와 MYTIME_MIN_VALUE 를 사용하는 것으로 변경되었어요.

MYTIME_MIN_VALUE 는 0 이 대입되고 MYTIME_MAX_VALUE 는 32536771199 으로 대입돼요.

Unix_timestamp의 0(MYTIME_MIN_VALUE)은 1970–01–01 00:00:00.000000 가 되고, 32536771199(MYTIME_MAX_VALUE)는 3001–01–18 23:59:59.000000 이 되게 됩니다.

그래서 결론적으로 MySQL 8.0.28 버전부터는 timestamp의 날짜 지원 범위는 1970–01–01 00:00:00.000000 ~ 3001–01–18 23:59:59.000000 으로 확장 되게 되는 것이지요.

mysql> select @@version;
+------------+
| @@version |
+------------+
| 8.0.28 |
+------------+

mysql> select from_unixtime('0') as 'from_unixtime' ;
+-----------------------------+
| from_unixtime |
+-----------------------------+
| 1970-01-01 00:00:00.000000 |
+-----------------------------+

mysql> select from_unixtime('32536771199') as 'from_unixtime' ;
+----------------------------+
| from_unixtime |
+----------------------------+
| 3001-01-18 23:59:59.000000 |
+----------------------------+

그러면 여기서 한 가지 더! 의구심이나 궁금한 부분이 있으실 거예요~

그건 아마도 long형 데이터 타입이 8 bytes이고 9,223,372,036,854,775,807(signed 기준)까지 사용 가능한데 왜 표현 범위를32,536,771,199 까지로 하였는가 일거 같아요!

MySQL 8.0.28 버전에서의 timestamp 개선점은 Linux, MacOS, and Windows 64-bit 플랫폼에서만 적용되었어요.

그런데 Windows에서 사용되는 localtime_s inline 함수는 UTC 3001년 1월 18일 23:59:59까지 날짜 표현이 가능하게 구현이 되어 있습니다.

그래서 MySQL 사용 시 플랫폼별로 다른 값을 출력하는 것이 아닌 같은 값을 위해서, 즉 통일성을 위해서 Windows, MacOS, Linux 3개 시스템 모두 MYTIME_MAX_VALUE 의 값을 magic constant으로 32536771199 정하게 되었습니다.
(참고로 32-bit 플랫폼은 여전히 2038–01–19 03:14:07 까지 표현되며, 이런 개선점이 반영되지는 않았어요!)

위에서 확인한 내용처럼 Windows의 localtime_s inline 함수는 UTC 3001년 1월 18일 23:59:59 까지 표현 가능해요 그렇다면 이 이후는 어떻게 되는 걸까요?

그래서 이와 관련해서 벌써 Windows Y3K Bug 이란 칼럼 글이나 블로그 글도 찾아볼 수 있습니다.

MySQL에서 3001년 1월 18일 23:59:59 이후 날짜에 대해서 정말 문제가 발생할까요? 확인해 보면 Y2K38 Problem 과 동일하게 Unix_timestamp 가 0 이 되는 것을 확인할 수 있어요.

mysql> select unix_timestamp('3001-01-19 00:00:05')  as 'unixt_imestamp';
+-----------------+
| unixt_imestamp |
+-----------------+
| 0 |
+-----------------+

MySQL의 현재 개발 정책에서는 3001년 1월 18일 23:59:59 이후 날짜 사용이 필요한 경우 datetime 사용을 가이드 하고 있어요.
( datetime 표현 범위 : ‘1000–01–01 00:00:00’ ~ ‘9999–12–31 23:59:59’ )

MySQL에서 timestamp를 사용하였을 때 이런 제약사항 또는 고려해야 할 부분이 있을 수 있지만, Unix 타임스탬프는 timezone 을 반영할 수 있으며, 지역과 관계없이 1970년 1월 1일부터 시작하는 즉, 동일 시점에서 계산을 할 수 있고 이러한 동일한 시점에 증가되는 방식을 이용하여 순차적인 증가하는 고유의 값을 생성하여 사용하거나 2개의 시간을 비교하여 크고 작음을 분별할 수 있는 등의 여러 장점과 활용도가 있습니다.

2022년인 지금에서 보면 2038년도 멀어 보이는데, 3001년은 아주 먼 나중의 일이지만 그래도 3001년에 가면 문제가 생길 텐데? 하는 걱정도 해보며 이 글을 작성해보았습니다.

역사는 반복되고 항상 그랬듯이 이런 날짜 와 시간에 대한 이슈는 다시 또 좋은 방향으로 수정되고 개선이 되지 않을까? 하는 예상을 해보면서 이번 글을 이만 마무리하겠습니다.

2022년 한 해 얼마 남지 않았네요, 올해 모두 수고하셨습니다.

즐거운 연말 보내시고, 새해 복 많이 받으세요~

--

--

Hyunho Jung
finda 기술 블로그

Database Administrator at finda, Tech Stack : MySQL, AWS Aurora , Oracle, MongoDB, Linux