특정 화면이 느려서 고치려 했는데 grep 으로 어느 코드가 도는지 찾는 데만 반나절이 걸렸다.
Table of contents
Open Table of contents
주소로는 못 찾았다
주소는 이것이었다.
/order/list
라우팅 파일을 봤다.
$route['order/list'] = 'order/index';
OrderController::index 로 간다고 돼 있어 열어 봤다.
public function index() {
$this->load->library('legacy_bridge');
$this->legacy_bridge->dispatch();
}
화면을 그리는 코드가 없고 다른 데로 넘기기만 한다. dispatch 를 따라갔다.
public function dispatch() {
$path = $this->uri->segment_array();
require_once APPPATH . '../legacy/' . implode('/', $path) . '.php';
}
주소 조각으로 파일 경로를 만들어 그것을 읽고 있었다. 실제 코드는 legacy/order/list.php 에 있었다.
경로가 문자열로 조립되니 검색으로는 안 나온다. 주소와 실제로 일하는 자리가 두 단계 떨어져 있었다.
grep 으로 order/list 를 찾으면 $route 한 줄만 나온다. 그 뒤로는 implode 가 만든 문자열이라 검색어가 코드에 없다.
찾는 방법이 없는 것이 아니라 grep 이라는 방법이 안 통하는 자리였고 그것을 아는 데 반나절을 썼다.
원인 — 옮기는 중인 구조
svn log 로 왜 이렇게 돼 있는지를 보니 옛 시스템을 새 프레임워크로 옮기는 중이었다. 옮긴 것은 Controller 로 가고 안 옮긴 것은 legacy 로 넘긴다.
중간 상태라서 그런 것이고 그 자체는 이해할 수 있었다. 한 번에 다 옮기는 것이 더 위험하다.
legacy 284개를 한꺼번에 옮기면 무엇이 깨졌는지 못 가리고 하나씩 옮기며 공존하는 것이 맞는 선택이다.
다만 공존하는 동안 읽는 사람이 헷갈리는 비용이 붙는데 그것을 아무도 계산해 두지 않았다.
문제는 어느 것이 옮겨졌는지 알 방법이 없다는 것이다. 그것을 알려면 매번 dispatch 를 읽어야 했다.
목록을 만들었다
먼저 규모부터 셌다.
$ ls legacy/**/*.php | wc -l
284
옛 파일이 284개이고 컨트롤러는 62개였다. 어느 주소가 어디로 가는지를 표로 만들었다.
/order/list legacy/order/list.php
/order/detail OrderController::detail (2017-05 이관)
/product/list legacy/product/list.php
...
만드는 데 하루가 걸렸지만 그 뒤로 찾는 시간이 없어졌다. 다만 새로 옮길 때마다 이 표가 낡는다.
표를 만들어 두면 다음 사람이 그 하루를 안 쓰는데 표가 틀리기 시작하면 없는 것보다 나쁘다.
틀린 표를 믿고 엉뚱한 파일을 열면 처음부터 다시 찾게 되고 낡는 속도가 빠르면 만들 이유가 없다.
조치 — 어디로 갔는지 응답에 남기기
표가 낡는 문제를 코드가 스스로 말하게 해서 풀었다.
if (ENVIRONMENT !== 'production') {
header('X-Handler: legacy/' . $path);
}
X-Handler 헤더에 실제 처리 자리를 담는다.
$ curl -sI http://dev/order/list | grep X-Handler
X-Handler: legacy/order/list.php
주소를 하나 열어 보면 어디서 처리되는지가 나오므로 표를 안 봐도 된다. 운영에서는 안 나오게 했는데 안쪽 구조를 밖에 알릴 이유가 없었다.
표는 사람이 갱신해야 하고 헤더는 코드가 바뀌면 저절로 따라온다. 낡을 수 있는 것과 없는 것의 차이였다.
로그에도 남겼다
느린 요청을 찾을 때도 같은 값이 필요했다.
log_message('info', sprintf('%s handler=%s time=%.3f', $uri, $handler, $elapsed));
handler 와 time 을 한 줄에 남긴다.
/order/list handler=legacy/order/list.php time=4.221
/order/detail handler=OrderController::detail time=0.182
느린 것이 옛 파일 쪽에 몰려 있는지 옮긴 쪽에도 있는지가 보였다. 며칠 보니 옛 파일 쪽이 대부분이었다.
이것이 옮기는 순서를 정할 근거가 됐다. 느린 것부터 옮기면 옮기는 작업의 이득이 빨리 나타난다.
옮기는 작업은 기능이 안 늘어 값을 보이기 어려운데 느린 화면이 빨라지면 그 값이 눈에 보인다.
어느 것을 먼저 옮길지가 그 값을 언제 보일지를 정했고 handler 한 줄이 그 근거를 줬다.
주의 — 옛 파일 안의 또 다른 분기
옛 파일을 열었더니 거기서 또 나뉘었다.
include '../common/order_query.php';
if ($_GET['type'] === 'excel') { include '../export/order_excel.php'; }
읽어 들이는 파일이 또 있고 조건에 따라 다른 파일을 읽는다. 이런 것은 표로 못 만들고 실행해 봐야 안다.
register_shutdown_function(function () {
if (ENVIRONMENT !== 'production') {
log_message('debug', '읽은 파일: ' . implode(', ', get_included_files()));
}
});
get_included_files 로 요청이 끝날 때 무엇을 읽었는지를 남겼다. 짐작이 아니라 실제로 실행된 경로가 나온다.
조건 분기가 있는 곳에서는 정적인 목록이 통하지 않는다. 무엇이 실행됐는지를 남기는 쪽이 정확했다.
판단 기준 — 옮기는 순서
전부 옮기는 데 오래 걸리므로 순서가 필요했다.
1. 느린 것
2. 자주 고치는 것
3. 새 기능이 붙을 것
4. 나머지
자주 고치는 것을 옮기면 그 뒤로 고치기가 쉬워진다. 어느 파일을 자주 고치는지는 이력에서 셌다.
$ svn log -v -r {2017-03-01}:HEAD legacy/ | awk '/^ M/ {print $2}' | sort | uniq -c | sort -rn | head
순서를 안 정하면 손이 가는 것부터 옮기게 되는데 그것이 반드시 중요한 것은 아니다.
정리
- 주소가 가리키는 파일과 실제로 도는 코드가 다를 수 있다
- 경로가 문자열로 조립되면 검색으로 안 나온다
- 옮기는 중이면 어느 것이 옮겨졌는지 알 방법이 필요하다
- 표를 만들되 새로 옮길 때마다 낡는다는 것을 안다
X-Handler헤더에 처리 자리를 담으면 코드가 스스로 말한다- 로그에
handler를 남기면 느린 것이 어느 쪽인지 보인다 - 그것이 옮기는 순서를 정할 근거가 된다
- 조건에 따라 갈리는 곳은
get_included_files로 실제 경로를 남긴다 - 느린 것과 자주 고치는 것을 먼저 옮긴다
- 자주 고치는 것은
svn log에 찍힌 횟수로 센다