본문으로 바로가기 본문 바로가기
WIKI 기술 지식 베이스

분산 구성 저장소 구축하기 (Build a distributed configuration store)

원문 보기 위키 갱신

Workers KV를 지리적으로 분산된 저지연 구성 저장소로 사용하는 예시 문서예요. 외부 데이터베이스의 구성 데이터를 Workers KV에 동기화하고 Worker에서 활용하는 방법을 보여줘요.

출처: 문서

본문

Build a distributed configuration store

Workers KV를 Workers 애플리케이션의 지리적으로 분산된 저지연 구성 저장소로 사용하세요.

애플리케이션 구성 데이터를 저장하는 것은 Workers KV에 이상적인 사용 사례예요. 구성 데이터에는 각 사용자나 테넌트에 맞게 애플리케이션을 개인화하는 데이터, 사용자 그룹에 기능을 활성화하는 데이터, 허용 목록/차단 목록으로 접근을 제한하는 데이터 등이 포함될 수 있어요. 이러한 사용 사례는 읽기 볼륨이 높을 수 있고 Workers KV가 높은 캐시 효율을 제공하므로 Workers 애플리케이션에서 저지연 읽기를 보장할 수 있어요.

이 예시에서 애플리케이션 구성 데이터는 각 사용자에 맞게 Workers 애플리케이션을 개인화하는 데 사용돼요. 구성 데이터는 외부 애플리케이션과 데이터베이스에 저장되며, REST API를 사용해 Workers KV에 작성돼요.

외부 애플리케이션에서 Workers KV로 구성 작성하기

경우에 따라 구성 데이터의 원본(source-of-truth)이 Workers KV가 아닌 다른 곳에 저장될 수 있어요. 이런 경우 Workers KV REST API를 사용해 구성 데이터를 Workers KV 네임스페이스에 작성하세요.

다음 외부 Node.js 애플리케이션은 데이터베이스에서 사용자 데이터를 읽고 REST API 라이브러리를 사용해 Workers KV에 작성하는 간단한 스크립트를 보여줘요.

const postgres = require('postgres');
const { Cloudflare } = require('cloudflare');
const { backOff } = require('exponential-backoff');

if(!process.env.DATABASE_CONNECTION_STRING || !process.env.CLOUDFLARE_EMAIL || !process.env.CLOUDFLARE_API_KEY || !process.env.CLOUDFLARE_WORKERS_KV_NAMESPACE_ID || !process.env.CLOUDFLARE_ACCOUNT_ID) {
console.error('Missing required environment variables.');
process.exit(1);
}

// Setup Postgres connection
const sql = postgres(process.env.DATABASE_CONNECTION_STRING);

// Setup Cloudflare REST API client
const client = new Cloudflare({
apiEmail: process.env.CLOUDFLARE_EMAIL,
apiKey: process.env.CLOUDFLARE_API_KEY,
});

// Function to sync Postgres data to Workers KV
async function syncPreviewStatus() {
console.log('Starting sync of user preview status...');

    try {
    	// Get all users and their preview status
    	const users = await sql`SELECT id, preview_features_enabled FROM users`;

    	console.log(users);

    	// Create the bulk update body
    	const bulkUpdateBody = users.map(user => ({
    		key: user.id,
    		value: JSON.stringify({
    			preview_features_enabled: user.preview_features_enabled
    		})
    	}));

    	const response = await backOff(async () => {
    		console.log("trying to update")
    		try{
    			const response = await client.kv.namespaces.bulkUpdate(process.env.CLOUDFLARE_WORKERS_KV_NAMESPACE_ID, {
    				account_id: process.env.CLOUDFLARE_ACCOUNT_ID,
    				body: bulkUpdateBody
    			});
    		}
    		catch(e){
    			// Implement your error handling and logging here
    			console.log(e);
    			throw e; // Rethrow the error to retry
    		}
    	});

    	console.log(`Sync complete. Updated ${users.length} users.`);
    } catch (error) {
    	console.error('Error syncing preview status:', error);
    }

}

// Run the sync function
syncPreviewStatus()
.catch(console.error)
.finally(() => process.exit(0));

.env 파일:

DATABASE_CONNECTION_STRING = <DB_CONNECTION_STRING_HERE>
CLOUDFLARE_EMAIL = <CLOUDFLARE_EMAIL_HERE>
CLOUDFLARE_API_KEY = <CLOUDFLARE_API_KEY_HERE>
CLOUDFLARE_ACCOUNT_ID = <CLOUDFLARE_ACCOUNT_ID_HERE>
CLOUDFLARE_WORKERS_KV_NAMESPACE_ID = <CLOUDFLARE_WORKERS_KV_NAMESPACE_ID_HERE>

db.sql 스키마:

-- Create users table with preview_features_enabled flag
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  username VARCHAR(100) NOT NULL,
  email VARCHAR(255) NOT NULL,
  preview_features_enabled BOOLEAN DEFAULT false
);

-- Insert sample users
INSERT INTO users (username, email, preview_features_enabled) VALUES
('alice', 'alice@example.com', true),
('bob', 'bob@example.com', false),
('charlie', 'charlie@example.com', true);

이 코드 스니펫에서 Node.js 애플리케이션은 Postgres 데이터베이스에서 사용자 데이터를 읽고, Workers 애플리케이션에서 구성에 사용할 사용자 데이터를 Cloudflare REST API Node.js 라이브러리를 사용해 Workers KV에 작성해요. 애플리케이션은 또한 오류 발생 시 재시도를 처리하기 위해 지수 백오프(exponential backoff)를 사용해요.

Worker 애플리케이션에서 Workers KV의 구성 데이터 사용하기

이제 구성 데이터가 Workers KV 네임스페이스에 있으므로, 이를 Workers 애플리케이션에서 사용해 각 사용자에 맞게 애플리케이션을 개인화할 수 있어요.

// Example configuration data stored in Workers KV:
// Key: "user-id-abc" | Value: {"preview_features_enabled": false}
// Key: "user-id-def" | Value: {"preview_features_enabled": true}

interface Env {
  USER_CONFIGURATION: KVNamespace;
}

export default {
  async fetch(request, env) {
    // Get user ID from query parameter
    const url = new URL(request.url);
    const userId = url.searchParams.get('userId');

    if (!userId) {
      return new Response('Please provide a userId query parameter', {
        status: 400,
        headers: { 'Content-Type': 'text/plain' }
      });
    }

		const userConfiguration = await env.USER_CONFIGURATION.get<{
			preview_features_enabled: boolean;
		}>(userId, {type: "json"});

		console.log(userConfiguration);

    // Build HTML response
    const html = `
      <!DOCTYPE html>
      <html>
        <head>
          <title>My App</title>
          <style>
            body {
              font-family: Arial, sans-serif;
              max-width: 800px;
              margin: 0 auto;
              padding: 20px;
            }
            .preview-banner {
              background-color: #ffeb3b;
              padding: 10px;
              text-align: center;
              margin-bottom: 20px;
              border-radius: 4px;
            }
          </style>
        </head>
        <body>
          ${userConfiguration?.preview_features_enabled ? `
            <div class="preview-banner">
              🎉 You have early access to preview features! 🎉
            </div>
          ` : ''}
          <h1>Welcome to My App</h1>
          <p>This is the regular content everyone sees.</p>
        </body>
      </html>
    `;

    return new Response(html, {
			headers: { "Content-Type": "text/html; charset=utf-8" }
    });
  }
} satisfies ExportedHandler<Env>;
{
	"$schema": "node_modules/wrangler/config-schema.json",
	"name": "<ENTER_WORKER_NAME>",
	"main": "src/index.ts",
	"compatibility_date": "2025-03-03",
	"observability": {
		"enabled": true
	},
	"kv_namespaces": [
		{
			"binding": "USER_CONFIGURATION",
			"id": "<YOUR_BINDING_ID>"
		}
	]
}

이 코드는 URL 안의 경로를 사용해 KV 저장소 안의 경로에 해당하는 파일을 찾아요. 또한 응답에 적절한 MIME 유형을 설정해 브라우저가 응답을 처리하는 방법을 알려줘요. KV 저장소에서 값을 가져오기 위해 이 코드는 arrayBuffer를 사용해 이미지, 문서, 동영상/오디오 파일 같은 이진 데이터를 적절히 처리해요.

구성을 위한 성능 최적화

성능을 최적화하려면 값을 더 적은 수의 키-값 쌍으로 통합할 수도 있어요. 이렇게 하면 캐시 효율이 높아지고 대기 시간이 줄어드는 이점을 얻을 수 있어요.

예를 들어 각 사용자의 구성을 별도의 키-값 쌍에 저장하는 대신 모든 사용자의 구성을 단일 키-값 쌍에 저장할 수 있어요. 이 방식은 구성 데이터가 작고 단일 키-값 쌍으로 쉽게 관리할 수 있는 사용 사례에 적합할 수 있어요. (Workers KV 값의 크기 제한은 25 MiB예요.)

더 알아보기 (Learn more)