당신의 Fork는 당신의 인내심보다 오래 살아남을 것이다. A Systems Thinking Post-Mortem.

발행: (2026년 2월 16일 오후 10:36 GMT+9)
13 분 소요
원문: Dev.to

Source: Dev.to

위에 제공된 내용만으로는 번역할 텍스트가 없습니다. 번역을 원하는 전체 본문을 복사해서 알려주시면, 요청하신 대로 한국어로 번역해 드리겠습니다.

모든 내부 포크는 한 줄짜리 선언으로 시작한다

“우리는 이 파일 하나만 패치하면 돼.”

6개월 후, 당신은 네 개의 병렬 저장소를 관리하며, 모든 upstream 릴리스를 두려워하고, 패치를 유지하는 데에 실제로 구현하려던 일보다 더 많은 시간을 쓰게 된다.

나는 바로 그 경험을 했기에 안다. 나는 973 ROS 패키지를 지원되지 않는 OS로 포팅하기 위해 네 개의 upstream 도구를 포크했다. 성공했다 — 61 %의 패키지가 컴파일됐고, turtlesim이 실행됐으며, 내 데모는 성공적이었다. 하지만 그 포크가 나를 삼켜버렸다.

이것은 전쟁 이야기가 아니다. 이것은 시스템‑다이내믹스 진단으로, upstream 도구를 포크하는 것이 어떠한 규율도 뛰어넘을 수 없는 구조적 함정을 만든다는 것을 설명한다.

설정

나는 ROS 2 Jazzy(https://docs.ros.org/en/jazzy/)(Robot Operating System)를 openEuler 24.03 LTS에 포팅하고 있었다 — ROS가 공식적으로 지원하지 않는 Linux 배포판이다. ROS 빌드 툴체인(bloom, rosdep, rospkg, rosdistro)은 지원 플랫폼 목록을 하드코딩하고 있으며, openEuler는 그 목록에 없다.

옵션

옵션설명장점단점
업스트림에 기여공식 도구에 openEuler 지원을 추가하는 PR을 제출한다.지속 가능하고 커뮤니티가 소유함.느리며 유지관리자의 호의에 의존함.
전체 포크네 개의 저장소를 복제하고 직접 openEuler 지원을 추가한 뒤 소스에서 빌드한다.빠르고 독립적임.이제 유지 관리 부담을 내가 짊어짐.

나는 옵션 2를 선택했다. 당연히 그렇다. 시연을 전달해야 했거든요.

The Fix That Fails (R1)

        (Problem)                      (Relief)
    TOOLCHAIN DOESN'T    ---------->  TOOLCHAIN WORKS
    RECOGNIZE openEuler                   |
         ^                                |
         |         (Short Term)           |
         |          BALANCING             |
         |            LOOP                |
         |                                v
         +------  ---------+
         |         (Intervention)         |
         |                                |
         |                                |
         |  (Long Term Side‑Effect)       |
         |    REINFORCING LOOP (R1)       |
         |    "Fixes that Fail"           |
         |                                |
         |                                v
    +-----------+                  +-----------------+
    | FORK GETS |   (frozen in time)
          ^                              |
          |                              |
          |       REINFORCING            v
          |         LOOP (R2)        METADATA ROTS
          |      "Data Decay"        (Wrong versions,
          |                           missing packages)
          |                              |
          |                              v
          |                        BUILD FAILURES
          |                        INCREASE
          |                              |
          +------------------------------+
              (Need more manual
               patching of YAML)

공식 rosdistro가 매일 업데이트를 받으면, 내 포크는 점점 뒤처집니다. 매일 뒤처질수록 openEuler 호환성과 무관한 이유로 빌드가 더 많이 실패합니다 – 메타데이터가 오래되어서 실패합니다.

나는 공식 YAML을 읽고 각 의존성을 dnf list를 통해 openEuler 패키지에 매핑하려는 스크립트(auto_generate_openeuler_yaml.py)를 작성했습니다. 안타깝게도:

  • 실제 openEuler 머신에서만 실행할 수 있습니다.
  • CI에서는 실행할 수 없습니다.
  • 오프라인에서는 실행할 수 없습니다.

그래서 실행을 기억해야 하는 수동 프로세스가 되며, 기억하지 못할 때마다 데이터가 조금씩 부패합니다.

R1 + R2가 실제로 어떻게 보이는가

아래는 내 시스템(EulerMaker)에서 실제로 측정한 수치입니다:

ArchitectureSuccessDep GapsFailuresInterruptedTotal
aarch64606215152973
x86_6459721415111973
  • 성공률 61 %turtlesim이 실행됩니다. 이것이 좋은 소식입니다.
  • 나쁜 소식: 214개의 의존성 격차와 151개의 빌드 실패는 두 개의 강화 루프에 의해 축적된 문제들입니다. 각 격차는 내 포크된 메타데이터가 잘못되었거나 포크된 툴체인이 실제 툴체인에서는 하지 않을 일을 했다는 것을 의미합니다. 업스트림이 움직일 때마다, 그때그때 내 포크가 따라가지 못해 597개의 성공 중 일부가 새로운 실패로 전환됩니다.

시스템이 “실패”하고 있는 것이 아니라 드리프트하고 있습니다. 드리프트는 포크를 통해 도입된 구조적 함정 때문에 발생합니다. 지속 가능한 해결책은 끊임없이 포크를 수정하는 것이 아니라 업스트림에 기여하여 루프를 닫는 것입니다.

내가 놓친 지렛대 포인트

시스템 사고에서 지렛대 포인트라는 개념이 있습니다 — 구조의 작은 변화가 행동에 큰 변화를 일으키는 지점들. Meadows는 시스템의 규칙을 가장 높은 지렛대 포인트 중 하나로 평가했습니다.

내 포크는 하나의 암묵적인 규칙 아래에서 동작하고 있었습니다:

“우리는 툴체인의 자체 버전을 유지합니다.”

이 규칙은 업스트림과의 모든 상호작용을 적대적인 관계로 만들었습니다. 업스트림 업데이트는 개선이 아니라 위협이었습니다.

고지렛대 대안은 규칙을 다음과 같이 바꾸는 것이었습니다:

“우리는 우리의 패치를 업스트림에 받아들여지게 합니다.”

이 규칙 하에서는 모든 업스트림 업데이트가 우리 플랫폼 지원을 포함한 개선이 될 것입니다. 내 시스템을 파괴하던 동일한 힘(업스트림 모멘텀)이 이제는 시스템을 유지시키는 힘이 될 것입니다.

왜 이렇게 하지 않았는지 저는 압니다. 업스트림에 기여하는 것은 느리고, 정치적이며, 불확실합니다. 포크는 빠르고, 제어 가능하며, 확실합니다. 하지만 단기적으로는 “빠르고 확실함”이 장기적으로는 “비싸고 취약함”으로 변했습니다. 그것이 바로 Fixes that Fail 전형의 전체 요점입니다 — 증상적인 해결책이 순간적으로 항상 더 매력적입니다.

실제로 배운 점

  • 포크는 자산이 아니라 부채이다.
    포크를 하는 순간, 모든 업스트림 커밋마다 증가하는 유지보수 의무가 생깁니다. 제한된 시간 안에 변경 사항을 업스트림에 반영하지 못하면, 복리로 늘어나는 구조적 부채를 쌓게 됩니다.

  • 데이터 포크는 코드 포크보다 더 나쁘다.
    코드를 포크하는 것 자체도 나쁘지만, 데이터를 포크하는 것은(예: 내 rosdistro YAML 파일) 더 악화됩니다. 데이터는 조용히 오래되어 버립니다. 코드는 크게 깨지는데—함수 시그니처가 바뀌면 컴파일 오류가 발생합니다. 데이터는 조용히 부패해—패키지 버전이 잘못되어 몇 주 뒤에 알 수 없는 런타임 오류가 발생합니다.

  • 무차별 접근법은 가치가 있다 — 탐색 수단으로서.
    v1은 실패가 아니었습니다. 의도적인 무차별 조사를 통해 인텔리전스 맵을 만들었습니다:

    • 973개의 패키지 식별
    • 어떤 것이 동작하는지
    • 정확히 어디에 격차가 있는지

    실패는 그 탐색 수단이 실제 시스템이 될 수 있다고 생각한 데에 있습니다. 탐색 수단은 일회용이며, 실제 시스템은 구조적 완전성이 필요합니다.

  • 당신이 사용하는 임시 방편을 알아라.
    나는 시스템에 virtualenv 우회, RHEL‑클론 등록, 고정된 YAML 스냅샷 등을 가지고 있습니다. 각각이 임시 방편임을 알고 있습니다. 대부분의 팀은 이를 추적하지 않으며, 누군가 “왜 우리 빌드가 45분이나 걸리고 30%는 실패하나요?”라고 물을 때까지 조용히 쌓여갑니다. 아무도 답을 못합니다.

후속 작업

v1은 구조적 한계에 부딪히면 브루트‑포스 파이프라인이 어떻게 보이는지를 알려주었습니다. 저는 **v1 사후 분석 레포지토리**에 트랩 아키텍처를 포함한 전체 시스템 동역학을 문서화했습니다.

v2는 사이클을 깨기 위해 설계되었습니다: 구축하기 전에 검증하고, 구축 후가 아니라. 973개의 패키지를 파이프라인에 투입하고 40 %가 실패하는 대신, v2는 먼저 OS 환경을 탐색해 격차를 식별하고 빌드 자원을 소비하기 전에 검증된 의존성 그래프를 기반으로 작동합니다. 자세한 내용은 **v2 검증 엔진 레포지토리**에 있습니다.

이 구조적 교훈은 ROS 포팅을 훨씬 넘어 적용됩니다:

  • OSS 라이브러리의 내부 포크: R1을 사용하고 있습니다. 패치를 업스트림에 보내거나 유지보수 비용을 계획하세요.
  • 업스트림이 덮어쓰는 설정 파일을 패치하는 경우: R2를 사용하고 있습니다. 병합을 자동화하거나 데이터 부패를 받아들여야 합니다.
  • 빌드 스크립트에서 --skip-broken, --force 또는 || true 사용: 증상을 가리고 있는 것입니다. 각 플래그는 임시방편—그 수를 세어 보세요.

모든 포크는 “이번 한 번만 패치”로 시작합니다.
모든 중독은 “이번 한 번만 흡연”으로 시작합니다.

시스템은 당신의 의도를 신경 쓰지 않습니다; 구조를 신경 씁니다.

시스템 동역학 다이어그램이 포함된 v1 사후 분석: the_brute_force_probe
v2 검증 엔진: the_adaptive_verification_engine

0 조회
Back to Blog

관련 글

더 보기 »