우후죽순 등장하는 서비스들을 보고 있으면 묘한 감정이 든다. 반듯하고 깔끔하지만 천편일률적인 디자인과 사용 방식. 우리는 이런 결과물을 보며 흔히 “AI 같다”고 한다. 하지만 AI 같다는 느낌은 단순히 AI를 사용했다는 데서 오는 게 아니다. 서비스를 실물로 만들어 낼 때까지의 길고 긴 의사결정 과정에서, 그 선택의 길목마다 사람의 흔적이 느껴지지 않는 데서 온다.
엔지니어는 시스템적인 문제만 푼다고 생각하기 쉬운데, 엔지니어는 결국 세상의 문제를 푸는 사람이다. 같은 수학 공식이 모든 문제에 적용될 수 없듯, 문제마다 규모와 제약이 다르고 복잡성도 다르다.
결국 엔지니어는 무엇을 얻고 무엇을 포기할지 선택하는 역할을 가지고 있다.

취향은 단순히 엔지니어가 선호하는 코드 스타일이나 기술만을 뜻하지 않는다. 어떤 문제를 중요하게 보고, 복잡성을 어디에 둘 것이며, 어떤 트레이드오프를 감수할지 결정하는 기준을 말한다. 취향은 근거 없는 고집이 아니라, 반복된 실패와 기술적 고찰을 통해 압축된 판단 기준이다.
나에게 취향은 좋아하는 특정 기술의 목록이라기보다, 문제를 만났을 때 자연스럽게 떠오르는 질문들의 목록에 가깝다. 이 정도의 트래픽에 정말 이 구조가 필요한가. 이 복잡성을 지금 우리 팀이 감당할 수 있는가. 장애가 발생했을 때 원인을 설명할 수 있는가. 지금 얻는 편리함 때문에 나중에 어떤 비용을 지불하게 될까.
같은 문제를 보더라도 어떤 질문부터 떠올리는지는 엔지니어마다 다르다. 그리고 그 차이가 결국 선택의 차이를 만든다.
AI에게 시스템 아키텍처를 설계해 달라고 요청해 보면 꽤 그럴듯한 답을 받아볼 수 있다. API 게이트웨이를 두고, 서비스를 적절히 분리하고, Redis를 캐시로 사용하고, Kafka를 통해 이벤트를 전달하고, 쿠버네티스(Kubernetes) 위에서 서비스를 운영하는 구조를 제안할 수도 있다. 이런 답이 틀렸다고 말하는 것은 아니다.
다만 엔지니어링 결정은 일반적인 해답만으로 내려질 수 없다고 이야기하고 싶다.
다른 조직에서 검증된 아키텍처가 우리 조직에 꼭 맞는다는 보장은 없다. 수백 명의 엔지니어가 운영하는 시스템에서 좋은 구조가 다섯 명의 팀에서도 좋은 구조라는 보장 역시 없다.
맥락을 고려하지 않은 채 일반적인 해답을 적용하면, 실제 사용자의 요구와 데이터의 성격, 조직의 역량이라는 변수를 만나는 순간 쉽게 무너지기 시작한다. 풀려던 문제의 일부만 해결한 시스템의 토대 위에는 임시방편이 계속 쌓이고, 쉽게 걷어낼 수 없는 기술 부채가 남을 수 있다.
좋은 기술을 많이 가져다 쓴다고 좋은 시스템이 만들어지는 것이 아니라고 생각한다. 이런 생각을 하게 된 계기가 있다.
예전에 마이크로서비스 아키텍처(MSA)와 쿠버네티스를 사용하는 시스템을 보면서 이런 의문이 들었다. “서비스를 나누고 운영을 자동화하면, 시스템을 다루는 일도 그만큼 쉬워질까?” 각 서비스를 따로 개발하고 배포할 수 있다는 장점은 이해했다. 하지만 나뉜 서비스들이 함께 작동하도록 관리하는 일까지 생각하면, 무엇을 기준으로 이런 구조를 선택해야 하는지 궁금했다.
처음에는 그 기준을 명확한 공식으로 찾을 수 있을 것이라고 생각했다. 기술 블로그나 책을 더 읽으면 어떤 상황에서 서비스를 나누는 것이 유리하고, 언제부터 그 이점이 운영의 부담보다 커지는지 알 수 있을 것 같았다. 그러나 기술의 장단점을 아는 것만으로는 눈앞의 시스템에 필요한 선택을 판단하기 어려웠다. 팀의 규모와 운영 역량, 해결해야 하는 문제가 달랐기 때문이다.
당시 한 선배 개발자와 이야기를 나누다가, 추상화 수준이 높아진다고 복잡성이 사라지는 것은 아니고 보이지 않는 곳으로 옮겨갈 뿐이라는 말을 들었다. 쿠버네티스가 배포와 장애 복구를 편리하게 만들어준다고 해서 그 아래의 동작까지 단순해지는 것은 아니었다. 평소에는 신경 쓰지 않아도 되는 부분이 늘어나지만, 그곳에서 문제가 생기면 다시 들여다볼 수 있어야 했다.
서비스를 나누는 선택에도 비슷한 절충이 있었다. 실제로 여러 서비스에 흩어진 로그를 뒤지며 간단한 버그 하나를 잡는 데 하루를 쓴 적이 있었다. 문제를 더 쉽게 추적하려고 OpenTelemetry와 Jaeger 같은 도구를 도입하기도 했다. 각 서비스를 따로 다루기는 편해졌지만, 서비스 사이에서 무슨 일이 일어나는지 파악하는 일은 별도로 필요했다.
그 경험 이후 나는 “MSA가 좋은가, 모놀리식이 좋은가?”라는 질문을 다르게 보기 시작했다. 중요한 것은 어느 쪽이 더 좋은지가 아니라, 지금 해결해야 하는 문제가 무엇이며 그 선택으로 생기는 부담을 우리가 감당할 수 있는지였다. 기술을 고르는 일은 장점의 개수를 세는 것이 아니라, 무엇을 쉽게 만들고 무엇을 직접 책임질지 정하는 일이었다.
처음부터 자신의 엔지니어링 취향을 구축하기란 쉽지 않다. 좋다고 알려진 설계와 추상화를 적용했는데 구조가 복잡해지거나 코드를 이해하기 어렵게 만들기도 한다. 편리하다고 생각했던 데이터 접근 방식이 데이터 규모가 커지자 예상하지 못한 성능 문제를 만들기도 한다.
나 역시 저장 프로시저(Stored Procedure) 기반 시스템을 ORM으로 전환하며 비슷한 경험을 했다.
처음 ORM을 배우면 엔티티 사이의 관계를 자연스럽게 객체의 연관 관계로 표현하고 싶어진다. @OneToMany와 @ManyToOne 같은 관계를 잘 정의하면 객체 지향적으로 깔끔한 모델을 만들 수 있을 것처럼 보인다.
그런데 내가 작업하려고 했던 데이터베이스는 처음부터 ORM을 고려해 설계된 구조가 아니었다. 이미 많은 테이블이 복잡한 상태로 만들어져 있었고, 이 관계를 ORM으로 표현하는 과정에서 조회 방식도 다시 살펴야 했다. 연관 관계를 어떻게 로딩하느냐에 따라 불필요한 조인(JOIN)이 발생하거나, 목록을 조회한 뒤 각 항목의 연관 데이터를 가져오는 쿼리가 반복되는 N+1 문제가 생길 수 있었기 때문이다. 결국 모든 관계를 예쁘게 연결하는 대신, 일부 구간에서는 연관 관계를 의도적으로 만들지 않고 식별자를 기반으로 데이터를 조회하는 방식을 택했다.
또 어떤 조회에서는 여러 쿼리로 나눠 데이터를 가져오는 것이 나았고, 어떤 경우에는 오히려 DB에서 JOIN을 수행하는 것이 효율적이었다.
이 과정에서 재밌는 점은 어느 한쪽이 항상 옳지 않았다는 것이다. 단계적으로 조회하면 불필요한 데이터 로딩을 줄일 수 있지만 쿼리 횟수가 늘어난다. 데이터를 한꺼번에 가져와 애플리케이션에서 매핑하면 유연하지만 데이터가 커졌을 때 메모리와 네트워크 비용이 커진다.
애플리케이션에서 Java Stream으로 후처리하면 코드는 읽기 쉬워질 수 있지만, DB에서 거를 수 있는 데이터까지 가져오거나 불필요한 연산을 반복한다면 대용량 데이터에서는 부담이 커질 수 있다. 결국 또 같은 질문으로 되돌아왔다. “그래서 지금 이 문제에서는 무엇을 포기하고 무엇을 얻을 것인가?”
이런 경험이 하나둘 쌓이면 자신만의 기준이 생긴다. 누군가는 불필요한 추상화를 경계하게 되고, 누군가는 데이터 왕복 횟수에 민감해진다. 또, 누군가는 장애가 발생했을 때 설명할 수 없는 시스템을 불편해하고, 누군가는 운영할 사람이 없는 기술을 쉽게 도입하지 않는다.
겉으로만 보면 이런 모습은 개인적인 고집처럼 보일 수도 있다. 그 판단 뒤에는 나름의 이유가 있을 수 있다. 이전에 너무 많은 추상화 때문에 디버깅이 어려웠거나, 데이터 왕복이 병목이 되어 밤새 성능을 잡아본 적이 있거나, 아무도 운영할 수 없는 기술을 도입했다가 낭패를 봤던 경험이 있을 수 있다.
취향은 결국 그런 경험의 압축본이다.
하지만 취향이 고집이 되어서는 안 된다. “나는 MSA를 싫어한다”는 말만으로는 판단의 근거를 알 수 없다. “우리 팀의 규모와 운영 역량에서는 서비스 분리로 얻는 이점보다 운영 복잡성이 더 크기 때문에 지금은 모놀리식을 선택하겠다”는 설명에는 선택의 조건이 드러난다.
좋은 취향은 자신만의 기준을 가지고 있지만 새로운 근거 앞에서는 바뀔 수 있다. 취향은 답을 미리 정해두는 것이 아니라, 답을 고를 때 무엇을 살펴볼 것인지 알려주는 기준이다.
한번 겪은 실패는 오래 남는다. 분산된 서비스를 운영하느라 고생했다면 다음에는 서비스 분리부터 경계할 수 있다. ORM을 사용하면서 성능 문제를 겪었다면 연관 관계를 최대한 없애는 것이 안전하다고 생각할 수도 있다. 경험에서 얻은 경계심은 유용하지만, 그때의 조건을 함께 기억하지 않으면 다음 문제를 잘못 판단할 수 있다.
서비스가 늘어났을 때의 부담과 하나의 서비스가 너무 커졌을 때의 부담은 다르다. 조회가 느렸던 이유도 ORM 자체가 아니라 데이터를 가져오는 방식이나 필요한 양보다 많이 읽은 데 있을 수 있다. 이전에 피했던 방법이 지금의 팀과 데이터 규모에서는 더 적절할 수도 있다.
그래서 취향을 설명할 때는 어떤 비용이 걱정되는지, 그 비용이 지금도 발생할 것인지, 무엇을 확인하면 생각을 바꿀 수 있는지 말할 수 있어야 한다. 익숙해서 구현하기 쉬운 것과 지금 문제에 적합한 것은 다를 수 있기 때문이다.
팀 안에서 서로 다른 취향이 만났을 때도 마찬가지다. 한 사람은 배포의 독립성을 중요하게 보고, 다른 사람은 장애를 추적하기 쉬운 구조를 중요하게 볼 수 있다. 어느 쪽이 더 세련된 설계인지 따지기 전에 각자 무엇을 지키려는지 확인해야 한다. 그래야 개인적인 선호를 넘어 실제로 감수해야 할 비용을 함께 검토할 수 있다.
AI는 수많은 사례에서 학습한 패턴을 바탕으로 그럴듯한 답을 만들어낸다. 하지만 그 답은 결론이 아니라 후보일 뿐이다. 엔지니어는 현재의 문제와 제약을 파악하고 그 조건에 맞는 결정을 내려야 한다. 자신의 판단 기준이 없다면 AI의 답을 평가하기보다 일반적인 선택을 그대로 받아들이기 쉽다.
반대로 판단 기준이 있다면 AI는 훨씬 강력한 도구가 된다. 하나의 답을 받아들이는 대신 여러 대안을 요구할 수 있다.
“왜 Kafka여야 하지?”, “트래픽이 이 정도라면 더 단순한 방법은 없을까?”, “이 구조에서 가장 큰 운영 비용은 무엇일까?”
이렇게 되물으면 처음 제안에서 빠져 있던 조건을 드러내고 다른 선택지를 비교할 수 있다. AI에게 판단을 맡기는 것이 아니라 AI를 이용해 판단의 재료를 더 많이 얻는 것이다. 물론 답변이 구체적이라는 이유만으로 검증이 끝나는 것은 아니다. 실제 데이터와 운영 환경에서도 그 설명이 맞는지 확인하는 일은 남는다.
다시 돌아가보자. 왜 요즘 새롭게 등장하는 서비스들은 종종 비슷하게 느껴질까. 그것은 단순히 AI를 사용했기 때문만은 아닐 것이다.
왜 이 기능을 만들었는지, 왜 이런 식으로 동작하는지, 왜 이 기술을 선택했는지에 대한 고찰이 없는 서비스에서는 사람의 흔적을 느끼기 어렵다. 반대로 누군가 오랫동안 고민하고 선택한 결과물에는 흔적이 남는다.
어떤 기능은 과감하게 빠져 있고, 어떤 부분은 유난히 단순하며, 어떤 곳에는 필요 이상으로 보일 만큼 많은 안전장치가 있다. 그 선택의 이유를 따라가다 보면 결국 그 서비스를 만든 사람들이 무엇을 중요하게 생각했는지 보이기 시작한다.
그렇다고 남들과 다른 기술이나 독특한 구조를 써야 취향이 생기는 것은 아니다. 모두가 사용하는 평범한 방법이라도 지금의 문제에 적합하다는 근거가 있다면 충분히 좋은 선택이다. 반대로 새로운 방법이 필요하다면 익숙한 취향을 수정할 수 있어야 한다.
AI를 통해 코드와 시제품, 여러 설계안을 빠르게 얻을 수 있는 시대다. 그럴수록 무엇을 만들 것인지, 무엇을 만들지 않을 것인지 결정하는 능력은 더 중요해진다고 생각한다.
취향은 단순히 무엇을 좋아한다는 것을 의미하지 않는다. 문제를 어떻게 분해하고 해결할 것인지, 그 과정에서 무엇을 얻고 잃을 것인지에 대한 판단 기준이다. 그 기준은 실패하고, 운영하고, 다시 선택하는 과정에서 만들어진다.
평균적인 해답을 누구나 얻을 수 있는 시대다. 그래서 나는 엔지니어의 취향이 강력한 무기가 될 수 있다고 생각한다.
ⓒ요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.