최근 화제가 된 트윗들을 모아봤다.
나도 git checkout만 쓰다가 git switch, git restore를 쓴다는 얘기를 들고 깜짝 놀랐다. 아니 git을 쓰기 시작한지도 얼마 안 됐는데 벌써 새로운 게 나왔단 말인가? 기술의 발전이 참 빠르다, 나도 벌써 뒤쳐지는 것인가, 내가 그렇게 늙었나, 여러가지 생각이 들었다.
그런데 git이 참 어렵긴 하다. 지금 다니는 회사에서 가장 적응하기 힘들었던게 git이었도 했고, 지금도 여전히 힘들고 여전히 고민이 많은 기술이다. 좋은 점이라면 사실 그 전에는 사람들 사이에서 암시적으로 했던 작업들을 이제는 명시적인 git 명령으로 한다는 것인데, 나쁜 점이라면 그게 git 명령으로 바뀌었을 뿐이지 문제는 여전하다는 것이다.
git은 장점과 단점이 항상 같이 다니는데, 첫째로 분기가 된다는 것이다. 회사는 혼자가 아니라 여럿이 같이 일을 하는데, 그러다보니 동시에 여러 일을 진행할 때도 있다. 그런데 불행히도 같은 소스코드를 여러 사람이 동시에 고쳐야 할 경우가 생긴다면 어떻게 할 것인가? git은 평행 세계처럼 branch를 둘로 나눈다. 그래서 각자 작업하다가 하나로 합친다. 근데 똑같은 파일을 둘 다 고쳤다면 conflict이 난다. 그럼 어떻게 합칠 것인가?
내가 옛날에 Visual sourcesafe를 쓸 때만 해도 이런게 없었다. 내가 한 파일을 Lock하면 Unlock할때까지 다른 사람은 고칠 수 없었다. 차례를 지켜가며 한 파일을 한 사람씩 수정하는 방식이었다.
제가 병특 하던 시절 (2001-2003)년에 소스 세이프 썼죠. 심심하면 “XX야 파일 좀 풀어라.”
from the protected account
Visual sourcesafe의 Lock-Modify-Unlock 방식은 DB의 pessimistic locking과 마찬가지로 문제 발생을 원천 차단하지만, 혹시라도 먼저 한 사람이 Lock을 오래 잡고 있으면 뒤의 사람들이 계속 기다려야 하는 문제가 생긴다. 그러다 그 사람이 Lock을 건채로 퇴사를 하거나 연락이 끊겨버린다면?
물론 관리자 권한으로 Lock을 강제로 풀 수 있긴 한데 그것도 한두번이지… 귀찮은 일이 자꾸 생긴다. 아니 그러면 일단 각자 알아서 고친 다음에 맨 마지막에 합치면 되지 않나? 그게 DB로 치면 optimistic locking이고 그럼 누군가 victim이 생겨서 죽는다…는건 DB의 경우고 git의 경우는 잘 합치면 된다.
그럼 어떻게 잘 합칠 것인가? 두 소스를 잘 읽어보고 이해해서 논리적으로 잘 판단하면 된다. 여기는 들어가야 하고, 여기는 빠져야 하고, 여기는 둘 다 들어가야 하면 정말 어렵지만 두개를 잘 조합해서 새걸 만들어내면 된다. 아니 그러니까 그걸 어떻게 합치냐 하면 결국 사람이 매번 해야 한다는 것이다. 아까처럼 한참을 기다려야 하는 경우는 없지만 이것도 나름의 비효율이 생긴다.
이게 그나마 팀 규모가 작을 경우에는 충돌도 적게 일어나서 해결할 만 하지만, 팀 규모가 커지면 충돌을 급격히 많아진다. 그래서 “피자 두 판의 법칙”이 git의 경우 잘 통한다. 팀을 작게 쪼개고, 시스템을 작게 쪼개야 한다. microservice로 나누는 것도 좋다.
Uber에서도 그래서 이런 충돌을 자동으로 해결하는 시스템을 만들었다. 논문으로만 공개하고 시스템을 공개하지는 않았지만. 근데 읽어보면 해결하기 쉬운 경우를 해결하는거지, 어려운 경우까지 완벽하게 해결하는 건 아니다. 빌드가 성공하면 commit을 받아들이고 실패하면 거부하는 거라서, 어느 정도는 기존에 잘 돌아가던 소스가 빠지거나, 새로 추가한 코드가 빠지는 경우가 생길 수 있다. (그보다 이 논문은 규모가 커져도 빠르게 실행하는 걸 목표로 한다)
나도 가끔 내가 몇 달 전에 고쳤던 다른 사람이 덮어써서 없어지는 경우도 있고, 아니면 내가 잘못해서 남이 고쳐놓은 코드를 덮어쓰는 경우도 있다. 그게 사소할 수도 있고 심각할 수도 있다. 하지만 위의 Uber와 같이 전체 시스템의 빌드가 성공해서 파란불이 켜지고, 일단 시스템이 돌아가는데 크게 문제가 없다면 이런 사소한 문제들을 발견하기가 매우 어렵다.
그럼 이런 문제들을 어떻게 해결할 것인가? 아무리 생각해도 완전히 막을 수 있는 방법이 없다. 문제가 덜 일어나도록 완화하는 방법밖에 없다. 테스트 케이스들을 촘촘하게 많이 만들어놔서, 빌드할 때 테스트케이스가 실패하는 것으로 알아차리는 방법이 있고, 코드를 합칠 때 그냥 합치는 게 아니라 pull request로 code review를 반드시 받도록 해서 개발자들끼리 서로 문제가 있는지 다시 한번 확인하는 방법이 있고, 아니면 github 등에서 제공하는 기능을 이용해서 중요한 branch에는 함부로 합치지 못하도록 여러 제한을 걸 수 있다. 실제로 다들 많이 하는 방법들이다.
결국 분기했다 합치는 게 문제다. 그럼 너무 분기하지 말고 조금만 분기했다 합치면 문제가 덜 생기지 않을까? 해서 branch 전략을 극단적으로 단순하게 만든 github flow도 있다.
근데 이러면 참 좋겠지만 현실은 그보다 지저분할 수 있다. 당장 수정해야 하는 hotfix가 필요할 수도 있고, 당장까지는 아닌데 하여튼 빨리 해야 하는 coldfix가 있을 수 있고, 이것도 해야 하고 저것도 해야 해서 정기 배포에 딱 맞춰서 나갈 수 없는 자잘한 배포들이 따로 생기면 branch의 tree path가 점점 지저분해진다.
그렇다고 이 지저분함을 어느 정도 용인하지 않으면 빨리 대응할 수가 없다. 완전무결하려다가 시간이 가버린다. 일단 빨리 한 다음에 나중에 지저분한 걸 정리하면서 따라 잡아야 한다. 근데 git switch / restore에서 어쩌다 얘기가 여기까지 왔지?