포럼
XE를 사용하면서 서버1대면 충분한걸 3대를 써야하는 상황
2012.04.22 22:40
하나의 게시판에 게시물 10만개 올려두면 mysql의 cpu사용양이 클릭때 마다 40%가까이 가네요,
cpu는 q8400입니다, 최고라고 할순 없지만 상위입니다.
이 문제를 지금까지 1년 6개월간 해결법을 찾고 있는중입니다,
일단 apache+mysql+php로 돌리는데 mysql에 초짜라 원인 및 해결법을 찾는데 많이 힘들었습니다,
초당 8회정도의 페이지뷰가 생기는데 게시판당 10만개 게시물을 넣을시 방문이 불가능하더군요,
cpu는 100%
조금씩 게시물을 쭐여보니 게시물이 14000개 정도 까지 남기면 홈페이지가 정상으로 열리더군요,
그래서 mysql에서 show processlist;를 사용해 연결상태를 확인해본결과
조회수를 올려주는 명령과 select count(*) 명령이 가득 있더군요,
그래서 일단 XE소스에 조회수를 조회때마다 올려주는 소스부분을 삭제했습니다,
그러고 보니 남은건 select count(*) 명령인데,
Id | User | Host | db | Command | Time | State | Info | |
---|---|---|---|---|---|---|---|---|
291638 | root | localhost:3704 | phone | Query | 1 | Sending data | select count(*) as "count" FROM `xe_documents` as ... |
이런 Sending data상태의 똑같은 검색명령이 수십개 가득 있네요,
이쪽으로 잘아시는분 찾아서 부탁하니 MYSQL문제가 아닌 xe자체의 검색명령이 너무 db에 부담을 준다네요.
해당 검색명령이 XE소스 어디쯤에 있는지 찾아보니 \classes\db\DBMysql.class.php 바로 여기에 있더군요?
그래서 이부분도 주석처리를 해봤죠, 게시판에 10만개 게시물을 등록시 먹통되게 만드는 주 원인이니까요?
그런데 그 결과 참 재미 있더군요, 홈페이지 바로 열리네요,
10만게시물이 한 게시판에 있고 초당 페이지뷰가 대략 7-10회 입니다,
속도가 엄청 빠르더군요, 깜짝 놀랐습니다,
그런데 기쁨도 잠시,
게시판 하단을 보니 페이지가 1/1
10만개의 게시물이라면 1/5000페이지정도 되야하는게 아닌가요?
그리고 다시 해당 소스 아래로 쭉 훓어보니 알겠더군요,
문제를 일으킨 소스는 바로 게시판 첫 페이지 게시물 리스트를 가져오는 소스가 아닌,
게시판 페이지수를 가져오는 소스 비슷한듯 하네요(확신 하긴 힘들지만요, 초짜라),
원인을 찾았으니 어떻게든 해결을 해볼려고 했으나 초짜로서 마구잡이로 수정해서는 해결하기가 힘들더군요,
다른 xe로 만든 대형 사이트를 찾아보면 게시판당 게시물이 5만개이상인 사이트 몇 곧 잘 되는데
어떻게 해결한건지 도저히 공개를 않해주시니 저로서는 어쩔수 없는 상황,
mysql최적화하는 글을 다 찾아봐서 최적화 시도를 해봤지만 이건 분명 최적화로 해결할수 있는 문제가 아닌거 같더근요,
mssql로 바꿔보자니 엄두가 나질 않고,
이 문제로 현재 1년넘게 홈페이지 확장도 못하고 홍보도 이 문제때문에 두려워 이대로 유지하고 있는 상황이네요.
$limit = $queryObject->getLimit();
// Total count
$temp_where = $queryObject->getWhereString(true, false);
$count_query = sprintf('select count(*) as "count" %s %s', 'FROM ' . $queryObject->getFromString(), ($temp_where === '' ? '' : ' WHERE '. $temp_where));
if ($queryObject->getGroupByString() != '') {
$count_query = sprintf('select count(*) as "count" from (%s) xet', $count_query);
}
$count_query .= (__DEBUG_QUERY__&1 && $queryObject->query_id)?sprintf (' '.$this->comment_syntax, $this->query_id):'';
$result_count = $this->_query($count_query, $connection);
$count_output = $this->_fetch($result_count);
$total_count = (int)$count_output->count;
$list_count = $limit->list_count->getValue();
if (!$list_count) $list_count = 20;
$page_count = $limit->page_count->getValue();
if (!$page_count) $page_count = 10;
$page = $limit->page->getValue();
if (!$page) $page = 1;
// total pages
if ($total_count)
$total_page = (int) (($total_count - 1) / $list_count) + 1;
else
$total_page = 1;
// check the page variables
if ($page > $total_page) $page = $total_page;
$start_count = ($page - 1) * $list_count;
$query = $this->getSelectPageSql($queryObject, true, $start_count, $list_count);
$query .= (__DEBUG_QUERY__&1 && $queryObject->query_id)?sprintf (' '.$this->comment_syntax, $this->query_id):'';
$result = $this->_query ($query, $connection);
if ($this->isError ())
return $this->queryError($queryObject);
$virtual_no = $total_count - ($page - 1) * $list_count;
$data = $this->_fetch($result, $virtual_no);
$buff = new Object ();
$buff->total_count = $total_count;
$buff->total_page = $total_page;
$buff->page = $page;
$buff->data = $data;
$buff->page_navigation = new PageHandler($total_count, $total_page, $page, $page_count);
댓글 44
-
銀童
2012.04.22 22:52
-
해커다
2012.04.22 22:54
해당 방법을 공유 할순 없을까요?
XE개발팀이 이 문제를 최적화 해주면 더 좋을텐데 전 이런 희망은 이미 포기 상태네요 ㅋㅋ
-
銀童
2012.04.22 23:02
어차피 게시판의 count 같은건 바로바로 갱신 안되도 되니 캐시로 1분 단위로만 사용해도 괜찮을꺼같습니다.
APC 같은걸 쓰시면 될듯.
-
해커다
2012.04.22 23:06
답변 감사드리지만 초짜라 머가먼지 도통 모르겠네요 ^^
-
jahong
2012.04.22 22:57
documents 파일이 워낙 크다 보니 카운트를 해도 속도가 느리군요.
어느 db 나 웹에서 페이지를 나오게 하려면 totalcount 를 가져와야 하는데, 튜닝을 할 줄 아는분이 지원을 해주셨으면 좋겠습니다. mysql은 인덱스를 어떻게 사용하는 모르지만 인덱스를 만들어서 오라클처럼 /*+ index_desc */ 힌트를 준것처럼 나올수 있게하면 빨라질텐데요.
아시는 분 있으시면 답변부탁합니다.
분석을 잘하셔서 문제점이 어느곳에 있는지는 정확하게 찾으신듯 합니다.
그래도 100만건정도라면 모를까 10만건 밖에 안되는데 count가 그렇게 느린건 좀 이해가 안되긴 합니다.
-
해커다
2012.04.22 23:04
10만건 뿐이 아니죠, ㅋㅋ 2만부터 체감속다가 확 떨어집니다, 5만이면 페이지뷰 초당 7-10회는 못견뎌요.
-
카르마
2012.04.22 23:24
제로보드 시절에는 total_count는 테이블에 컬럼으로 저장해두는 간단한 방법을 사용했는데 XE에서는 왜 매번 쿼리를 하는지 이해를 잘 못하겠습니다.
어차피 XE에서도 module_info를 가져오면 total_count까지 가져오게 될테니 쓸데없는 쿼리가 줄어들텐데...
insert, delete 때만 total_count를 재계산하면 속도개선이 되지않을까 싶습니다.
개인적으로 사용하는 모듈에서 group by 쿼리의 문제로 직접 SQL을 작성해서 쿼리하는 방법은 하다보니 page_navigation을 수동으로 만들어야하고 그때마다 total_count가 문제가 되어서 이방법을 적용해봤는데 XE 전체에 확대적용하는 것도 그리 복잡해 보이지는 않은데 말입니다...
-
해커다
2012.04.22 23:30
전 현재 게시판이 하나뿐인 모바일 홈페이지는 select count(*)를 모두 주석하고
$total_page = 숫자는 직접 페이지수를 보고 부여한 상테네요, 그런데 문제는 다른 2개의 사이트는 게시판이 여러개라 직접 $total_page수를 부여하는건 힘들고,, 참 속타는 문제네여...
매달 서버비용만 40만씩 더 들어가는 상황,,,
-
jahong
2012.04.22 23:59
http://www.mysqlkorea.co.kr/gnuboard4/bbs/board.php?bo_table=community_03&wr_id=3029
이와 같이 근본적으로는 count 테이블을 만들어야 할듯하고, count(1) 로 해보면 조금은 나아질듯합니다.
-
해커다
2012.04.23 00:10
게시글 갯수를 따로 db에 저장하는게 좋긴한데 등록 삭제 코어 모두 수정해야하니.....
-
misol
2012.04.23 00:28
만들다 만거 같은데.. DB.class.php 에 resetCountCache 라는 함수가 있긴 있어요. 만들어 보려고 하신거 같은데, 그냥 return false 하게 되어 있네요..
-
misol
2012.04.23 01:57
이렇게 하면 어때요??
30초 단위로 캐시 하도록 해봤어요.DBMysql.class.php
-
고수군
2012.04.23 14:11
1.4.x 버전은 아니군요. -0-; 갈아타지 못하는 중생도 인도해 주시기 바랍니다. ㅎㅎ
-
misol
2012.04.23 16:54
효과가 있는지도 모르고 부작용도 있을 수 있어요 ㅠㅠㅎ -
해커다
2012.04.23 15:55
멋있네요, 테스트를 해봐야겠군요, 정식버전에도 이 문제가 완전 해결됬으면 하는 마음 ...
-
misol
2012.04.23 16:55
문제 생기면 알려주세요 ㅠ -
KTK
2013.03.15 05:34
DBMysql_innodb.class.php innob는 어떻게 해야하나요 ㅠㅠ 제발 알려주시면 감사하겠습니다 ㅠㅠ -
misol
2013.03.17 15:24
아주 오래 전에 만든 것이라 정확하게 기억 나진 않습니다;
-
KTK
2013.03.17 16:06
그렇군요.. 신경써 주셔서 감사합니다..
-
KTK
2012.07.26 15:00
수정된 코드를 알수 있을까요.. 이거 1.5.2.7 용인가요?
-
SCAC
2012.07.26 15:56
1.5.3 버전 기준입니다.
577번째 줄에
$count_query = sprintf('select count(*) as "count" %s %s', 'FROM ' . $queryObject->getFromString($with_values), ($temp_where === '' ? '' : ' WHERE '. $temp_where));
가 있는데 그 밑에
$count_cache = FileHandler::readFile('./files/cache/queries/total_count_'.md5($queryObject->getFromString($with_values)).'.php');
if($count_cache)
{
if(strlen($count_cache) < 2024)
{
$count_cache = unserialize(str_replace(array('<?php /**','**/ ?>'),array('',''),$count_cache));
if($count_cache[md5($count_query)])
{
// cache count for 30 seconds.
if(time() - $count_cache[md5($count_query)]['mtime'] < 30)
{
$count_output = unserialize($count_cache[md5($count_query)]['data']);
}
else
{
unset($count_cache[md5($count_query)]);
}
}
}
else
{
// remove too much big cache file.
$count_cache = FileHandler::removeFile('./files/cache/queries/total_count_'.md5($queryObject->getFromString($with_values)).'.php');
}
}그리고 마우스로 조금 내려가다 보면
$count_query .= (__DEBUG_QUERY__&1 && $queryObject->queryID)?sprintf (' '.$this->comment_syntax, $queryObject->queryID):'';
$result_count = $this->_query($count_query, $connection);
$count_output = $this->_fetch($result_count);가 있는데
$count_query .= (__DEBUG_QUERY__&1 && $queryObject->queryID)?sprintf (' '.$this->comment_syntax, $queryObject->queryID):'';
if(!isset($count_output))
{
$result_count = $this->_query($count_query, $connection);
$count_output = $this->_fetch($result_count);
$count_cache1[md5($count_query)]['data'] = serialize($count_output);
$count_cache1[md5($count_query)]['mtime'] = time();
$count_cache1[md5($count_query)]['count_query'] = $count_query;
FileHandler::writeFile('./files/cache/queries/total_count_'.md5($queryObject->getFromString($with_values)).'.php','<?php /**'.serialize($count_cache1).'**/ ?>');
}로 바꿔주면 되는 것 같네요;;;
이전 버전에서는 에러가 안났을지 모르지만 수정된 부분만 끼워넣었더니 에러가 나서.. count_cache1을 넣었습니다.
-
또별
2013.03.19 13:33
SCAC 님 코어1.7.3 에서 반영되었는지 확인해 보셨나요?
-
Youth_child
2012.04.23 10:33
서버 3대라구요?
-
delphiXE2
2012.04.23 13:44
한마디만 할래요.
-_-
-
윈컴이
2012.05.15 16:18
헉...
-
해커다
2012.05.15 19:47
헉 ㅋㅋ
-
푸하라
2012.04.24 12:08
제가 느낀 바로는 index가 좀 문제가 있는듯 합니다.... index가 정확하게 작동을 한다면 count가 그렇게 오래걸리지 않는데..
아마도 index가 문제가 좀 있는거 같고여... 서버및 설치시마다 조금씩 달라지네요.. 웃끼게도 같은곳에 똑같이 설치해봤는데 다르현상이 생기는게 어떤문제인지 도통 ㅠㅠ;;
list_order 그리고 xe의 경우 게시판의 글중에 뒤로 가면 갈수록 느려지는 현상이 있는데.. 이럴때는 차라리 index만 검색해서 검색되어진 index를 이용하여 재검색 시켜도 전보다 쾌적해질수 있습니다.
저같은경우 google의 클롤링때문인지 주로 뒷부분 검색 되어지는 경우가 많아서 이런식으로 수정하였더니 보통1초이상 걸리던 것이 0.04~1 초사이로 검색이 끝나더군요....
게시판에서 가장큰 문제중 하나는 limit 을 쓰는데 리밋이 30000이상의 데이터가 되면 느려지는것이 보여서....
-
해커다
2012.05.15 00:58
일베의 잡담게시판을 지켜본결과 [글수]가 역시 캐시설정했더군요,
일베도 같은 문제를 다 격은듯합니다,
새글은 올라오는데 [글수]는 변화가 없네요.
그리고 저~~~위에 미솔님 감사합니다,, 덕뿐에 방문자 3만IP를 도전중입니다.
-
misol
2012.05.15 09:23
오! 효과가 그래도 있나보군요! -
데미갓
2012.07.26 14:56
xe 성능개선을 위해 이리저리 보던중 여기까지 오게됐습니다. 5월 포스팅이라 의미가 있을지 모르겠습니다만.
1.5.2.7 버젼을 사용중입니다. 서버 상태를 보면 db 쪽에서 순간적으로 peak를 치는데 적을땐 25~30 많을땐 70~90 정도의 peak 를 mysqld 가 쓰고 있습니다. slow log 에도 잡히지를 않고, process list 를 보아도 별로 잡히는게 없는데요.
select count(*) 이 구문도 현재 버젼에서는 개선이 된건가요? processlist 에 왜 안잡히는지;; 서버는 2socket 웨스트미어 장비로 성능은 충분하다고 생각되고요. mysql 관련 파라메터 튜닝은 어느정도 해두었습니다. 캐시쪽, 버퍼쪽, 등등
xe의 웹캐시는 리프레시가 많은 페이지는 거의 대부분 적용을 해둔 상태입니다.
의견 부탁드려요!! ㅜㅡ xe 느려서 고생중입니다. ㅡㅜ
-
해커다
2012.09.26 14:14
현재 저는 select count(*)를 캐싱처리하고 조회수업하는 소스는 아예 지워버렸습니다, 그리고 서버는 CENTOS6이구요, nginx사용중입니다, db와 웹이 서버 한대를 사용하면 페이지뷰 일일 30만이상아고 서버 메모리는 많이 남아있는상태고 cpu는 10프로 상하로 움직이고 있네요,,100만 페이지뷰까지는 쉽게 살수 있을듯 하네요.
-
KTK
2013.03.15 05:33
미솔님 죄송하지만.. 이팁 1.7버전용으로 다시 알려주시면 안될까요 팁게시판에 올라가도 엄청 도움 될것 같아요.
엄청나게 빨라지네요...
-
misol
2013.03.17 15:26
그런가요??;;
-
XE러버
2013.03.18 11:26
1.7 전용으로 다시 팁 써주시면 많은 도움이 될 것 같아요
-
데브위트™
2012.05.15 09:39
그누는 튜닝없이 그정도는 커버되던데 말입니다. ㅇㅅㅇ xe경우 느리더라구요
-
궁금궁금궁금이
2012.05.15 16:14
@난다날아 @똥똥 코어 개발팀 여러분은 여기에서의 담론들에 귀 기울여 주셨으면 합니다.
-
Garon
2012.07.26 19:09
아주 극소수의 일부(?) 댓글 빼고는 좋은 이야기이니
개발자 포럼으로 가서, 토론을.....
(개발자 포럼이 있으니까 하는이야기임)
-
K.Soma
2012.09.27 09:29
DB선택도 중요한것 같습니다.
한 인도네시아 신문 사이트인데 mysql Sam을 쓰던 사이트를 Inodb로 바꾸고 Inodb캐시를 좀 적당히 주니 대부분의 요청이 캐시에 의해 처리 되더군요. 단 글이 많이 올라오는 곳은 효과가 적긴 합니다. -
K.Soma
2012.09.27 09:34
캐시는 /etc/my.cnf 에서 설정 하시면 =3=3 -
KTK
2013.03.15 05:32
이거 속도 엄청나게 빨라지내요... 장난아니네요...
-
Reejang
2013.03.15 18:55
미솔님 팁으로 속도가 빨라졌다는 말씀인가요?
밖이라서 테스트를 못해보고 질문만 드립니다. ㅠㅠ
-
KTK
2013.03.15 20:41
캐쉬를 설정해서 인지... 첨로딩후 미틴듯한 속도...
-
비밀얌
2013.03.16 03:35
저는 미솔님 파일을 대체하니깐 빨라지는 것 같으면서도 조금 이따가 다시 게시판에 들어가보니 이상하게도 상단에 에러가 나와서 다시 원본파일로 복구했어요 ㅠㅠ CORE 버전은 1.5.4.2입니다.
-
또별
2013.03.18 01:16
튜닝 방식보다는 원초적 원인을 잡아내야 하는데..가능할것 같은데요...
검색범위 때문인지..검색단계 때문인지..둘중 하나인것 같아보여요.
제시된 대안이 1.7.3 에서 반영된것인지 궁금합니다.
저같은 경우도 innodb 를 사용하는데 select(*) 가 너무 자원을 많이 잡아먹어서
select(*) 결과 자체를 캐싱해두고 insert 등이 벌어질때 해당 캐싱을 삭제하는 방식으로 구현했습니다.
꽤 효과가 좋더군요.