pt-online-schema-change에 숨겨진 무시무시한 이슈!

Overview

최근들어 거의 연단위로 블로깅을 하나씩 올리는 듯 하는군요. 여기저기 시국이 어지럽고, 바쁘다는 말도 안되는 핑계를 무마시키기 위해.. 아무튼 간만에 블로깅 하나 올려봅니다.

MySQL은.. 특히나 온라인 스키마 변경이 취약합니다. 물론 5.6부터는 online alter기능이 포함되어 있다고는 하나.. 100% 완벽하게 모든 상황을 온라인스럽게 제공해주지도 않고.. 그렇다하더라도, 일정 트래픽 이상의 데이터 변경이 이루어지는 경우, 게다가 슬레이브 지연을 염두한다면.. 꺼려지는 상황이 있지요. (참고로, 마스터에서 온라인 스키마 변경이 이루어졌을지라도, 이 관련 alter구문이 슬레이브로 넘어갔을 때는, alter이후 데이터 변경을 수행해야 하므로, 그만큼 복제 지연이 발생합니다. 미네럴~)

아무튼.. 이런저런 이유로.. 기승전툴이라는 생각이드는데요. 그중, Percona에서 오픈소스 라이선스로 제공하는 pt-toolkit에 포함된 pt-online-schema-change툴을 저는 애용합니다.

참 좋지만.. 반드시 짚고 넘어가야할 이슈 하나만은 반드시 공유할 필요가 있다는 생각이 들어, 이렇게 늦은 시간 포스팅을 합니다. 🙂

pt-online-schema-change?

문제를 말하기에 앞서서, 먼저 pt-online-schema-change의 동작 방식에 대해서 정말 간~략하게 알아볼 필요가 있겠군요.

이 툴은 서비스 중지 없이 실시간으로 테이블 DDL Alter구문을 적용할 수 있도록 도와주는 하나의 “유틸리티”이며, 흔한 RDBMS에서 제공하는 기능 몇가지와, 머리좋은 개발자의 “꼼수”가 환상적인 조합을 이루며 만들어진 멋진 툴입니다. 짝짝짝~

간단한 테이블 변경 순서는 아래와 같아요.

  1.  클론 테이블 생성 후 의도한 스키마를 적용한다.
  2.  원본 테이블 변경 시 트리거로 이를 클론 테이블에 적용한다. (replace into/delete)
  3.  조각 조각 데이터를 잘라서, 데이터를 중복은 무시한 채 복사한다. (insert ignore)
  4.  테이블 복사가 완료되면, 클론<->원본 테이블명을 스위칭한다.
pt-online-schema-change
pt-online-schema-change

Percona..당신들의 잉여력은 많은 이들을 행복하게 하였습니다. 땡큐~!!

 

Problem

물론, pt-online-schema-change도 하나의 툴인 이상.. 분명 100% 모든 상황에 적합하지는 않습니다.

뭐.. 예를 들면.. 아무래도 데이터를 조각조각 나누어서 복사를 해야하는만큼, Primary Key는 반드시 존재해야하는 상황이어야하고, Foreign Key가 정의된 경우에도 제약이 있고.. 궁시렁궁시렁..

그렇지만, 오늘 여기서 짚어보자하는 문제는.. 사실은 pt-online-schema-change가 충분히 동작할 수 있는 상황에서, 데이터가 달라질 수 있는.. 곰곰히 생각을 해보면 서비스 오류까지 발생할 수 있는 그런 상황입니다요.

pt-online-schema-change에서 앞서 설명을 간단하게 했던 것처럼, 트리거를 통해서 현재 데이터 변경된 이력을 클론 테이블(스키마 적용된 최종본 구조 테이블)로 적용합니다. 적용시 insert/update 구문 모두 아래와 같이 replace into 구문으로 대동단결 하는데요. (참고로 _test_tmp는 클론테이블입니다.)

DELIMITER $$
CREATE TRIGGER trg_test_update
AFTER UPDATE ON test
FOR EACH ROW
BEGIN
 REPLACE INTO _test_tmp VALUES (NEW.id, .., NEW.created_at);
END$$
DELIMITER ;

동일 트리거이니.. 문제삼을 update 트리거만..

멀쩡한 상황에서는 이슈가 아닙니다만.. 여기서 replace의 동작 방식을 이해하고, 조금만 고개를 갸우뚱거리면 무엇이 문제인지 이해하실 수 있을 것이예요. 귀찮으니, 매뉴얼 원문을 퍼오겠습니다. 크하하

REPLACE works exactly like INSERT, except that if an old row in the table has the same value as a new row for a PRIMARY KEY or a UNIQUE index, the old row is deleted before the new row is inserte

그렇답니다. 기존의 pk/uk와 같이 충돌나는 것들은 다 날리고.. 신규 데이터로 밀어넣는다는 것인데요. 그렇기에 pk가 없으면 대 재앙일 것이고.. 이런 부분에 대해서는 나름 pt-online-chema-change에서 제약 조건을 체크하기는 합니다. (그랬던가.. 가물가물..)

그렇다면.. 이런 경우 말고.. PK가 변경되는 상황을 생각해볼까요? 이런 상황에서도 역시 replace into 구문으로 트리거는 동작할 것이고.. 트리거 내부에는 “NEW.id, .., NEW.created_at”와 같이 변경될 데이터 기준으로 클론 테이블에 적용될 것이고.. NEW.id가 PK라면, 이 기준과 동일한 데이터를 날리고 엎어버릴 것이고..

그렇다면.. 우움.. 기준이 되는 NEW.id가 변경된 경우에는.. -_-;; 예전 OLD.id의 행방은??

딩동댕~ 맞습니다. ㅠㅠ 어이없게도.. 업데이터로 동작을 해야할 상황이.. 기준점이 변경되었다는 이유로.. 여전히 남아있는 상태가 되겠지요. 미네럴2.

즉.. 로직적인 이슈로.. 기준점이 되는 PK를 변경하는 경우 여전히 과거의 이력이 남아있게 되는.. 어이없는 상황이 발생한다는 것입니다!! <==== 주의주의1

Solutions

사실.. 해결책은 의외로 간단합니다.

PK는 업데이트 하지 말고, 트랜잭션으로 묶어서 DELETE -> INSERT 처리한다. 참 쉽죠잉~! 이것은.. 뭐.. 해결방안이라기 보다는 그냥 “우회처리”라고 해야하나.. -_-;;

그치만.. 사용자를 쿼리를 날리는 경우도 있고.. 기타 이런 상황을 인지 못하는 경우를 대비해서.. 본질적인 해결 방안이 필요합니다.

PK가 변경된 경우 트리거 내부적으로 과거 데이터를 날려주는 로직을 넣어준다.

트리거 정의하는 부분에.. PK 변경 시 OLD 이미지에 해당하는 데이터를 날리도록 강제하면 되지요. 뭐.. 아래 예시는.. 쿼리 레벨 간단하게 소스를 약간 수정한 것일뿐.. 조금 수정해서 트리거 내부에 if else 구문을 넣어도 되고..암튼 그렇답니다.

sub create_triggers {
  .. 중략 ..
  ## 추가 ##
  my $upd_index_cols = join(" AND ", map {
    my $new_col = $_;
    my $old_col = $old_col_for{$new_col} || $new_col;
    my $new_qcol = $q->quote($new_col);
    my $old_qcol = $q->quote($old_col);
    "OLD.$old_qcol <=> NEW.$new_qcol"
  } @{$tbl_struct->{keys}->{$del_index}->{cols}} );

  .. 중략 ..

  ## DELETE 부분 추가 ##
  my $update_trigger
    = "CREATE TRIGGER `${prefix}_upd` AFTER UPDATE ON $orig_tbl->{name} "
    . "FOR EACH ROW "
    . "BEGIN "
    . "DELETE IGNORE FROM $new_tbl->{name} WHERE !($upd_index_cols) AND $del_index_cols;"
    . "REPLACE INTO $new_tbl->{name} ($qcols) VALUES ($new_vals);"
    . "END ";
  .. 중략 ..
}

PK가 변경된 경우에는 데이터를 지워버리고, 그렇지 않으면.. 그냥 예전처럼 replace~ 트리거 내부적으로 update시에 쿼리는 많아지겠고, 트랜잭션이 없는 상황에서는 약간 걱정될만한 사항도 있겠지만.. 다들 기본적으로 InnoDB사용하시잖아요? ㅋㅋ

사실.. 이런 부분은 간단하게 소스를 수정해서 활용을 해도 됩니다. 어려운것도 아니고.. 잉여력이 조금 남아있다면 얼마든지 상황에 맞게 고쳐 쓸 수 있겠지요. 그치만.. 이 부분에 대해서 Percona에 문의를 해보니.. 그들의 입장은.. 번역하면 아래와 같았습니다.

어째서 PK가 변경이 되어야하는 것인가? 테이블에서 PK는 절대적이므로, 결코 업데이트 작업이 이루어지지 않아야 한다. 우리들은 이런 현상을 버그로 인정하지 않는다. – 어느 개발자 –

버그가 아니라면.. 이런 상황을 감지해서 제약을 걸든가.. 미네럴3..

사실.. 가장 경계를 해야하는 문제는.. (제 개인적인 의견이지만) 런타임 오류라고 생각합니다. 서비스 오픈 전에 발생하는 모든 이슈는 사전에 불을 끄고 튜닝하고 해결할 수 있지만.. 런타임 도중 특이 케이스에서만 노출되는 이슈는 정말로 답이 없습니다.

위와 같은 동일한 방식은 아니더라도, 적어도 PK변경 시 뒤따르는 이러한 데이터 꼬임 현상에 대한 것들은 Percona 측에서 제대로 이해하고 GE버전에 적용을 해줬으면 하는 마음이 있고.. 뭐.. 개인적으로, 조직적으로 푸시를 넣고 있습니다만..

좋은 소식이 있겠지요. ^^

Conclusion

pt-online-schema-change 툴은 굉장히 유용하고, 제가 정말 자주 쓰는 권고할만한 유틸입니다. 게다가 소스도 오픈되어 있고, 누구나 수정할 수 있는 라이선스 정책이고요.

그렇지만, 오픈소스를 사용하는만큼.. 그렇게 접근을 하기에.. 상용 솔루션과는 다르게 발생하는 이슈에 대해서 분석을 해보고 해결책을 가져가보고, 대안도 고민해보고.. 사용자 자신도 오픈소스를 사용하는 대가를 치뤄야 하겠지요. 뭐.. 제 능력으로는 지금과 같이 경험을 공유하는 것이 그나마 최선일 듯 합니다만.. ^^

올해 금융업에 뛰어들어, 오픈소스DBMS 적용을 어찌어찌 하다보니.. 예전에는 별 생각이 없이 넘어가던 가벼운 생각들도, 꽤나 진지한 주제로 제게 다가오네요.

다음 포스팅은 언제일지는 모르지만.. 시간이 될 때.. adt를 주제로 한번 읊어보는 것도 좋겠다는 생각이..

좋은 밤 되시와요~!!

파티션 제약 극복기! 유니크한 토큰 값을 만들어보자!

Overview

MySQL에는 날짜 별 데이터 관리를 위해 파티셔닝이라는 좋은 기능(?)을 5.1버전부터 무료(!)로 제공합니다. 일정 시간 지난 후 불필요한 데이터는 간단하게 해당 파티셔닝을 제거하면, 굳이 DELETE 쿼리로 인한 오버헤드를 방지할 수 있죠.

그러나, 파티셔닝 적용 시, “파티셔닝 키는 반드시 PK에 포함되어야 한다”, “추가 제약조건(유니크 속성)을 부여할 수 없다”라는 대표적인 제약 조건으로 인하여, 유니크 속성을 가지는 데이터를 파티셔닝 적용이 불가한 경우가 있는데.. 이것을 해결할 수 있는 간단한 트릭을 이 자리에서 설명하고자 합니다. ^^

Plan to..

토큰을 생성하는 경우를 간단하게 생각해볼까요? ^^

토큰, 특히 일정 시간 이후에는 절대로 활용이 되지 않는 Access Token을 생각해본다면.. 수많은 유저들이 어떤 권한을 위해 반드시 발급받아야하는 데이터죠. 기본적으로 이 값은 중복이 발생해서도 안되고, 일정 시간이 지나버리면 폐기해도 무관한 데이터입니다.

  • 반드시 유니크 속성을 보장해야한다.
  • 파티셔닝 관리가 가능해야 한다.

간단하게 스키마를 상상해보면, 아래와 같습니다. ^^

CREATE TABLE access_tokens (
  token         char(64) NOT NULL,
  expires_at    datetime NOT NULL,
  PRIMARY KEY (token)
);

자.. 이제 이 엄청난 데이터를 파티셔닝을 하고 싶은데.. 흠.. 가장 간단한 방법으로는 아래처럼 PK제약 조건을 충족하기 위해 PK 속에 파티셔닝 키를 포함시켜서 테이블을 파티셔닝 하는 방법이 있습니다.

CREATE TABLE access_tokens (
  token         char(64) NOT NULL,
  expires_at    datetime NOT NULL,
  PRIMARY KEY (token, expires_at)
)
PARTITION BY RANGE COLUMN (expires_at)
(PARTITION PF_20150513 VALUES LESS THAN ('2015-05-14') ,
 PARTITION PF_20150514 VALUES LESS THAN ('2015-05-15') ,
 PARTITION PF_20150515 VALUES LESS THAN ('2015-05-16'));

아.. 그런데 여기서 중요한 문제가.. 반드시 유니크성을 보장해야할 Token을 스키마 레벨에서 완벽하게 보장할 수 없다는 말이죠. 즉, 정말 유니크한지 확인을 하려면 발급할 Token값이 현재 테이블에 존재 여부를 사전에 조회해서 체크 해봐야 합니다. 물론, 랜덤한 Hash로 Token을 발급한다면.. 중복은 매우 희박하기는 하나.. 최악의 경우를 무시할 수는 없을테니요. ^^

How to?

이러한 상황에서 과연 어떤 트릭(?)을 써서 파티셔닝을 하면서 유니크한 Token 발급이 가능할까요? Token 특성 상 굉장히 많은 데이터가 적재될 것이기에.. 테이블 사이즈를 고려하여 파티셔닝을 위한 별도의 칼럼(daynum) 칼럼을 생각해봅시다. 이 값은 smallint 타입으로 2바이트로, PK에 8바이트짜리 datetime 타입이 들어가는 것 보다는 훨씬 유리하죠. 특히나 인덱스를 추가로 만들 경우!! Token에 트릭을 가미하기 위해 기존보다 4바이트가 늘어납니다. 즉, PK는 6바이트 증가!

아래와 같이 테이블을 만들어봅시다.

CREATE TABLE access_tokens (
  token         char(68) NOT NULL,
  daynum        smallint unsigned not null,
  expires_at    datetime NOT NULL,
  PRIMARY KEY (token, daynum)
)
PARTITION BY RANGE (daynum)
(PARTITION PF_20150513 VALUES LESS THAN (134),
 PARTITION PF_20150514 VALUES LESS THAN (135),
 PARTITION PF_20150515 VALUES LESS THAN (136));

daynum에는 무슨 값이 들어가냐고요? to_days(now())에서to_days(‘2014-12-31’)을 뺀 값이 들어갑니다. 이렇게 함으로써 단순 2바이트 사이즈로 몇 십년 치(아마도 늙어 죽을 때까지)를 관리할 수 있는 테이블 파티셔닝 관리가 가능한 것이죠.

자, 그럼.. 데이터 처리를 위한 쿼리를 만들어볼까요? Token은 SHA2로 만든다고 가정해봅시다.

## => shar2함수 안의 스트링 값과 날짜 값만 인수로..
insert into access_tokens 
  ( token, daynum, expires_at )
 values 
 (
   concat(SHA2(?, 256), right(hex(TO_DAYS(?)-735963),4)),
   TO_DAYS(?)-735963,
   ?
 );

setString(1, authInfo + nanoTime)
setString(2, expires_at)
setString(3, expires_at)

ex)
insert into access_tokens
 ( token, daynum, expires_at )
values
 (
   concat(SHA2('user1', 256), right(hex(TO_DAYS('2015-05-17')-735963),4)),
   TO_DAYS('2015-05-17')-735963,
   '2015-05-17'
 );

복잡하쥬? 간단하게 말하자면.. SHA2로 64바이트 키를 만들고, 가장 마지막 4글자를 날짜가 가미된 문자열을 넣자는 것입니다. (4글자를 맞추기 위해.. )

결국 PK는 아래와 같은 형태로 관리가 되기에, Token은 유니크 보장이 된다고 할 수 있습니다. ^^ (슈퍼 울트라 캡숑 “꼼수”)

  • Primary Key : (SHA2+날짜, 날짜)
  • “SHA2+날짜”는 유니크 속성 보장

데이터를 넣었으니, 데이터를 끄집어 내는 방법도 고민해봐야겠죠? 매번 조회 시 전체 파티셔닝을 뒤지지 않기 위해서는 원하는 데이터가 위치한 곳의 적절한 파티셔닝 키도 같이 전달을 해야할텐데..  그렇다고, 어플리케이션에서 늘 daynum을 가지고 있을 수는 없는 노릇! Token에서 날짜 정보를 추출할 수 있는 방안을 고안해야 합니다. 아래처럼..

## => 토큰 값만 쿼리에 인수로..
select * from access_tokens 
where daynum = conv(trim((substr(?, 65))), 16, 10) and token = ?;
setString(1, token_string)
setString(2, token_string)

ex)
select *
from access_tokens
where daynum = conv(trim((substr('0a041b9462caa4a31bac3567e0b6e6fd9100787db2ab433d96f6d178cabfce9089', 65))), 16, 10)
and token = '0a041b9462caa4a31bac3567e0b6e6fd9100787db2ab433d96f6d178cabfce9089';

Token에서 65번째 부터 4글자를 가져와서, 그 데이터를 16진수 -> 10진수로 변환하여 최종적으로 daynum을 만들어보자는 것입니다. 실제 호출되는 쿼리는 바로 위 예제처럼 사용되겠죠. ^^

쿼리가 복잡해지기는 하나.. 나름 서버에 부담없이 데이터를 최적으로 추출할 수 있는 방안이라고 봅니다.

기존 토큰 길이가, 64->68로 길어진다는 단점이 있기는 하나.. 음.. 반드시 64글자여야 한다면, SHA2결과 값에서 마지막 4글자를 빼버리는 것도 나쁘지 않다고 생각합니다. 발급된 Token은 스키마 레벨에서 유니크를 보장하고, 날짜별로 파티셔닝도 가능하니.. 일석이조!!

Conclusion

약간(?)의 트릭으로 파티셔닝 제약을 극복하였습니다. 유니크한 Token을 파티셔닝 관리하는.. 유니크 보장을 위해 사전 SELECT 없이도 쿼리 레벨에서 해결할 수 있는 방안이죠.

Token에 날짜가 가미된 스트링을 넣되, PK를 날짜와 같이 엮어서 Token만으로 유니크를 보장하자는 것이 목적입니다.

  • Primary Key : (SHA2+날짜, 날짜) => “SHA2+날짜”는 유니크

정말 간단한 팁같지 않은 팁이기는 하나.. 대규모 Token 관리를 계획하고 있으신 분에게는 꽤 좋은 솔루션이 될 수 있다고 생각해요. 설명장애가 있어서.. 매끄럽지 않았지만.. 의미 전달이 잘 되었기를..

다음에는 또다른 재미난 팁을 소개하도록 할께요. ^^

새벽 4시, 이유없이 디스크 유틸이 튄다면? 디스크 성능에 영향을 주는 크론잡

Overview

새벽에 디스크 성능에 영향을 주는 요소로는 대표적으로 백업과 같은 디비 운영적인 업무가 있습니다. 각 운영 정책에 따라 다르겠지만, 순간적인 시스템 부하에도 굉장히 민감한 서비스 경우에는 별도의 스탠바이 용도의 슬레이브 서버를 두고 그곳에서 백업을 하기 마련입니다.

이런 상황 – 마스터에서는 백업과 같은 무거운 디스크 작업이 일어나지 않는 상황에서 알 수 없는 이유로 새벽 4시 혹은 4시 22분에 디스크가 유틸이 튀는 경우가 있습니다. 그리고 가벼운 쿼리일지라도 대거 슬로우 쿼리로 잡히기도 합니다.

범인은 의외로 리눅스 설치 시 기본적으로 등록되는 두 가지 크론잡에 있는데요, 얼마 전 이와 비슷한 사례를 경험하게 되어 공유 드립니다. (단, 고수님들은 출입금지!)

Default Cron Jobs

OS 설정에 따라 다르겠지만, CentOS를 설치하게 되면 다음과 같이 배치 작업이 등록이 되어 있습니다.

$ cat /etc/crontab
01 * * * * root run-parts /etc/cron.hourly >/dev/null 2>&1
02 4 * * * root run-parts /etc/cron.daily >/dev/null 2>&1
22 4 * * 0 root run-parts /etc/cron.weekly >/dev/null 2>&1
42 4 1 * * root run-parts /etc/cron.monthly >/dev/null 2>&1

이 상태에서 별다른 변경을 하지 않았다면 다음 두 개의 크론잡이 기본적으로 동작하게 되는데, 경우에 따라 두 개의 잡이 데이터베이스의 트랜잭션 로그 혹은 데이터 파일 쪽 디스크에 영향을 주어 순간 퍼포먼스가 크게 저하될 수 있습니다.

/etc/cron.daily/mlocate.cron
/etc/cron.daily/makewhatis.cron
/etc/cron.weekly/makewhatis.cron

간단하게 위 크론잡에 대해서 알아보도록 하겠습니다.

1) mlocate

파일 검색을 빠르게 검색하기 위해, 파일에 대한 색인 정보를 모아 데이터베이스를 만드는 역할을 하며, 매일 4시에 동작합니다.
mloate.cron에 포함된 내용은 아래와 같습니다.

$ cat /ec/cron.daily/mlocate.cron
 
#!/bin/sh
nodevs=$(< /proc/filesystems awk '$1 == "nodev" { print $2 }') 
renice +19 -p $$ >/dev/null 2>&1
/usr/bin/updatedb -f "$nodevs"

또한 이와 관련된 설정은 /etc/updatedb.conf 에 위치합니다.

$ cat /etc/updatedb.conf
PRUNEFS = "auto afs gfs gfs2 iso9660 sfs udf"
PRUNEPATHS = "/afs /media /net /sfs /tmp /udev /var/spool/cups /var/spool/squid /var/tmp"
  • PRUNEFS
    updatedb 가 스캔하지 않을 파일 시스템 리스트
  • PRUNEPATHS
    updatedb 가 스캔하지 않을 디렉토리 패스 리스트

매일 새벽 4시에 도는 작업으로 renice로 우선순위를 낮춰놓아도 디스크 자원을 아래와 같이 크게 잡아먹기도 합니다.

CPU     %user     %nice   %system   %iowait    %steal     %idle
all      0.00      0.51      3.03     46.46      0.00     50.00
all      0.00      1.01      4.55     44.44      0.00     50.00
all      1.00      0.50      2.00     47.50      0.00     49.00
all      0.51      0.00      2.03     47.72      0.00     49.75
all      0.50      0.50      3.50     46.50      0.00     49.00

만약 새벽 4시정도에 디스크 유틸이 이유없이 튀고 있다면, 이와 관련하여 살펴보시기 바랍니다.

2) makewhatis

처음 크론잡을 살펴보았을 때 별 것 아니라고 생각했었지만, 매주 일요일 새벽 4시 22분에 알 수 없는 디스크 유틸을 유발하던 장본인이었습니다.

간단하게 설명하자면, man에 관련된 내용을 신규 생성 또는 업데이트하며, 월~토요일은 증분으로 일요일은 전체를 풀로 새로 작성을 합니다.

## 일단위 크론잡
$ cat /etc/cron.daily/makewhatis.cron
.. 중략 ..
makewhatis -u -w
 
## 주단위 크론잡
$ cat /etc/cron.weekly/makewhatis.cron
.. 중략 ..
makewhatis -w

일 단위에서는 -u 옵션을 주어서, 기존 데이터베이스에 단순히 업데이트를 하지만, 주 단위 작업에서는 완전히 새로 작성합니다. 평일에는 크게 문제가 없다가 매주 일요일 새벽 4시 22분 정도에 시스템이 알 수 없이 튀는 현상을 보인다면, makewhatis 작업을 의심해보시기 바랍니다. ^^
다음은 간단하게 테스트 장비에서 돌렸을 때의 시스템 상황입니다.(다를 수 있으니, 참고만 하세요. ^^)

CPU     %user     %nice   %system   %iowait    %steal     %idle
all     31.00      0.00     17.50      4.50      0.00     47.00
all     30.46      0.00     14.72      7.61      0.00     47.21
all     31.31      0.00     13.64      9.09      0.00     45.96
all     31.31      0.00     15.15      6.06      0.00     47.47
all     35.18      0.00     16.08      1.51      0.00     47.24

Conclusion

운영 정책 및 트래픽에 따라 다르겠지만, 적어도 실DB 장비에서 굳이 디스크 자원을 소비하면서까지 이러한 인덱싱 혹은 매뉴얼을 작성할 필요는 없을 것이라 생각합니다. (물론 이것은 지극히 개인적인 소견이기는 하지만..^^)

만약 새벽 4시 이후로 알 수 없는 이유로 디스크 유틸이 튀고, 이로 인하여 슬로우 로그가 대거 발생하고 있다면 리눅스 새벽 크론작업을 의심해보시기 바랍니다.

(무언가 거창하게 시작했는데.. 성급하게 끝내버린 듯한 이 기분은 무엇일까요? ^^;;)