티스토리 뷰

프롤로그: 출항하지 못한 배를 위한 변명

어떤 프로젝트는 단순한 과업이 아니라, 하나의 세계를 창조하는 일과 같다. 백지 위에 도시를 계획하고, 강줄기의 방향을 정하며, 보이지 않는 곳에 전기와 수도관을 묻는다. 개발자에게 코드란 그 세계를 지탱하는 물리 법칙이자, 사용자들이 거닐게 될 거리의 이름이다. 내가 몸담았던 'AI 디지털 교과서' 프로젝트는 그런 의미에서 한 국가의 미래 세대를 위한 새로운 대륙을 발견하는 일과 다르지 않았다. 낡은 칠판과 분필 가루의 시대를 넘어, 모든 학생이 자신만의 속도와 지도를 가지고 지식의 바다를 항해하게 하자는 원대한 비전. 그 비전의 무게는 개발자의 어깨에 묵직한 사명감으로 내려앉았다.

 

그 거대한 항해의 시작점에서 나는, 솔직히 말해, 외딴섬에 가까웠다. 이전 팀에서 나는 유능함보다는 유별남으로 알려졌고, 물리적으로도, 심리적으로도 동료들과 멀리 떨어진 채 나만의 작은 프로젝트에 몰두하고 있었다. 스스로를 고립시키며 이직을 고민하던 어느 날, 회사의 '드림팀'이라 불리는 신사업부로의 발령은 난파 직전의 내게 던져진 구원의 동아줄이자, 새로운 대륙으로 떠나는 탐험선의 승선권처럼 느껴졌다. 어둠침침한 레거시 시스템의 기관실을 벗어나, 반짝이는 새 배의 갑판에 올라서게 된 것이다.

 

하지만 모든 위대한 서사에는 회의론자라는 감초가 빠지지 않는 법이다. 우리의 배가 출항 준비를 하는 동안, 항구에는 여러 종류의 수군거림이 떠다녔다. 아이들을 스마트 기기라는 판도라의 상자로부터 지키려는 부모들의 걱정 어린 눈빛, 그리고 교육의 본질은 기술이 아닌 인간의 상호작용에 있다며 학습 효과의 저하를 우려하는 전문가들의 날카로운 지적이 그것이다. 나 역시 그들의 비판이 타당한 근거를 갖고 있음을 부정하지 않았다. 기술은 만병통치약이 아니며, 때로는 득보다 실이 많을 수 있다는 사실을 알고 있었기 때문이다.

 

더 위험한 것은 눈에 보이는 파도가 아니었다. 우리의 항해는 순수한 기술 탐사가 아니라, '정부의 공약'이라는 깃발을 높이 내건, 고도의 정치적 원정이었다. 이는 정권이라는 바람의 방향이 바뀌거나, 국회라는 예측 불가능한 해협을 통과하지 못하면 언제든 좌초될 수 있음을 의미했다. 훗날 현실이 된 이 불길한 예감은, 당시에는 그저 안개 속에 희미하게 보이는 위협 정도로만 여겨졌다.

 

이 글은 결국 항구에 영원히 정박하게 된 그 배에 대한 기록이다. 배의 용골(아키텍처)은 어떻게 세워졌고, 엔진(핵심 기술)은 어떤 원리로 힘차게 고동쳤으며, 예측 불가능한 돌발 변수(요구사항 변경)와 거친 파도(기술적 난제)를 어떻게 헤쳐 나갔는지에 대한 기술적 항해 일지다. 동시에 이는 완벽하게 건조된 배가 왜 출항조차 하지 못했는지에 대한 해부학 보고서이기도 하다. 비록 우리의 배는 신대륙의 흙을 밟지 못했지만, 그 과정에서 우리가 그린 항해도는 기술이라는 나침반과 현실이라는 지도 사이에서 길을 찾는 또 다른 항해자들에게 의미 있는 참고자료가 되리라 믿는다. 이것은 실패에 대한 구차한 변명이 아니라, 실패를 통해 얻은 값진 교훈에 대한 겸허한 고백이다.

 

1장: 백지 위에 그리는 설계도 – 모놀리식과 MSA 사이의 줄다리기

새로운 프로젝트의 시작은 창세기의 첫 장과도 같다. 태초에 혼돈과 공허뿐이었듯, 내 앞에는 거대한 백지와 '전국의 모든 학생을 위한 AI 기반 교육 플랫폼'이라는 막막한 목표만이 놓여 있었다. 나를 이끌어 줄 경험 많은 항해사, 즉 직속 시니어는 정부 관계자 및 외부 협력사들과의 외교전이라는 더 큰 파도를 막기 위해 선교(船橋)에서 내려올 틈이 없었다. 결국 배의 심장부인 기관실에는 이제 막 견습을 마친 패기 넘치는 후배 개발자와 나, 단둘이 남겨졌다. 우리는 이 거대한 배의 뼈대를 어떻게 세울지, 동력은 어디서 얻을지, 수많은 구획을 어떻게 나눌지를 결정해야 했다. 이는 갓 면허를 딴 운전사에게 대륙 횡단 트레일러의 열쇠를 건넨 것과 같은, 아찔한 책임감과 동시에 평생에 한 번 올까 말까 한 기회였다.

 

소프트웨어 아키텍처를 설계하는 일은 도시 계획과 유사하다. 한번 도로를 내고 상하수도관을 묻으면, 나중에 도시가 아무리 커져도 그 기본 골격을 바꾸기란 거의 불가능하다. 우리가 내리는 첫 결정은 앞으로 수많은 개발자가 따르게 될 일종의 헌법 조항과도 같았기에, 나는 신중해야만 했다. 가장 먼저 부딪힌 근본적인 질문은 '우리는 어떤 형태의 도시를 건설할 것인가?'였다. 이는 기술의 언어로 '모놀리식(Monolithic)으로 갈 것인가, 마이크로서비스(MSA, Microservice Architecture)로 갈 것인가'의 문제로 번역된다.

 

모놀리식 아키텍처는 마치 잘 계획된 하나의 거대한 건물, 가령 뉴욕의 그랜드 센트럴 터미널과 같다. 매표소, 식당, 상점, 승강장 등 모든 기능이 하나의 견고한 구조물 안에 유기적으로 연결되어 있다. 모든 것이 한 지붕 아래 있기에 내부 통신은 매우 빠르고 효율적이며, 건물을 짓는 초기 단계에서는 설계가 비교적 단순하고 직관적이다. 길을 잃을 염려도 적다. 나는 우리가 마주한 교육 서비스의 특성을 꼼꼼히 분석했다. 사용자의 트래픽은 등하교 시간과 정해진 수업 시간표에 따라 거대한 해일처럼 밀려왔다가, 그 외의 시간에는 잔잔한 호수처럼 평온해질 것이 분명했다. 이는 예측 가능하고 통제된 트래픽 패턴이었다. 이런 환경이라면, 굳이 여러 개의 건물을 짓고 그 사이를 잇는 복잡한 도로망을 관리하느니, 차라리 모든 것을 감당할 수 있는 튼튼한 요새 같은 단일 건물, 즉 잘 설계된 모놀리식 아키텍처가 개발 속도나 운영 효율성 측면에서 훨씬 합리적이라는 결론에 이르렀다. 그것은 마치 특정 목적을 위해 완벽하게 제작된 스위스 군용 칼과 같았다. 조금 투박해 보여도, 필요한 모든 기능을 안정적으로 제공하는 신뢰의 상징. 이것이 데이터에 기반한 나의 순수한 기술적 판단이었다.

모놀리식 아키텍처는 뉴욕의 그랜드 센트럴 터미널과 같다.

 

하지만 세상에는 다른 종류의 건축 철학도 존재했다. 바로 MSA, 마이크로서비스 아키텍처다. 이는 하나의 거대 건물을 짓는 대신, 각기 다른 기능을 가진 작은 전문 건물들로 이루어진 현대적인 도시를 건설하는 방식과 같다. 금융가, 쇼핑 지구, 주거 단지가 독립적으로 존재하되, 잘 닦인 도로와 지하철(API)을 통해 서로 통신하는 모습이다. 이 도시의 가장 큰 장점은 유연성과 확장성이다. 쇼핑 지구가 붐빈다고 해서 도시 전체를 재건축할 필요 없이, 쇼핑몰만 몇 개 더 지으면 그만이다. 한 건물에 불이 나도 도시 전체가 마비되지 않는다는 '장애 격리'의 미덕도 갖추고 있다. 미래의 불확실한 요구사항에 대응하고, 각 서비스 팀이 독립적으로 빠르게 움직일 수 있다는 점에서 MSA는 의심할 여지없이 매력적인 최신 트렌드였다.

 

문제는 이 프로젝트의 발주서에 있었다. 고객, 즉 정부 부처의 요구사항 명세서에는 'MSA 구축'이라는 단어가 마치 종교적 신념처럼 굵은 글씨로 박혀 있었다. 기술적 합리성이나 효율성에 대한 논의는 끼어들 틈이 없었다. 그것은 선택지가 아니라, 반드시 따라야 할 계시와도 같았다. 나는 마치 최고의 프랑스 요리사에게 세상에서 가장 섬세한 수플레를 만들되, 도구는 오직 이 커다란 쇠망치만 사용해야 한다는 주문을 받은 듯한 기분이었다. 기술자의 양심은 모놀리식이라는 잘 벼려진 칼을 가리키고 있었지만, 비즈니스라는 거대한 힘은 MSA라는 낯설고 복잡한 조립식 도구 세트를 강요하고 있었다.

 

결국, 우리는 외줄타기를 시작해야 했다. 기술적 신념과 비즈니스 현실이라는 두 개의 절벽 사이에서 균형을 잡아야 하는 것, 어쩌면 이것이 모든 아키텍트의 숙명일지도 모른다. 우리는 MSA라는 목적지를 향해 돛을 올리기로 결정했다. 하지만 이 결정은 맹목적인 추종이 아니었다. '만약 우리가 MSA라는 도시를 건설해야만 한다면, 혼란스러운 난개발이 아닌, 명확한 구획과 원칙을 가진 계획도시를 만들자.' 이것이 우리의 새로운 목표가 되었다. 나는 도메인 주도 설계(DDD)라는 도시 계획 이론을 나침반 삼아, 각 서비스라는 구역의 경계를 명확히 긋고, 그들이 서로 어떻게 소통하고 협력해야 할지에 대한 상세한 도시 조례를 만들기 시작했다.

 

그렇게 나의 백지는, 순수한 기술적 이상향이 아닌, 현실과의 치열한 타협과 고민이 담긴 복잡한 설계도로 서서히 채워져 나갔다. 이것은 항해의 시작에 불과했다. 이제 우리는 이 설계도를 들고, 실제 벽돌을 쌓고 배관을 연결하는 고된 노동의 단계로 나아가야 했다.

 

2장: 엔진실의 열기 – CQRS, Kafka, 그리고 창의적 타협

본격적인 개발 단계에 접어들자, 엔진실은 뜨거운 열기로 가득 찼다. 우리는 도메인 주도 설계(DDD)라는 정교한 설계 원칙에 따라, 거대한 시스템이라는 선체를 여러 개의 독립된 구획으로 나누는 작업부터 시작했다. 학생의 학습 활동 데이터가 오가는 '교과학습 서비스'는 배의 심장부인 엔진실, 교사의 성적 및 출결 관리를 책임지는 '학습관리(LMS) 서비스'는 모든 것을 통제하는 조타실, 그리고 교과서와 같은 핵심 콘텐츠를 저장하고 배포하는 '콘텐츠 관리 서비스'는 배의 모든 자원을 보관하는 거대한 화물칸과 같았다. 이처럼 각자의 역할이 명확한 작은 배(마이크로서비스)들이 탄생했고, 우리는 이 분산된 함대를 지휘하기 위해 'API 게이트웨이'라는 이름의 관제탑을 세웠다. 모든 외부 선박(클라이언트 요청)은 반드시 이 관제탑을 거쳐야만 했고, 관제탑은 요청의 종류를 파악해 가장 적합한 배로 안내하는 역할을 맡았다.

 

명령과 조회, 그 신성한 분리 (CQRS)

엔진실이 한창 뜨겁게 달아오르던 어느 날, 갑판 위에서 쩌렁쩌렁한 확성기 소리가 들려왔다. "고용량 트래픽 처리 인증을 받아야 한다!"는, 마치 전시 동원령과도 같은 갑작스러운 명령이었다. 사업의 성패를 결정짓는 상부에서, 최종 평가를 통과하기 위한 기술적 증명이 필요했던 모양이다. 기술적 관점에서 이는 명백한 오버 엔지니어링이었다. 아직 승객도 태우지 않은 유람선에 항공모함급 엔진을 달라는 격이었으니까. 하지만 이 불합리해 보이는 요구는, 역설적으로 우리가 설계한 아키텍처의 기술적 깊이를 증명할 절호의 기회이기도 했다. 여기서 우리는 숨겨두었던 비장의 무기, CQRS(Command Query Responsibility Segregation, 명령과 조회의 책임 분리) 패턴을 꺼내 들었다.

 

CQRS를 이해하기 위해 잠시 분주한 레스토랑 주방을 상상해보자. 전통적인 시스템은 한 명의 만능 요리사가 주문을 받고(쓰기, Command), 요리법을 찾아보며(읽기, Query), 실제 요리를 하고, 완성된 음식을 내어주는 모든 일을 처리하는 방식과 같다. 손님이 한두 명일 때는 문제가 없지만, 저녁 피크타임이 되면 이 만능 요리사는 병목 현상의 주범이 된다. 주문을 받느라 요리를 못 하고, 요리를 하느라 다음 주문을 받지 못하는 아수라장이 펼쳐진다. CQRS는 이 주방의 역할을 극적으로 분리하는 혁신적인 아이디어다. '주문 받기'라는 쓰기(Command) 작업은 전적으로 카운터의 점원에게 맡기고, '요리하기'와 관련된 읽기(Query) 작업, 즉 주문 확인과 조리 등은 주방 안의 요리사들만 담당하게 하는 것이다. 

주방 안의 요리사들은 직접 주문을 받지 않는다.

 

우리는 이 원리를 PostgreSQL 데이터베이스에 적용했다. 모든 데이터 변경 작업, 즉 학생의 답안 제출이나 교사의 평가 입력 같은 '쓰기(Command)' 요청은 오직 하나의 견고한 '주방장 DB(Primary DB)'에서만 처리하도록 했다. 반면, 수많은 학생과 교사가 동시에 데이터를 조회하는 '읽기(Query)' 요청은, 주방장 DB의 데이터를 실시간으로 복제한 여러 개의 '보조 요리사 DB(Replica DBs)'가 나누어 처리하도록 했다. 이로써 답안지를 제출하는 한 명의 학생 때문에, 수천 명의 다른 학생이 문제지를 읽지 못하는 재앙을 원천적으로 차단할 수 있었다. 우리는 성능과 안정성이라는 두 마리 토끼를 모두 잡으며, 갑작스러운 상부의 명령을 기술적 성숙도를 뽐내는 무대로 바꿔버렸다.

 

멈추지 않는 강, 이벤트의 연대기 (Apache Kafka)

엔진실 한편에서는 또 다른 종류의 작업이 조용하지만 쉴 새 없이 이루어지고 있었다. 학생의 모든 학습 로그를 기록하거나, 성적 처리가 끝났음을 알리는 알림을 발송하는 일처럼, 굳이 실시간으로 "처리 완료!"를 외칠 필요가 없는 작업들이었다. 이러한 작업들은 즉각적인 응답보다는 '언젠가는 반드시, 그리고 절대로 유실 없이' 처리되는 것이 더 중요했다. 이런 비동기(Asynchronous) 세상의 질서를 잡기 위해, 우리는 Apache Kafka라는 거대한 중앙 우체국 시스템을 도입했다.

 

하지만 Kafka를 단순한 우체국이나 메시지 큐(Message Queue)로 생각한다면, 그것은 고래를 커다란 물고기 정도로 여기는 것과 같다. 전통적인 메시지 큐는 편지를 한 번 배달하고 나면 우체통에서 사라지는, 소비 지향적인 모델에 가깝다. 반면, Kafka는 모든 사건(Event)을 시간 순서대로 기록하고 영원히 보관할 수 있는, 분산 이벤트 스트리밍 플랫폼(Distributed Event Streaming Platform) 이다. 그것은 우체국이라기보다, 인류의 모든 역사를 발생 순서대로 기록하는 거대한 연대기 혹은 멈추지 않고 흐르는 강물과 같다. 한번 강에 흘려보낸 것은 사라지지 않고 계속해서 하류로 흘러가며, 누구든 강가에 서서 그 물을 떠다 볼 수 있다.

 

Kafka의 세계에서 모든 데이터는 이벤트(Event) 라는 형태로 존재한다. '오후 2시 10분, 학생 A가 3번 문제를 클릭함'이라는 하나의 사건이 바로 이벤트다. 이러한 이벤트들은 관련 있는 것끼리 묶여 토픽(Topic) 이라는 이름의 채널 혹은 강줄기를 형성한다. 예를 들어 '학습활동_로그'라는 토픽에는 모든 학생의 클릭, 스크롤, 답안 입력 이벤트들이 차곡차곡 흘러간다. 이 토픽이라는 강줄기는 다시 파티션(Partition) 이라는 여러 개의 작은 샛강으로 나뉜다. 이는 엄청난 양의 강물이 한꺼번에 밀려올 때, 여러 물길로 나누어 병목 현상 없이 빠르게 흘려보내기 위함이다. 덕분에 Kafka는 초당 수백만 개의 이벤트를 처리할 수 있는 괴물 같은 처리량을 자랑한다.

 

이벤트의 생산자(Producer)는 강 상류에서 물건을 띄워 보내는 사람과 같다. 우리의 '교과학습 서비스'는 학생의 활동이 발생할 때마다 해당 이벤트를 만들어 '학습활동_로그' 토픽으로 흘려보내는 생산자였다. 이벤트의 소비자(Consumer)는 강 하류에서 필요한 물건을 건져 올리는 사람이다. '학습 분석 AI 서비스'는 이 토픽의 이벤트를 가져가 학생의 학습 패턴을 분석했고, '학부모 알림 서비스'는 특정 이벤트(예: 시험 완료)를 감지하여 알림을 발송하는 소비자였다. 중요한 것은, 소비자가 이벤트를 가져가더라도 강물 속 이벤트는 사라지지 않는다는 점이다. 이는 마치 역사책을 누군가 읽는다고 해서 그 내용이 지워지지 않는 것과 같다. 덕분에 새로운 소비자(예: '오답노트 자동 생성 서비스')가 나중에 생겨나더라도, 처음부터 모든 역사를 다시 읽으며 필요한 작업을 수행할 수 있는 놀라운 유연성을 제공한다.

 

이 방식의 가장 큰 아름다움은 '느슨한 결합(Loose Coupling)' 이라는 개념에 있다. 생산자와 소비자는 서로의 존재를 전혀 알 필요가 없다. 그들은 오직 Kafka라는 강을 매개로 소통할 뿐이다. 특정 소비자 서비스가 잠시 아파서 드러눕거나 시스템 업데이트를 위해 잠시 멈추더라도, 생산자는 아랑곳하지 않고 계속해서 이벤트를 강에 흘려보낸다. 이벤트들은 Kafka라는 안전한 저장소에 차곡차곡 쌓여 있다가, 소비자가 복귀하면 자신이 마지막으로 읽었던 지점부터 다시 이벤트를 처리하기 시작한다. 덕분에 시스템의 일부에 장애가 발생하더라도 전체 시스템이 멈추는 일 없이, 각자의 속도에 맞춰 탄력적으로 운영될 수 있었다. Kafka는 우리 함대 내의 각 함선들이 서로 직접 소리쳐 통신하는 대신, 모두가 공유하는 안전하고 신뢰할 수 있는 방송 채널을 통해 소통하게 만든, 마이크로서비스 아키텍처의 신경계와도 같은 존재였다.

 

데이터로 빚은 유령, 실시간의 재구성 (WebSocket)

허나 이 모든 기술적 도전 중에서도, 가장 정교한 창의력을 요구했던 과제는 단연 '교사의 학생 화면 실시간 제어' 기능이었다. 기획서에 명시된 이 한 줄의 요구는, 기술적으로 번역하면 '모든 학생의 컴퓨터 화면을 실시간 영상으로 교사에게 스트리밍하라'는, 사실상 불가능에 가까운 요구였다. 이는 교실의 모든 학생에게 개인용 방송 중계차를 한 대씩 붙여주는 것과 같은, 물리적으로 불가능한 수준의 네트워크 자원을 소모하는 일이었다. 수많은 반론과 토론의 소용돌이 속에서, 나는 한 걸음 물러서 문제의 본질을 파고들었다. 교사에게 진정으로 필요한 것은 학생의 마우스 커서 움직임 하나하나를 픽셀 단위로 감시하는 전지전능한 통제(Control)인가, 아니면 학생이 지금 무엇을 하고 있는지, 혹시 학습의 길 위에서 헤매고 있지는 않은지를 파악하는 섬세한 인지(Awareness)인가.

 

그 해답은 '제어'가 아닌 '인지'에 있었다. 이 깨달음은 우리로 하여금 완전히 새로운 길을 고안하게 했다. 우리는 발상을 전환하여, 콘서트 실황을 통째로 스트리밍하는 대신, 악보를 실시간으로 전송하는 방식을 택했다. 이 악보 전송을 위한 특별한 통로가 바로 WebSocket 기술이었다. 전통적인 웹 통신(HTTP)은 마치 편지를 주고받는 것과 같다. 클라이언트가 서버에게 "이 정보 좀 주세요"라고 요청(Request) 편지를 보내면, 서버는 그에 대한 응답(Response) 편지를 보내고 둘 사이의 연결은 끊어진다. 실시간 채팅처럼 계속해서 대화를 이어가려면, 1초에 수십 번씩 "새로운 메시지 있나요?"라는 편지를 보내야 하는, 매우 비효율적인 방식(Polling)을 사용해야 한다.

콘서트 실황을 실시간 전송하는 대신 악보를 복사해서 나눠 준다면 데이터 비용이 획기적으로 줄어든다.

 

반면, WebSocket은 편지가 아닌, 클라이언트와 서버 사이에 한번 연결되면 계속 열려 있는 전용 전화선을 개설하는 것과 같다. 이 전화선은 전이중(Full-duplex) 통신을 지원하여, 양쪽 모두가 원할 때 언제든지 상대방에게 말을 걸 수 있다. 서버는 클라이언트의 요청이 없더라도 새로운 데이터가 생기면 즉시 클라이언트에게 "새 소식이야!"라며 데이터를 밀어 넣어(Push)줄 수 있다. 이 방식은 불필요한 요청-응답의 반복을 없애고, 매우 낮은 지연 시간(Low Latency)으로 실시간에 가까운 데이터 교환을 가능하게 한다.

 

우리는 이 WebSocket이라는 전화선을 통해, 학생의 화면 픽셀 데이터를 보내는 것이 아니라, 학생의 의미 있는 '행동'—문제 클릭, 답안 입력, 스크롤 이동 등—만을 가벼운 텍스트 기반의 JSON 데이터로 구조화하여 전송했다. 교사의 클라이언트는 이렇게 전송된 '악보'를 받아, 학생의 화면을 눈앞에서 그대로 '연주'해내는 정교한 오케스트라의 역할을 수행했다. '오후 3시 15분, 학생 B가 5번 문제의 빈칸에 숫자 45 입력'이라는 JSON 데이터는 단순한 텍스트 로그가 아니라, 교사 화면의 5번 문제 빈칸에 '45'라는 숫자를 그려 넣으라는 명확한 지시(Instruction)로 해석되었다. 그 결과, 교사는 막대한 자원 소모 없이도 학생의 화면을 데이터로 재구성된 유령(a data-driven doppelgänger)처럼 실시간으로 목도할 수 있게 된 것이다. 이 창의적 타협은 불가능의 영역에 있던 비즈니스 요구사항과 기술적 현실의 간극을 메운, 우리 팀의 지성이 가장 빛났던 순간으로 기록될 만했다. 그렇게 엔진실의 열기는 단순한 기계의 소음을 넘어, 복잡한 문제들을 풀어내는 지성의 협주곡으로 승화하고 있었다.

 

3장: 갑판 위의 사람들 – 데이터라는 공용어, 문서라는 유산

모든 공학적 도전의 이면에는 언제나 인간이라는 가장 예측 불가능한 변수가 존재한다. 견고한 강철도 지치지 않는 증기기관도 결국은 그것을 다루는 사람의 손에 의해 그 운명이 결정되기 때문이다. 기술만큼이나, 어쩌면 그보다 더 지난했던 것은 바로 갑판 위의 사람들과의 관계였다. 프로젝트라는 배가 순항하기 시작할 무렵, 우리 배에는 나보다 10년은 족히 많은 바닷바람을 맞으며 잔뼈가 굵은 베테랑 선원들, 즉 시니어 프리랜서 개발자들이 다수 합류했다. 젊은 항해사가 그린 설계도를 들고 그들에게 다가가 때로는 방향을 제시하고 업무를 요청해야 하는 상황은, 마치 갓 임관한 초임 장교가 백전노장들로 가득한 부대를 지휘해야 하는 것과 같은 아득한 심리적 해협을 건너는 일이었다. 나의 서툰 소통 방식과 증명해야 한다는 조바심에서 비롯된 의욕 과잉이 때로는 오해를 사기도 했고, 이전 조직에서 느꼈던 고립의 망령이 희미하게 되살아나는 듯한 순간도 있었다.

 

그 아슬아슬한 관계의 외줄 위에서 나를 구원한 것은 화려한 언변이나 직급이 주는 공허한 권위가 아니었다. 그것은 바로 누구도 부정할 수 없는 객관적 실체, 데이터와 문서였다. 나는 "코드는 끊임없이 변하지만, 잘 쓰인 문서는 역사가 되어 남는다"는 신념을 가지고 있었다. 이 프로젝트에서 문서화는 단순한 기록 행위가 아니었다. 그것은 서로 다른 언어를 사용하는 여러 독립 왕국을 잇는 거대한 외교 시스템이자, 이 복잡한 프로젝트의 유일한 '단일 진실 공급원(Single Source of Truth)'이었다. 우리는 Confluence를 이 외교 시스템의 수도로 삼았다.

이 수도에서 발행된 문서들은 각기 다른 목적을 가지고 여러 왕국으로 퍼져나갔다. 시스템 아키텍처 다이어그램과 데이터베이스의 ERD는 우리 개발팀이라는 왕국 내에서 통용되는 헌법이자 정밀한 지도였다. 이는 신규 팀원이 빠르게 항로를 파악하게 하는 안내서였고, 베테랑 개발자들과 기술적 합의를 이끌어내는 근거 자료였다. 회의 테이블에서, 나는 나의 짧은 경험을 내세우는 대신 벤치마크 테스트가 보여주는 냉정한 성능 수치를 펼쳐 보였다. "A안은 B안에 비해 응답 시간이 평균 30ms 단축되고, DB 커넥션 사용률을 15% 감소시킵니다"라는 객관적 언어는, 주관적 선호를 넘어선 합리적 의사결정의 기반이 되었다.

 

하지만 이 문서들의 진정한 힘은 왕국의 경계를 넘어설 때 발휘되었다. 상세하게 작성된 API 명세서는 '인공지능 연구팀'이라는 이웃 왕국과의 공식적인 통상 조약이었다. 그들은 이 조약문을 통해 우리 시스템에서 어떤 데이터를, 어떤 형식으로 주고받을 수 있는지를 명확히 이해하고, 자신들의 정교한 AI 모델을 우리 배에 매끄럽게 통합할 수 있었다. 서버와 네트워크를 관장하는 '인프라팀'이라는 또 다른 왕국에는, 각 마이크로서비스가 필요로 하는 자원(CPU, 메모리 등)의 요구사항 명세서가 전달되었다. 그들은 이 문서를 바탕으로 각 서비스에 최적화된 땅을 할당하고 길을 내어주었다. 또한, 이 모든 기술 문서는 경영진이라는 상부 조직에 보고하기 위해, 복잡한 공학 언어를 비즈니스 언어로 번역한 세련된 보고서로 재가공되었다. 문서 하나가 때로는 개발 가이드가 되고, 때로는 부서 간 협업의 계약서가 되며, 때로는 프로젝트의 진행 상황을 알리는 공식 성명서의 역할을 했던 것이다.

 

이처럼 잘 구축된 문서 시스템은 기술적 리더십의 형태마저 바꾸어 놓았다. 권위는 더 이상 직급이나 목소리의 크기에서 나오지 않았다. 가장 최신의 정보를 담고 있는 문서를 작성하고, 그 논리를 명확하게 설명할 수 있는 사람이 자연스럽게 토론의 중심에 서게 되었다. 데이터라는 반박할 수 없는 사실 앞에서, 우리는 나이와 경력을 넘어 오직 기술적 합리성만을 기준으로 소통하는 진정한 전문가 집단이 될 수 있었다. 기술의 청사진만큼이나 중요한 것은, 그 청사진을 조직의 모든 구성원이 각자의 언어로 이해하고 신뢰하게 만드는 소통의 아키텍처임을, 나는 갑판 위의 사람들을 통해 배우고 있었다.

 

에필로그: 항구에 정박된 배가 남긴 것

결론부터 말하자면, 우리의 배는 끝내 출항하지 못했다. 이는 소설의 가장 허무한 결말과도 같았다. 수많은 역경을 헤치고 마침내 최종 목적지를 눈앞에 둔 주인공이, 사소한 실수로 발을 헛디뎌 낭떠러지로 떨어지는 그런 이야기 말이다. 우리는 모든 기술적 요구사항을 완벽하게 구현했다. 치열한 논쟁 끝에 탄생한 CQRS 패턴은 부하 테스트에서 안정적으로 작동했고, Kafka의 이벤트 파이프라인은 단 하나의 데이터 유실 없이 흘러갔으며, WebSocket을 이용한 실시간 화면 공유 기능은 경쟁사들로부터 "가장 기술적으로 뛰어난 결과물"이라는 찬사까지 받았다. 우리의 배는 칠흑 같은 바다를 항해할 준비를 마친, 견고하고 아름다운 강철의 거인이었다.

 

하지만 배를 띄우는 데 필요한 것은 튼튼한 엔진과 정확한 항해술만이 아니었다. 최종적으로 우리의 발목을 잡은 것은 엔진의 결함이나 설계도의 오류가 아니었다. 그것은 정부 사업 경험 부족으로 인한 '행정 서류 미비'라는, 어이없을 정도로 비기술적인 문제였다. 수천, 수만 줄의 코드로 이루어진 정교한 시스템이, 누군가의 책상 서랍 속에 있어야 할 서류 몇 장 때문에 그 가치를 증명받지 못한 것이다. 그것은 마치 완벽한 교향곡을 작곡하고 모든 악단의 연주 준비를 마쳤지만, 연주회장 대관 신청 서류의 서명이 누락되어 공연이 영원히 취소된 것과 같은 상황이었다.

 

정교하게 만든 배가 출항 서류 문제로 항구에 영원히 묶이게 되자, 배 안에서는 혼란이 시작되었다. 실패의 책임이라는 보이지 않는 폭탄은, 그 원인을 제공한 곳이 아닌 가장 연약한 곳에서 터지기 마련이다. 책임의 화살은 보이지 않는 손에 의해 묵묵히 엔진실을 지켰던 개발팀의 몫으로 전가되었고, 한때 '드림팀'이라 불렸던 우리의 배는 해체라는 씁쓸한 결말을 맞았다. 항해의 꿈을 함께 꾸었던 동료들은 모두 회사를 떠나게 되었고, 나 역시 권고 사직이라는 차가운 통보와 함께 쥐꼬리만 한 보상금을 손에 쥐고 텅 빈 부두에 홀로 남겨졌다.

 

한동안은 분노와 허탈함에 잠 못 이루는 밤이 계속되었다. 내가 쏟아부었던 수많은 밤과 주말, 치열했던 고민과 논쟁의 순간들이 모두 물거품이 되었다는 생각에 사로잡혔다. 그러나 시간이 지나고 폭풍우가 걷히자, 비로소 잔잔한 수면 아래 가라앉아 있던 것들이 그 모습을 드러내기 시작했다. 나는 이번 프로젝트를 통해 아키텍처란 단순히 최신 기술의 목록을 나열하는 행위가 아님을 배웠다. 그것은 비즈니스의 본질과 기술의 한계, 현재의 제약과 미래의 불확실성이라는 서로 다른 힘들이 팽팽하게 맞서는 지점에서, 최적의 균형점을 찾아내는 고도의 의사결정 과정임을 온몸으로 체감했다.

 

또한, 뛰어난 기술력만으로는 결코 성공적인 프로젝트를 이끌 수 없다는 뼈아픈 교훈을 얻었다. 다른 언어를 쓰는 이해관계자들을 설득하고, 명확한 문서로 지식을 공유하며, 신뢰를 기반으로 협업을 이끌어내는 소통의 기술이야말로, 때로는 가장 강력하고 효율적인 알고리즘이 될 수 있음을 깨달았다. 리소스 부족, 예측 불가능한 요구사항, 불합리한 조직 문화, 그리고 프로젝트의 좌초라는 수많은 역경 속에서도, 나는 내가 맡은 기술적 책임을 끝까지 완수했다. 이 경험은 나에게서 자신감을 앗아가는 대신, 어떤 어려운 상황에서도 흔들리지 않고 제 역할을 해내는 단단한 회복탄력성(Resilience)을 선물했다. 실패의 경험이 나의 기술력에 대한 의심이 아닌, 오히려 더 큰 자신감의 원천이 된 것이다.

 

비록 프로젝트는 좌초되었지만, 그 안에서 피어난 모든 것이 사라진 것은 아니었다. 내가 주도하여 작성했던 수많은 설계 문서와 기술 가이드는 회사의 공식적인 기술 자산(PoC, Proof of Concept)으로 남아, 다음 R&D 사업의 초석이 되었다는 소식을 전해 들었다. 우리의 배는 출항하지 못했지만, 그 배를 만들며 축적된 기술과 경험은 다음 세대의 배를 건조하는 데 쓰일 귀중한 유산으로 남은 것이다.

 

지금 나는 잠시 항해를 멈추고 뭍에 올라 숨을 고르고 있다. 이따금 항구를 바라보며 우리가 만들었던 그 배를 떠올린다. 그 배는 실패의 상징이 아니라, 내 청춘의 가장 치열했던 고민과 성장이 담긴 기념비다. 나는 안다. 이 씁쓸하지만 귀중했던 항해의 기록이 나의 다음 여정에 든든한 등대가 되어줄 것임을. 그리고 언젠가 다시 새로운 배에 오를 그날, 나는 오늘보다 조금 더 현명하고, 조금 더 단단하며, 기술의 깊이와 사람의 마음을 함께 헤아릴 줄 아는 그런 항해사가 되어 있을 것이다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/09   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함