iBetter Books
수정

디버깅 _ 반응형 실행 추적하기

marimo 노트북을 실제로 운영하다 보면 이런 상황을 만납니다. 슬라이더를 하나 움직였는데 관련 없어 보이는 셀이 재실행되거나, 반대로 분명히 바뀌었는데 어떤 셀이 업데이트되지 않는 것처럼 보입니다. 또는 편집 모드에서 셀을 저장하는 순간 에러 표시가 나타나는데 원인 변수를 찾기 어렵습니다.

이 챕터에서는 그런 상황을 추적하는 실전 방법을 다룹니다. PART 02에서 DAG와 자동 재실행 원리를 이미 이해했으니, 여기서는 그 원리를 실제 디버깅 상황에 적용합니다.

어느 셀이 왜 재실행되는가

반응형 실행에서 "왜 이 셀이 실행됐는가"를 묻는 것은 DAG를 따라가는 질문입니다. 특정 셀이 예상하지 않은 시점에 재실행된다면, 그 셀이 의존하는 변수 중 하나가 변경됐다는 뜻입니다.

추적 방법은 다음 순서로 접근합니다.

첫째, 의심되는 셀의 입력 변수 목록을 확인합니다. marimo 에디터에서 변수 이름을 클릭하면 그 변수를 정의한 셀과 참조하는 셀이 함께 강조 표시됩니다. 재실행 체인의 시작점을 찾는 가장 빠른 방법입니다.

둘째, 변수 패널을 활용합니다. 에디터 우측 패널에서 현재 노트북에 정의된 모든 변수와 각 변수를 정의한 셀을 볼 수 있습니다. 예상하지 못한 변수가 어디서 왔는지 파악하는 데 씁니다.

셋째, 셀 함수 시그니처를 읽습니다. marimo 파일을 텍스트 에디터로 열면 각 셀이 @app.cell 함수로 표현됩니다. 함수 매개변수가 그 셀의 입력 의존성입니다.

# 아래처럼 저장된 파일에서 _의 뒤에 오는 함수 인자가 의존 변수입니다.@app.celldef _(df, threshold):          # df와 threshold 둘 다 바뀌면 이 셀이 재실행됩니다.    filtered = df[df["score"] >= threshold.value]    return (filtered,)

이 함수 시그니처를 보면 셀이 무엇에 의존하는지 에디터 UI 없이도 파악할 수 있습니다.

에러 셀 격리하기

에러가 난 셀 자체보다 그 에러의 파급 범위를 파악하는 것이 중요합니다. marimo에서 어떤 셀이 에러를 일으키면, 그 셀의 출력 변수에 의존하는 모든 하위 셀도 실행되지 않습니다.

# Cell A — 에러 발생import pandas as pddf = pd.read_csv("data/missing_file.csv")   # FileNotFoundError 발생
# Cell B — df에 의존하므로 실행 자체가 막힘filtered = df[df["value"] > 0]filtered
# Cell C — filtered에 의존하므로 역시 실행 안 됨summary = filtered.describe()summary

에러 셀은 빨간 테두리로 표시됩니다. 하위 셀들은 입력이 준비되지 않았다는 상태로 표시됩니다. 에러를 수정하면 A, B, C가 순서대로 자동으로 재실행됩니다.

에러를 일시적으로 격리하고 싶다면 해당 셀의 코드를 주석 처리하거나, mo.stop으로 조건부로 실행을 멈추는 방법을 씁니다.

import marimo as mo# 파일이 없을 때 하위 셀이 에러로 물드는 것을 막는 방법import osdata_file = "data/dataset.csv"mo.stop(    not os.path.exists(data_file),    mo.md(f"`{data_file}` 파일이 없습니다. 데이터를 준비해 주세요."))import pandas as pddf = pd.read_csv(data_file)

파일이 없으면 mo.stop이 실행을 여기서 끊고 메시지를 표시합니다. df를 정의하지 않으므로 df에 의존하는 하위 셀들도 깔끔하게 대기 상태로 남습니다.

Jupyter에서는 중간 계산 결과를 확인하려고 print()를 자주 씁니다. marimo에서는 셀의 마지막 표현식이 자동으로 출력됩니다. print()보다 이 방식이 더 유용합니다.

# Jupyter 스타일 — marimo에서도 동작하지만 출력이 터미널로만 가고# 재실행 시 이전 print 결과가 지워지지 않습니다.data = [1, 2, 3, 4, 5]print(f"데이터 길이: {len(data)}")print(f"합계: {sum(data)}")
# marimo 스타일 — 셀 출력 영역에 표시, 재실행 시 갱신됩니다.import marimo as modata = [1, 2, 3, 4, 5]mo.md(f"데이터 길이: `{len(data)}`, 합계: `{sum(data)}`")

또는 중간 변수를 별도 셀에 두고 그 셀의 마지막에 변수를 출력하는 방법도 있습니다.

# 확인이 필요한 중간 계산을 별도 셀로 분리data = [1, 2, 3, 4, 5]processed = [x * 2 for x in data]processed   # 이 셀의 출력으로 표시됩니다.
# 중간값이 확인됐으면 다음 단계로 진행total = sum(processed)total

이렇게 하면 processed 값이 노트북 안에서 항상 보이고, 재실행 시 자동으로 갱신됩니다.

로깅이 필요한 경우, 즉 시간 경과에 따른 이벤트를 기록해야 한다면 Python 표준 logging 모듈을 쓰되 로그 파일로 출력하고 별도로 확인하는 방식을 권장합니다. marimo 셀 출력은 최신 상태 표시에 적합하고, 이벤트 기록에는 파일 로그가 더 자연스럽습니다.

marimo check로 정적 문제 잡기

편집 중 에디터가 표시하는 에러 외에도, 파일 전체를 한 번에 검사하는 방법이 있습니다.

marimo check notebook.py

PART 05에서 이 명령을 CI 맥락에서 소개했습니다. 로컬 디버깅에서도 유용합니다. 에디터를 열지 않고 파일 상태를 점검하거나, 편집 중 에러 표시를 놓친 경우를 잡는 데 씁니다.

출력 예시입니다.

notebook.py:  critical: multiple-definitions (MB002)    Cell at line 42: variable 'threshold' is defined in multiple cells    hint: rename one variable or merge the cells  warning: unused-variable    Cell at line 67: variable 'temp_result' is defined but never referenced

critical 수준 문제(MB002, MB003)는 노트북 실행을 막을 수 있습니다. warning 수준은 즉각적인 오류는 아니지만 검토할 만한 사항입니다. 소스 위치(파일 내 줄 번호)와 힌트가 함께 표시되므로 문제를 찾는 시간이 줄어듭니다.

marimo check가 아무것도 출력하지 않고 종료되면 정적 검사상 문제가 없는 상태입니다.

정리

  • 어떤 셀이 예상치 않게 재실행된다면, 그 셀의 입력 변수를 에디터에서 클릭해 의존 관계를 추적합니다.
  • 에러가 난 셀은 빨간 테두리로 표시되고, 그 셀의 하위 셀들도 실행이 막힙니다. mo.stop으로 에러를 격리해 파급 범위를 제어할 수 있습니다.
  • print() 대신 셀의 마지막 표현식으로 출력합니다. 재실행 시 자동으로 갱신되고 이전 출력이 쌓이지 않습니다.
  • marimo check notebook.py로 MB002·MB003 같은 정적 문제를 에디터 없이도 확인합니다.