TL;DR
코드를 작성하던 시대가 생산하는 시대로 옮겨간다.
도구는 바뀌어도, 본질은 바뀌지 않는다.
우리는 모두 엔지니어다.무엇이 변했는가
수십 년 동안 소프트웨어를 만드는 일에는 분명한 주체가 있었다.
사람이 키보드 앞에 앉아 코드를 직접 쓰고, 컴파일하고, 디버깅했다.
코드는 언제나 사람의 손끝에서 나왔고, 그 행위 자체가 직업의 가장 가시적인 표면이었다.
그 표면이 지금 빠르게 얇아지고 있다.
AI Code Assistant는 함수 단위가 아니라 모듈 단위로, 어떤 경우엔 시스템 단위로 코드를 짜고 검토하고 수정한다.
사람은 더 이상 모든 키 입력의 기원이 아니다.
이 사실이 누군가에게는 위협으로 들린다.
충분히 이해할 수 있는 일이다.
코드를 한 줄 한 줄 적는 행위 그 자체에서 즐거움을 찾고, 그 안에서 정체성을 길러온 사람들에게 도구의 변화는 곧 자기 자신의 위치 변화를 의미하기 때문이다.
여기서 분명히 해두자.
코드를 한 줄씩 직접 쓰는 즐거움 자체는 결코 부정할 대상이 아니다.
컴파일러가 등장한 이후에도 어셈블리어로 시스템을 만드는 사람들은 여전히 존재했고, 고수준 언어가 보편화된 이후에도 저수준 영역에서 컴퓨팅 자원을 다루는 일은 사라지지 않았다.
각 방식에는 고유한 장점이 있다.
고수준 언어는 코드 생산의 난이도를 낮추지만 자원을 가장 효율적으로 다루기는 어렵고, 저수준 언어는 자원을 정밀하게 다룰 수 있지만 생산성이 낮다.
개발이 취미라면, 즐거움 그 자체가 목적이라면, 그 방식은 여전히 완벽하게 정당하다.
다만 엔지니어로 일하는 사람들 대부분은 소프트웨어 개발로 돈을 벌고 있거나, 앞으로 이 일로 돈을 벌고자 한다.
비즈니스의 영역에서는 답이 분명하다.
더 적은 시간과 자원으로 더 많은 가치를 만들어내는 쪽을 선택할 수밖에 없다.
그것이 비즈니스의 정의 그 자체이기 때문이다.
따라서 AI Code Assistant 도입은 비즈니스의 관점에서 필수불가결한 결정이다.
이 글은 이 전제 위에서 출발한다.
전제에 의문을 가지는 시각도 존재할 수 있으나, 이 글에서 다루고자 하는 것은 그 전제 너머의 질문이다.
정체성 — Engineer라는 단어
우리는 코드를 작성하는 사람들인가, 소프트웨어로 문제를 해결하는 사람들인가?수단과 목적을 혼동하는 순간, 정체성도 함께 흔들린다.
답을 찾으려면 우리가 불려지는 언어부터 다시 봐야 한다.
소프트웨어 개발 직군을 부르는 명칭은 시대마다, 분야마다 달라져 왔다.
Software Engineer, DevOps Engineer, Site Reliability Engineer, Product Engineer, Forward Deployed Engineer.
매년 새로운 직무명이 생겨나고, 어떤 명칭은 빠르게 사라지기도 한다.
그러나 이 모든 명칭에서 사라지지 않는 단어가 하나 있다. Engineer.
엔지니어를 가장 단순하게 정의하면 각자가 가진 기술로 현실 세계의 문제를 해결하는 사람이다.
토목 엔지니어가 다리를 놓아 강 양쪽의 거리를 줄이듯, 소프트웨어 엔지니어는 정보의 흐름을 설계해 사람들 사이의 거리를 줄이고, 기계의 시간을 줄이고, 비즈니스의 비용을 줄인다.
이 정의에는 코드를 한 줄씩 쓴다는 행위가 포함되어 있지 않다.
코드는 도구이고, 결과물의 한 형태일 뿐이다.
엔지니어의 본질은 문제를 해결한다는 사실에 있지, 그 문제를 어떤 도구로 해결했는가에 있지 않다.
코드 생산의 주체가 “사람”에서 “사람과 AI의 협업”으로 옮겨가는 일은 그래서 엔지니어의 본질을 흔드는 사건이 아니다.
다리를 놓는 도구가 망치에서 크레인으로 바뀐다고 해서 토목 엔지니어가 사라지지 않은 것과 같다.
도구의 자리만 바뀐다.
본질 — Tension
그러나 문제를 해결한다는 일은, 그렇게 단순하지 않다.There is No Silver Bullet.
소프트웨어 엔지니어링에는 오랜 격언이 하나 있다.
모든 문제를 한 번에 해결하는 마법의 탄환은 존재하지 않는다는 것이다.
이 격언이 말하는 바는 단순하다.
어떤 문제를 해결하기 위해 내린 결정은, 거의 언제나 다른 문제를 불러온다.
보통 이런 상황을 trade-off라고 부른다.
어느 한쪽을 얻기 위해 다른 한쪽을 양보해야 하는, 두 가치 사이의 균형점을 묘사하는 단어다.
그러나 이 글에서는 이를 다른 이름으로 부르고자 한다. Tension.
trade-off는 본질적으로 균형점에 시선을 두는 단어다.
"이 정도가 양보의 한계지"라는, 받아들이고 멈추는 자세가 단어 안에 묻어 있다.
하지만 엔지니어가 실제로 마주하는 상황은 정적인 균형점이 아니다.
Plus와 Minus는 한 선택이 동시에 가지는 두 얼굴이고, 그 두 얼굴은 다른 선택을 통해 깨뜨릴 수 있는 긴장으로 존재한다.
정합성을 강하게 보장하는 그 선택이 곧 처리량을 떨어뜨리는 선택이지만, 그 선택만 가능한 것은 아니다.
이 깨뜨릴 수 있는 긴장이 tension이다.
trade-off가 균형점에서 멈춰 결과를 받아들이는 단어라면, tension은 그 균형점이 사실 깰 수 있는 무엇이라고 보는 단어다.
균형점에서 멈추는 사람과, 그 균형점을 깨려고 다음 선택을 시도하는 사람의 차이.
이 차이가 엔지니어가 마주하는 거의 모든 본질적인 문제 앞에서 작동한다.
사고의 확장 — 하나의 비즈니스 사례
추상은 길어지면 공허해진다.
하나의 구체적인 비즈니스 사례 위에서 tension이 어떻게 출현하고, 어떻게 깨지고, 어떻게 다시 새로운 tension을 부르는지를 따라가 보자.
좌석이 정해진 공연 티켓 예매 시스템을 떠올려 보자.
좌석은 N장 한정이고, 같은 좌석을 두고 동시에 수천 명이 경쟁한다.
시스템이 충족해야 할 두 가지가 있다.
한 좌석은 단 한 사람에게만 팔려야 한다는 정합성.
그리고 사용자가 좌석을 누르고 결제 화면에 도달하기까지 답답함을 느끼지 않아야 한다는 사용자 경험.
출발점 — Pessimistic Lock
가장 직관적이고 안전한 선택은 비관적 락이다.
좌석 레코드를 잡고, 다른 트랜잭션이 그 레코드를 건드리지 못하게 막은 뒤, 한 사람의 결제 결과가 확정될 때까지 다른 모든 시도를 줄 세운다.
정합성은 압도적이다.
같은 좌석이 두 사람에게 팔리는 일은 거의 발생하지 않는다.
그러나 곧 새로운 사실이 드러난다.
동시 사용자 수가 늘어나면 줄은 길어지고, 응답은 느려지고, 사용자는 결제 화면을 보기까지 화면 가운데서 빙글빙글 도는 로딩 아이콘을 한참 응시해야 한다.
정합성(+)을 얻은 대가가 처리량과 응답 시간(−)이라는 형태로 청구된다.
여기서 멈추는 사람은 이렇게 말한다.
"비관적 락에는 처리량 한계라는 trade-off가 있다."
사실이지만, 멈춘다.
엔지니어는 멈추지 않는다.
tension을 깨려고 한다.
첫 번째 깨뜨림 — Cache 도입
Cache라는 도구를 떠올려 보자.
자주 묻는 데이터의 읽기 부하를 DB가 아닌 메모리 계층에서 받아낸다면, Lock 경합 자체가 그만큼 줄어든다.
처리량 병목의 한 축이 가벼워진다.
물론 이 선택도 무료가 아니다.
Cache는 본질적으로 stale한 데이터를 가진다.
DB의 진실과 Cache의 응답 사이에는 시간차가 존재하고, 그 사이 누군가 막 잡은 좌석이 다른 사용자의 화면에서는 여전히 비어보일 수 있다.
하나의 tension을 깨뜨리자 다른 tension이 등장했다.
정합성과 처리량 사이의 갈등이, 정합성과 진실의 시점 사이의 갈등으로 변형된 것이다.
두 번째 깨뜨림 — 영향을 덜 중요한 곳으로
새로 등장한 stale tension을 어떻게 다룰 것인가?
한 가지 답은 완전히 없애려 하지 않는 것이다.
모든 읽기를 Cache로 보내는 대신, 사용자 여정의 어느 지점에서 stale을 견딜지, 어느 지점에서 우회시킬지를 의식적으로 설계한다.
좌석 예매 시스템에서 발생하는 모든 요청이 정합성을 동등한 강도로 요구하지는 않는다.
결제 직전의 최종 좌석 확정 요청과, 사용자가 좌석 배치도를 둘러보면서 보내는 잔여 좌석 조회 요청은 무게가 다르다.
결제 확정 단계에서 stale은 치명적이다.
다른 사람에게 이미 팔린 좌석을 한 번 더 결제하게 두는 일은 시스템 신뢰의 근간을 흔든다.
그러나 좌석 배치도에서 잔여 좌석을 둘러보는 단계에서 stale은 약간의 불편 정도에 그친다.
클릭 후 "방금 매진된 좌석입니다" 같은 메시지가 뜨는 정도로 충분히 회복할 수 있다.
이 관찰을 그대로 시스템에 반영한다.
좌석 조회는 Cache에서 답하고, 결제 확정은 짧은 TTL의 좌석 hold를 잡은 뒤 DB로 마무리한다.
stale의 영향이 덜 중요한 곳으로 옮겨졌다.
tension의 Minus는 여전히 존재하지만, 그 Minus가 사용자 경험에서 차지하는 비중은 의도적으로 작아졌다.
세 번째 깨뜨림 — 비동기와 이벤트 기반
여기까지 와도 답하지 않은 질문이 남는다.
결제 확정 단계의 락 경합은 어떻게 해야 하는가?
인기 공연의 티켓팅 오픈 직후, 같은 좌석을 두고 수십 명이 동시에 결제 버튼을 누르는 순간을 생각하자.
그 순간만큼은 락의 줄이 다시 길어지고, 사용자 경험은 다시 흔들린다.
이 지점에서 한 발 떨어져 문제 자체를 다시 본다.
정말로 동기적 결제가 답이어야 하는가?
다른 모델이 가능하다.
사용자가 결제 버튼을 누르는 순간 시스템은 "좌석 잡기 요청을 접수했다"는 응답을 즉시 돌려주고, 실제 좌석 확정은 큐에 들어간 요청을 백엔드가 직렬로 처리하면서 결정한다.
사용자에게는 빠른 응답을, 시스템에는 직렬 처리의 안전성을 동시에 줄 수 있다.
동시 좌석 잡기라는 문제가 선점·확정 분리라는 문제로 재정의된 것이다.
물론 이 모델도 새로운 tension을 부른다.
응답 시점과 실제 확정 시점이 분리되었으므로 결과를 사용자에게 어떻게 알릴지 설계해야 하고, 확정에 실패한 경우 보상 트랜잭션을 정의해야 하며, 강한 정합성에서 약한 정합성과 보상으로 옮겨가는 만큼 디버깅과 운영의 난이도가 올라간다.
닫기
이 사례에서 한 가지가 분명해진다.
모든 깨뜨림은 새로운 tension을 부른다.
No Silver Bullet은 어디에서나 작동한다.
비관적 락의 처리량 문제를 깨자 stale이 등장했고, stale을 덜 중요한 곳으로 밀자 비동기 처리의 복잡성이 등장했다.
이 사례를 끝까지 따라온 사람은 또 한 가지를 보았을 것이다.
매 단계마다 사고의 모양은 달랐다.
그러나 그 모두를 관통하는 자세는 하나였다.
-
Cache로 옮긴 순간 — 정합성과 응답 속도라는 두 요구를 분리했다.
-
결제와 조회를 나눈 순간 — Minus의 영향을 덜 중요한 경로로 옮겼다.
-
비동기로 전환한 순간 — 강한 정합성의 자리에 약한 정합성과 보상을 두어 Minus를 완화했고, 동시에 문제 자체를 다시 정의했다.
이름을 굳이 붙이자면 Decouple, Shift, Mitigate, Reframe이라고 부를 수 있다.
그러나 이름이 본질은 아니다.
본질은 — tension을 식별하고, 의식적으로 다루어, 끝내 깨보려는 그 자세다.
우리는 결국 엔지니어다
엔지니어는 tension의 한가운데에 서서 일하는 사람이다.
어떤 도구를 손에 쥐든, 어떤 시대에 일하든 그 사실은 변하지 않는다.
망치에서 크레인으로 도구가 바뀌어도 다리를 놓는다는 본질이 바뀌지 않는 것처럼, 사람의 손끝에서 AI와의 협업으로 코드 생산 방식이 옮겨가도 현실 세계의 문제를 해결한다는 엔지니어의 본질은 그대로다.
오히려 AI Code Assistant 시대에 tension을 깨뜨리는 사고는 그 어느 때보다 큰 레버리지를 가질 수 있다.
코드 한 줄을 적는 시간은 짧아졌다.
한 발 떨어져 시스템을 보고, tension을 식별하고, 그것을 깨뜨릴 다음 선택을 설계하는 일에 절약된 시간과 정신을 의식적으로 쏟을 때 키보드의 시간이 줄어든 만큼 사고의 시간이 자란다.
도구의 변화는 종종 우리 자신에 대한 정의를 흔든다.
그러나 변한 것은 우리가 무엇을 하는가의 표면일 뿐, 우리가 누구인가의 본질이 아니다.
수단이 옮겨갔다고 목적이 옮겨가는 것은 아니다.
도구는 또 바뀔 것이다.
5년 후, 10년 후의 코드 생산 방식은 오늘과 또 다를 것이다.
그때마다 같은 고민이 반복될지도 모른다.
그 고민의 한가운데에서 한 가지만 잊지 않으면 된다.