Cloudflare Workers с нуля: worker, KV, D1, R2

Эта статья для того, кто читает серию, живя на PHP + MySQL + папке на диске, и хочет понять не «как написать hello world на воркере» (это делает туториал вендора), а почему данные здесь устроены по-другому. На VPS у вас один источник: строка живёт в колонке MySQL, файл — в папке, кэш — в Redis или opcache, и всё это «просто ещё одна колонка/файл». В Workers того же самого каскада нет: доступ к данным разнесён на три сервиса с разными таймингами, разной консистентностью и — главное — разной ценовой единицей. Здесь «сохранить строчку» и «прочитать строчку» стоят разных денег, и это не придирка биллинга, а следствие того, как физически разложены примитивы.

Все числа — из официальной документации Cloudflare (developers.cloudflare.com/* и страница планов Developer Platform) на снимок 2026-11-06; самые свежие лимит-страницы — Oct 29–30, 2026. Тарифы и лимиты двигаются по релизам, поэтому в конце стоит блок «что движется», а кодовое значение каждого числа — сверяйте по документации в день внедрения. Примеры кода абстрактные (кондиции example.test), имена API Workers — публичные, но точные сигнатуры методов живут в API-референсе.


Часть I. Первый worker и как к нему цепляются ресурсы

1. Воркер — это обработчик, а не процесс

Серверный воркер Workers — один exported-объект с хендлером fetch. Никакого «подними сервер, слушай порт» — платформа сама вызывает ваш fetch на входящий запрос. Мини-скелет (имена API публичные, точные сигнатуры — в API-референсе Workers):

// src/index.js — минимальный воркер
export default {
  async fetch(request, env, ctx) {
    // request — стандартный Request; env — ваши биндинги; ctx — context (waitUntil и т.п.)
    const url = new URL(request.url);

    if (url.pathname === "/health") {
      return new Response("ok", { status: 200 });
    }

    // дальше — обращения к env.MY_KV / env.MY_DB / env.MY_BUCKET (см. Части II–IV)
    return new Response("hello from workerd", { status: 200 });
  },
};

Три аргумента — это и есть граница платформы:

  • request — что пришло (метод, заголовки, тело как ReadableStream).
  • env — все ваши ресурсы: каждый биндинг (KV, D1, R2, секреты, переменные) появляется как свойство env. Это то, чем Workers принципиально отличается от «require('mysql2') с адресом из .env»: вы не открываете соединение и не держите пул — платформа даёт вам уже связанный объект-примитив.
  • ctx — контекст исполнения; главное, что здесь нужно в первых строках, — ctx.waitUntil(), которым можно доделать фоновую работу после отправки ответа (доки: «can extend execution for up to 30 seconds after the response is sent»).

Для читателя с Node/Deno: это ровно тот же export default { fetch }-контракт из статьи №2 про перенос — поэтому один хендлер едет и на облаке, и на workerd, и на celld (№3).

2. Биндинг вместо подключения: как выглядит wrangler.json

Ресурсы описываются в конфиге и прилетают в env. Учебный скелет (имена ключей — из официальной конфигурации Wrangler, но сверяйте по «Configuration — Wrangler» в день внедрения; здесь — форма, а не копия):

# wrangler.toml — учебный скелет, НЕ копия из доки
name = "example-worker"
main = "src/index.js"
compatibility_date = "2026-11-06"

[[kv_namespaces]]
binding = "MY_KV"          # -> env.MY_KV
id = "<namespace-id>"

[[d1_databases]]
binding = "MY_DB"          # -> env.MY_DB
database_name = "example"
database_id = "<d1-id>"

[[r2_buckets]]
binding = "MY_BUCKET"      # -> env.MY_BUCKET
bucket_name = "example"

Здесь уже видно «другую экономику» из TL;DR: вы не выбираете соединение к базе — вы выбираете примитив. Тот же самый «сохранить строчку» — это три разных объекта (MY_KV / MY_DB / MY_BUCKET) с тремя разными ценами, таймингами и моделями консистентности. На VPS этот выбор был невидимым (всегда «та же MySQL»); здесь он явный и в конфиге, и в счёте.

3. Лимиты, которые определяют всё остальное

Прежде чем разбирать примитивы — три числа самой платформы, потому что к ним сводятся все ограничения «сколько раз я могу сходить в данные»:

  • CPU vs wall-clock — разные лимиты. Это ключевая опора всей статьи, дословно из доков: «CPU time measures how long the CPU spends executing your Worker code. Waiting on network requests (such as fetch() calls, KV reads, or database queries) does not count toward CPU time.» То есть поход в KV/D1/R2 не платится CPU-квотой — он платится временем ответа (для HTTP-запроса wall-clock-лимита нет) и ценой самой операции примитива. Средний воркер, по ориентиру доков, тратит «approximately 2.2 ms» CPU на запрос.
  • Память — 128 MB на изолят (не на инвокацию). Прямая рекомендация доков дословно называет наши примитивы: «Avoid large in-memory objects — Store large data in KV, R2, or D1 instead of holding it in Worker memory.»
  • Параллельность похода в данные — 6 одновременных исходящих соединений на инвокацию (одинаково Free и Paid; D1-страница повторяет: «You can open up to six connections (to D1) simultaneously»). В этот лимит входят и fetch(), и get/put/list/delete у KV, и get/put/list/delete/head у R2. Это реальная «ширина трубы», которая часто важнее скорости самого примитива: шесть одновременных обращений к данным — и точка, всё остальное сериализуется. (Важно: никакого лимита «128 соединений на воркер» в доках нет — 128/Worker относится к переменным окружения, а не к соединениям; число соединений — именно 6.)

Выносим эти три числа вперёд: они объясняют, почему «тяжёлый SQL-запрос в воркере» — не про CPU-лимит, а про wall-clock и про 6 connections, и почему большие данные всегда выносят в R2/KV, а не держат в памяти.


Часть II. KV: глобальный ключ/значение с задержанной видимостью

1. Что это по модели

KV — это глобальное хранилище «ключ → значение», спроектированное так, чтобы читать быстро и часто, писать редко и поздно видимым. Дословно из доков: «KV is a global, low-latency, key-value data store. It stores data in a small number of centralized data centers, then caches that data in Cloudflare’s data centers after access.» и «KV is suitable for use cases where you need to write relatively infrequently, but read quickly and frequently.» Архитектура — запись уходит в центральный стор и не рассылается автоматически во все локации; остальное догоняет через кэш с TTL. Отсюда всё остальное.

2. Твёрдые границы (числа)

ПараметрЗначение (Free = Paid)
Максимальный размер значения25 MiB
Максимальная длина ключа512 bytes
Метаданные ключа1024 bytes (JSON)
list() за один вызов1,000 ключей — и это же максимум (дальше только cursor)
Запись одного и того же ключа1 в секунду (выше — 429)
cacheTtl по умолчанию / минимум60 s / 30 s

Два неочевидных, но важных момента:

  • Авто-чанкования значения нет. 25 MiB — жёсткий потолок одного значения; дробить большой объект на несколько ключей — это ваш ручной «multipart», автоматической загрузки по частям в KV нет (bulk-API чанкует несколько пар, не одно значение).
  • Пагинация list() коварна: доки предупреждают, что пустой массив keys не означает «больше нет» («recently expired or deleted keys must be iterated through but will not be included»); критерий продолжения — флаг list_complete, а не длина массива. Для PHP-программиста, привыкшего к «SELECT вернул пусто — значит пусто», это ловушка.

3. Eventual consistency — вот где KV отличается от вашего кэша на VPS

Точные формулировки, потому что на них держится и выбор, и грабли:

«Workers KV is eventually consistent.» «In other global network locations changes may take up to 60 seconds or more to be visible … however, this is not guaranteed and therefore it is not advised to rely on this behaviour.» «Negative lookups indicating that the key does not exist are also cached» — то есть вы так же поздно увидите и создание значения, которого «ещё нет» в этом кэше.

Что это значит практически: KV не годится как источник истины для write-after-read в разных гео-локациях. Записали в одном PoP — в другом PoP до 60+ секунд будут видеть старое. Документированный обход (обязательно в скобках, что это не «в KV», а комбинаторное решение): «An approach to achieve write-after-write consistency is to send all of your writes for a given KV key through a corresponding instance of a Durable Object, and then read that value from KV in other Workers.» — то есть Durable Object становится вашим сериализатором записи поверх KV.

4. Экономика KV и почему она «read-heavy»

Биллинг — операциями на ключ (не байтами). Ориентиры на снимок (Paid, сверяйте по pricing-доке): чтение ~$0.50 за миллион, запись/удаление/list — ~$5.00 за миллион, то есть запись в 10 раз дороже чтения; плюс хранение ~$0.50/GB-month после включённого гигабайта. Это не случайность ценника, а прямое отражение модели: писать в глобальный кэш дорого, читать дёшево. Биллятся даже промахи: «All operations incur charges, including fetches for non-existent keys that return null». Если вам нужно обновлять один ключ десятки раз в секунду — KV, по прямым словам доков, «will not be an ideal fit» (это Redis-профиль нагрузки, а KV — нет).


Часть III. D1: SQL с дорогой записью и одной локацией на базу

1. Что это по модели — и чего в названии нет

D1 — управляемая serverless-база с семантикой SQLite. Дословно: «D1 is Cloudflare’s managed, serverless database with SQLite’s SQL semantics, built-in disaster recovery, and Worker and HTTP API access.» Для вас это привычный SELECT/INSERT/UPDATE — но с двумя не-MySQL свойствами, которые и есть предмет Части III.

Честно о «single writer». В разговорах D1 называют «single-writer». Такой формулировки в доках нет — есть документированные эквиваленты, и полагаться надо на них: «Each individual D1 database is inherently single-threaded, and processes queries one at a time» и «Each individual D1 database is backed by a single Durable Object». Числа одновременных записей в доках тоже нет. Строить аргумент на слове «single writer» как цитате — нельзя; на приведённых формулировках — можно.

2. Почему «SQL с задержкой на запись» — это про деньги и про сериализацию

Центральный тезис серии подтверждается двумя независимыми числами одного примитива:

  • Про латентность (оценка источника, не SLA): «Write queries like INSERT or UPDATE can take several milliseconds for SQL duration … Writes need to be durably persisted across several locations», против чтения — «less than a millisecond». Обращаем внимание: объясняющая ссылка уводит на blog-пост (вне API-доков), фиксированного числа p50/p99 на запись в доках нет — «several milliseconds» названы «rough guideline».
  • Про цену (самое сильное подтверждение): биллинг по строкам — чтение ~$0.001 за миллион строк против записи ~$1.00 за миллион строк (~в 1000 раз дороже записи; сверяйте по pricing-доке). И это не «наказание за запись», а физика: коммит надо durable-persist-нуть в несколько локаций.

Дальше — как это вести себя под нагрузкой (дословно, это и есть «модель производительности D1»):

  • Пропускная способность одной базы = 1 / длительность запроса: «If your average query takes 1 ms, you can run at most 1,000 queries per second. If your average query takes 100 ms, you can run at most 10 queries per second.» (с оговоркой про small per-request overhead).
  • Перегруз ведёт себя не как MySQL: «A database that receives too many concurrent requests will first attempt to queue them. If the queue becomes full, the database will return an «overloaded» error».
  • Документированная рекомендация против частых мелких запросов: «reduce the number of requests rather than only the duration of each query. For example, use batch() to send multiple statements in a single request». Это ровно то, чего нет в модели «у меня constant пул к MySQL».

3. Горизонтальная модель: «много маленьких баз»

Ключевое отличие от MySQL, которое меняет схему приложения (дословно): «D1 is designed for horizontal scale out across multiple, smaller (10 GB) databases, such as per-user, per-tenant or per-entity databases.» Потолок одной базы — 10 GB (Paid) / 500 MB (Free), и он «cannot be further increased». То есть вместо «одна большая база с шардом» модель обратная: база на сущность (пользователь, тенант), а масштабируют их количеством. Для PHP-разработчика, привыкшего к одной большой БД на проект, это сдвиг мышления: реляционные JOIN через границы баз недоступны — у вас их просто несколько.

4. Что ещё надо знать, прежде чем строить

  • Где физически лежит. Без read replication: «D1 routes all queries (both read and write) to a specific database instance in one location in the world». Локацию можно задать при создании (--location=weur), а юрисдикцию (eu, us, fedramp) — для требований data locality (GDPR/FedRAMP). То есть глобальной базы «как KV» здесь нет: одна локация, и это плата за реляционную консистентность.
  • Read replication = Beta и называется «Global Read Replication»: read-реплики «asynchronously replicated» и «may be arbitrarily out of date» (replica lag); чтобы не словить рассинхрон, обязателен Sessions API (env.DB.withSession(bookmark)), дающий «read my own writes». Биллинг реплик — «не берёт денег отдельно», платите по rows_read/rows_written.
  • Бэкапов «как продукта» нет — есть Time Travel: «Time Travel is D1’s approach to backups and point-in-time-recovery … always on … incur no additional costs», глубина 30 days (Paid) / 7 days (Free). Ветки/клонирование баз — отсутствуют: «Time Travel does not yet allow you to clone or fork an existing database». Долгоживущий снапшот делают выгрузкой в R2 (хороший стык «D1 → R2» для бэкапа).
  • int64-ловушка (прямо к PHP-аудитории): «Any numeric value … is affected by JavaScript’s 52-bit precision … the returned value may be less precise». Большие BIGINT/snowflake-ID через D1 могут терять точность — то, чего на PHP-pdo вы не ловили.

Часть IV. R2: объекты без платы за egress (и честная формулировка этого)

1. Что это по модели

R2 — объектное хранилище, S3-совместимое, спроектированное убрать то, что дорожает в облачном хранилище, — плату за отдачу данных наружу. Дословно из Overview: «Cloudflare R2 Storage allows developers to store large amounts of unstructured data without the costly egress bandwidth fees associated with typical cloud storage services», и (how-r2-works): «an S3-compatible object storage service with no egress fees». Для вас это замена «папке uploads на диске / бакету S3».

2. Zero egress — сформулируем точно (важная поправка к тезису)

Подтверждённые дословные формулировки: «There are no charges for egress bandwidth for any storage class»; строка тарифа «Egress (data transfer to Internet) | Free | Free | Free»; и официальная страница планов: «R2 — Store and retrieve objects with zero egress fees».

Но, чтобы не соврать, две честные оговорки:

  • «Zero egress» не уникально для R2 внутри платформы Workers. Отсутствие платы за исходящий трафик заявлено и для Workers Paid («no additional charges for data transfer (egress) or throughput»), и для D1, и для Queues. Уникальность R2 — в объектном масштабе (до 5 TiB на объект, неограниченный бакет) и в прямом сравнении с S3-классом провайдеров, где egress — главная статья. Формулировать «у R2 zero egress, в отличие от S3» — корректно; «zero egress есть только у R2 в Cloudflare» — некорректно.
  • Дыра в формулировке, о которой доки предупреждают сами: «If you connect other metered services to an R2 bucket, you may be charged by those services». Бесплатный egress из R2 не отменяет, что висящий сверху CDN или другой метируемый сервис возьмёт своё.

3. Твёрдые границы (числа)

ПараметрЗначение
Размер объекта5 TiB (фактически 4.995 TiB)
Одинарный PUT5 GiB (single-part)
Multipart: часть5 MiB (min, кроме последней) … 5 GiB (max), до 10,000 частей; «All parts except the last must be the same size»
Длина ключа / метаданные1,024 bytes / 8,192 bytes
Запись одного и того же ключа1 в секунду (выше — 429)
Единицыважны: MiB/GiB/TiB base-2 ≠ GB base-10 — KV пишет 25 MiB, D1 — 10 GB, это разные числа

4. Консистентность: вот где R2 лучше KV (важно не перепутать)

Прямое отличие от Части II: R2 — «strongly consistent» глобально: «The effect of an operation will be observed globally, immediately, by all clients», read-after-write, удаления, list — всё сильно-консистентно (исключение: IAM-права — eventual). Долговечность — «99.999999999% (eleven 9s)». И тонкая грань, которая честнее «магической сильной консистентности»: «Reads are never stale, but they do not wait for or cancel earlier writes» — прочитанный 404 не отменяет PUT с ещё неизвестным исходом. Кэш снимает гарантию: если повесить на бакет домен с включённым кешированием, консистентность «расслабляется» для reads через этот домен (но не для Worker-API/S3-бинадингов — те идут прямо в бакет). Условные записи (If-None-Match: *) атомарны — «safe to rely … for create-once semantics».

5. Экономика R2

Биллинг — объём хранения (GB-month по пику за период) + два класса операций: Class A (дороже, изменяющие — PutObject, CreateMultipartUpload, ListObjects) против Class B (читающие — GetObject, HeadObject). Ориентиры на снимок (Standard, сверяйте по pricing-доке): PutObject ≈ 4.50 $/млн против GetObject ≈ 0.36 $/млн (~12.5×), а DeleteObject/DeleteBucket/AbortMultipartUpload — бесплатны; egress — Free. Инференция та же, что по всему семейству: изменение данных дороже их чтения, и R2 не исключение. Для PHP-читателя отдельно: R2 не помечен «включён в Workers Free/Paid» — в отличие от KV и D1 у него собственная бесплатная квота и свой счётчик; бакет-квота «applies regardless of your Workers plan».


Часть V. Три экономики доступа к данным — и прямое сравнение с PHP+MySQL+диск

1. Одна таблица, из которой видно всю «другую экономику»

ОсьKVD1R2
Модель данныхkey/value глобальноSQL (SQLite-семантика), база в одной локацииобъект, S3-совместимый
Единица биллингаоперации per key + GB-monthrows read / rows written + GB-mo + $/базаClass A/B операции + GB-month
Запись против чтения по цене (Paid, ориентиры)~$0.50/M vs ~$5.00/M (10×)~$0.001/M vs ~$1.00/M (1000×)Class B ~$0.36/M vs Class A ~$4.50/M (12.5×); delete бесплатен
Консистентностьeventual: «up to 60 s or more»1 база = single-threaded / single DO; read-реплики = «arbitrarily out of date» + Sessionsstrong globally read-after-write (IAM — eventual)
Потолок единицызначение 25 MiBстрока/BLOB 2 MB; результат запроса 50 MBобъект 5 TiB; PUT 5 GiB; часть 5 MiB–5 GiB
Key / метаданныеkey 512 B, metadata 1024 Bколонок 100; SQL-запрос 100 KB; параметров 100key 1,024 B, metadata 8,192 B
List1,000/вызов (=max), cursor, list_completeлимита строк выдачи нет (50 MB результат, 30 s запрос)ListObjects = Class A, strongly consistent
Потолок записи1/сек на ключ (429 выше)нет числа; «queue then «overloaded»»1/сек на ключ (429 выше)
Egressнет отдельной платы (в рамках Workers)«no data transfer (egress) … for data accessed from D1»«no charges for egress bandwidth for any storage class»
Free-квота1 GB; 100k reads/день, 1k writes/день5 GB (total); 5M reads/день, 100k writes/день10 GB-month; 1M Class A; 10M Class B

Это числа-ориентиры с официальных лимит/pricing-страниц на снимок 2026-11-06; каждое сверяйте по документации в день внедрения (см. финальный блок «движется»).

2. То же самое предложение: «нужно хранить и читать данные» на двух стеках

Ключевая разница — не «облако vs железка», а что стоит за словом «сохранить строчку». На VPS это одно действие с одной ценой (запись в MySQL). На Workers это выбор примитива, и каждый выбор меняет три вещи сразу: консистентность, латентность и единицу биллинга.

Задача на вашем сайтеИнстинкт PHP+VPSПримитив WorkersЦена ошибки выбора
Сессии/кук-проверка по токенамколонка в MySQL / RedisKVесли нужна write-after-read по миру — eventual KV это сломает
Профиль пользователя + JOIN с заказамита же MySQLD1 (база на юзера)глобальных JOIN через базы нет; частая запись дорога (1000×)
Загрузка картинок/экспортовпапка на диске / S3R2сильно-консистентно и egress-бесплатно, но изменение дороги Class A
Live-счётчик, обновляется 100 раз/секRedisне KV (1 запись/сек/ключ)нужен Durable Object, не эти три примитива
Тяжёлая транзакция (несколько таблиц, BEGIN)MySQL InnoDBD1.batch() (одна база, одна локация)нет кросс-базовых транзакций; 30 s/запрос

Мораль таблицы: выбирают примитив не по «модельности», а по форме данных и профилю запись/чтение. Тот же самый набор фич, который на VPS помещался в «MySQL + папка + Redis», здесь распадается на три сервиса, и перепутать KV с D1 — это не «стиль», а конкретный счёт и конкретный баг консистентности.

3. Почему это «экономика», а не «технология»

Всё, что объединяет три примитива в один тезис: в каждом из них запись дороже чтения и разнесена по своей единице биллинга (10× / 1000× / 12.5× — три независимых подтверждения одного принципа). На flat-VPS такой разницы физически нет: запрос к MySQL стоит вам электричество и амортизацию машины независимо от того, SELECT это или UPDATE. Поэтому перенос приложения, где состояние часто пишется (счётчики, live-редактор, телеметрия), на Workers превращает ваш бесплатный инстинкт «обновляю колонку в цикле» в растущую строку счёта. И ровно наоборот: приложение, где читают в сотни раз больше, чем пишут, попадает в sweet spot этой экономики.


Часть VI. Нужны ли KV/D1/R2 вам, у кого WordPress + PHP + VPS

1. Когда точно не надо (и это нормальный ответ)

Если у вас WordPress + PHP-FPM + MySQL на VPS, а аудитория из одного-двух регионов, то связка KV/D1/R2 не решает вашу задачу, а создаёт вторую. Причины те же, что в №5, теперь на уровне примитивов:

  • Одна MySQL с JOIN дороже-любой-комбинации здесь. Как только вам нужен нормальный реляционный запрос по связанным таблицам, D1 даёт вам SQLite-семантику, но без кросс-базовых JOIN, с записью ×1000 дороже чтения и 30-секундным лимитом на запрос. На VPS этот запрос — одна строка SQL.
  • Узкая география убивает смысл глобального KV. Если пользователи не раскиданы по континентам, « eventual с видимостью через мир» — не фича, а риск без награды. Локальный object-cache/opcache решает то же быстрее.
  • Flat-цена против per-op. Стабильный поток обращений к данным на VPS не превращается в строку «$X за миллион операций», которая растёт линейно. SELECT раз в секунду и SELECT раз в миллисекунду на вашей машине стоят одинаково.

Полезная «edge-часть» для такого сайта почти всегда — CDN и кэш статики (CDN и edge-вычисления, Edge-кэш и персонализация), а не перенос состояния в KV/D1/R2.

2. Когда присмотреться (и что брать)

  • Есть данные, которые «пишут редко, читают часто и глобально» — конфиги, флаги, текстовые куски, кэш результатов внешних API → KV (но помните eventual и 1 запись/сек/ключ).
  • Нужен пер-юзерный/пер-тенантный реляционный кусок, географически распределённый — сады приложений с изоляцией по клиенту → D1 по модели «много маленьких баз», с batch() и дорогими записями в расчёте.
  • Много отдаваемой наружу статики/экспортов, и egress на S3 больно бьёт по счёту → R2 (с честной оговоркой: сам egress внутри платформы бесплатный и без R2, уникальна объектная ёмкость и сравнение с S3-классом).
  • Live-состояние, частые мелкие правки, совместное редактирование — это не эти три, а Durable Objects; механизм и self-host (celld) разбирает №3.

3. Что делать прямо сейчас

Ничего срочного. Проверьте гипотезу одним вопросом из Части V: профиль доступа к вашим данным — «читаю много, пишу редко» или «пишу часто»? Первое — повод прицениться к примитивам; второе — повод остаться на MySQL. Если решили пробовать — начинайте не с миграции WordPress, а с одного нового stateless-куска (например, кэш внешнего API в KV на свободном воркере), и снимите свои цифры rows_read/rows_written/операций, прежде чем нести это на продакшн: на снимке-ориентире из Части V видно, что платная часть у D1/R2 — это запись, а не объём.


Инструменты под задачу

  • Разобрать, какой из KV/D1/R2 под ваши данные, или остаться на MySQL — AI-ассистент для кода на codestack.space: вставьте фрагмент схемы БД или обработчик и попросите пройтись по таблице Части V — где ваша «ещё одна колонка» превращается в дорогой Class A/rows written, а где честнее остаться на своей машине.

Чек-лист граблей

  1. Держать большое состояние в памяти воркера. Память — 128 MB на изолят; доки прямо велели: «Store large data in KV, R2, or D1 instead of holding it in Worker memory».
  2. Ждать от KV write-after-read по миру. Eventual consistency: «up to 60 seconds or more», и «not advised to rely on this behaviour». Нужна строгая последовательность — Durable Object поверх, либо D1 в одной локации, либо R2 (strongly consistent).
  3. Обновлять один KV-ключ часто. 1 write per second per key, выше — 429. Redis-профиль нагрузки KV не тянет (это сказано в доках прямым текстом).
  4. Использовать list() KV как «SELECT, вернувший пусто». Пустой массив keys ≠ «кончились»: продолжение определяют по list_complete, не по длине массива.
  5. Считать D1 «ещё одной MySQL, только облачной». Одна база = одна локация = single-threaded/single DO; 10 GB не увеличить; запись в ~1000 раз дороже чтения по биллингу; JOIN через границы баз нет.
  6. Писать в D1 часто-маленькими запросами вместо batch(). Пропускная способность = 1 / длительность запроса; доки советуют «reduce the number of requests», batch() платит per-request-overhead один раз.
  7. Класть в D1 большие int64/snowflake-ID. «52-bit precision» — значение может вернуться менее точным; то, чего pdo на PHP не подсовывал.
  8. Врать про «zero egress» в обе стороны. Корректно: R2 убирает egress для объектного S3-класса; некорректно: «только R2 бесплатный на исходящем» (внутри платформы egress не платится и для Workers Paid/D1/Queues) и «сверху ничего не возьмут» (висящий CDN/другой метируемый сервис — возьмёт).
  9. Ждать сильной консистентности от R2 сквозь кэширующий домен. «The consistency model is necessarily relaxed when accessing content via a domain with caching enabled» — гарантия держится для Worker/S3-биндингов, а не для кэшируемого HTTP-доступа.
  10. Считать MiB и GB одним числом. KV 25 MiB (base-2) ≠ D1 10 GB (base-10) — при переносе лимитов между примитивами их путают.
  11. Опираться на несуществующий лимит «128 соединений на воркер». Документированный потолок одновременных исходящих соединений — 6 (Paid тоже 6); 128/Worker относится к переменным окружения.
  12. Взять числа этой статьи как финальные. Тарифы и лимиты двигаются (см. ниже «движется»): любое число сверяется по официальной доке в день внедрения.

👤 Зарегистрируйтесь, чтобы добавить материал в закладки.