← 소프트웨어와 제품
심층 리포트DEEPER INTO TECHNOLOGY

D1의 50MB 결과 제한: LIMIT만 줄이면 충분할까?

D1의 50MB 쿼리 결과 제한에 대비해 행 개수·응답 바이트·대량 내보내기를 어떻게 나눠 설계할까?

댓글 남기기 ↓
먼저 읽는 핵심

LIMIT은 반환할 행 수를 줄이지만 각 행의 본문 크기까지 제한하지 않는다. D1의 새 결과 제한에 대비하려면 목록에 필요한 열을 먼저 고르고, 한 페이지의 바이트 예산과 큰 단일 행의 처리 경로를 정해야 한다. 전체 DB 내보내기는 일반 조회와 다른 기능·운영 조건으로 판단한다.

11월 4일에 바뀌는 것은 저장 용량이 아니라 한 번의 결과다

Cloudflare는 10월 30일, 2026년 11월 4일부터 D1의 쿼리 결과를 최대 50MB로 제한한다고 공지했다. 이를 넘는 조회에는 다음 오류가 반환될 예정이다. 이 글의 확인일인 11월 2일에는 적용 예정인 변경이다. 공식 변경 공지

D1 query result exceeded the maximum response size limit of 50 MB

D1은 Workers 애플리케이션에서 사용하는 SQL 데이터베이스다. 이번 제한은 “DB 전체가 얼마나 큰가”와 구분해야 한다. 큰 DB에서 몇 개의 작은 열만 조회할 수 있고, 상대적으로 작은 DB에서도 큰 본문들을 한꺼번에 반환할 수 있다.

공식 제한표에는 문자열·BLOB·행의 최대 크기 2,000,000바이트, SQL 문장 크기 100,000바이트, 쿼리 실행 시간 30초가 별도로 있다. 새 결과 크기 제한만 맞췄다고 다른 조건까지 통과하는 것은 아니다. D1 제한표와 적용일 각주

따라서 먼저 찾을 대상은 단순히 행 수가 많은 테이블이 아니다. 전체 보고서 본문, 큰 JSON, 여러 문서의 내용을 SELECT *로 가져와 목록이나 CSV를 만드는 경로다. 이 글은 그 경로를 목록·상세·대량 반출로 나누는 설계 분석이다. D1 원격 서버를 호출하거나 변경 적용 후 오류를 직접 관측한 글은 아니다.

같은 100행이 94KB와 54MB가 되는 사례

가상의 보고서 관리 서비스를 생각해 보자. 목록 화면에는 보고서 번호와 제목만 필요하지만, 서버는 상세 화면에 쓸 본문까지 미리 읽고 있다. 처음에는 짧은 문서만 있어서 문제가 보이지 않는다. 이후 장문 보고서를 가져오면 같은 페이지 크기에서도 결과가 커진다.

이를 확인하려고 Python 3.11.12·SQLite 3.51.0의 메모리 DB에 직접 만든 보고서 100개를 넣었다. 열은 id, title, body 세 개다. 짧은 본문은 한글 ‘가’ 300자, 긴 본문은 같은 글자 300,000자다. 긴 본문 하나는 이 입력에서 UTF-8 900,000바이트다. 실제 업무 문서나 개인정보를 사용하지 않았다.

로컬 입력과 조회 반환 행 본문 구성 결과 배열의 UTF-8 JSON 크기
작은 문서, 세 열 조회 100 모두 짧은 본문 94,285바이트
혼합 문서, 세 열 조회 100 긴 본문 60개 + 짧은 본문 40개 54,040,285바이트
같은 혼합 문서, ID·제목만 조회 100 본문을 반환하지 않음 3,285바이트

이는 ensure_ascii=False, 구분자 사이 공백 없는 Python JSON 직렬화의 실제 실행값이다. DB 파일 크기·압축 전송량·D1 내부 응답 크기를 측정한 값은 아니다. 메타데이터나 외부 응답 객체도 포함하지 않았다. 로컬의 54,040,285바이트를 D1의 동일 쿼리가 반드시 실패한다는 실험 증거로 쓰지는 않는다. 이 대조가 보여 주는 것은 행 수가 같아도 반환 내용에 따라 크기가 크게 달라진다는 사실이다.

한글 글자 수를 그대로 바이트 수로 세는 것도 피해야 한다. 이 예제의 ‘가’는 UTF-8에서 3바이트다. JSON 인코더가 문자를 이스케이프하거나 따옴표·역슬래시가 많은 내용이면 또 달라진다. 특정 문자열 길이에 고정 배수를 곱하는 것보다 서비스가 실제 생성하는 표현을 직렬화해 재는 것이 유용하다.

목록에 필요한 열부터 고정한다

아래 두 조회는 위 로컬 예제에서 실행한 형태다. 바인딩 값은 마지막 ID=0, 행 수=100이다. 실제 서비스에서는 인증된 사용자의 접근 범위·필터를 추가해야 한다.

-- 큰 본문까지 페이지마다 가져오는 경로
SELECT id, title, body
FROM reports
WHERE id > ?
ORDER BY id
LIMIT ?;

-- 목록에 필요한 열을 명시한 경로
SELECT id, title
FROM reports
WHERE id > ?
ORDER BY id
LIMIT ?;

필요한 열을 명시하는 것을 열 투영(projection)이라고 한다. 위 사례에서는 페이지당 문서 수를 줄이지 않고도 가장 큰 열을 결과에서 뺄 수 있었다. 본문은 사용자가 문서를 열 때 해당 ID의 상세 조회로 가져오도록 분리할 수 있다. 이는 이 글의 설계 제안이며 완성된 관리 화면을 배포한 결과는 아니다.

화면 요구도 함께 바뀐다. 목록에는 제목·상태처럼 선택에 필요한 정보를 두고, 긴 본문은 상세 영역에서 읽게 한다. 좁은 화면에서는 목록으로 돌아갈 때 필터·이어 읽기 위치를 보존해야 한다. 본문 검색 미리보기가 필요하다면 전체 본문 대신 길이 제한이 있는 요약 열을 별도로 설계할 수 있다. 요약의 크기·생성 방식도 계약에 포함해야 목록이 다시 무거워지지 않는다.

ID·제목만 골랐다고 언제나 작은 것은 아니다. 제목 자체가 무제한이거나, 조인으로 같은 큰 값이 반복되면 여전히 커질 수 있다. 열 이름을 줄이는 작업 뒤에는 각 반환 필드의 허용 크기를 확인하는 일이 남는다.

페이지 예산은 평균 행 크기보다 최악의 행을 본다

다음은 D1이 정한 공식 권장값이 아니라 애플리케이션 설계용 가정이다. 한 페이지 목표를 4,000,000바이트, 응답 부가 정보 여유를 100,000바이트, 직렬화한 한 행의 상한을 1,000,000바이트라고 정하면 다음과 같다.

페이지 행 수 상한
= floor((페이지 예산 - 부가 정보 여유) / 한 행 상한)
= floor((4,000,000 - 100,000) / 1,000,000)
= 3행

이 계산은 행 상한과 부가 정보 여유가 실제로 지켜질 때만 유효하다. 평균 행이 1KB였다는 관측으로 같은 식을 쓰면 장문이 몰린 페이지를 놓친다. 평균·상위 분위수는 초기 크기를 고르는 참고 자료가 될 수 있지만 엄격한 상한의 증거는 아니다.

또한 4MB 목표가 모든 서비스에 적합하다는 뜻은 아니다. 모바일 첫 화면에는 훨씬 작은 예산이 필요할 수 있다. 여기서는 공급자 제한에 바짝 붙이는 대신 자기 서비스의 지연·메모리·읽기 목적에 맞는 예산을 먼저 정한다는 관계를 설명하려고 선택했다. 조회 순서와 데이터 시점의 일관성은 별개다. 커서를 쓴다는 이유만으로 내보내기 전체가 같은 시점의 데이터가 되지는 않는다. 그 문제는 키셋 페이지네이션과 누락의 조건에서 다룬다.

행 개수 제한, 열 선택, 페이지 바이트 예산을 구분하고 목록·상세·내보내기 경로를 나누는 설명도

직접 그린 설계 설명도다. 실제 D1 화면이나 내부 처리 구현도가 아니다. 4MB는 본문 계산의 가상 애플리케이션 예산이다.

결과를 받은 뒤 자르면 이미 늦을 수 있다

“일단 모두 읽고 JSON이 크면 뒤를 잘라 보내면 되지 않을까?”라는 접근에는 두 단계가 섞여 있다. D1에서 결과를 가져오는 단계와 Worker가 클라이언트 응답을 만드는 단계다. 앞 단계가 크기 초과로 실패하면 뒤 단계의 잘라내기 코드는 정상 결과를 받지 못한다. 클라이언트 전송을 스트리밍하는 것만으로 앞선 거대 SELECT가 작아지는 것도 아니다.

D1 Prepared statement 문서는 all()과 동등한 run()의 반환 객체에 결과 배열과 메타데이터가 들어가며, raw()는 배열들의 배열을 돌려준다고 설명한다. 표현을 바꾸면 애플리케이션 직렬화 크기는 달라질 수 있지만 raw()를 50MB 제한의 우회 경로로 취급할 근거는 없다.

응답 바이트 관측 자체는 도움이 된다. 작은 열 집합과 제한된 행 수로 조회한 뒤 결과 바이트·지연·오류를 기록하면 초기 페이지 크기를 조정할 수 있다. 다만 원문 본문을 로그에 복사할 필요는 없다. 쿼리 유형, 반환 행 수, 선택한 열 집합, 측정 위치, 크기, 오류 분류면 이 판단에 필요한 정보를 상당 부분 남길 수 있다.

크기 초과에서는 같은 경계를 더 작게 읽는다

공식 오류 문서는 결과 크기 초과에 대해 필요한 열만 선택하거나 결과를 페이지로 나누도록 안내한다. 잠깐의 네트워크 장애와 달리, 동일한 큰 결과를 그대로 재시도하면 크기 문제를 해결하지 못한다. 애플리케이션은 오류를 구분하고 조회 내용을 바꾸는 경로가 필요하다.

자체 실험에서는 D1 오류를 흉내 냈다고 주장하지 않고, 로컬 JSON에 자체 4,000,000바이트 예산 검사를 붙였다. 실패하면 마지막 성공 경계를 유지한 채 행 수를 절반으로 줄였다.

같은 시작 경계에서 시도 로컬 JSON 바이트 자체 예산 판정
100행 54,040,285 초과
50행 45,002,133 초과
25행 22,501,058 초과
12행 10,800,499 초과
6행 5,400,247 초과
3행 2,700,124 통과

성공한 첫 페이지는 ID 1, 2, 3, 다음 경계는 3이다. 이어 읽기에서 4, 5, 6이 나오는 것도 확인했다. 실패했던 100행의 마지막 ID로 경계를 옮기면 사용자에게 돌려주지 않은 데이터를 건너뛴다. 실제 서비스에서는 재시도 횟수·전체 대기 시간을 제한하고, 같은 조건에서 줄여 읽어도 시점이 달라질 수 있음을 명시해야 한다. 로컬 실험은 정적인 입력만 사용했다.

한 행만 남아도 실패할 수 있다. 같은 900KB대 행에 자체 예산을 512,000바이트로 낮추자 더 줄일 행 수가 없었다. 예제는 빈 성공 페이지로 위장하거나 ID를 건너뛰지 않고 needs-projection-or-separate-path 상태와 기존 경계 0을 반환한다. 이는 예제 내부 상태 이름이며 D1 오류 코드가 아니다. 서비스에서는 “상세 조회로 열기” 또는 별도 파일 요청처럼 사용자가 완료할 수 있는 경로를 제시해야 한다.

이 반복은 경계 보존을 검증하기 위한 실험이다. 운영에서 큰 실패를 여러 번 발생시키며 적정 크기를 찾으라는 권장이 아니다. 입력·출력 크기가 통제된다면 처음부터 보수적인 행 수를 고르고, 실패 복구를 예외 경로로 두는 편이 낫다.

전체 데이터가 필요하면 export의 다른 대가를 본다

공식 변경 공지는 REST API의 import·export 작업은 이번 제한의 대상이 아니라고 명시한다. 이 예외를 “REST API로 SELECT를 보내면 제한이 없다”로 읽어서는 안 된다. 작업 종류의 예외다. 적용 범위와 예외

또한 export는 일반 사용자의 필터된 CSV 다운로드와 목적이 다를 수 있다. 공식 import/export 문서는 DB나 단일 테이블을 SQL 파일로 내보내는 방식을 설명하며, export 실행 중 다른 DB 요청이 차단되고 가상 테이블이 포함된 DB의 export가 지원되지 않는다는 제한을 적고 있다. 이 글에서는 export를 실행하지 않았다.

필요한 결과 우선 검토할 경로 결정 전에 확인할 것
사용자가 훑는 보고서 목록 작은 열 집합 + 제한된 페이지 필드 상한, 느린 기기의 응답 크기
특정 보고서의 본문 권한 확인 후 상세 조회 단일 행·본문 크기와 실패 안내
특정 조건의 많은 행을 CSV로 전달 별도 작업에서 작은 조회들을 이어 읽고 파일 생성 데이터 시점, 재시작 경계, 중복·누락, 파일 권한
DB·테이블의 SQL 반출 공식 export 기능 요청 차단 시간, 가상 테이블, SQL 형식의 적합성

세 번째 경로는 설계 제안이다. 쿼리를 나눈다고 일관된 스냅샷이나 정확한 전체 내보내기가 자동으로 생기지는 않는다. 파일을 생성할 동안 변경을 허용할지, 특정 시점의 값을 보존해야 할지 먼저 정해야 한다. 반대로 작은 관리 목록 하나 때문에 DB 전체 export를 실행하면 필요 이상의 범위와 운영 부담을 떠안게 된다.

적용 전에 남길 조회별 판단표

운영팀이 이번 변경에 대비해 적을 최소 단위는 “D1 사용 여부”보다 큰 결과를 반환하는 조회 경로 한 개다. 다음 정보를 같은 기록에 남기면 대응을 고를 수 있다.

  1. 목록·상세·파일 반출 중 어떤 결과가 필요한가. 현재 선택 열에 큰 본문이 포함되는가.
  2. 실제 직렬화 크기를 어느 위치에서 측정했는가. 평균뿐 아니라 장문이 몰린 입력과 가장 큰 단일 행을 확인했는가.
  3. 초기 페이지 크기와 자체 예산은 무엇인가. 초과하면 같은 경계에서 줄여 읽을지, 열을 줄일지, 별도 작업으로 돌릴지 정했는가.
  4. 실패한 페이지에서 경계가 진행되지 않는가. 한 행도 처리하지 못할 때 사용자에게 다음 행동이 있는가.
  5. export를 선택했다면 요청 차단과 가상 테이블 조건을 확인했는가. 적용 후 공식 공지와 실제 오류를 다시 확인할 담당·시각이 있는가.

오프라인 실험 파일과 실행 결과를 내려받아 대조할 수 있다. Python 표준 라이브러리만 사용하며 python3 d1-byte-budget-lab.py로 실행한다. 외부 서비스·기존 DB·개인 파일에 접근하지 않는다. 결과의 assertions: passed는 위 유한 입력의 열 선택·크기·경계 복구 검사가 통과했다는 뜻이며 D1 호환성이나 운영 성능의 인증이 아니다.

연속 처리에서 메모리를 제한하는 방법은 스트림 역압과 버퍼, 실패 후 대기 비용을 제한하는 방법은 재시도와 전체 시간 예산으로 이어진다. 위 판단표를 다시 쓸 때는 상단 저장 기능을 이용하고, 소프트웨어 주제 모음과 RSS에서 후속 글을 확인할 수 있다.

다시 읽을 때의 기준
  • 100행이라는 같은 LIMIT도 큰 본문이 섞이면 결과가 수백 배 커질 수 있다. 목록의 열 계약부터 줄인다.
  • 애플리케이션 JSON 크기는 유용한 관측값이지만 D1 내부 제한의 바이트 계산을 그대로 재현한 값은 아니다.
  • 크기 초과에는 조회 범위를 바꾸고, 한 행도 예산을 넘으면 상세·별도 파일 경로를 둔다. 실패한 페이지의 경계는 진행시키지 않는다.
  • REST API import/export 예외를 일반 SELECT의 예외로 확대하지 않는다. export 중 요청 차단과 가상 테이블 제한을 확인한다.
SOURCES & CONTEXT

근거와 원자료

자료의 발행 시점과 이번 글에서 참고한 범위를 함께 기록합니다.

  1. D1 will enforce a maximum query result size of 50 MB ↗Cloudflare Developers · 2026-10-30

    10월30일 발표, 11월4일 적용 예정의50MB 결과 제한과 REST import/export 예외. 원격 실행·실제 제한 시험 없음.

  2. D1 Limits ↗Cloudflare Developers · 상시 갱신 문서

    2026-10-30 갱신 표와 각주5: 행·문장·시간·결과 한도를 구분. 내부50MB 바이트계산과 앱JSON의 동등성은 미확인.

  3. Debug D1 — error list ↗Cloudflare Developers · 상시 갱신 문서

    50MB 오류 문자열·시행일과 열 선택·페이지분할 권고. 실제 오류 수신 미실행.

  4. Import and export data ↗Cloudflare Developers · 상시 갱신 문서

    SQL export 범위, 가상 테이블 포함 DB 제한 및 export 중 다른 요청 차단. 원격 export 미실행.

  5. Prepared statement methods ↗Cloudflare Developers · 상시 갱신 문서

    run/all 반환 배열·메타데이터와 raw 배열 형식. raw의 제한 우회나 내부 직렬화 동일성 주장 없음.

갱신 기록

첫 원고. 공식 적용 예고와 API 문서를 대조하고 자체 SQLite·UTF-8 JSON 예제로 크기 차이와 실패 복구를 검증했다. 원격 D1 실행은 하지 않았다.

오류를 발견했다면 댓글로 알려 주세요 →
S
Starhunter

테크독해 운영·편집. AI·IT의 개념과 기술을 연결해 읽습니다.

QUESTIONS & DISCUSSION

이 글에 댓글 남기기

댓글 공개 방식을 확인하고 있습니다. 개인정보나 비밀 값은 적지 마세요.

댓글을 불러오는 중입니다.

    새 댓글

    8자 이상. 비밀번호를 잊으면 직접 수정·삭제할 수 없습니다. 다른 서비스와 다른 비밀번호를 사용해 주세요.

    내 댓글 확인·수정·삭제

    등록 후 안내된 댓글 번호와 비밀번호를 입력하세요. 승인 대기 중인 댓글도 확인할 수 있습니다.