티스토리 뷰

다른 개발자들이 보통 생각하는 것과 마찬가지로, 나도 내 코드가 대형사고를 초래할 것이라고는 생각하지 않았다. 그래서인지 모니터에 떠있는 몇 줄 안되는 코드를 보는 심정이 복잡해질 수 밖에 없었다. 일단 현재 상황은 대형사고가 난 상황은 아니다. 단지 대형사고가 나기 직전의 상황일 뿐이었다. 내가 보고 있던 코드들은 일괄적으로 수행되는 작업을 위한 배치 프로그램(Batch program)의 코드였는데, 매일 새벽 2시가 되면 스케줄러에 의해서 자동으로 실행되어 DB에서 데이터들을 조회하고 조건에 맞는 데이터들에 일정한 처리를 해서 상태를 변경해주는 역할을 하고 있었다. 내가 보고 있던 코드는 DB에서 사용자들이 처리해야 될 일감들을 뽑아낸 다음에 각각의 사용자에게 배정해주는 역할을 하는 코드였다.


언제부터 무엇이 잘못된 것인지는 잘 모르겠지만, 그 배치 코드가 새벽에 제대로 수행이 되지 않은 것은 확실했다. 어느때와 다름없이 출근한 사용자들은 오늘 할 일이 굉장히 적다는 사실에 좋아하기 보다는 불안감을 느껴했고, 주변의 다른 사람들과 상황을 공유한 뒤 '뭔가 잘못되었다'는 사실을 깨달았다. 시스템 오픈 직후 전쟁터와 다를 것이 없었던 프로젝트 룸에서 의문의 전화 한 통을 받고 고객들에게 뛰어갔다 오신 PL 님의 얼굴은 미드웨이 해전이 끝난 뒤의 일본군 제독의 표정이 저랬을까 싶었을 정도로 심각했었는데, 다행히도 그분은 상식이 있으신 분이었고 배치 프로그램의 담당자, 그러니까 나를 불러서 현재 사태의 과거와 현재, 그리고 미래를 간단히 정리해주셨다. '돌아가게 만들어라.'


배치 프로그램은 새벽 2시에 돌아갈 것이다. 물론 수작업으로 돌리는 방법이 없는 것은 아니었지만, 배치 프로그램이라는 것이 혼자서 도는 프로그램은 거의 없었기 때문에 앞 뒤로 수행되어야 되는 다른 프로그램들을 생각하면 스케줄러가 시간에 맞춰서 돌리도록 유도하는 쪽이 큰 사고를 막을 수 있었다. 자연스럽게 정해진 타임아웃이 상황을 명쾌하게 만들어줬다. 2시가 되기 전까지 원인을 찾아서 수정을 하지 못하면 내일 출근한 고객들은 여전히 할 일이 없는 상황이 될 것이다. 그리고 고객들이 그 상황을 놀라운 행운으로 받아들이고 다 같이 커피나 마시러 가지는 않을 것 같았다.


사실 대형사고가 임박한 상황에서도 스스로에 대한 자괴감 보다는 그냥 복잡한 심정이 들었던 이유는 그 코드는 내 코드가 아니었기 때문이다. 한달 전 쯤, 프로젝트에서 철수가 결정된 개발자가 '이거 데이터가 없어서 한 번도 돌려보지는 않았는데 아마 내가 최선을 다해서 만들었으니까 이론상으로는 완벽하게 돌아갈거에요'라는 수상한 논리로 인수인계를 해주고 간 코드였고, 개발자들이 철수한 다음에 그 코드들에 테스트 데이터를 살짝 넣어봤더니 아주 당연하게도 하나도 제대로 돌아가지 않았다. 물론 크게 놀라지는 않았다. 오히려 전임 개발자의 말 처럼 모든 코드가 다 완벽하게 돌아갔다면 더 놀랐을 것이 분명했다. 아무리 그래도 주석에 '//자바에서는 배열을 어떻게 초기화 할까'라고 남겨두고 결국 초기화 못해서 NPE를 발생시킨 것은 좀 심했다 싶었지만.


그러니까 지금의 상황은 결국 조금 더 살펴보지 못한 내 잘못도 컸다. 물론 '이건 내가 짠 코드가 아니야'라는 말로 열심히 멘탈을 방어하고는 있었지만. 내가 짠 코드가 아니더라도 내가 해결해야 될 상황은 분명했다. 주위를 슬쩍보니 어떤 후배는 화면에 한글이 제대로 나오지 않아서 울상이었고 어떤 선배는 멀쩡하던 데이터가 이유없이 변경돼서 고생이었다. 당분간은 나를 도와줄 사람이 없는 것도 분명했다. 모든 상황이 분명해졌으니, 이제 문제를 해결하기만 하면 됐다.


불변의 진리. 문제를 해결하기 위해서는 원인을 찾아야 했다. 원인이 불분명한 대부분의 현실세계 문제들과 달리 프로그램에서 발생하는 문제들은 대부분 원인이 분명했고, 그 원인을 찾는 방법도 간단했다. 자신이 짠 코드에 언젠가 문제가 발생할 것을 믿는 프로그래머들은 프로그램이 수행되는 단계 단계마다 로그를 자주 남긴다. 이 로그는 프로그램이 성공하거나 실패하거나 꾸준히 파일로 저장되기 때문에 문제의 원인을 찾아주는 일종의 블랙박스 역할을 해준다. 어제 프로그램이 제대로 돌지 않았다면 어제 로그를 보고 어디서 오류가 발생했는지 찾아내면 되는 것이었다. 내 직업에 축복이 내린 기분이었다.


다른 프로그램과 달리 배치 프로그램은 단시간에 굉장히 많은 작업을 수행하기 때문에 뿜어내는 로그의 양도 어마어마했다. 당시 프로젝트에서는 로그 파일이 10메가바이트를 넘어가면 파일 뒤에 001 002 003과 같은 표시를 붙여서 다른 파일로 로그를 분리하도록 구성되어 있었는데, 배치 프로그램의 로그들은 10메가 넘는 일이 예사였다. 말이 좋아서 10메가지 글자로 치면 천만자 분량이라는 것을 생각하면 질리도록 많은 양이었다. 하지만 질리도록 많은 로그 속 어딘가에 내가 찾는 답이 있다는 사실을 알고 있다면 그렇게 짜증스러울 상황은 아니었다.


어제 새벽에 말썽을 부린 배치 프로그램을 포함한 일련의 로그 파일은 2개가 생성되어 있었다. 20메가에 약간 못미치는 용량의 로그가 생긴 것을 보니까 어쨌든 프로그램이 열심히 돌기는 돌았던 것 같았다. 만약에 시작하자마자 에러를 내고 종료되었다면 로그는 굉장히 짧았을 것이다. 로그가 워낙 많았기에 FTP를 통해서 파일을 다운로드 받은 뒤 텍스트 에디터로 로그를 열었다. 그리고 검색기능을 열심히 활용해서 무엇이 잘못되었는지 찾아나가기 시작했다. 생각보다 일이 쉽게 풀릴 것 같다는 생각에 살짝 즐거운 기분도 들었다. 오늘도 또 하나 해결하는구나.


그런데 생각처럼 일이 잘 풀리지 않았다. 처음 들었던 생각은 뭔가 불완전하다는 것이었다. DB에서 배치 프로그램에서 쓰는 것과 똑같은 쿼리로 조회해봤는데, 이 프로그램이 대상으로 삼는 데이터는 수 천건을 넘어섰는데, 로그에 나타나는 데이터는 너무 간헐적이었다. 시험삼아서 DB에서 조회한 데이터 중 아무 데이터나 하나 골라서 검색을 해봤는데 나오지 않았다. 이 데이터는 안 돌았나? 싶어서 다른 데이터를 조회해보니 검색에 잡혔다. 처리에 실패하기는 했지만 어쨌든 있기는 있었다. 몇 가지 데이터를 더 로그에서 찾아보니 어떤 데이터는 나오고 어떤 데이터는 나오지 않았다. 뭔가 돌다가 중간에 종료되어 버렸나? 싶었는데 오히려 로그에 나오는 데이터는 순서상 나중에 처리되는 데이터가 대부분이었다.


내가 대상이 되는 데이터들을 조회할 때 쓴 쿼리는 배치 프로그램에서 데이터를 조회할 떄 쓰는 쿼리를 그대로 복사한 것이니까, 내가 조회한 수천건의 데이터는 로그에도 분명히 있어야 됐다. 그런데 없었다. 아예 없었으면 다른 원인을 찾아봤을텐데 아예 없는 것도 아니었고 일부는 있고 일부는 없었다. 일관성이 사라진 현장에서의 프로그래머의 심리상태는 놀이공원에서 길을 잃은 5살 아이와 크게 다를 것도 없었다. 문제의 원인이 불분명 해졌고, 아무것도 할 수 있는 것이 없었다. 내 직업에 저주가 내린 기분이었다.


어느덧 시간은 자정이 되었다. 첫번째 배치 프로그램이 돌기 시작했고, 오늘의 로그가 다시 쌓이기 시작했다. 시간이 얼마 남지 않았다. 머리속에 떠오르는 해결책은 하나 밖에 없었다. 오늘 새벽 2시에 돌아가는 배치 프로그램이 출력하는 로그를 실시간으로 보고 원인을 찾는 것. 물론 그렇게 된다면 오늘도 배치 프로그램이 제대로 돌지 않을테니 내일 아침에 사무실에 불벼락이 떨어질 것은 자명한 사실이었지만 다른 방법이 떠오르지 않았다. 그래서 화면에 올라오는 로그를 가만히 보고 있었다.


첫번째 배치 프로그램은 3분만에 자신의 임무를 정상적으로 수행했고, 올라가던 로그는 00시 03분 몇 초쯤에 마지막 성공 메시지를 출력하고 잠시 대기하고 있었다. 아마도 몇 분쯤 뒤에는 두번째 배치가 스케줄에 맞추어서 동작하겠지. 배치 프로그램이 실행되는 스케줄을 몇시 17분 28초 이런식으로 정하는 경우는 없기 때문에 대부분 배치는 정시 혹은 10분이나 최소 5분 정도 단위로 실행되는 것이 정상이었다.


그런데 문득 어제 실패한 프로그램은 2시 정각에 제대로 실행이 되기는 한 것이었을까 궁금해졌다. 그래서 다시 텍스트 에디터를 전면에 올려서 로그 파일의 시작지점으로 이동했다. 그런데 뭔가 이상했다. 로그 파일의 시작지점에 표시된 시각이 2시 정각이 아니라 2시 십 몇 분 몇 초 정도로 굉장히 불규칙했다. 이 프로그램 전에 돌아야 했던 어떤 배치가 너무 오래 걸려서 스케줄이 밀린 걸까 고민해봤는데, 문득 로그의 첫 줄이 이상하다는 느낌을 받았다. 대부분의 프로그램은 시작할 때 자기가 실행된다는 사실을 굉장히 요란하게 로그로 남기는 편이었는데 이 로그의 첫 줄에는 그런 표시는커녕 마치 한참 실행하던 중에 '아 맞다! 로그 남겨야 되는데 깜빡했네?'라고 실수를 인정하고 그 때부터 남기기 시작한 것처럼 굉장히 프로그램이 실행되는 중간부터 기록된 모양새였다.


파일명의 뒤 쪽에 001이나 002등의 표시가 기록된 파일은 어딘가의 중간에서 짜르고 이어서 기록하는 파일이기 때문에 충분히 그런 모양일 수 있었다. 하지만 첫 번째 로그는 그럴 수 없었다. 만약에 첫 번째 로그 파일이 중간이라면 그건 로그가 여태까지 잘못기록되고 있었다는 의미가 된다. 혹시나 해서 그저께의 로그를 봤다. 역시나 시작지점이 없었다. 3일전의 로그를 보니 마찬가지였다. 설마 로그가 잘못된 것일까 하고 어제의 로그 두 개를 다시 꺼내서 살펴보는데 뭔가 위화감이 들었다. 조금 더 자세히 살펴보니 001 파일, 그러니까 두 번째 로그가 첫 번째 로그보다 더 이전의 시점을 기록하고 있었다.


황급히 개발서버의 터미널을 띄워서 로그파일이 기록된 디렉토리로 이동했다. 그리고 로그파일들의 생성시각을 조회해봤다. 역시나 첫번째 로그가 두번째 로그보다 나중에 수정되어 있었다. 다른 날짜의 로그들도 살펴보니 이것도 들쑥날쑥했다. 어떤 날은 첫번째 로그가 정상적으로 더 먼저 기록되었고 어떤 날은 나중에 기록되었다. 어떤 날은 로그 파일이 하나만 있었다. 그 순간 무언가 원인이 어렴풋이 보이는 기분이 들었다.


시스템이 오픈한 첫 날에는 다른 파트의 프로그램들도 문제가 있어서 데이터가 그리 많이 쌓아지 않았다. 그래서 이 배치 프로그램도 처리할 데이터가 그리 많지는 않았고 로그 파일은 하나만 생성되고 종료되었다. 물론 뭔가 문제가 있어서 제대로 돌지는 않았고 처리할 데이터는 제대로 수정되지 않고 그대로 쌓여있었다. 그 다음날이 되자 데이터는 더 쌓였고 여전히 문제가 있어서 수정은 정상적으로 이루어지지 않았다. 로그는 더 커져서 10메가를 넘겼고 2번째 파일로 분리가 되었다. 그렇게 처리하지 못한 데이터가 쌓이자 로그는 계속 커지게 되었고 결국 002라는 표시를 붙인 3번쨰 로그를 만들어야 될 때가 왔다.


그리고 프로그램은 세 번쨰 로그를 생성하지 않고 첫 번쨰 로그에 로그를 덮어 씌웠다.


그렇게 로그가 쌓일 떄마다 첫 번째 로그와 두 번째 로그를 번갈아 가면서 덮어씌운 프로그램은 대량의 로그를 유실시켰다. 그래서 로그를 아무리 들여다봐도 제대로 된 문제의 원인을 찾을 수 없었다. 혹시나 해서 로그를 기록하는 프로그램의 코드를 보니 역시나 변수 처리를 잘못해서 세 번쨰 로그를 만들어내지 못하는 버그가 있었다.


로그를 기록하는 코드를 만든 사람이 누굴까 찾아보니 공통 파트에서 일하고 있는 동기였다. 잽싸게 메신저를 통해서 '배치쪽 로그 기록하는거 지금 버그떄문에 지난 몇 일동안 로그 다 날라갔는데 어쩌냐'라고 물어봤더니 '아 지금 바쁜데 형이 알아서 좀 고쳐봐요'라는 답장이 왔다. 순간 너는 지금 이 상황이 장난처럼 보이냐 지금 PL님은 내일 고객한테 원자폭탄을 맞게 생겼는데 로그 다 날려먹는 사고를 치고 바쁘다는 소리가 나오냐 내가 지금 처음으로 내 직업에 회의를 느끼고 있는데 네 버그를 고쳐줄 마음이 들겠냐 등등의 험한 생각이 머리를 스쳐지나갔다. '일단 알았으니까 내일 꼭 고쳐달라' 정도의 답장을 보내고는 당장 눈앞에 놓인 문제를 해결했다.


불완전한 로그 때문에 방황하지 않고, 오픈 직후 쌓인 완전한 로그를 보고 시작지점 부터 문제점을 찾으니 역시나 원인은 쉽게 찾을 수 있었다. 그저 변수의 스코프와 관련된 흔히 발생하는 오류 중 하나였을 뿐이었다. 테스트할 시간이 없었기에 운 좋이면 돌아가겠지 라는 심정으로 수정한 코드를 올려놓고 2시가 되길 기다렸다. 그리고 그 날은 운이 좋은 날이었다.


그렇게 마무리를 하고 퇴근을 했다. 결국 찾고 나니 별로 큰 문제도 아니었는데 이렇게 마음 고생에 몸 고생까지 해야 됐나 생각하니 짜증이 올라왔다. 따지고 보면 내가 하지도 않은 다른 사람들의 실수 때문에 결국 나만 고생했나 싶었다. 아까 그렇게 두리뭉실하게 답장할 것이 아니라 확 화를 내는 것이 좋았을까. 그랬다면 과연 좋은 결과가 나왔을까.


모두에게 힘든 프로젝트였다. 다들 자기만의 전쟁을 치르고 있었고 그렇기에 다른 사람에게는 상대적으로 무관심 할 수 밖에 없었다. 내가 보고 있는 문제가, 내가 짜고 있는 코드가, 내가 하고 있는 일이 세상에서 제일 중요하고 심각한 일이었고 다른 사람들이 하는 일은 내가 상관할 바가 아니었다. 그렇게 짜증과 미움이 쌓이자 자연스럽게 화살은 서로를 향할 수 밖에 없었다. 사실 그 화살들은 이런 상황을 만든 조직을 향해야 될 것이 분명했는데.


유명한 게임이론인 죄수의 딜레마에 따르면, 사람들은 항상 자신의 이익을 우선시 하기 때문에 결과적으로는 모두에게 최악의 선택을 하게 된다고 그랬다. 당장 내가 감수할 수 있는 손해의 폭이 좁은 상황에서 다른 사람을 위해 희생하는 것은 절대 쉬운 일이 아닐것이다. 그런데 훨씬 덜 유명한 게임이론인 반복 죄수의 딜레마에 따르면, 사람들은 최악의 상황을 반복해서 겪다보면 자신의 이익만 우선시 하는 것 보다는 다른 사람을 위해서 조금씩 희생하는 것이 결과적으로 더 유리하다는 것을 알게 되어서 점점 공동의 이익을 추구하는 화합의 형태를 가지게 된다고 그랬다. 단 여기에는 조건이 하나 있었다. 최악의 상황을 그저 잊어버리고 싶은 상황으로 치부하고 과거로 보내지 않고, 그래도 뭔가 한 두 가지 교훈은 얻어서 나올 것.


그러니까 조금이라도 더 나은 상황을 만들고자 하는 의지가 있는 사람들이 모인다면, 서로 싸우는 것이 결과적으로는 상황을 악화시키기만 한다는 것을 다들 알게 된다면, 프로젝트에서 화합을 추구하는 것도 그렇게 안 될 일은 아닐 것 같다.

'에세이' 카테고리의 다른 글

Routine  (1) 2016.08.01
우리에게 좋은 키보드가 필요한 이유  (1) 2016.04.12
10년이 지났다  (1) 2015.12.15
개발의 현황  (0) 2015.09.23
전역변수 Apocalypse  (0) 2015.03.28
댓글