캐싱(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.cache는 pin_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는 디스크에 저장해 노트북 재시작 후에도 캐시를 유지합니다.- 비싼 연산을 함수로 감싸고 데코레이터를 붙이면, 같은 인자로 반복 호출할 때 재계산 없이 이전 결과를 반환합니다.
- 반응형 노트북에서는 위젯 값을 함수 인자로 전달하는 패턴으로 캐싱과 반응형 실행을 함께 활용합니다.