9P by GN⁺ 2일전 | ★ favorite | 댓글 2개
  • 의존성은 무료 기능처럼 보이지만, 실제로는 다양한 비용과 복잡성을 동반함
  • 잘못된 의존성은 러닝커브, 갑작스런 인터페이스 변경, 배포·설치 문제 등 다양한 리스크를 초래함
  • 대표 사례로 TigerBeetle은 보안·성능·운영 단순성을 위해 "제로 의존성" 정책을 지향함
  • 저자는 의존성 평가 프레임워크(보급성, 안정성, 깊이, 사용성, 완전성) 를 제안함
  • 좋은 의존성과 나쁜 의존성을 구별하는 비판적 사고와 의존성 선택 시 단기 생산성만이 아니라 전체 비용과 위험을 감안한 신중한 판단이 필수임

NIH(Not Invented Here)보다 잘못된 의존성이 더 큰 비용임

의존성의 단점과 숨겨진 비용

  • 많은 개발자들이 의존성 추가를 "공짜 기능" 처럼 여기지만, 실제로는 다음과 같은 비용이 발생함
    • 배우는 데 드는 시간과 복잡성
    • 의존성 설치 자체가 어려운 경우도 빈번함
    • 주요 버전 업그레이드 시 내 코드까지 크게 수정해야 하는 부담
    • 의존성이 결국 배포/설치 환경에 반드시 들어가야 함 (컨테이너, 번들링 등 복잡성 유발)
  • 잘못된 의존성 도입으로 인해 핵심 기능과 관계없는 복잡한 배포 구조가 만들어지기도 함

TigerBeetle의 제로 의존성 원칙

  • TigerbeetleVanilla Zig 기반의 재무 데이터베이스로, 제로 의존성 정책을 채택함
    • Zig 언어만으로 개발하며, Zig 툴체인 외의 외부 의존성을 두지 않음
    • 의존성으로 인한 공급망 공격 위험, 성능 저하, 설치 시간 증가 등 문제를 피하려는 목적
    • 인프라에 깊게 뿌리내린 소프트웨어일수록 의존성으로 인한 비용이 전체 스택에 증폭
    • 표준화된 작은 툴박스 활용이 유지보수와 개발 능률에 유리함
  • Zig 하나로 새로운 문제를 빠르게 다루고 복잡도를 줄이는 데 집중함

의존성 평가 프레임워크

  • 모든 개발자가 완전한 무의존은 불가능함을 인정하면서도, 의존성은 신중히 평가해야 함을 강조함
  • 저자는 다음 5가지 기준으로 의존성 평가를 제안함
    • 보급성(Ubiquity): 대상 환경에 얼마나 흔하게 존재하는가? 별도 배포/설치 필요성 여부
    • 안정성(Stability): 하위 호환성, API 변경 빈도, 지원 중단 등 이슈 발생 빈도
    • 깊이(Depth): API 아래 숨어있는 실제 기능량, 직접 구현 시 대체 난이도
    • 사용성(Ergonomics): API가 직관적/선언적이며 쓰기 쉬운지, 문서화 현황
    • 완전성(Watertightness): 추상화가 얼마나 잘 동작하는가, 하부 기술을 얼마나 신경 쓰지 않아도 되는가
  • 개발 커뮤니티는 주로 사용성만 논의하며 나머지 네 가지는 간과되는 경우가 많음

좋은 의존성 사례

  • POSIX 시스템 호출

    • 보편성: Linux, Android, macOS, BSD 등 거의 모든 플랫폼에서 사용 가능함
    • 안정성: 인터페이스 호환성이 매우 높고 변화 거의 없음
    • 깊이: 단일 API로 수십만 줄의 커널 코드 감춤
    • 사용성: 다소 전통적인 C 스타일이기는 하나 사용에 큰 무리는 없음
    • 완결성: 대부분 문제 없으나 저장 장치의 데이터 영속 처리 등 세부 이슈 있음
  • ECMA-48 터미널 제어 코드

    • 보편성: Windows의 cmd.exe를 제외하면 대부분의 터미널에서 지원함
    • 안정성: 1991년 이후 변경 없음
    • 깊이: 직접 표준을 만드는 것이 터무니없이 힘듦
    • 사용성: Esc 캐릭터로 인한 난독성을 제외하면 무난함
    • 완결성: 하드웨어 의존성 걱정이 매우 적음
  • 웹 플랫폼 (Web API, HTML, JS, CSS 등)

    • 보편성: 웹 브라우저가 전 세계 거의 모든 환경에 설치됨
    • 안정성: 강력한 하위 호환성 정책
    • 깊이: 자체 브라우저 제작은 현실적으로 불가능할 정도로 깊음
    • 사용성: 약간의 복잡함 있으나 문서화와 개발 도구 우수함
    • 완결성: 파일, 오디오, 비디오 등 특이 상황 제외하면 매우 완결성이 높음

결론

  • 카피-앤-페이스트, 의존성 남용 대신 비판적 사고와 총체적 비용 분석이 필수임
  • 의존성을 도입할 때는 모든 의존성의 비용과 이득을 비판적으로 평가하고,
    전체 시스템의 잠재적 위험과 비용도 반드시 고려해 신중하게 선택하는 것이 장기적으로 훨씬 저렴하고 안전함
Hacker News 의견
  • TigerBeetle라는 예시를 처음 들어서 직접 찾아봤음. 만약 안전성, 성능이 중요한 Zig 기반의 금융 원장 데이터베이스를 만드는 상황이고 CPU 한 코어에서 초당 백만 건의 트랜잭션을 감당해야 한다면, 의존성 추가는 위험 부담이 커서 피하는 게 완전히 합리적임. 하지만 대부분의 일반 개발자들은 평범한 CRUD 시스템을 만들고 있고, 이들은 일반적으로 그렇게 강하지 않은 경우가 많음. 많은 의존성이 버그투성이일 수는 있지만, 대다수 개발자가 직접 만드는 것보다 품질이 높은 경우도 많음. 기업도 실제로는 충원 가능한 개발자의 수준에 의해 병목이 생김. 각자의 상황이 다르니, 반대되는 조언도 각각의 맥락에서는 모두 옳을 수 있음을 기억해야 함

    • TigerBeetle에서 실제로 일하고 있음. 핵심은 맥락임. TigerBeetle이나 rust-analyzer는 강한 개발 문화가 있지만 해결하는 문제가 달라 서로 다른 문화를 형성함. 글에서 언급된 의존성은 POSIX, ECMA-48, 웹 플랫폼처럼 라이브러리라기보다는 시스템 인터페이스임. 라이브러리 의존성은 문제 생기면 직접 새로 쓰면 그만이지만, 시스템 인터페이스 같은 근본적인 것들은 바꾸기가 사실상 불가능하거나 비용이 큼. 소프트웨어의 범위에 맞지 않는 일을 하지 않는 결정을 내리는 것이 강력함. 예를 들어 행렬 곱셈 코드를 만들 전문 팀이라면, 코어 업무와 상관없는 다른 라이브러리는 외부 것을 사용해도 되지만, 제품의 책임 분해를 더 잘 설계해야 한다고 생각함. 이렇게 하면 필수 의존성을 시스템 내에서 더 잘 격리할 수 있음

    • 이 관점의 문제는 바닥 수준의 개발자만 염두에 두었을 때만 성립함. 기술 분야는 조언을 가장 낮은 수준에 맞게 희석시키는 경향이 있는데, 솔직히 내가 일한 곳에서는 개발자들이 고장 난 의존성을 복제해 문제를 해결하지 못할 정도로 약했던 환경이 거의 없음. CRUD에 대한 비하도 많은데, 나쁜 추상화는 CRUD에서 엄청난 시간 낭비와 고통을 유발함. 인기 있는 것들도 실제로 진짜 기초적인 튜토리얼 수준이 아니면 여러 문제가 많고 생산성이 떨어짐

    • 개발자 수준 자체와 관련 없는 이야기임. 어떤 툴체인이나 제품을 쓰든 결국 남이 만든 의존성을 사용하고 있음. 주변에 직접 행렬 곱셈 코드 구현하는 사람은 극소수이고, 그들조차도 자신과 상관없는 오픈소스 라이브러리까지 직접 구현하진 않음. 대개 규제 요건이나 개인적 관심, 혹은 특정 라이브러리에 대한 애착이 있을 때 의존성 자체에 집착하게 됨. 모두가 이런 원칙을 완전히 적용하면 해변에서 모래만 주우며 살고 있을 것임

    • "평균적인 CRUD 개발자들은 강하지 않다"라는 의견은 지나치게 단정적임. 대부분의 개발자는 자신이 일할 시스템을 선택할 수 없고, 개발 기간에도 리소스가 항상 부족함. '싼' 의존성을 활용해야 정상 동작하는 소프트웨어를 빠르게 출시할 수 있음. 이 현실을 제대로 모르고 한 이야기 같음

    • TigerBeetle은 비교적 최근에 출시한 스타트업이고 1.0 정식 버전도 아직임. 이런 접근이 정말 효과가 있을지는 너무 이르다고 생각함

  • NIH(Not Invented Here) 자체는 어디까지 책임질지 현실적으로 판단해서 사용할 때 굉장히 쓸모있음. 예를 들어, 내 도메인에 딱 맞는 웹 프론트엔드 프레임워크는 독자적으로 만들어 유지관리할 만한 가치가 있는 경우가 많음. 그러나 데이터베이스, 게임 엔진, 웹 서버, 암호화 관련 기본 기능 등은 얘기가 다름. 기존에 나온 솔루션으로는 해결할 수 없을 정도로 난이도가 높다면, 문제 재정의를 먼저 고민해야 함. SQLite 테스트 전체를 처음부터 다시 만들 바에야, 문제 자체를 새롭게 정리하는 게 훨씬 저렴하다고 생각함

    • 데이터베이스, 게임 엔진, 웹 서버, 암호 프리미티브 등도 경우에 따라 직접 만드는 게 더 나은 사례가 많음. 단순 파일과 런타임 인덱스만으로 충분하다면, SQLite조차 과한 선택일 때가 많음. 많은 게임은 소규모 팀일수록 커스텀 엔진이 유리할 때가 많음. 완성된 엔진의 장점은 파이프라인뿐이지만, 큰 오버헤드가 따르기 때문임. 웹서버는 FastCGI 앱과 복잡도 차가 없음. 암호화도 모든 상황이 보안 문제는 아니어서, 단순 해시 확인 같은 건 직접 구현해도 무방함. 어려워 보이는 주제에 배운 무기력감을 가지는 건 좋지 않다고 생각함. 기존 솔루션이 문제를 해결한다고 해서 그게 최적이거나 가장 효율적인 방식은 아니라는 점도 중요함

    • 그렇다면 왜 많은 데이터베이스 엔진이 존재하나? 결국 복잡한 컴퓨터 시스템은 각자의 트레이드오프가 존재함. 제약 조건, 확장성, 동시성, 보안, 데이터 특성, 저장 방식 등 선택지가 매우 다양함. 나는 의존성 비용이 클수록, 그 의존성 자체가 타 의존성 최소화를 추구할 때 더 신뢰가 간다고 생각함. 자동화된 의존성 관리 시스템이 오히려 문제를 복잡하게 만드는 경향이 있어서, 신중한 수동 관리가 부담을 줄여준다고 봄

    • 서드파티 의존성을 사용하는 이유는 두 가지로 생각함. (1) 서비스 제공자가 직접 퍼블리시하며, 상대적으로 수명이 일치하는 경우. (2) 내가 작성하고 싶지 않은 복잡한 코드를 대신하는 경우. (1)은 비즈니스적 이유가 있으니 문제 없음. 단, 해당 서비스가 업데이트될 때 큰 변화가 생기는 건 감수해야 함. (2)는 내가 피해가려는 코드의 복잡성에 따라 가치가 달라짐. 의존성을 도입한다는 것은 상대방의 일정에 맞춰 내가 업데이트/테스트에 시간과 리소스를 소비해야 하고, 그 책임을 떠안게 됨

    • RDBMS로 해결할 수 없는 문제를 빨리 만나게 됨. RDBMS는 동시 데이터 수정과 가변 데이터셋 지원 중심으로 짜여 있어서, 이게 필요 없다면 단순 인덱스로도 엄청난 성능 향상이 가능함. 데이터가 불변이라면 RDBMS 대비 훨씬 빠른 자체 구현이 가능함

    • RDBMS 사례가 흥미로움. 위키피디아에는 100가지가 넘는 RDBMS가 있고, 각각이 해결할 수 있는 문제와 해결 못하는 문제가 있음. 실용적 해결을 고민하고 실제로 실행한 결과임

  • 의존성은 위험을 유발하지만, 전혀 사용하지 않으면 개발 및 시장 출시 경쟁력에서 뒤처질 수 있음. 그래서 의존성 관리 프로세스가 중요함

    1. 오픈소스 의존성만 고려함
    2. 신규 도입 시 코드 리뷰뿐 아니라, 라이선스 확인, 제거 시 소요 노력, 보안 취약점 및 버그 이력, 업데이트 지속성, 커뮤니티 활력도 등 다각적으로 검토함
    3. 가능하다면 의존성 보안 이슈 주기적 검사 필요. 대규모로 할 때는 비용이 큰 문제임. (관련 아이디어 공유: https://blog.majid.info/supply-chain-vetting/)
    4. 유지/포크 부담을 감당할 수 있는 의존성만 도입. 최소한 소스에서 직접 빌드해본 적은 있어야 함
    5. 모든 의존성을 선제적으로 포크함. left-pad 사태처럼 저장소가 갑자기 사라질 수 있기 때문임
    • 4번, 5번 항목은 정말 중요하지만 자주 잊혀짐. 개인 프로젝트에서도 한동안 방치 후 복귀하면 의존성이 구식이 되어 있거나 리포가 삭제된 경우를 겪어봄. 그래서 요즘은 소스 자체를 비공개로 포크해서 직접 빌드해보고, 모든 의존성의 의존성까지 소스 수준으로 포크해둠. 이렇게 하면 나중에 에코시스템이 급격히 변해도 피해를 최소화할 수 있음. 바이너리보다 소스 라이브러리를 선호하게 됨

    • 5번에서 포크는 지나치게 부담이 클 수 있음. 나만의 git이나 캐시 프록시에 의존성을 넣어두는 벤더링도 괜찮은 방법임. 특히 장수하는 프로젝트에 더 어울림. NodeJS처럼 의존성 파일이 많을 때는 Yarn, PNPM 같은 툴이 좀 더 효율적임

    • 4번과 관련해, SQLite 같은 유명 의존성은 내가 만드는 제품보다 훨씬 오래 지속됨. 내 제품이 해당 오픈소스 자체보다 오래 갈 거라 생각하면 그게 더 오만한 자세임. Linux 커널도 직접 빌드할 생각 없음

    • 모든 코드는 최소한 네트워크 연결 없이 빌드가 가능해야 함. 바이너리 아티팩트 없는 게 가장 좋지만, 항상 현실적으로 가능한 건 아님

    • 훌륭한 통찰임. 이 내용을 내 의존성 도입 절차 문서에 추가할 예정임. 문제는 자바스크립트 같이 의존성트리가 너무 깊은 경우임

  • 오랜 경험상 모든 것은 결국 "상황에 따라 다름(It Depends™)"임. 젊었을 때는 원칙만 따르고 예외 없음에 집착하다가, 부적합한 라이브러리나 패러다임을 억지로 적용하면서 최악의 코드를 만들기도 했음. 지금은 자주 사용하는 것들은 직접 라이브러리화해 패키지로 빼놓고, 필요한 것 중 내가 못하는 사항만 외부 의존성으로 채움. 품질이나 관리성만 납득할 수 있으면 외부 것도 기꺼이 받아들임

  • 에너지 업계는 의존성을 의도적으로 피하는 경향임. 외부 의존성을 도입하면 변경 사항을 모두 살펴야 하기 때문임. AI 코드 도구가 큰 도움이 되었고, 주로 CLI 도구 생성 등에 활용함. 오픈API 문서도 LLM을 활용해서 만들고, 이를 Go 표준 라이브러리로 서비스함. LLM 자체는 외부 의존성이지만, 그것으로 만든 CLI 도구는 실제 코드와 무관해서 품질 요구가 낮음. 물론 프론트엔드 개발자들이 React 없이 일하고 싶어하지는 않지만, 그런 제품은 외부로 다루니까 예외임. 엔지니어들의 품질 의존성 집착을 덜게 만들 소도구를 제공하면 의존성 최소화 정책이 더 쉬워짐

    • LLM이 학습에 사용한 오픈소스 코드 일부를 코드로 내뱉기도 하는데, 그런 경우 의존성 포크해서 자기 소유물처럼 다루는 것과 별 차이 없지 않나 궁금함
  • 의존성의 좋고 나쁨을 구분할 줄 아는 것이 중요한 역량임. 내 생각에는 유료 의존성은 대개 불리함. 제공하는 회사가 락-인 유도를 위해 설계했을 가능성이 큼. "의존성 미니멀리즘"이 좋은 컨셉임 (VitalikButerin의 관련 트윗)

    • 유료 의존성은 지원이 한 군데밖에 없어서, 제공 회사가 문 닫으면 프로젝트 전체가 위험해짐. 대부분의 회사는 영원하지 않기 때문에, 의존성의 미래가 내 프로젝트 궤도에 영향을 미치는지 꼭 따져야 함

    • 비기술 출신 팀이 강제한 유료 의존성에서 나쁜 경험 있음. 반면 사이드키크처럼 커뮤니티에서 널리 쓰이는 '오픈코어' 의존성은 갑자기 사라질 확률이 훨씬 낮아서 믿을 만함. 유료의 장점은 회사가 건전하게 운영될 때 지원 걱정을 안 해도 된다는 점임

    • 회사 제공 유료/무료 컴포넌트 모두 벤더 락인은 존재함. 위험 관리는 통합 팀의 몫이고, 대안을 찾거나 모듈화해서 리스크를 조절해야 함

    • 유료라면 반드시 오픈 표준이나 대체 구현체가 필요한 인터페이스로 납품해야 락-인을 막을 수 있음. 다른 선택지가 있으면 전환 옵션이 유지됨

    • 유료 의존성이 나쁘다는 것은 비용이 부족할 수 있다는 신호일 수 있음. 내 코드를 지원하는 게 누군가의 '업무'이길 원함. 지원자 수가 많거나 자발적으로 책임지는 개발자가 많으면 안정되고, 개인 프로젝트라면 소스 오픈이라도 문제 생길 때 지원해줄 사람이 필요함. 회사가 중단해도 방치 않으려는 책임감이 중요함

  • 많은 사람들이 신규 코드 작성에 집착하는 경향이 있는데, 실제로는 형편없는 의존성조차 사용이 훨씬 더 효율적인 경우가 9할임

    • 의존성은 양날의 검임. 소프트웨어 대기업은 코드 유지를 포기하고 새로 작성하는 게 더 싸서 그렇게 하는 경우가 많음. 소규모 웹/브랜딩 업체에선 고품질 백엔드가 사실상 필요 없음. 대신 dreaded 엔터프라이즈 패턴이 생긴 이유는, 5년 후에도 문서화와 업계 기억이 사라져도 외부 의존성 없이 보존되는 코드의 격리와 유지보수가 가능하게 하려는 목적임. 외부 의존성은 지원 중단, 혹은 파괴적 변화 발생 리스크 두 가지를 내포함. 결국 기능 개발 흐름에 영향이 감. 내부 컴포넌트면 이런 트레이드오프도 내부에서 통제 가능함. SaaS라면 단기적 성공을 위해 의존성을 빠르게 쓰는 게 맞고, 안전과 장기 지원이 필수라면 멀리 봐야 성공함. 신규 코드 작성이 조직의 병목이 되는 경우는 거의 없음

    • 회사에서 보안 취약점 및 라이선스를 얼마나 진지하게 관리하는지 궁금함. 예전에는 의존성에 관대했는데, 보안과 라이선스에 엄격한 회사로 옮긴 뒤 엄청나게 관점이 바뀜

  • 라이브러리와 프레임워크의 차이도 중요함. 라이브러리는 한 가지 일을 잘 하는 툴이고, 프레임워크는 앱 구조 전체를 규정함. Go 커뮤니티는 대형 프레임워크를 멀리하며, 표준 라이브러리와 경량 라이브러리, 그리고 필요하면 소스 복사/붙여넣기를 선호함. 예를 들어 Gin(웹API), GORM(ORM) 같은 프레임워크는 사용성도 좋지만, 내부 구조를 제한하고 복잡성을 키움. Go는 표준 SDK 자체가 상당히 강하기 때문에 필요 이상으로 의존성 추가 안하는 게 맞다고 생각함

  • 저자는 뉴질랜드 출신임. 뉴질랜드의 Number 8 wire 정신, 즉 '있는 것과 손재주로 어떻게든 해결하는 태도'가 배경에 깔려 있음 (Number 8 wire 위키 문서)

    • 뉴질랜드가 아닌 다른 나라의 경험 많은 개발자들도 비슷하게 동의하는 경우가 많음. 잘못된 의존성 선택이나 필요 없는 라이브러리 업그레이드로 고생한 경험은 거의 모두가 있음

    • 뉴질랜드 출신으로 느끼기에 Number 8 Wire 멘탈리티는 이미 20년 전에 사라진 것 같음

    • 오늘 처음 알게 된 사실임. 호주의 "She'll buff out, mate" 유행어가 떠오름

  • 의존성의 대규모, 상용화 기반은 확장성에도 영향을 줌. 나보다 100~1000배 큰 규모로 배포하는 곳에서 활용된 도구라면 내 문제에서 한계에 부딪칠 확률이 낮음. 그 규모에서 버그도 먼저 발견하거나 고치고, 결과적으로 내게 더 안전하게 돌아옴

    • 대형 라이브러리도 소규모 환경에서 아예 안 돌아가는 경우가 있음. 예를 들어 Swift 프로토콜버퍼 컴파일러가 예전엔 예측 못한 필드에서 크래시가 났음. 많은 대형 회사도 대규모 목적 외에는 직접 해당 경로를 테스트하지 않음

    • 큰 회사(Meta, Google, Microsoft 등)에서 만든 유명 라이브러리에서 심각한 버그 발견 경험이 있음. 이슈 신고해도 고치는데 시간이 오래 걸리고, 변화 거부감도 심함. 이런 상황에서 결국 직접 구현하니 오히려 더 빠르고 성능도 향상됐음. 특히 컨설팅 업계는 고객의 불합리한 요청으로 작업의 방향이 흔들리곤 함. 개발자로서 내가 직접 할 수 있다는 확신이 커지니, 굳이 거대한 외부 의존성보다 내가 구현하는 게 나을 때가 많아짐. 물론 브라우저나 AI 모델처럼 정말 대규모 과제는 안 하지만, 예를 들어 로컬 기반 추론 엔진이나 HTML 렌더러, 직접 만든 그래프 데이터베이스 등은 직접 구현함. 의뢰인의 기대는 새로운 것(혁신)이 아니라 위험 감소임. 내가 직접 개발하면 일정 준수가 훨씬 수월함. 구글이나 다른 대기업의 문서를 들여다보는 시간보다 직접 만드는 게 더 효율적임. 최근 3개월 동안 12시간씩 야근하며 이전 팀이 방치한 프로젝트 구하느라 밤늦게 이런 생각이 많아졌음