PR과 이슈의 TTL 설계 — PR은 7·14일, 이슈는 14·30일로 나눈 이유

정기창·

PR과 이슈를 같은 기한으로 묶을 수 없다고 판단해서, PR은 7일과 14일에, 이슈는 14일과 30일에 다시 판단하도록 TTL을 설계했습니다. 이 글은 그 숫자를 왜 이렇게 정했는지에 대한 기록입니다.

여기서 TTL은 기한이 지나면 작업을 자동으로 닫는 장치가 아닙니다. 정해 둔 시점이 오면 그 PR이나 이슈가 지금의 기획에도 맞는지 다시 판단하고 근거를 남기겠다는 약속에 가깝습니다. 날짜는 다시 판단하라는 신호일 뿐, 날짜만으로 작업을 폐기하지는 않습니다.

먼저 밝혀 둘 것이 있습니다. 이 숫자들은 통계로 입증한 최적값도, 업계 표준도 아닙니다. 기획이 바뀌는 속도, 한 번 재검토하는 데 드는 비용, 오래 방치된 작업의 맥락을 되살리는 데 드는 비용을 함께 놓고 정한 시작값입니다. 이 기한의 효과는 아직 측정하지 않았으므로, 아래 내용은 효과의 증거가 아니라 무엇을 고려해 그렇게 정했는지에 대한 설명입니다.

PR과 이슈는 낡는 방식이 다릅니다

PR은 특정 시점의 코드와, 그 코드가 기대는 계약(서버와 화면이 주고받는 API와 데이터 형식), 의존하는 다른 작업, 그 상태에서 얻은 검증 결과에 묶여 있습니다. 그래서 기준이 되는 브랜치가 앞서가면 코드를 한 줄도 고치지 않은 PR이 예전 기준의 결과물이 됩니다. 시험이 통과했다는 사실도 «예전 기준에서 통과했다»는 뜻으로 좁아집니다.

AI와 함께 개발한 프로젝트에서도 그랬습니다. 기획이 계속 보강되었고, 서버와 화면은 서로 다른 PR로 움직였습니다. 최신 기획이 dev 브랜치에 들어가도 이전 기획을 바탕으로 열려 있던 PR의 내용까지 함께 바뀌지는 않았고, 앞선 구현을 다른 PR이 대체했는데도 원래 PR이 열린 채 남아 있는 경우도 있었습니다.

이슈는 사정이 다릅니다. 이슈에 담긴 것은 해결할 문제와 요구사항이어서, 구현 방식이 달라져도 그 문제는 여전히 유효할 수 있습니다. 같은 시간이 지났을 때 PR은 «이 코드가 아직 맞는가»를, 이슈는 «이 문제가 아직 맞는가»를 묻게 되는데, 앞의 질문은 답이 더 빨리 바뀝니다.

기한을 하나로 두면 어느 한쪽이 어긋납니다. 이슈에 맞추면 PR이 낡은 채 오래 남고, PR에 맞추면 아직 유효한 요구사항을 너무 자주 따지게 됩니다. 그래서 PR은 짧게, 이슈는 더 긴 주기로 다시 판단하도록 나누었습니다.

재검토 시점을 한눈에 보면

정한 시점을 판단 목적과 이유로 나누어 보면 다음과 같습니다.

시점판단 목적이유
PR 7일범위·의존성·최신 dev 호환성 확인한 주 동안 쌓인 기획·dev 변경 반영
PR 14일계속 진행·분할·인계·종료 결정남은 이유를 확인하고 무기한 대기를 막기 위해
이슈 14일요구사항 유효성·해결 여부·담당·완료 조건 확인구현이 바뀌어도 문제는 유효할 수 있어서
이슈 30일현재 기획에서 추적할 위치 재정리월 단위 재정리와 장기 요구사항 보존을 함께 고려
매일새 기획·계약·dev 변경의 영향 확인변화가 생긴 곳에서 출발해 낡는 항목을 빨리 찾기 위해
3일마다변화 감지에서 빠진 항목, 오래된 대기 상태 확인변화 신호 없이 멈춘 항목을 목록 기준으로 찾기 위해

표의 앞 네 줄은 PR과 이슈가 만들어진 날부터 각자 세는 기한이고, 뒤 두 줄은 목록을 살펴보는 주기입니다. 이 둘은 역할이 달라서 아래에서 나누어 설명합니다.

PR은 7일에 점검하고 14일에 결정합니다

7일은 첫 점검입니다. 한 주가 지나면 그동안 보강된 기획과 dev의 변경이 쌓이므로, 이 PR이 맡은 범위가 아직 맞는지, 의존하는 작업이 바뀌지 않았는지, 최신 dev와 함께 두어도 문제가 없는지를 한 번 확인합니다. 어긋남이 더 쌓이기 전에 짚어 두려는 시점입니다.

14일은 결정의 시점입니다. 첫 점검을 지나서도 열려 있다면 거기에는 이유가 있을 것입니다. 리뷰나 다른 작업을 기다리는 중일 수도 있고, 범위가 커서 더딘 것일 수도 있습니다. 그 이유를 확인하고 계속 진행할지, 나눌지, 다른 작업으로 인계할지, 닫을지 중 하나를 고릅니다.

모든 개발을 14일 안에 끝내라는 뜻은 아닙니다. 오래 걸리는 작업도 근거가 있으면 계속 진행하는 것이 맞습니다. 이 기준이 막으려는 것은 긴 작업이 아니라, 아무도 판단하지 않은 채 같은 상태로 무기한 기다리는 일입니다.

이슈는 14일에 유효성을, 30일에 추적 위치를 봅니다

이슈의 14일에는 요구사항이 아직 유효한지, 그사이 다른 작업으로 이미 해결되지는 않았는지, 담당과 완료 조건이 분명한지를 확인합니다. PR의 첫 점검보다 두 배 긴 간격인데, 구현이 바뀌어도 문제는 남을 수 있으니 PR과 같은 속도로 의심할 필요는 없다고 보았습니다. 다만 담당과 완료 조건이 흐려진 채 오래 열려 있으면 누구의 일도 아닌 이슈가 될 수 있어서, 이 두 가지는 이때 함께 확인합니다.

30일에는 질문의 무게가 달라집니다. 닫을지보다 먼저, 이 문제를 현재 기획의 어디에서 추적할지를 다시 정리합니다. 이미 최신 기능 이슈가 같은 문제를 다루고 있다면 그쪽으로 통합하고, 아니라면 현재 기획에 맞게 위치와 범위를 고칩니다. 기획이 보강되는 사이 문제는 그대로인데 그 문제가 놓일 자리는 달라질 수 있기 때문입니다.

30일이라는 간격에는 두 가지 생각이 겹쳐 있습니다. 하나는 이슈를 월 단위로 다시 정리하는 흐름에 맞추는 것이고, 다른 하나는 오래 걸리는 요구사항을 날짜 때문에 잃지 않는 것입니다. 30일에 통합과 재배치를 먼저 묻는 것은 이 두 번째 생각 때문입니다.

매일 점검과 3일 점검은 보는 방향이 다릅니다

7일이나 14일 같은 기한은 PR과 이슈가 만들어진 날부터 각자 세는 시계입니다. 그런데 기한이 오기 전에 이미 어긋나는 항목이 있습니다. 새 기획이 정해지거나 계약이 바뀌거나 dev에 큰 변경이 들어오면, 만든 지 이틀밖에 안 된 PR도 방향이 달라질 수 있습니다.

그래서 매일의 점검은 변화에서 출발합니다. 새 기획, 계약, dev의 변경이 생기면 그 영향을 받는 항목을 골라 확인합니다. 모든 항목을 매일 처음부터 다시 읽는 점검이 아니라, 변화가 닿은 항목만 보는 점검입니다.

다만 변화에서 출발하는 점검에는 맹점이 있습니다. 변화와 이어지지 않은 항목은 신호가 없어서 눈에 띄지 않습니다. 리뷰를 기다리다 멈춘 PR이나 담당이 흐려진 이슈가 그런 경우입니다. 3일마다 하는 점검은 그 빈틈을 메우려고 목록 전체를 훑으면서, 변화 감지에서 빠진 항목과 오래된 대기 상태를 한 번 더 확인합니다.

3일이라는 간격도 시작값입니다. 간격이 길면 놓친 항목이 오래 남고, 짧으면 같은 목록을 반복해서 읽는 비용이 커집니다. 변화가 닿은 곳은 매일 보고 목록 전체는 3일에 한 번 보는 것은 그 둘 사이의 절충입니다.

연장은 근거를 남기고, 예외는 보존하며 재검토합니다

기한이 왔을 때 계속 진행할 근거가 있다면 연장할 수 있게 했습니다. PR은 7일, 이슈는 14일씩 늘리고, 연장 기간은 각각 첫 점검 간격과 같습니다. 다만 연장하려면 근거, 남은 작업, 담당, 다음 재검토일을 함께 남겨야 합니다. 이 네 가지가 없으면 연장은 판단이 아니라 미루기가 되기 때문입니다.

두 번 연장한 뒤에는 연장하는 대신 작업의 구조를 다시 판정합니다. 같은 항목이 계속 연장된다면 의지가 부족해서라기보다 범위, 의존성, 우선순위의 문제일 수 있습니다. 그럴 때 세 번째 연장은 시간만 더 줄 뿐 원인은 그대로 둡니다. 나누거나, 다른 작업과 합치거나, 우선순위를 다시 정하는 쪽이 맞는 질문이라고 보았습니다.

예외도 두었습니다. 보안, 장애, 데이터 정합성과 관련된 항목과 사용자의 결정을 기다리는 항목은 기한이 지났다는 이유로 정리하지 않고 보존합니다. 대신 유지하는 사유와 다시 움직일 조건을 적어 두고 재검토는 계속합니다.

이유는 항목마다 조금 다릅니다. 보안이나 장애, 정합성 문제는 시간이 지났다고 중요도가 낮아지지 않습니다. 사용자의 결정은 기한이 지났다고 우리가 대신 내릴 수 없고, 답이 오지 않았다고 해서 승인한 것으로 보지도 않습니다.

기한을 되돌리는 우회로는 인정하지 않습니다

댓글이 달렸다거나, 봇이 활동했다거나, 브랜치에 dev를 합쳐 최신으로 만들었다는 이유만으로는 기한을 초기화하지 않습니다. 이런 일은 무언가 일어났다는 사실만 알려 줄 뿐, 이 작업이 지금의 기획에 맞는지 누군가 판단했다는 뜻은 아니기 때문입니다. 반대로 재검토가 늦어졌다고 다음 기한이 뒤로 밀리지도 않습니다.

새 PR 번호를 만든다고 코드와 기획이 최신화되지는 않습니다. 이전 브랜치를 그대로 옮겨 새 PR을 열면 번호와 생성일만 새로워질 뿐 내용은 그대로입니다. 시계만 처음으로 돌아가고 낡은 판단은 남는 셈이어서, 재생성이 기한을 피하는 길이 되어서는 안 됩니다. 최신인지는 번호나 날짜가 아니라 현재 기획, 계약, 의존성과 맞춰 본 결과로 알 수 있습니다.

닫기 전에 남은 것은 후속 작업에 넘깁니다

종료를 결정했더라도 닫기 전에 할 일이 있습니다. 그 PR에만 남아 있는 구현, 고유한 시험, 풀리지 않은 리뷰 지적을 먼저 후속 작업에 인계합니다. 후속 PR로 옮기더라도 기존 리뷰 지적은 함께 이어져야 합니다. 새 번호를 받았다고 해결하지 않은 지적이 사라지지는 않기 때문입니다.

PR 하나를 정리하다가 이 순서가 왜 필요한지 확인했습니다. 엑셀 파일에 열을 추가하는 PR이었는데, 그 기능은 나중에 병합된 다른 PR이 이미 대체한 상태였습니다. 기능만 보면 닫아도 되는 PR이었지만, 열마다 다른 값을 넣어 셀 위치를 확인하는 시험과 숫자 0을 미상값과 구분하는 시험은 이전 PR에만 있었습니다.

그래서 종료 댓글에 기능이 대체된 근거와 이 고유 시험을 남기고, 후속 체크리스트에도 연결했습니다. 이 PR은 TTL 규칙에 따라 정리한 것이 아니라 개별 정리 요청으로 닫은 것이어서, 규칙의 효과를 보여 주는 사례는 아닙니다. 다만 기능이 대체되었다는 것과 남은 것이 없다는 것이 같은 말이 아니라는 점은 분명하게 보여 주었습니다.

효과는 열린 항목 수만으로 판단하지 않습니다

앞서 밝혔듯이 이 기한의 효과는 아직 측정하지 않았습니다. 다만 무엇으로 판단할지는 미리 정해 두려 합니다. 열린 PR과 이슈의 수만 보면 서둘러 닫아서 줄어든 경우와 제대로 정리되어 줄어든 경우를 구별할 수 없기 때문입니다.

그래서 함께 볼 것으로 네 가지를 꼽았습니다. 닫는 과정에서 후속 작업으로 넘기지 못한 것이 있었는지(인계 누락), 한 번 재검토하는 데 시간이 얼마나 드는지(재검토 비용), 같은 항목이 반복해서 연장되는지(반복 연장), 닫은 작업을 나중에 다시 살려야 했는지(작업 복구 비용)입니다. 이 가운데 어느 하나가 나빠진다면 숫자를 조정해야 한다는 신호로 읽을 생각입니다.

이 방식에는 비용도 따릅니다. 인계 내용을 쓰고 확인하는 시간이 들고, 진행 중인 PR을 기계적으로 다시 만들면 리뷰 맥락만 잃고 일이 늘어납니다. 그래서 기한은 어디까지나 다시 판단하게 만드는 기준이고, 실제로 진척이 있는 작업은 근거를 남기고 이어 갈 수 있게 했습니다.

결국 중요한 것은 숫자보다 판단의 시점이라는 생각이 들었습니다. 작업을 오래 남겨 둔다고 맥락이 보존되지는 않습니다. 지금의 기획에도 유효한지 확인하고, 남은 일을 누가 이어받는지 연결해 두어야 기록이 다음 행동에 도움이 됩니다. PR과 이슈에 둔 기한은 그 확인을 미루지 않기 위한 장치이고, 숫자 자체는 써 보면서 고쳐 갈 시작값입니다.

AI 개발GitHubPull Request이슈 관리개발 워크플로