본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 27. 05:51

AI가 찾아낼 수 없는 소프트웨어 버그

요약

AI가 코드를 작성하는 속도는 빨라졌지만, 예상치 못한 엣지 케이스를 예측하는 상상력의 한계는 여전합니다. 시스템이 조용히 실패(Fail silently)하지 않고, 오류를 명확히 드러내는 명시적 실패(Fail loudly)를 설계에 포함하여 데이터 무결성을 유지해야 합니다.

핵심 포인트

  • AI는 구현을 가속화하지만 엣지 케이스 예측의 한계는 극복하지 못함
  • 시스템이 조용히 실패(Fail silently)하는 것은 설계상의 결함임
  • 명시적 실패(Fail loudly)는 시스템 무결성을 지키는 핵심 기능임
  • 원자적 트랜잭션은 예기치 못한 상황에서 시스템을 보호하는 가장 단순하고 강력한 방법임

코딩은 어려운 부분이 아닙니다.

합리적인 기술을 갖춘 개발자라면 설명된 요구사항을 구현할 수 있습니다. 명확한 사양(Specification)이 주어지면 구현은 뒤따릅니다. 이는 항상 사실이었으며, AI는 이를 더욱 사실로 만들었습니다. 설명을 작동하는 코드로 바꾸는 기계적인 작업은 이제 그 어느 때보다 빠르고 저렴해졌습니다.

어려운 부분은 다른 것입니다. 잘못될 수 있는 모든 것을 생각하는 것입니다. 아무도 예상하지 못한 데이터의 조합. 사용자가 발견하기 전까지는 불가능해 보였던 작업 순서. 아무도 모델링하지 않은 방식으로 두 개의 정당한 비즈니스 시나리오가 충돌할 때에만 나타나는 엣지 케이스(Edge case). 이것들은 코딩의 실패가 아닙니다. 이것들은 상상력의 실패입니다. 그리고 어떤 개발자도, 어떤 팀도, 어떤 AI도 이것으로부터 자유로웠던 적은 없습니다.

업계는 이 교훈을 이전에 한 번 배운 적이 있습니다. 폭포수(Waterfall) 모델의 핵심 가정은 구축을 시작하기 전에 요구사항을 완전히 명시할 수 있다는 것이었습니다. 즉, 초기에 충분히 깊게 생각한다면 모든 것을 예측할 수 있다는 것이었습니다. 하지만 그것은 불가능했습니다. 구축하는 행위 자체가 구축을 시작하기 전에는 아무도 몰랐던 것들을 드러냈습니다. 어떤 사양 정의 세션에서도 드러나지 않았던 시나리오들이 실제 사용 과정에서 나타났습니다. 업계는 결국 이를 받아들이고 다음 단계로 넘어갔습니다.

동일한 가정이 분산 아키텍처(Distributed architectures) 내부, 즉 한 단계 아래에서도 살아 숨 쉬고 있습니다. 시스템이 실제 데이터를 만나기 전에는 모든 장애 모드(Failure mode)를 예측할 수 없습니다. 문제는 그 간극을 어떻게 없애느냐가 아닙니다. 없앨 수 없기 때문입니다. 문제는 이것입니다: 현실이 그 간극을 발견했을 때, 시스템이 얼마나 빨리 당신에게 알려주는가?

두 가지 가능한 답변이 있습니다. 시스템이 크게 실패(Fail loudly)하는 경우입니다. 즉, 작업이 중단되고, 부분적인 커밋(Commit)이 이루어지지 않으며, 에러가 가시적으로 드러나 개발자가 이를 발견하고 수정하게 됩니다. 또는 시스템이 조용히 실패(Fail silently)하는 경우입니다. 즉, 작업이 성공한 것처럼 보이고, 어딘가에 부분적인 커밋이 이루어지며, 데이터에 불일치(Inconsistency)가 발생하지만 아무도 모르게 됩니다.

명시적인 실패(Loud failure)는 좋은 아키텍처의 부작용이 아닙니다. 그것은 하나의 기능(Feature)입니다. 즉, 현실이 드러남에 따라 시스템이 스스로의 공백을 수정하는 메커니즘입니다. 이는 의도적으로 설계에 포함되어야 합니다. 하지만 지난 10년 동안 업계가 정상화해 온 놀라울 정도로 많은 기술적 선택들이, 조용히 이 기능을 제거하도록 설계되어 있습니다.

이후에 이어지는 모든 내용은 그러한 차이에서 비롯된 결과입니다.

트랜잭션(Transaction)은 기술적 세부 사항이 아니다

단일 원자적 트랜잭션(Atomic transaction)은 명시적인 실패를 구현하는 가장 단순한 방법입니다.

예기치 않은 일이 발생합니다. 트랜잭션이 실패합니다. 일관성 경계(Consistency boundary) 내부의 모든 것이 롤백(Rollback)됩니다. 주문은 생성되지 않았고, 재고는 줄어들지 않았으며, 인보이스(Invoice)는 생성되지 않았습니다. 작업 전의 상태가 정확하게 복구됩니다. 사용자는 에러를 보고, 개발자는 그 에러를 확인합니다. 그들은 고려되지 않았던 시나리오를 찾아내고, 이를 수정합니다. 피드백 루프는 몇 달이 아니라 몇 시간 단위로 이루어집니다. 시스템의 무결성(Integrity)은 결코 훼손되지 않았습니다. 단지 특정 작업에 대해 일시적으로 가용성(Availability)이 저하되었을 뿐입니다.

이것은 버그가 아닙니다. 이는 예기치 않은 조건 하에서 시스템이 올바르게 작동하고 있다는 증거입니다. 즉, 무언가가 손실되기 전, 그리고 불일치(Inconsistency)가 누적될 기회를 갖기 전, 가장 저렴한 시점에 이해의 공백을 드러내는 것입니다.

이것이 바로 엔터프라이즈 애플리케이션을 위한 기술적 선택이 단순한 선호도의 문제가 아닌 이유입니다. 그것들은 구조적 결과를 초래하는 엔지니어링 결정입니다. 관계형 데이터베이스(Relational database)는 명시적인 실패를 위해 30년 동안 검증된 인프라를 제공합니다: Non-nullable 제약 조건(Constraints), 고유 제약 조건(Unique constraints), 외래 키 제약 조건(Foreign key constraints), 체크 제약 조건(Check constraints) 등이 그것입니다. 이것들은 편의 기능이 아닙니다. 이것들은 그 어떤 애플리케이션 코드보다 데이터에 더 가깝게 위치하며, 어떤 서비스가 필드 설정을 누락했는지 또는 어떤 이벤트 핸들러(Event handler)가 실행되지 않았는지와 관계없이 강제되는 검증 계층(Validation layer)입니다. 데이터베이스는 단순히 거부합니다. 명시적으로, 그리고 즉각적으로.

특정한 상황에서 관계형 데이터베이스 (Relational database)를 벗어나기로 결정하는 것은 정당한 엔지니어링 결정입니다. 하지만 그것은 중립적인 결정이 아닙니다. 데이터베이스가 강제하던 모든 제약 조건(Constraint)은 다음 두 가지 중 하나가 됩니다. 데이터베이스가 강제하던 제약 조건이 애플리케이션 (Application) 내부로 이동하여, 그곳에서 신뢰성이 낮아지고, 찾기 어려워지며, 왜 존재하는지조차 모르는 사람들에 의해 유지 관리되거나, 혹은 그것이 방지하려 했던 데이터 조합을 아무도 생성하지 않기를 바라는 희망으로 대체되어 완전히 사라져 버립니다. 검증 (Validation)은 사라지지 않습니다. 그것은 위치를 옮기거나, 보이지 않게 될 뿐입니다. 두 결과 모두 침묵하는 실패 (Silent failure)를 향한 단계입니다.

어떤 기술이 인기가 있다는 이유로, 대기업이 그에 관한 논문을 발표했다는 이유로, 혹은 컨퍼런스에서 등장했다는 이유로 — 그것이 어떤 속성 (Properties)을 제공하고 어떤 속성을 제거하는지 묻지 않은 채 — 기술을 선택하는 것은 엔지니어링이 아닙니다. 그것은 유행입니다. 그리고 엔지터프라이즈 소프트웨어 (Enterprise software)에서 유행은 몇 년 후 아무도 설명할 수 없는 운영 데이터 (Production data)에서 나타나는 구조적인 결과를 초래합니다.

분산했을 때 발생하는 일

이제 동일한 예기치 않은 시나리오를 분산 시스템 (Distributed system)에서 실행해 보겠습니다.

서비스 A (Service A)가 자신의 부분을 처리하고 커밋 (Commit)합니다. 이벤트 (Event)가 발생합니다. 서비스 B (Service B)가 이를 수신하고 실패합니다. 이는 잘못된 코드 때문이 아니라, 이 특정한 데이터 조합이 전혀 예상되지 않았기 때문입니다. 보상 로직 (Compensation logic)이 일관성 (Consistency)을 회복할 수 있지만, 이는 오직 해당 로직이 처리하도록 작성된 시나리오에 대해서만 가능합니다. 아무도 이 조합을 예상하지 못했기 때문에, 이 조합을 위한 보상 로직을 작성한 사람도 없습니다. 서비스 A는 커밋했습니다. 서비스 B는 하지 않았습니다. 상태는 이제 불일치하며, 복구는 정의상 아무도 예상하지 못했던 시나리오를 위해 그 정확성 자체를 증명해야 하는 로직에 의존하게 됩니다.

사용자는 에러조차 보지 못할 수도 있습니다. 시스템은 정상적으로 작동한 것처럼 보입니다.

이제 그 불일치는 운영 환경(production)에 존재합니다. 하위 서비스(Downstream services)들이 이를 기반으로 의사결정을 내리고 있습니다. 이를 바탕으로 보고서가 생성되고 있습니다. 다른 작업들이 그 위에 구축되고 있습니다. 그리고 아무도 모릅니다. 시스템이 실패하지 않았기 때문입니다. 시스템은 부분적으로 성공(partially succeeded)했습니다. 이는 분산 아키텍처(distributed architectures)가 구조적으로 깔끔하게 드러낼 수 없는 실패 모드(failure mode)입니다.

18개월 후, 누군가 숫자가 맞지 않는다는 사실을 알아차립니다. 또는 배송 완료로 표시되었으나 실제로는 발송되지 않은 주문에 대해 고객이 전화를 겁니다. 또는 감사를 통해 서로 모순되는 재무 기록이 발견됩니다. 18개월 동안의 이벤트, 서비스 경계(service boundaries)를 가로지르는 과정, 그리고 1년 전에 퇴사한 사람이 작성한 보상 로직(compensation logic)을 거쳐 그 기원을 추적하는 포렌식(forensic) 작업은 엄청난 규모입니다. 해결책은 코드 변경이 아닙니다. 그것은 데이터 무결성(data integrity) 프로젝트이며, 실제 올바른 상태가 무엇이었는지에 대한 영구적인 불확실성을 동반합니다.

분산 시스템은 버그를 방지하지 못했습니다. 버그가 눈에 보이게 되는 것을 방지했을 뿐입니다. 이는 최악의 트레이드오프(trade)입니다. 왜냐하면 명확한 실패(loud failure)는 시스템이 학습하는 데 사용하는 메커니즘이기 때문입니다. 이를 제거하면 시스템은 가르침을 멈춥니다. 그저 축적될 뿐입니다.

"작동한다"는 논거가 놓치는 부분

여기서 합리적인 반론이 제기됩니다. 많은 분산 시스템은 실제로 잘 작동합니다. 마이크로서비스(Microservices) 애플리케이션들은 위에서 설명한 실패 모드가 실현되지 않은 채 수년간 운영 환경에서 돌아가기도 합니다. 만약 당신의 시스템이 그중 하나라면, 지금까지의 논거는 아마 이론적으로만 보일 것입니다.

이것은 이론적인 것이 아닙니다. 확률적인(probabilistic) 것입니다. 그리고 그 확률은 당신이 가장 확장(scale)하고 싶어 하는 요소와 직접적으로 비례하여 커집니다.

작은 애플리케이션. 제한된 도메인(Bounded domain). 제한된 엔티티(entities), 제한된 관계(relationships), 제한된 사용자, 제한된 수명. 가능한 데이터 조합의 공간이 작습니다. 고려되지 않은 시나리오가 시스템이 은퇴하기 전까지는 단순히 발생하지 않을 수도 있습니다.

이제 애플리케이션을 확장해 보십시오. 더 많은 엔티티 (Entities), 더 많은 관계 (Relationships), 더 많은 사용자가 더 오랜 시간 동안 더 많은 조합을 만들어냅니다. 가능한 데이터 조합의 공간은 팀이 성장하는 속도보다 더 빠르게 커집니다. 고려되지 않은 시나리오를 마주할 확률은 일정하게 유지되지 않고 — 복리로 증가합니다. 충분한 규모와 충분한 시간이 흐르면, 이는 더 이상 리스크가 아니라 수학적 확실성이 됩니다.

이는 소규모 시스템에서는 안전해 보였던 아키텍처 (Architectural) 선택이, 애플리케이션의 규모 및 수명과 정비례하여 커지는 부채 (Liability)가 된다는 것을 의미합니다. 조직이 가장 보호하고 싶어 하는 시스템 — 즉, 규모가 크고 오래 지속되며 비즈니스에 핵심적인 (Business-critical) 애플리케이션 — 이 바로 침묵하는 실패 (Silent failure)가 가능성이 아닌 확실성이 되는 바로 그 시스템입니다.

"그래서 뭐 어쨌다는 건가요, 잘 작동하는데"라고 말하는 개발자는 소규모 시스템을 설명하고 있는 것입니다. 그들의 말이 맞습니다. 다만 그들이 무엇을 설명하고 있는지 깨닫지 못하고 있을 뿐입니다.

AI는 축적을 가속화한다

이 지점이 바로 현재의 상황이 그 위험성을 부정할 수 없게 만드는 부분입니다.

AI는 모든 인간 개발자가 가진 것과 동일한 상상력의 한계를 가지고 있습니다. AI는 요청받은 대로 구현할 뿐입니다. 프롬프트 (Prompt)에 포함되지 않은 데이터 조합을 예측하지 않습니다. 아무도 설명할 생각을 하지 못했던 두 가지 정당한 비즈니스 시나리오 사이의 충돌을 모델링하지 않습니다. 그리고 AI는 자신에게 입력되는 도메인 지식 (Domain understanding)의 속도를 앞지르는 속도로 코드를 생성하며 — 그 어떤 인간 팀보다도 빠르게 고려되지 않은 시나리오들을 축적합니다.

그 이면에는 더 미묘한 문제가 있습니다. 복잡한 도메인(domain)을 위한 코드를 작성하는 것은 단순히 구현(implementation)의 문제가 아닙니다. 그것은 이해가 어떻게 발전하느냐의 문제입니다. 요구사항이 깔끔하게 맞아떨어지지 않을 때, 동일한 로직이 세 곳에 나타날 때, 메서드(method)가 읽기 어려운 방식으로 비대해질 때 — 그 저항감은 하나의 신호입니다. 도메인이 공백을 드러내고 있는 것입니다. 그 마찰(friction)은 엔지니어의 이해를 심화시키고 모델을 교정하는 피드백 루프(feedback loop) 역할을 합니다. 구현 도구(implementer)로서 사용되는 AI는 그 저항을 흡수해 버립니다. 코드는 작성됩니다. 하지만 불편함은 결코 찾아오지 않습니다. 교훈은 바로 그 불편함 속에 있었습니다.

이것은 새로운 실패 모드(failure mode)가 아닙니다. 프레임워크가 주도하는 개발(framework-dictated development)이 표준이 된 이후 업계가 지속해 온 추세의 연장선입니다. 즉, 미리 패키징된 아키텍처 레시피(architectural recipes)가 구조적 추론(structural reasoning)을 대체했고, 엔지니어들은 구조를 심문하기보다 템플릿을 채워 넣는 법을 배웠습니다. 구현 도구로서의 AI는 이와 동일한 역학 관계가 한 단계 더 높은 추상화 수준에서 더 빠르게 실행되는 것뿐입니다. 작동하는 소프트웨어(working software)와 이해된 소프트웨어(understood software) 사이의 간극은 AI가 등장하기 전부터 이미 벌어지고 있었습니다. AI는 그 간극을 물려받아 가속화했습니다.

일관된 일관성 경계(consistency boundary)를 가진 시스템에서는 아키텍처 수준에서 이 문제가 덜 중요합니다. 고려되지 않은 시나리오는 AI가 생성했든 아니든 여전히 명확하게 실패를 드러냅니다. 트랜잭션(transaction)이 실패하고, 에러(error)가 표면화되며, 간극이 발견되어 수정됩니다. 엔지니어의 이해가 불완전하더라도 시스템은 스스로 교정되는 상태를 유지합니다.

하지만 AI의 속도로 구축된 분산 시스템(distributed system)에서 고려되지 않은 시나리오는 이전 세대의 개발 방식에서는 결코 도달할 수 없었던 속도로 조용히 실패합니다. 이벤트(events)는 큐(queue)에 쌓입니다. 불일치(inconsistencies)는 누적됩니다. 데이터 드리프트(data drift)가 발생합니다. 그리고 마침내 진단이 내려질 때, 그것은 언제나 그랬듯 다음과 같을 것입니다: "도메인이 복잡했고, 요구사항이 변경되었으며, 이전 개발자들이 부주의했다."

"우리가 이해할 수 있는 범위를 앞지르는 속도로, 우리가 알지 못하는 것을 숨기도록 설계된 아키텍처 속에 구축했다"가 아닙니다.

업계가 이 지점에 도달한 이유

누구도 이를 의도적으로 선택하지 않았습니다. 어떤 진단을 내리기 전에 이 점을 분명히 말해둘 가치가 있습니다.

공공 소프트웨어 담론은 필연적으로 대규모로 가르치고, 반복하고, 검증할 수 있는 관행에 의해 형성됩니다. 컨퍼런스 강연, 블로그 게시물, 채용 공고, 그리고 면접 질문을 지배하는 패턴들은 실무자들 사이에서 신뢰할 수 있게 전달될 만큼 읽기 쉬운 것들입니다. 이는 반드시 10년 동안 일관성을 유지하는 시스템을 만들어내는 패턴을 의미하지는 않습니다. 이것은 관련 인력들에 대한 비판이 아닙니다. 가장 중대한 결과가 나타나기까지 수년이 걸리는 모든 분야에서 지식이 전파되는 방식일 뿐입니다.

그리고 모든 시스템은 단 한 번 구축되기 때문에 — 대안적인 접근 방식이 시스템과 함께 구축되지 않으므로 잘못된 선택의 비용을 직접적으로 관찰할 수 없기에 — 이 분야는 스스로의 경험으로부터 쉽게 배우지 못합니다. 시스템에 데이터 무결성 (Data Integrity) 문제가 발생하면, 그 원인은 도메인 복잡성이나 변경되는 요구사항의 탓으로 돌려집니다. 아키텍처가 변수였다고 결론 내리는 사람은 거의 없습니다. 왜냐하면 비교할 대조군 (Control Group)이 없기 때문입니다. 이러한 반증 불가능성 (Unfalsifiability) 문제는 신호가 그것을 가장 필요로 하는 사람들에게 도달하는 것을 가로막습니다.

마이크로서비스 (Microservices), 이벤트 기반 아키텍처 (Event-driven architecture), NoSQL 데이터베이스 — 각각은 실제 규모의 실제 문제에 대한 진정한 대응책으로 시작되었습니다. 하지만 각각은 해당 패턴이 해결하도록 설계된 규모의 문제를 경험해 본 적이 없는 팀들에 의해 기본값으로 채택되었으며, 엔지니어링적 특성이 아니라 문화적 가시성 때문에 선택되었습니다. 그리고 각각은 저마다의 방식으로 동일한 일을 수행합니다. 바로 신호를 재배치하는 것입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0