-
Cloudflare D1이 갑자기 오류를 내는 날: 무료 한도와 스캔 행 수Programming 2026. 9. 19. 22:16728x90반응형

한도와 사용량을 따로 살피는 상황을 비유한 AI 생성 이미지입니다. 코드와 데이터가 그대로인데 어느 시점부터 D1 쿼리가 실패한다면, 최근 배포만 조사해서는 원인을 찾지 못할 수 있습니다. 무료 플랜의 일일 사용량 한도에 도달한 상황이라면 쿼리를 다시 보내도 같은 제약이 이어질 수 있기 때문입니다.
D1 한도 오류가 나면 요청 횟수보다 읽고 쓴 행 수와 UTC 기준 초기화 시각부터 확인하세요.
Cloudflare의 2026년 9월 1일 공지는 Workers Free 계정이 D1의 일일 읽기·쓰기 행 한도를 넘으면 Workers Binding API와 REST API의 쿼리가 실패한다고 설명합니다. 한도는 UTC 자정에 초기화되며, 이 제한 때문에 저장된 데이터 자체가 바뀌는 것은 아니라고 명시합니다.
반환한 행 수와 읽은 행 수는 다릅니다
화면에 결과가 한 건 보였다고 데이터베이스도 한 행만 읽은 것은 아닙니다. 조건에 맞는 한 건을 찾기 위해 테이블의 많은 행을 확인했을 수 있습니다. D1 사용량을 이해하려면 쿼리 횟수, 읽은 행 수, 반환한 행 수를 구분해야 합니다.
현재 D1 요금·집계 문서는 Free의 읽기 한도를 하루 500만 행, 쓰기 한도를 하루 10만 행으로 안내합니다. 이는 쿼리 500만 번을 의미하지 않습니다. 읽기·쓰기 외의 저장 공간 한도와도 다른 항목입니다. 사용하는 시점에는 현재 플랜의 숫자를 다시 확인해야 합니다.
가상의 행사 테이블에 10만 행이 있고 특정 행사를 찾을 때마다 전체를 읽는다고 가정하겠습니다. 이를 50번 반복하면 읽기 작업의 산술 합은 10만 × 50 = 500만 행입니다. 실제 계정의 실행 결과가 아니라, 적은 요청 수로도 큰 읽기 사용량이 생길 수 있다는 계산입니다.
이 가정에서 화면에 몇 건이 반환되는지는 누적 스캔 행 수와 별개입니다. 따라서 트래픽 대시보드의 요청 개수만 보고 “사용자가 별로 없는데 한도를 넘을 리 없다”고 결론 내리면 실제 비용이 큰 쿼리를 놓칠 수 있습니다.
오류가 읽기인지 쓰기인지 먼저 분리합니다
오류 문구의 row read limit와 row write limit는 서로 다른 사용량을 가리킵니다. 읽기 제한인데 INSERT 코드만 수정하거나, 쓰기 제한인데 조회 캐시만 추가하면 막힌 조건과 다른 곳을 고치게 됩니다.
계정 수준의 한도와 개별 데이터베이스의 활동도 구분해야 합니다. 지금 문제가 보이는 화면 하나의 쿼리뿐 아니라 같은 계정에서 수행된 다른 작업이 있는지 확인합니다. 대량 가져오기나 주기적인 동기화가 사용자 요청과 함께 사용량을 만들었을 수 있습니다.
또 한도 오류를 빈 결과로 바꾸지 마세요. 조회가 실패했는데 “자료 없음”으로 처리하면 사용자는 데이터가 지워진 것으로 오해할 수 있습니다. 정상 조회의 빈 목록과 현재 조회할 수 없는 상태는 애플리케이션의 오류 처리에서도 구별해야 합니다.
원인을 적을 때는 확인한 사실만 남깁니다. 특정 시각에 어떤 오류가 발생했는지, 어떤 사용량이 한도에 도달했는지, 해당 기간에 어떤 작업이 있었는지를 연결합니다. 로그 없이 “사용자가 늘어서”라고 원인을 붙이는 것은 이 단계에서 필요하지 않습니다.
쿼리 결과의 meta로 한 번의 사용량을 봅니다
Workers 반환 객체 문서는 결과의 meta에
rows_read와rows_written을 제공한다고 설명합니다. 이런 값을 쿼리 종류별로 살펴보면 한 번의 요청이 어떤 사용량을 만드는지 좁힐 수 있습니다.다음은 이미 D1이
env.DB에 연결되어 있고 events 테이블에 event_id, event_name, region_code가 있다는 가정의 미실행 JavaScript 예시입니다. 사용자 이름이나 실제 행사 내용을 로그에 복사하지 않고 행 수만 확인합니다.async function inspectEventsQuery(env) { const result = await env.DB .prepare( "SELECT event_id, event_name FROM events WHERE region_code = ?" ) .bind("KR-11") .run(); if (!result.success || !Array.isArray(result.results)) { throw new Error("Query result is not available"); } return { query: "events_by_region", rowsReturned: result.results.length, rowsRead: result.meta.rows_read, rowsWritten: result.meta.rows_written, }; }이 코드의 입력은 지역 조건이고, 확인하려는 결과는 반환 행 수와 실제로 읽고 쓴 행 수의 관계입니다. 특정 숫자가 나온다고 가짜 출력까지 붙이지 않았습니다. 실제 환경에서 값을 확인한 뒤 쿼리 종류와 실행 횟수에 연결해야 합니다.
오류가 나거나 결과를 확인할 수 없다면 읽은 행을 0으로 채우지 않습니다. 위 예시는 그 경우에 정상적인 통계 값을 반환하지 않도록 구분합니다. 실제 서비스에서는 한도 오류와 다른 실패를 안전하게 기록하고, 이용자에게 필요한 상태를 전달하는 처리를 별도로 구성해야 합니다.
개별 데이터베이스의 추세는 같은 문서가 안내하는 D1의 Metrics → Row Metrics나 제공되는 집계 API에서 확인할 수 있습니다. 계정 전체의 한도를 판단할 때는 같은 계정의 다른 데이터베이스 활동과 집계 기간도 함께 맞춰 봅니다. 개별 응답의 meta를 하루 사용량 전체로 해석하지 않습니다.
인덱스가 정말 사용되는지 계획으로 확인합니다
행 수가 큰 조회를 찾았다면 다음은 왜 넓게 읽는지 확인할 차례입니다. D1 인덱스 안내는 조건에 맞는 인덱스와
EXPLAIN QUERY PLAN을 사용해 조회 경로를 확인하도록 설명합니다.앞의 가상 조회를 읽는 계획은 다음과 같이 살펴볼 수 있습니다.
EXPLAIN QUERY PLAN SELECT event_id, event_name FROM events WHERE region_code = 'KR-11';예시는 테이블과 데이터가 준비된 환경을 전제로 합니다. 출력에서 전체를 읽는 SCAN인지, 어떤 인덱스를 사용해 조건을 찾는지 살펴봅니다. 실제로 어떤 계획이 선택됐는지는 그 환경의 결과로 확인해야 합니다.
해당 조건에 인덱스가 필요하다고 판단했다면 검토용 정의는 다음처럼 구성할 수 있습니다.
CREATE INDEX idx_events_region ON events(region_code);이 명령은 조회가 아니라 스키마를 바꾸는 작업입니다. 운영 데이터베이스에 바로 실행하거나 요청이 들어올 때마다 반복할 명령이 아닙니다. 실제 적용은 변경 절차를 따르고, 계획과 사용량을 다시 확인해야 합니다.
대부분의 행이 같은 지역이면 인덱스로 기대만큼 범위가 줄지 않을 수 있습니다. 필터 외에 정렬이나 조인, 반복 하위 쿼리도 읽는 양을 바꿀 수 있습니다. 인덱스 이름이 있다는 사실만 확인하지 말고 자주 실행하는 실제 조건으로 계획을 읽어야 합니다.
쓰기와 저장 공간에 미치는 영향도 확인합니다
인덱스는 읽기 작업을 줄이는 데 도움이 될 수 있지만 공짜로 유지되는 구조는 아닙니다. D1 집계 문서는 인덱스가 걸린 열을 쓰는 경우 인덱스 쪽의 쓰기도 추가되며, 테이블과 인덱스가 저장 공간을 사용한다고 설명합니다.
따라서 읽기 한도를 낮추기 위해 인덱스를 추가했다면 쓰기 사용량과 저장 공간도 함께 확인합니다. 한 종류의 읽기 쿼리를 줄이는 대신 자주 갱신되는 열마다 불필요한 인덱스를 늘리면 다른 제약이 중요해질 수 있습니다.
쓰기 한도를 조사할 때는 대량 동기화나 같은 레코드의 반복 갱신도 봅니다. 실제로 변경할 필요가 없는 자료를 매번 다시 쓰는지, 한번 실패한 작업을 재시도하면서 같은 처리를 반복하는지 확인합니다. 다만 상태 확인 없이 작업을 임의로 생략하면 자료가 누락될 수 있으므로 사용량 감소만을 목표로 삼지는 않습니다.
초기화를 기다리는 것과 재발을 줄이는 것은 다릅니다
Free의 일일 구간은 UTC 자정에 초기화됩니다. 한국 표준시로는 오전 9시입니다. 앱의 한국 날짜 기준 집계와 서비스의 사용량 구간을 비교할 때는 같은 시간대로 맞춰야 합니다. 자정부터 오전 9시까지의 요청을 어디에 포함했는지에 따라 하루 합계가 달라질 수 있습니다.
일회성 자료 가져오기로 그날만 넘었다면 남은 작업과 다음 실행 시점을 정리할 수 있습니다. 반면 정상적인 업무를 수행할 때마다 반복된다면 쿼리 구조와 호출 빈도, 현재 플랜이 실제 작업량에 맞는지를 함께 판단해야 합니다.
유료 플랜으로 바꾸는 결정도 별도입니다. 기능과 사용량 단위, 포함량과 비용을 현재 문서에서 확인하고 필요한 범위를 검토해야 합니다. 플랜을 바꾸면 모든 런타임 제한이나 비효율적인 쿼리 문제가 사라진다고 보지는 않습니다. 이 글에서 계정의 과금 설정을 변경하지 않았습니다.
다음 날 오류가 사라졌다면 확인한 것은 한도 초기화 이후 요청이 다시 처리된다는 사실입니다. 쿼리가 개선됐다는 증거는 아닙니다. 변경 전후에 같은 종류의 정상 작업을 기준으로 읽기·쓰기 행과 결과를 대조해야 재발을 줄였는지 판단할 수 있습니다.
마지막에는 오류 종류, 같은 시간대로 맞춘 사용 구간, 사용량이 큰 쿼리, 실행 계획, 필요한 업무 결과를 함께 남기세요. 요청 수를 줄였다는 보고보다 어떤 작업이 얼마만큼 읽고 쓰는지를 설명할 수 있어야 D1 한도 문제를 정확히 다룰 수 있습니다.
728x90반응형'Programming' 카테고리의 다른 글
ruff check와 ruff format은 왜 둘 다 필요할까 (0) 2026.09.19 Cloudflare Workflows 실행 기록, 왜 예전보다 빨리 없어질까 (0) 2026.09.19 npm 로그인은 되는데 배포가 막혔다면: 복구 코드 사용 뒤 확인할 것 (0) 2026.09.19 API 키가 들어간 PR, push protection을 통과해도 머지를 막을 수 있을까 (0) 2026.09.17 Python 프로젝트가 여러 개일 때 uv workspace로 묶어도 될까 (1) 2026.09.17