INTEGRITY Документация

Репликация чтения D1 для вашего интернет-магазина

D1 Read Replication представляет собой функцию, которая позволяет реплицировать базу данных D1 в несколько регионов. Это полезно для сайта электронной коммерции, поскольку снижает задержки чтения и повышает пропускную способность чтения. В этом руководстве вы узнаете, как использовать репликацию D1 для чтения на сайте электронной коммерции.

В этом руководстве используется вымышленный интернет-магазин, но описанные принципы применимы к любому сценарию, где нужны низкие задержки чтения и масштабирование чтения, например к новостному сайту, социальной платформе или маркетинговому сайту.

быстрый старт

Если вы хотите пропустить шаги и быстро начать работу, нажмите кнопку ниже:

Deploy to Cloudflare

Это создаст репозиторий в вашем аккаунте GitHub и развернёт приложение в Cloudflare Workers. Также будет создана и привязана база данных D1, созданы необходимые таблицы и добавлены тестовые данные. Во время развёртывания установите флажок Enable read replication флажок, чтобы включить репликацию для чтения.

После этого можно перейти к развёрнутому приложению.

Предварительные требования

  1. Зарегистрируйтесь для получения Аккаунт Cloudflare.
  2. Установка Node.js.

менеджер версий Node.js

Используйте менеджер версий Node, например Volta или nvm чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.

Шаг 1: Создайте проект Workers

Создайте новый проект Workers, выполнив следующую команду:

npm create cloudflare@latest -- fast-commerce

Для настройки выберите следующие параметры:

Для создания маршрутов API вы будете использовать Hono. Вам нужно установить Hono, выполнив следующую команду:

npm i hono

Шаг 2: Обновите фронтенд

Приведённый выше шаг создаёт новый проект Workers со стандартным фронтендом и устанавливает Hono. Вы обновите фронтенд, чтобы отображать список товаров, а также добавите новую страницу для отображения отдельного товара.

Перейдите в папку только что созданного проекта Worker.

cd fast-commerce

Обновите public/index.html файл, чтобы вывести список товаров. Используйте приведённый ниже код в качестве примера.

public/index.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>E-commerce Store</title>
		<style>
			* {
				margin: 0;
				padding: 0;
				box-sizing: border-box;
				font-family: Arial, sans-serif;
			}

    		body {
    			background-color: #f9fafb;
    			min-height: 100vh;
    			display: flex;
    			flex-direction: column;
    		}

    		header {
    			background-color: white;
    			padding: 1rem 2rem;
    			display: flex;
    			justify-content: space-between;
    			align-items: center;
    			border-bottom: 1px solid #e5e7eb;
    		}

    		.store-title {
    			font-weight: bold;
    			font-size: 1.25rem;
    		}

    		.cart-button {
    			padding: 0.5rem 1rem;
    			cursor: pointer;
    			background: none;
    			border: none;
    		}

    		.products-grid {
    			display: grid;
    			grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    			gap: 1.5rem;
    			padding: 2rem;
    		}

    		.product-card {
    			background-color: white;
    			border-radius: 0.5rem;
    			overflow: hidden;
    			box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
    		}

    		.product-info {
    			padding: 1rem;
    		}

    		.product-title {
    			font-size: 1.125rem;
    			font-weight: 600;
    			margin-bottom: 0.5rem;
    		}

    		.product-description {
    			color: #4b5563;
    			font-size: 0.875rem;
    			margin-bottom: 1rem;
    		}

    		.product-price {
    			font-size: 1.25rem;
    			font-weight: bold;
    			margin-bottom: 0.5rem;
    		}

    		.product-stock {
    			color: #4b5563;
    			font-size: 0.875rem;
    			margin-bottom: 1rem;
    		}

    		.view-details-btn {
    			display: block;
    			width: 100%;
    			padding: 0.5rem 0;
    			background-color: #2563eb;
    			color: white;
    			border: none;
    			border-radius: 0.375rem;
    			cursor: pointer;
    			text-align: center;
    			text-decoration: none;
    			font-size: 0.875rem;
    		}

    		.view-details-btn:hover {
    			background-color: #1d4ed8;
    		}

    		footer {
    			background-color: white;
    			padding: 1rem 2rem;
    			text-align: center;
    			border-top: 1px solid #e5e7eb;
    			color: #4b5563;
    			font-size: 0.875rem;
    		}

    		/* Basic Responsiveness */
    		@media (max-width: 768px) {
    			.products-grid {
    				grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    			}
    		}

    		@media (max-width: 480px) {
    			.products-grid {
    				grid-template-columns: 1fr;
    			}
    		}
    	</style>
    </head>
    <body>
    	<header>
    		<h1 class="store-title">E-commerce Store</h1>
    		<button class="cart-button">Cart</button>
    	</header>

    	<main class="products-grid" id="products-container">
    		<!-- Products will be loaded here by JavaScript -->
    	</main>

    	<footer>
    		<p>© 2025 E-commerce Store. All rights reserved.</p>
    	</footer>

    	<script>
    		document.addEventListener('DOMContentLoaded', () => {
    			let products = [];
    			let d1Duration,
    				queryDuration = 0;
    			let dbLocation;
    			let isPrimary = true;

    			// Function to create product HTML
    			function createProductCard(product) {
    				return `
                <div class="product-card" data-category="${product.category}">
                    <div class="product-info">
                        <h3 class="product-title">${product.name}</h3>
                        <p class="product-description">${product.description}</p>
                        <p class="product-price">$${product.price.toFixed(2)}</p>
                        <p class="product-stock">${product.inventory} in stock</p>
                        <a href="product-details.html?id=${product.id}" class="view-details-btn">View Details</a>
                    </div>
                </div>
            `;
    			}

    			// Function to render content
    			function renderContent() {
    				try {
    					const productsContainer = document.getElementById('products-container');
    					if (!productsContainer) return;
    					productsContainer.innerHTML = '';

    					products.forEach((product) => {
    						productsContainer.innerHTML += createProductCard(product);
    					});
    				} catch (error) {
    					console.error('Error rendering content:', error);
    				}
    			}

    			// Fetch products
    			fetch('/api/products')
    				.then((response) => response.json())
    				.then((data) => {
    					products = data;
    					renderContent();
    				})
    				.catch((error) => console.error('Error fetching products:', error));
    		});
    	</script>
    </body>

</html>

Создайте новый public/product-details.html файл, чтобы отобразить один товар.

public/product-details.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Product Details - E-commerce Store</title>
		<style>
			* {
				margin: 0;
				padding: 0;
				box-sizing: border-box;
				font-family: Arial, sans-serif;
			}

			body {
				background-color: #f9fafb;
				min-height: 100vh;
				display: flex;
				flex-direction: column;
			}

			header {
				background-color: white;
				padding: 1rem 2rem;
				display: flex;
				justify-content: space-between;
				align-items: center;
				border-bottom: 1px solid #e5e7eb;
			}

			.store-title {
				font-weight: bold;
				font-size: 1.25rem;
				text-decoration: none;
				color: black;
			}

			.cart-button {
				padding: 0.5rem 1rem;
				cursor: pointer;
				background: none;
				border: none;
			}

			.product-container {
				max-width: 800px;
				margin: 2rem auto;
				background-color: white;
				border-radius: 0.5rem;
				box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
				padding: 2rem;
			}

			.product-title {
				font-size: 1.875rem;
				font-weight: bold;
				margin-bottom: 0.5rem;
			}

			.product-description {
				color: #4b5563;
				margin-bottom: 1.5rem;
			}

			.product-price {
				font-size: 1.875rem;
				font-weight: bold;
				margin-bottom: 0.5rem;
			}

			.product-stock {
				font-size: 0.875rem;
				color: #4b5563;
				text-align: right;
			}

			.add-to-cart-btn {
				display: block;
				width: 100%;
				padding: 0.75rem;
				background-color: #2563eb;
				color: white;
				border: none;
				border-radius: 0.375rem;
				cursor: pointer;
				text-align: center;
				font-size: 1rem;
				margin-top: 1.5rem;
			}

			.add-to-cart-btn:hover {
				background-color: #1d4ed8;
			}

			.price-stock-container {
				display: flex;
				justify-content: space-between;
				align-items: center;
				margin-bottom: 1rem;
			}

			footer {
				background-color: white;
				padding: 1rem 2rem;
				text-align: center;
				border-top: 1px solid #e5e7eb;
				color: #4b5563;
				font-size: 0.875rem;
				margin-top: auto;
			}

			/* Back button */
			.back-button {
				display: inline-block;
				margin-bottom: 1.5rem;
				color: #2563eb;
				text-decoration: none;
				font-size: 0.875rem;
			}

			.back-button:hover {
				text-decoration: underline;
			}

			/* Notification */
			.notification {
				position: fixed;
				top: 1rem;
				right: 1rem;
				background-color: #10b981;
				color: white;
				padding: 0.75rem 1rem;
				border-radius: 0.375rem;
				box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
				transform: translateX(150%);
				transition: transform 0.3s ease;
			}

			.notification.show {
				transform: translateX(0);
			}
		</style>
	</head>
	<body>
		<header>
			<a href="index.html" class="store-title">E-commerce Store</a>
			<button class="cart-button">Cart</button>
		</header>

		<main class="product-container">
			<a href="index.html" class="back-button">← Back to products</a>
			<h1 class="product-title" id="product-title">Product Name</h1>
			<p class="product-description" id="product-description">
				Product description goes here.
			</p>

			<div class="price-stock-container">
				<p class="product-price" id="product-price">$0.00</p>
				<p class="product-stock" id="product-stock">0 in stock</p>
			</div>

			<button class="add-to-cart-btn" id="add-to-cart">Add to Cart</button>
		</main>

		<div class="notification" id="notification">Added to cart!</div>

		<footer>
			<p>© 2025 E-commerce Store. All rights reserved.</p>
		</footer>

		<script>
			// Get query parameter from URL
			const url = new URL(window.location.href);
			const searchParams = new URLSearchParams(url.search);
			const productId = searchParams.get("id");

			// Fetch product details
			fetch(`/api/products/${productId}`)
				.then((response) => response.json())
				.then((product) => displayContent(product))
				.catch((error) =>
					console.error("Error fetching product details:", error),
				);

			// Function to display product details
			function displayContent(product) {
				document.title = `${product[0].name} - E-commerce Store`;
				document.getElementById("product-title").textContent = product[0].name;
				document.getElementById("product-description").textContent =
					product[0].description;
				document.getElementById("product-price").textContent =
					`$${product[0].price.toFixed(2)}`;
				document.getElementById("product-stock").textContent =
					`${product[0].inventory} in stock`;
			}
		</script>
	</body>
</html>

Теперь у вас есть фронтенд, который выводит список товаров и отображает отдельный товар. Однако он ещё не подключен к базе данных D1. Если запустить сервер разработки сейчас, товары не отобразятся. На следующих шагах вы создадите базу данных D1 и API для получения и отображения товаров на фронтенде.

Шаг 3: Создайте базу данных D1 и включите репликацию чтения

Создайте новую базу данных D1, выполнив следующую команду:

npx wrangler d1 create fast-commerce

Добавьте привязки D1, возвращённые в терминале, в wrangler файле:

{
	"d1_databases": [
		{
			"binding": "DB",
			"database_name": "fast-commerce",
			"database_id": "YOUR_DATABASE_ID"
		}
	]
}
[[d1_databases]]
binding = "DB"
database_name = "fast-commerce"
database_id = "YOUR_DATABASE_ID"

Выполните следующую команду, чтобы обновить Env интерфейс в worker-configuration.d.ts файл.

npm run cf-typegen

Далее включите репликацию чтения для базы данных D1. Перейдите в Workers & Pages > D1, затем выберите существующую базу данных > Настройки > Enable Read Replication.

Шаг 4: Создайте маршруты API

Обновите src/index.ts файл, чтобы импортировать библиотеку Hono и создать маршруты API.

import { Hono } from "hono";
// Set db session bookmark in the cookie
import { getCookie, setCookie } from "hono/cookie";

const app = new Hono<{ Bindings: Env }>();

// Get all products
app.get("/api/products", async (c) => {
	return c.json({ message: "get list of products" });
});

// Get a single product
app.get("/api/products/:id", async (c) => {
	return c.json({ message: "get a single product" });
});

// Upsert a product
app.post("/api/product", async (c) => {
	return c.json({ message: "create or update a product" });
});

export default app;

Приведённый выше код создаёт три маршрута API:

Однако маршруты API пока не подключены к базе данных D1. На следующих шагах вы создадите таблицу products в базе данных D1 и обновите маршруты API, чтобы подключить их к базе данных D1.

Шаг 5: Создайте схему локальной базы данных D1

Создайте таблицу products в базе данных D1, выполнив следующую команду:

npx wrangler d1 execute fast-commerce --command "CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, inventory INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)"

Далее создайте индекс для таблицы products, выполнив следующую команду:

npx wrangler d1 execute fast-commerce --command "CREATE INDEX IF NOT EXISTS idx_products_id ON products (id)"

Для целей разработки операторы INSERT можно также выполнить в локальной базе данных D1, запустив следующую команду:

npx wrangler d1 execute fast-commerce --command "INSERT INTO products (id, name, description, price, inventory, category) VALUES (1, 'Fast Ergonomic Chair', 'A comfortable chair for your home or office', 100.00, 10, 'Furniture'), (2, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing'), (3, 'Fast Wooden Desk', 'A wooden desk for your home or office', 150.00, 5, 'Furniture'), (4, 'Fast Leather Sofa', 'A leather sofa for your home or office', 300.00, 3, 'Furniture'), (5, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing')"

Шаг 6: Добавьте логику повторных попыток

Чтобы сделать приложение более отказоустойчивым, добавьте логику повторных попыток в маршруты API. Создайте новый файл с именем retry.ts в src каталог.

export interface RetryConfig {
	maxRetries: number;
	initialDelay: number;
	maxDelay: number;
	backoffFactor: number;
}

const shouldRetry = (error: unknown): boolean => {
	const errMsg = error instanceof Error ? error.message : String(error);
	return (
		errMsg.includes("Network connection lost") ||
		errMsg.includes("storage caused object to be reset") ||
		errMsg.includes("reset because its code was updated")
	);
};

// Helper function for sleeping
const sleep = (ms: number): Promise<void> => {
	return new Promise((resolve) => setTimeout(resolve, ms));
};

export const defaultRetryConfig: RetryConfig = {
	maxRetries: 3,
	initialDelay: 100,
	maxDelay: 1000,
	backoffFactor: 2,
};

export async function withRetry<T>(
	operation: () => Promise<T>,
	config: Partial<RetryConfig> = defaultRetryConfig,
): Promise<T> {
	const maxRetries = config.maxRetries ?? defaultRetryConfig.maxRetries;
	const initialDelay = config.initialDelay ?? defaultRetryConfig.initialDelay;
	const maxDelay = config.maxDelay ?? defaultRetryConfig.maxDelay;
	const backoffFactor =
		config.backoffFactor ?? defaultRetryConfig.backoffFactor;

	let lastError: Error | unknown;
	let delay = initialDelay;

	for (let attempt = 0; attempt <= maxRetries; attempt++) {
		try {
			const result = await operation();
			return result;
		} catch (error) {
			lastError = error;

			if (!shouldRetry(error) || attempt === maxRetries) {
				throw error;
			}

			// Add randomness to avoid synchronizing retries
			// Wait for a random delay between delay and delay*2
			await sleep(delay * (1 + Math.random()));

			// Calculate next delay with exponential backoff
			delay = Math.min(delay * backoffFactor, maxDelay);
		}
	}

	throw lastError;
}

withRetry функция представляет собой служебную функцию, которая повторяет заданную операцию с экспоненциальной задержкой. Она принимает объект конфигурации в качестве аргумента, что позволяет настраивать количество попыток, начальную задержку, максимальную задержку и коэффициент увеличения задержки. Повтор операции выполняется только в том случае, если ошибка вызвана потерей сетевого соединения, сбросом хранилища или обновлением кода.

Далее обновите src/index.ts файл, чтобы импортировать withRetry функцию и используйте её в маршрутах API.

import { withRetry } from "./retry";

Шаг 7: Обновите маршруты API

Обновите маршруты API для подключения к базе данных D1.

1. POST /api/product

app.post("/api/product", async (c) => {
	const product = await c.req.json();

	if (!product) {
		return c.json({ message: "No data passed" }, 400);
	}

	const db = c.env.DB;
	const session = db.withSession("first-primary");

	const { id } = product;

	try {
		return await withRetry(async () => {
			// Check if the product exists
			const { results } = await session
				.prepare("SELECT * FROM products where id = ?")
				.bind(id)
				.run();
			if (results.length === 0) {
				const fields = [...Object.keys(product)];
				const values = [...Object.values(product)];
				// Insert the product
				await session
					.prepare(
						`INSERT INTO products (${fields.join(", ")}) VALUES (${fields.map(() => "?").join(", ")})`,
					)
					.bind(...values)
					.run();
				const latestBookmark = session.getBookmark();
				latestBookmark &&
					setCookie(c, "product_bookmark", latestBookmark, {
						maxAge: 60 * 60, // 1 hour
					});
				return c.json({ message: "Product inserted" });
			}

			// Update the product
			const updates = Object.entries(product)
				.filter(([_, value]) => value !== undefined)
				.map(([key, _]) => `${key} = ?`)
				.join(", ");

			if (!updates) {
				throw new Error("No valid fields to update");
			}

			const values = Object.entries(product)
				.filter(([_, value]) => value !== undefined)
				.map(([_, value]) => value);

			await session
				.prepare(`UPDATE products SET ${updates} WHERE id = ?`)
				.bind(...[...values, id])
				.run();
			const latestBookmark = session.getBookmark();
			latestBookmark &&
				setCookie(c, "product_bookmark", latestBookmark, {
					maxAge: 60 * 60, // 1 hour
				});
			return c.json({ message: "Product updated" });
		});
	} catch (e) {
		console.error(e);
		return c.json({ message: "Error upserting product" }, 500);
	}
});

В приведенном выше коде:

Поскольку вы хотите начать сессию с самыми последними данными, используйте first-primary ограничение. Даже если вы используете first-unconstrained ограничение или не передадите bookmark, запрос на запись всегда будет направлен в основную базу данных.

Bookmark, сохранённый в cookie, позволяет гарантировать, что новая сессия будет читать версию базы данных, не менее актуальную, чем указанный bookmark.

Если для управления товарами вы используете внешнюю платформу, этот API можно подключить к ней так, чтобы при создании или обновлении товара на внешней платформе база данных D1 автоматически обновляла сведения о товаре.

2. GET /api/products

app.get("/api/products", async (c) => {
	const db = c.env.DB;

	// Get bookmark from the cookie
	const bookmark = getCookie(c, "product_bookmark") || "first-unconstrained";

	const session = db.withSession(bookmark);

	try {
		return await withRetry(async () => {
			const { results } = await session.prepare("SELECT * FROM products").run();

			const latestBookmark = session.getBookmark();

			// Set the bookmark in the cookie
			latestBookmark &&
				setCookie(c, "product_bookmark", latestBookmark, {
					maxAge: 60 * 60, // 1 hour
				});

			return c.json(results);
		});
	} catch (e) {
		console.error(e);
		return c.json([]);
	}
});

В приведенном выше коде:

3. GET /api/products/:id

app.get("/api/products/:id", async (c) => {
	const id = c.req.param("id");

	if (!id) {
		return c.json({ message: "Invalid id" }, 400);
	}

	const db = c.env.DB;

	// Get bookmark from the cookie
	const bookmark = getCookie(c, "product_bookmark") || "first-unconstrained";

	const session = db.withSession(bookmark);

	try {
		return await withRetry(async () => {
			const { results } = await session
				.prepare("SELECT * FROM products where id = ?")
				.bind(id)
				.run();

			const latestBookmark = session.getBookmark();

			// Set the bookmark in the cookie
			latestBookmark &&
				setCookie(c, "product_bookmark", latestBookmark, {
					maxAge: 60 * 60, // 1 hour
				});

			console.log(results);

			return c.json(results);
		});
	} catch (e) {
		console.error(e);
		return c.json([]);
	}
});

В приведенном выше коде:

Шаг 8: Протестируйте приложение

Теперь маршруты API обновлены для подключения к базе данных D1. Можно протестировать приложение, запустив сервер разработки и перейдя на фронтенд.

npm run dev

Перейдите в `http://localhost:8787. Вы должны увидеть список продуктов. Нажмите на продукт, чтобы просмотреть его детали.

Чтобы добавить новый продукт, используйте следующую команду (пока запущен сервер разработки):

curl -X POST http://localhost:8787/api/product \
     -H "Content-Type: application/json" \
     -d '{"id": 6, "name": "Fast Computer", "description": "A computer for your home or office", "price": 1000.00, "inventory": 10, "category": "Electronics"}'

Перейдите в http://localhost:8787/product-details?id=6. Вы должны увидеть новый продукт.

Обновите продукт с помощью следующей команды и перейдите в http://localhost:8787/product-details?id=6 ещё раз. Вы увидите обновлённый продукт.

curl -X POST http://localhost:8787/api/product \
     -H "Content-Type: application/json" \
     -d '{"id": 6, "name": "Fast Computer", "description": "A computer for your home or office", "price": 1050.00, "inventory": 10, "category": "Electronics"}'

Шаг 9: Разверните приложение

Поскольку база данных, использованная на предыдущих шагах, локальная, необходимо создать таблицу products в удалённой базе данных. Выполните следующие команды D1, чтобы создать таблицу products в удалённой базе данных.

npx wrangler d1 execute fast-commerce --remote --command "CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, inventory INTEGER NOT NULL DEFAULT 0, category TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)"

Далее создайте индекс для таблицы products, выполнив следующую команду:

npx wrangler d1 execute fast-commerce --remote --command "CREATE INDEX IF NOT EXISTS idx_products_id ON products (id)"

При необходимости вы можете добавить продукты в удалённую базу данных, выполнив следующую команду:

npx wrangler d1 execute fast-commerce --remote --command "INSERT INTO products (id, name, description, price, inventory, category) VALUES (1, 'Fast Ergonomic Chair', 'A comfortable chair for your home or office', 100.00, 10, 'Furniture'), (2, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing'), (3, 'Fast Wooden Desk', 'A wooden desk for your home or office', 150.00, 5, 'Furniture'), (4, 'Fast Leather Sofa', 'A leather sofa for your home or office', 300.00, 3, 'Furniture'), (5, 'Fast Organic Cotton T-shirt', 'A comfortable t-shirt for your home or office', 20.00, 100, 'Clothing')"

Теперь вы можете развернуть приложение с помощью следующей команды:

npm run deploy

Это развернёт приложение в Workers, а база данных D1 будет реплицирована в удалённые регионы. Если пользователь обращается к приложению из любого региона, запрос будет перенаправлен в ближайший регион, где реплицирована база данных.

Заключение

В этом руководстве вы узнали, как использовать репликацию для чтения D1 для сайта электронной коммерции. Вы создали базу данных D1 и включили для неё репликацию для чтения, а затем создали API для добавления и обновления товаров в базе данных. Также вы научились использовать bookmark, чтобы получать самые свежие данные из базы данных.

Затем вы создали таблицу товаров в удалённой базе данных и развернули приложение.

Такой же подход можно применить к уже существующему приложению с высокой нагрузкой на чтение, чтобы снизить задержки чтения и повысить его пропускную способность. Если для управления контентом используется внешняя платформа, её можно подключить к базе данных D1, чтобы контент автоматически обновлялся в базе данных.

Полный код этого руководства можно найти в репозиторий GitHub.