Haeminway haeminway
English
기술 노트로
3 분 분량

시트 셀에 JSON·이미지를 통째로 넣으면 터진다

구글 시트 셀은 5만 자에서 막힌다. 사진·서명을 data URL로 기록 행에 넣으면 저장이 깨진다. 셀에는 메타데이터만 남겨라.

셀 하나에 들어가는 텍스트는 5만 자가 한계다. 사진이나 서명을 data URL(data:image/png;base64,...)로 만들어 기록 행에 그대로 넣으면, 한 장만으로 수십 KB에서 수 MB가 되어 이 한계를 바로 넘는다. 저장은 조용히 잘리거나 실패한다.

왜 중요한가

현장 점검앱에서 한 행에 사진 여러 장과 서명을 함께 저장하려다 막히면, 그날 입력한 기록이 통째로 유실된다. 에러도 명확하지 않아 원인을 찾는 데 시간이 걸린다. 비용은 “다시 점검하러 가는 출장”이다.

정답: 바이너리는 분리, 셀에는 경로만

기록 행에는 메타데이터와 파일 ID만 저장한다. 바이너리는 Drive에 파일로 두고, 셀에는 fileId만 남긴다.

// 나쁨: data URL을 기록에 직접 저장 → 5만 자 초과
record.photo = "data:image/jpeg;base64,/9j/4AAQ...";

// 좋음: 파일은 Drive에, 셀에는 id와 메타만
function savePhoto(base64, mime) {
  const blob = Utilities.newBlob(Utilities.base64Decode(base64), mime, "photo.jpg");
  const file = DriveApp.getFolderById(FOLDER_ID).createFile(blob);
  return { fileId: file.getId(), bytes: blob.getBytes().length };
}

바이너리(data URL)는 화면에서 export할 때만 쓰는 export-only 상태로 두고, 저장 모델에는 절대 넣지 않는다.

같이 걸리는 함정 두 개

1) 수식 주입. =, +, -, @로 시작하는 사용자 입력은 시트에서 수식으로 해석된다. 저장 전에 '를 앞에 붙여 막는다.

function escapeFormula(v) {
  return /^[=+\-@]/.test(String(v)) ? "'" + v : v;
}

2) JSON 직렬화. JSON.stringify()Date, Map, Set, undefined, NaN을 안전하게 복원하지 못한다. 날짜는 ISO 문자열로 저장하거나 replacer/reviver를 명시한다.

깊이: Properties도 같은 함정

PropertiesService는 값 하나당 9KB 한계다. 커서·작은 설정·idempotency key 정도만 두는 저장소이지 데이터베이스가 아니다. 9KB를 넘을 수 있는 상태는 Sheet, Drive JSON blob, CacheService, 외부 DB 중 하나로 설계한다.

핵심

큰 것은 셀 밖에, 셀에는 가리키는 값만. 바이너리는 Drive에 두고 기록 행에는 fileId와 메타데이터만 남긴다.

자주 묻는 질문

구글 시트 셀의 텍스트 한도는 얼마이고, 초과하면 어떻게 되나요?
셀 하나에 들어가는 텍스트는 5만 자가 한계입니다. 초과 시 저장이 조용히 잘리거나 실패해 그날 입력한 기록 전체가 유실될 수 있습니다.
사진이나 서명을 기록 행에 저장할 때 올바른 방법은 무엇인가요?
data URL을 셀에 직접 넣지 말고, 파일을 Drive에 저장한 뒤 셀에는 fileId와 메타데이터만 남깁니다. data URL은 화면 export 전용 상태로만 사용합니다.
사용자 입력을 시트에 저장할 때 수식 주입을 막으려면 어떻게 하나요?
=, +, -, @로 시작하는 값은 시트가 수식으로 해석하므로, 저장 전에 앞에 작은따옴표(')를 붙여 문자열로 처리합니다.