요즘IT
위시켓
최근 검색어
전체 삭제
최근 검색어가 없습니다.

국내 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하고 있을까요?

회원가입을 하면 원하는 문장을
저장할 수 있어요!

다음

회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!

확인

개발

MySQL 타임스탬프와 Y2K38 문제

년차,
어떤 스킬
,
어떤 직무
독자들이 봤을까요?
어떤 독자들이 봤는지 궁금하다면?
로그인

국내 IT 기업은 한국을 넘어 세계를 무대로 할 정도로 뛰어난 기술과 아이디어를 자랑합니다. 이들은 기업 블로그를 통해 이러한 정보를 공개하고 있습니다. 요즘IT는 각 기업의 특색 있고 유익한 콘텐츠를 소개하는 시리즈를 준비했습니다. 이들은 어떻게 사고하고, 어떤 방식으로 일하고 있을까요?

 

이번 글에서는 핀테크 스타트업 핀다 DBA가 MySQL 타임스탬프와 Y2K38 문제에 대해 소개합니다. 작년에 작성된 글이어서, 글에서 '올해'라고 표현한 것은 모두 2022년에 해당됨을 미리 밝힙니다.

 

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

 

그리니치 천문대(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> 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년에 가면 문제가 생길 텐데?하는 걱정도 해보며 이 글을 작성해 보았습니다. 역사는 반복되고 항상 그랬듯이 이런 날짜와 시간에 대한 이슈는 다시 또 좋은 방향으로 수정되고 개선하지 않을까?라는 예상을 해보며, 이번 글을 마무리하겠습니다.

 

<원문>

MySQL timestamp 와 Y2K38 Problem

 

요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.

좋아요

댓글

공유

공유

댓글 0
핀다
기업
작가
8
명 알림 받는 중

작가 홈

핀다
기업
작가
8
명 알림 받는 중
안녕하세요! 핀다 개발조직 담당자입니다! 핀다는 더 나은 대출/금융상품 경험을 제공하는 혁신적인 핀테크 스타트업입니다. 저희 개발조직은 프로덕트 조직과 공통조직(데브옵스, 인프라, 데이터베이스 엔지니어, 품질 관리 등)이 협력 하여 고객 중심의 서비스를 지속적으로 제공하고 있습니다.

이 기술블로그에서는 고객이 왜 저희 서비스를 좋아하는지, 그리고 서비스 개발 과정에서 겪는 다양한 도전과 그 해결 전략을 공유할 예정입니다. 기술적 통찰 뿐만 아니라, 사용자 경험을 최적화하는 방법에 대한 실질적인 이야기도 들려드릴 것입니다.

코드만 작성하는 것이 아닌, 실제로 사람들의 금융 경험을 향상시키는 제품을 만드는 것이 저희의 궁극적인 목표입니다. 이 공간에서 그 과정과 성과를 함께 나눌 수 있기를 기대합니다. 감사합니다!

좋아요

댓글

스크랩

공유

공유

요즘IT가 PICK한 뉴스레터를 매주 목요일에 만나보세요

요즘IT가 PICK한 뉴스레터를
매주 목요일에 만나보세요

뉴스레터를 구독하려면 동의가 필요합니다.
https://auth.wishket.com/login