iBetter Books
수정

캐싱(mo.cache)과 비싼 연산 최적화

marimo의 반응형 실행은 강력하지만 트레이드오프가 있습니다. 어떤 위젯을 조작하면 그것에 의존하는 모든 셀이 자동으로 재실행됩니다. 가벼운 연산이라면 문제없습니다. 하지만 수 초 이상 걸리는 연산이 재실행 체인 안에 있다면, 조금만 조작해도 오래 기다려야 합니다.

marimo는 이 문제를 위한 캐싱 데코레이터를 세 가지 제공합니다.

캐싱이 필요한 상황

먼저 캐싱이 없을 때 어떤 문제가 생기는지 봅니다.

import marimo as mo# 셀 1 — 파라미터 선택n_clusters = mo.ui.slider(2, 10, value=3, label="클러스터 수")n_clusters
import marimo as moimport time# 셀 2 — 비싼 연산 (시간이 걸리는 계산을 가정)def train_model(n):    time.sleep(3)  # 무거운 학습 과정을 가정    return {"clusters": n, "score": 0.85}result = train_model(n_clusters.value)result

슬라이더를 3에서 4로 바꾸면 셀 2가 재실행되어 3초를 기다려야 합니다. 4에서 3으로 돌려도 또 3초입니다. 같은 입력인데도 계산을 처음부터 다시 합니다.

캐싱 데코레이터를 쓰면 같은 입력에 대해 이전 결과를 돌려줍니다.

@mo.cache

@mo.cache는 함수 인자와 클로저 변수, 함수 코드 자체를 기반으로 캐시 키를 만듭니다. 같은 인자로 다시 호출하면 재계산 없이 캐시된 결과를 반환합니다.

import marimo as mo@mo.cachedef compute_statistics(data_path: str, column: str):    # 파일을 읽고 통계를 계산 (시간이 걸린다고 가정)    import pandas as pd    df = pd.read_csv(data_path)    return {        "mean": df[column].mean(),        "std": df[column].std(),        "min": df[column].min(),        "max": df[column].max(),    }
import marimo as mo# 같은 path와 column으로 반복 호출해도 첫 번째 이후는 캐시에서 즉시 반환stats = compute_statistics("data/scores.csv", "score")stats

@mo.cache는 메모리에 결과를 저장합니다. 노트북을 종료하면 캐시가 사라집니다.

캐시 키는 인자 값, 함수 코드, 클로저에 있는 외부 변수를 함께 고려합니다. 인자가 pickle 가능하면 hashable 여부에 관계없이 캐시 키로 쓸 수 있습니다. thread-safe하게 설계되어 있어 병렬 셀 실행 환경에서도 안전합니다.

함수 코드를 수정하면 캐시가 무효화됩니다. 함수 내부 로직을 바꾸면 이전 결과가 더 이상 유효하지 않으므로, marimo가 자동으로 처리합니다.

@mo.lru_cache

@mo.lru_cache는 캐시 크기를 제한하는 버전입니다. 많은 종류의 인자로 함수를 반복 호출할 때 메모리가 무한히 늘어나는 것을 방지합니다.

import marimo as mo@mo.lru_cache(maxsize=128)def fetch_user_data(user_id: int):    # user_id마다 다른 결과를 캐시    import time    time.sleep(0.5)    return {"user_id": user_id, "name": f"User {user_id}"}
import marimo as mo# 서로 다른 user_id 100개 이상 호출 시,# 오래된 캐시부터 제거하며 최신 128개만 유지data = fetch_user_data(42)data

maxsize 인자로 최대 캐시 항목 수를 정합니다. 이 수를 초과하면 가장 오래된 항목부터 제거합니다(LRU, Least Recently Used). 기본값은 Python 표준 functools.lru_cache와 유사하게 동작합니다.

@mo.persistent_cache

@mo.persistent_cache는 디스크에 결과를 저장합니다. 노트북을 종료하고 다시 시작해도 캐시가 남아 있습니다.

import marimo as moimport numpy as np@mo.persistent_cachedef compute_embedding(text: str, model_name: str) -> np.ndarray:    # 텍스트 임베딩 계산 — 수 분이 걸릴 수 있는 연산    import time    time.sleep(5)  # 임베딩 모델 추론을 가정    # 실제 코드에서는 sentence-transformers 등을 씀    return np.random.rand(768)
import marimo as mo# 노트북을 재시작해도 이전에 계산된 결과를 디스크에서 불러옴embedding = compute_embedding("안녕하세요, 반갑습니다.", "multilingual-e5")embedding

@mo.persistent_cache는 데이터셋 전처리, 모델 학습, 임베딩 계산처럼 한 번에 수 분씩 걸리고 노트북 재시작마다 반복하기 싫은 연산에 씁니다.

디스크의 어디에 저장되는지는 공식 문서에 명시적으로 기재되어 있지 않습니다. 프로젝트 디렉터리 안이거나 사용자 캐시 디렉터리일 가능성이 있지만, 이 부분은 실제 실행으로 확인하는 것을 권장합니다.

세 가지 데코레이터 비교

데코레이터 저장 위치 노트북 재시작 후 크기 제한 적합한 상황
@mo.cache 메모리 사라짐 없음 반복 호출이 많고 빠른 응답이 필요할 때
@mo.lru_cache 메모리 사라짐 있음 (maxsize) 메모리 사용량을 제한해야 할 때
@mo.persistent_cache 디스크 유지됨 없음 노트북 재시작 후에도 결과를 보존해야 할 때

반응형 재실행에서 캐싱 활용 패턴

캐싱 데코레이터를 반응형 노트북의 흐름 안에서 쓸 때는 함수로 감싸는 패턴이 핵심입니다.

import marimo as mo# 셀 1 — 파라미터 위젯threshold = mo.ui.slider(0, 100, value=50, label="임계값")threshold
import marimo as moimport pandas as pd@mo.cachedef load_and_filter(path: str, min_value: int):    df = pd.read_csv(path)    return df[df["value"] >= min_value]# 셀 2 — threshold.value가 바뀔 때마다 이 셀 재실행# 같은 threshold.value + path 조합이면 캐시에서 반환filtered_df = load_and_filter("data/dataset.csv", threshold.value)filtered_df

슬라이더를 50에서 60으로 바꾸면 load_and_filter("data/dataset.csv", 60)이 호출됩니다. 60을 처음 본 것이면 실제로 계산합니다. 60으로 돌아오면 캐시에서 즉시 반환합니다. 50과 60 사이를 오가는 탐색에서 파일 I/O가 한 번씩만 일어납니다.

pin_modules 옵션

@mo.cachepin_modules=True 옵션을 제공합니다. 이 옵션을 켜면 함수가 사용하는 모듈의 버전을 캐시 키에 포함시킵니다. 라이브러리 업그레이드 후 결과가 달라질 수 있는 상황에서 오래된 캐시가 그대로 쓰이는 것을 방지합니다.

import marimo as mo@mo.cache(pin_modules=True)def vectorize_text(texts: list) -> list:    from sentence_transformers import SentenceTransformer    model = SentenceTransformer("all-MiniLM-L6-v2")    return model.encode(texts).tolist()

기본값은 pin_modules=False입니다. 모듈 버전 변경이 결과에 영향을 주지 않는 경우라면 굳이 켤 필요는 없습니다.

정리

  • @mo.cache는 메모리에 캐시를 저장합니다. 노트북 종료 시 사라집니다.
  • @mo.lru_cache는 메모리 캐시에 크기 제한을 추가합니다.
  • @mo.persistent_cache는 디스크에 저장해 노트북 재시작 후에도 캐시를 유지합니다.
  • 비싼 연산을 함수로 감싸고 데코레이터를 붙이면, 같은 인자로 반복 호출할 때 재계산 없이 이전 결과를 반환합니다.
  • 반응형 노트북에서는 위젯 값을 함수 인자로 전달하는 패턴으로 캐싱과 반응형 실행을 함께 활용합니다.
Ch 05. 캐싱(mo.cache)과 비싼 연산 최적화 — 실전 marimo | iBetter Books