INTEGRITY Dokumentace

Persistentní ukládání dat pomocí R2

Připojte buckety objektového úložiště jako cesty v lokálním souborovém systému a uchovejte data napříč životními cykly sandboxu. Tento návod používá Cloudflare R2, ale stejný postup funguje u libovolného poskytovatele kompatibilního s S3.

Tento návod ukazuje, jak zachovat externí datový adresář připojený na /data. Pokud chcete mít pracovní projekt v /workspace aby zůstaly zachovány, viz Zálohování a obnova.

Doba dokončení: 20 minut

Co vytvoříte

Worker, který zpracovává data, ukládá výsledky do bucketu R2 připojeného jako lokální adresář a ukazuje, že data přetrvají i po zničení a opětovném vytvoření sandboxu.

Klíčové koncepty, které se naučíte:

Předpoklady

  1. Zaregistrujte si účet Cloudflare ↗.
  2. Nainstalujte Node.js ↗.

Správce verzí Node.js

Použijte správce verzí Node, jako je Volta ↗ nebo nvm ↗ abyste se vyhnuli problémům s oprávněními a mohli měnit verze Node.js. Wrangler, o kterém se dozvíte dále v této příručce, vyžaduje verzi Node 16.17.0 nebo novější.

Budete také potřebovat:

1. Vytvořte projekt

npm create cloudflare@latest -- data-pipeline --template=cloudflare/sandbox-sdk/examples/minimal
cd data-pipeline

2. Nakonfigurujte R2 binding

Přidejte binding R2 bucket do svého wrangler.json:

wrangler.json
{
  "name": "data-pipeline",
  "compatibility_date": "2025-11-09",
  "durable_objects": {
    "bindings": [
      { "name": "Sandbox", "class_name": "Sandbox" }
    ]
  },
  "r2_buckets": [
    {
      "binding": "DATA_BUCKET",
      "bucket_name": "my-data-bucket"
    }
  ]
}

Nahraďte my-data-bucket svým názvem R2 bucketu. Bucket nejprve vytvořte v Cloudflare dashboard ↗.

3. Sestavte procesor dat

Nahraďte src/index.ts s kódem, který připojí R2 a zpracovává data:

import { getSandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

export default {
	async fetch(request, env) {
		const url = new URL(request.url);
		const sandbox = getSandbox(env.Sandbox, "data-processor");

		// Mount R2 bucket to /data directory
		await sandbox.mountBucket("my-data-bucket", "/data", {
			endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
		});

		if (url.pathname === "/process") {
			// Process data and save to mounted R2
			const result = await sandbox.exec("python", {
				args: [
					"-c",
					`
import json
import os
from datetime import datetime

# Read input (or create sample data)
data = [
    {'id': 1, 'value': 42},
    {'id': 2, 'value': 87},
    {'id': 3, 'value': 15}
]

# Process: calculate sum and average
total = sum(item['value'] for item in data)
avg = total / len(data)

# Save results to mounted R2 (/data is the mounted bucket)
result = {
    'timestamp': datetime.now().isoformat(),
    'total': total,
    'average': avg,
    'processed_count': len(data)
}

os.makedirs('/data/results', exist_ok=True)
with open('/data/results/latest.json', 'w') as f:
    json.dump(result, f, indent=2)

print(json.dumps(result))
				`,
				],
			});

			return Response.json({
				message: "Data processed and saved to R2",
				result: JSON.parse(result.stdout),
			});
		}

		if (url.pathname === "/results") {
			// Read results from mounted R2
			const result = await sandbox.exec("cat", {
				args: ["/data/results/latest.json"],
			});

			if (!result.success) {
				return Response.json(
					{ error: "No results found yet" },
					{ status: 404 },
				);
			}

			return Response.json({
				message: "Results retrieved from R2",
				data: JSON.parse(result.stdout),
			});
		}

		if (url.pathname === "/destroy") {
			// Destroy sandbox to demonstrate persistence
			await sandbox.destroy();
			return Response.json({
				message: "Sandbox destroyed. Data persists in R2!",
			});
		}

		return new Response(
			`
Data Pipeline with Persistent Storage

Endpoints:
- POST /process  - Process data and save to R2
- GET /results   - Retrieve results from R2
- POST /destroy  - Destroy sandbox (data survives!)

Try this flow:
1. POST /process  (processes and saves to R2)
2. POST /destroy  (destroys sandbox)
3. GET /results   (data still accessible from R2)
		`,
			{ headers: { "Content-Type": "text/plain" } },
		);
	},
};
import { getSandbox, type Sandbox } from '@cloudflare/sandbox';

export { Sandbox } from '@cloudflare/sandbox';

interface Env {
	Sandbox: DurableObjectNamespace<Sandbox>;
	DATA_BUCKET: R2Bucket;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		const url = new URL(request.url);
		const sandbox = getSandbox(env.Sandbox, 'data-processor');

		// Mount R2 bucket to /data directory
		await sandbox.mountBucket('my-data-bucket', '/data', {
			endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com'
		});

		if (url.pathname === '/process') {
			// Process data and save to mounted R2
			const result = await sandbox.exec('python', {
				args: ['-c', `
import json
import os
from datetime import datetime

# Read input (or create sample data)
data = [
    {'id': 1, 'value': 42},
    {'id': 2, 'value': 87},
    {'id': 3, 'value': 15}
]

# Process: calculate sum and average
total = sum(item['value'] for item in data)
avg = total / len(data)

# Save results to mounted R2 (/data is the mounted bucket)
result = {
    'timestamp': datetime.now().isoformat(),
    'total': total,
    'average': avg,
    'processed_count': len(data)
}

os.makedirs('/data/results', exist_ok=True)
with open('/data/results/latest.json', 'w') as f:
    json.dump(result, f, indent=2)

print(json.dumps(result))
				`]
			});

			return Response.json({
				message: 'Data processed and saved to R2',
				result: JSON.parse(result.stdout)
			});
		}

		if (url.pathname === '/results') {
			// Read results from mounted R2
			const result = await sandbox.exec('cat', {
				args: ['/data/results/latest.json']
			});

			if (!result.success) {
				return Response.json({ error: 'No results found yet' }, { status: 404 });
			}

			return Response.json({
				message: 'Results retrieved from R2',
				data: JSON.parse(result.stdout)
			});
		}

		if (url.pathname === '/destroy') {
			// Destroy sandbox to demonstrate persistence
			await sandbox.destroy();
			return Response.json({ message: 'Sandbox destroyed. Data persists in R2!' });
		}

		return new Response(`
Data Pipeline with Persistent Storage

Endpoints:
- POST /process  - Process data and save to R2
- GET /results   - Retrieve results from R2
- POST /destroy  - Destroy sandbox (data survives!)

Try this flow:
1. POST /process  (processes and saves to R2)
2. POST /destroy  (destroys sandbox)
3. GET /results   (data still accessible from R2)
		`, { headers: { 'Content-Type': 'text/plain' } });
	}
};

4. Nasaďte do produkce

Vygenerujte tokeny R2 API:

  1. Přejděte na R2 > Přehled v Cloudflare dashboard ↗
  2. Vyberte Správa tokenů R2 API
  3. Vytvořte token s Object Read & Write oprávnění
  4. Zkopírujte Access Key ID a Secret Access Key

Nastavte přihlašovací údaje jako tajné klíče Workeru:

npx wrangler secret put AWS_ACCESS_KEY_ID
# Paste your R2 Access Key ID

npx wrangler secret put AWS_SECRET_ACCESS_KEY
# Paste your R2 Secret Access Key

Tajné klíče Workeru jsou šifrované a přístupné pouze vašemu nasazenému Workeru. SDK tyto přihlašovací údaje automaticky rozpozná, když mountBucket() je volána.

Nasaďte svůj Worker:

npx wrangler deploy

Po nasazení wrangler vypíše URL vašeho Workeru (např. https://data-pipeline.yourname.workers.dev).

5. Otestujte průběh perzistence

Nyní otestujte proti nasazenému Workeru. Nahraďte YOUR_WORKER_URL skutečnou URL adresou vašeho Workeru:

# 1. Process data (saves to R2)
curl -X POST https://YOUR_WORKER_URL/process
# Returns: { "message": "Data processed...", "result": { "total": 144, "average": 48, ... } }

# 2. Verify data is accessible
curl https://YOUR_WORKER_URL/results
# Returns the same results from R2

# 3. Destroy the sandbox
curl -X POST https://YOUR_WORKER_URL/destroy
# Returns: { "message": "Sandbox destroyed. Data persists in R2!" }

# 4. Access results again (from new sandbox)
curl https://YOUR_WORKER_URL/results
# Still works! Data persisted across sandbox lifecycle

Klíčové zjištění: po zničení sandboxu vytvoří další požadavek novou instanci sandboxu, připojí stejný R2 bucket a data v něm stále najde.

Co jste se naučili

V tomto tutoriálu jste vytvořili datový pipeline, který demonstruje perzistenci souborového systému pomocí připojení R2 bucketu:

Další kroky