구독해서 새 게시물에 대한 알림을 받으세요.

Vectorize: AI 기반 앱을 프로덕션에 빠르게 제공하기 위한 벡터 데이터베이스

2023. 09. 27.

21분 읽기
Vectorize: a vector database for shipping AI-powered applications to production, fast

Vectorize는 전적으로 Cloudflare의 전역 네트워크에서 전체 스택 AI 기반 응용 프로그램을 구축할 수 있도록 설계된 새로운 벡터 데이터베이스 제품으로, 즉시 구축을 시작할 수 있습니다. Vectorize는 오픈 베타 중이며 Cloudflare Workers를 사용하는 모든 개발자가 사용할 수 있습니다.

Vectorize를 Workers AI와 함께 사용하면 Workers로 직접 시맨틱 검색, 분류, 추천 및 이상 감지 사용 사례를 강화하고, LLM(대규모 언어 모델)에서 답변의 정확성과 컨텍스트를 개선하고, OpenAI 및 Cohere 등 인기 있는 플랫폼에서 자체 임베딩을 가져올 수 있습니다.

먼저 Vectorize의 개발자 설명서를 참조하거나, 설명서를 계속 읽으면 벡터 데이터베이스가 하는 일과 Vectorize가 어떻게 다른지 더 잘 이해할 수 있습니다.

벡터 데이터베이스가 필요한 이유는 무엇인가요?

머신러닝 모델은 학습한 내용만 기억할 수 있으며 모든 내용을 기억할 수는 없습니다.

벡터 데이터베이스는 ML 모델에서 정형 및 비정형 텍스트, 이미지, 오디오 등 데이터를 표현하는 방식을 식별하고 향후 입력과 비교할 수 있는 방식으로 저장하여 이 문제를 해결하도록 설계되었습니다. 이를 통해 학습하지 않은 콘텐츠에서도 기존 머신 러닝 모델과 LLM(대규모 언어 모델)의 힘을 활용할 수 있고, 모델 학습에 드는 막대한 비용을 고려할 때 매우 강력한 것으로 밝혀졌습니다.

Vectorize와 같은 벡터 데이터베이스가 유용한 이유를 더 잘 설명하기 위해, 벡터 데이터베이스가 없다고 가정하고, 시맨틱 검색이나 추천 작업을 위해 필요한 컨텍스트를 ML 모델이나 LLM에 제공하는 것이 얼마나 힘든 일인지 알아봅시다. 당사의 목표는 자체 데이터세트에 기반하여 쿼리와 유사한 콘텐츠가 무엇인지 파악하고 그 콘텐츠를 반환하는 것입니다.

  1. 사용자 쿼리를 수신하였으며, 사용자는 “Cloudflare Workers에서 R2에 쓰는 방법”을 검색하고 있습니다.
  2. 당사는 65,000개 문장으로 구성된 "작은" 전체 설명서 데이터세트 2.1GB를 로드하여 사용자의 쿼리와 함께 제공합니다. 이를 통해 당사의 데이터를 기반으로 모델에 필요한 컨텍스트를 제공할 수 있습니다.
  3. 기다립니다.
  4. (오랜 시간 동안)
  5. 사용자의 쿼리와 가장 유사한 문장을 사용하여 유사도 점수를 얻은 다음, 이를 URL에 매핑하는 작업을 거쳐 검색 결과를 반환합니다.

…그런 다음, 다른 쿼리를 수신하고 이 작업을 처음부터 다시 시작해야 합니다.

이런 경우는 실제로 불가능합니다. 대부분의 머신 러닝 모델에 API 호출(프롬프트)을 통해 그 정도의 컨텍스트를 전달할 수 없으며, 설령 가능하다고 해도 데이터세트를 계속 반복적으로 처리하려면 엄청난 양의 메모리와 시간이 소모됩니다.

벡터 데이터베이스를 사용하면 2단계를 반복할 필요가 없습니다. 한 번 또는 데이터세트를 업데이트할 때 이 단계를 수행하고 벡터 데이터베이스를 사용하여 머신 러닝 모델에 장기 메모리의 형태로 제공하기만 하면 됩니다. 워크플로우는 다음과 같습니다.

  1. 전체 설명서 데이터세트를 로드하고 모델을 통해 실행하여 그 결과로 얻은 벡터 임베딩을 벡터 데이터베이스에 저장합니다(한 번만).
  2. 각 사용자 쿼리(쿼리만 해당)에 대해 동일한 모델에 질의하고 벡터 표현을 검색합니다.
  3. 해당 쿼리 벡터로 벡터 데이터베이스를 쿼리하면 쿼리 벡터에 가장 가까운 벡터를 반환합니다.

이 두 흐름을 나란히 살펴보면, 벡터 데이터베이스 없이 기존 모델로 자체 데이터세트를 사용하는 것이 얼마나 비효율적이고 실용적이지 않은지 금방 알 수 있습니다.

벡터 데이터베이스를 사용하여 머신 러닝 모델의 메모리를 지원합니다.

이 간단한 예시를 보고 어느 정도 이해가 되셨겠지만, 왜 일반 데이터베이스가 아니라 벡터 데이터베이스가 필요한지 궁금하실 수도 있습니다.

벡터는 모델이 입력을 표현한 것으로서 해당 입력을 내부 구조 즉, “특징”에 매핑하는 방식입니다. 일반적으로 벡터가 유사할수록 모델은 입력에서 특징을 추출하는 방식에 따라 해당 입력 간의 유사성이 더 높다고 생각하는 경향이 있습니다.

차원이 몇 개밖에 없는 벡터의 예시를 보면 이 방식 쉬워 보일 수 있습니다. 하지만 실제 결과를 사용할 때 10,000~250,000개의 벡터(벡터당 1,536개의 차원)를 검색하는 것은 결코 쉬운 일이 아닙니다. 이런 경우에 벡터 데이터베이스가 도움이 됩니다. 벡터 데이터베이스는 대규모 검색 작업을 위해 k-최근접 이웃(kNN) 또는 다른 근사 근접 이웃(ANN) 알고리즘 등 특정 종류의 알고리즘을 사용하여 벡터 유사성을 판단합니다.

또한 벡터 데이터베이스는 AI와 머신 러닝 기반 애플리케이션을 구축할 때 매우 유용하지만, 이러한 사용 사례에서 유용한 것은 아닙니다. 벡터 데이터베이스는 다양한 분류 및 이상 감지 작업에 사용할 수 있습니다. 쿼리 입력이 다른 입력과 유사하거나 잠재적으로 다를 가능성이 있는지 여부를 파악하면 콘텐츠 조정(알려진 악성 콘텐츠와 일치하는지 확인) 및 보안 경고(이전에 본 적이 있는지 여부) 작업에도 활용할 수 있습니다.

벡터 검색으로 추천 엔진 구축하기

Vectorize를 Workers AI와 함께 원활하게 사용할 수 있도록 구축했습니다. 그래서 사용자와 가능한 한 가까운 곳에서 벡터 검색 작업을 실행할 수 있으며, 운영 환경에 맞게 확장하는 방법을 고민할 필요도 없습니다.

전자 상거래 매장을 위한 (제품) 추천 엔진을 구축하는 실제 사례를 들고 몇 가지를 단순화해 보겠습니다.

각 제품 목록 페이지에 "관련 제품" 목록을 표시하는 것이 목적이므로 벡터 검색에 완벽한 사용 사례입니다. 이 예시에서는 입력 벡터는 플레이스홀더로 되어 있지만, 실제 애플리케이션에서는 Worker AI의 텍스트 임베딩 모델과 같은 문장 유사성 모델을 통과하여 제품 설명 및/또는 장바구니 데이터를 기반으로 입력 벡터를 생성할 수 있습니다.

각 벡터는 매장 전체에 있는 제품을 나타내며 우리는 제품의 URL을 해당 벡터와 연결합니다. 각 벡터의 ID를 제품 ID로 설정할 수도 있습니다. 두 가지 접근 방식 모두 유효합니다. 쿼리(벡터 검색)는 사용자가 보고 있는 제품의 설명과 콘텐츠를 나타냅니다.

코드에서 어떤 모습인지 단계별로 살펴보겠습니다. 이 예제는 당사의 개발자 설명서에서 직접 가져온 것입니다.

export interface Env {
	// This makes our vector index methods available on env.MY_VECTOR_INDEX.*
	// e.g. env.MY_VECTOR_INDEX.insert() or .query()
	TUTORIAL_INDEX: VectorizeIndex;
}

// Sample vectors: 3 dimensions wide.
//
// Vectors from a machine-learning model are typically ~100 to 1536 dimensions
// wide (or wider still).
const sampleVectors: Array<VectorizeVector> = [
	{ id: '1', values: [32.4, 74.1, 3.2], metadata: { url: '/products/sku/13913913' } },
	{ id: '2', values: [15.1, 19.2, 15.8], metadata: { url: '/products/sku/10148191' } },
	{ id: '3', values: [0.16, 1.2, 3.8], metadata: { url: '/products/sku/97913813' } },
	{ id: '4', values: [75.1, 67.1, 29.9], metadata: { url: '/products/sku/418313' } },
	{ id: '5', values: [58.8, 6.7, 3.4], metadata: { url: '/products/sku/55519183' } },
];

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		if (new URL(request.url).pathname !== '/') {
			return new Response('', { status: 404 });
		}
		// Insert some sample vectors into our index
		// In a real application, these vectors would be the output of a machine learning (ML) model,
		// such as Workers AI, OpenAI, or Cohere.
		let inserted = await env.TUTORIAL_INDEX.insert(sampleVectors);

		// Log the number of IDs we successfully inserted
		console.info(`inserted ${inserted.count} vectors into the index`);

		// In a real application, we would take a user query - e.g. "durable
		// objects" - and transform it into a vector emebedding first.
		//
		// In our example, we're going to construct a simple vector that should
		// match vector id #5
		let queryVector: Array<number> = [54.8, 5.5, 3.1];

		// Query our index and return the three (topK = 3) most similar vector
		// IDs with their similarity score.
		//
		// By default, vector values are not returned, as in many cases the
		// vectorId and scores are sufficient to map the vector back to the
		// original content it represents.
		let matches = await env.TUTORIAL_INDEX.query(queryVector, { topK: 3, returnVectors: true });

		// We map over our results to find the most similar vector result.
		//
		// Since our index uses the 'cosine' distance metric, scores will range
		// from 1 to -1.  A value of '1' means the vector is the same; the
		// closer to 1, the more similar. Values of -1 (least similar) and 0 (no
		// match).
		// let closestScore = 0;
		// let mostSimilarId = '';
		// matches.matches.map((match) => {
		// 	if (match.score > closestScore) {
		// 		closestScore = match.score;
		// 		mostSimilarId = match.vectorId;
		// 	}
		// });

		return Response.json({
			// This will return the closest vectors: we'll see that the vector
			// with id = 5 has the highest score (closest to 1.0) as the
			// distance between it and our query vector is the smallest.
			// Return the full set of matches so we can see the possible scores.
			matches: matches,
		});
	},
};

상단의 코드는 의도적으로 단순화한 것이지만 벡터 검색의 핵심을 보여줍니다. 데이터베이스에 벡터를 삽입하고 쿼리 벡터와 가장 가까운 거리에 있는 벡터를 대상으로 쿼리합니다.

다음은 값을 포함한 결과입니다. 쿼리 벡터 [54.8, 5.5, 3.1]가 검색 결과에서 반환된 가장 높은 점수를 얻은 일치 항목 [58.799, 6.699, 3.400]와 유사한 것을 시각적으로 확인할 수 있습니다. 이 인덱스는 코사인 유사도를 사용하여 벡터 간의 거리를 계산하므로 점수가 1에 가까울 수록 일치 항목이 쿼리 벡터와 더 유사하다는 것을 의미합니다.

{
  "matches": {
    "count": 3,
    "matches": [
      {
        "score": 0.999909,
        "vectorId": "5",
        "vector": {
          "id": "5",
          "values": [
            58.79999923706055,
            6.699999809265137,
            3.4000000953674316
          ],
          "metadata": {
            "url": "/products/sku/55519183"
          }
        }
      },
      {
        "score": 0.789848,
        "vectorId": "4",
        "vector": {
          "id": "4",
          "values": [
            75.0999984741211,
            67.0999984741211,
            29.899999618530273
          ],
          "metadata": {
            "url": "/products/sku/418313"
          }
        }
      },
      {
        "score": 0.611976,
        "vectorId": "2",
        "vector": {
          "id": "2",
          "values": [
            15.100000381469727,
            19.200000762939453,
            15.800000190734863
          ],
          "metadata": {
            "url": "/products/sku/10148191"
          }
        }
      }
    ]
  }
}

실제 애플리케이션에서는 가장 유사한 제품을 기반으로 제품 추천 URL을 빠르게 반환합니다. 점수의 오름차순 또는 내림차순으로 제품을 정렬할 수도 있고, topK 값을 높여 더 많은 제을 표시할 수도 있습니다. 각 벡터와 함께 저장되는 메타데이터에는 R2 개체의 경로, 데이터베이스에 있는 행의 UUID, 또는 Workers KV의 키-값 쌍이 포함될 수도 있습니다.

Workers AI + Vectorize: Cloudflare의 전체 스택 벡터 검색

실제 애플리케이션에서는 원본 데이터세트에서 벡터 임베딩을 생성하고(데이터베이스 시드 목적), 사용자 쿼리를 벡터 임베딩으로 빠르게 변환할 수 있는 머신 러닝 모델이 필요합니다. 각 모델은 특징을 다르게 표현하므로 벡터 임베딩은 같은 모델에서 가져와야 합니다.

다음은 Cloudflare에서 전체 엔드투엔드 벡터 검색 파이프라인을 구축하는 간단한 예제입니다.

import { Ai } from '@cloudflare/ai';
export interface Env {
	TEXT_EMBEDDINGS: VectorizeIndex;
	AI: any;
}
interface EmbeddingResponse {
	shape: number[];
	data: number[][];
}

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const ai = new Ai(env.AI);
		let path = new URL(request.url).pathname;
		if (path.startsWith('/favicon')) {
			return new Response('', { status: 404 });
		}

		// We only need to generate vector embeddings just the once (or as our
		// data changes), not on every request
		if (path === '/insert') {
			// In a real-world application, we could read in content from R2 or
			// a SQL database (like D1) and pass it to Workers AI
			const stories = ['This is a story about an orange cloud', 'This is a story about a llama', 'This is a story about a hugging emoji'];
			const modelResp: EmbeddingResponse = await ai.run('@cf/baai/bge-base-en-v1.5', {
				text: stories,
			});

			// We need to convert the vector embeddings into a format Vectorize can accept.
			// Each vector needs an id, a value (the vector) and optional metadata.
			// In a real app, our ID would typicaly be bound to the ID of the source
			// document.
			let vectors: VectorizeVector[] = [];
			let id = 1;
			modelResp.data.forEach((vector) => {
				vectors.push({ id: `${id}`, values: vector });
				id++;
			});

			await env.TEXT_EMBEDDINGS.upsert(vectors);
		}

		// Our query: we expect this to match vector id: 1 in this simple example
		let userQuery = 'orange cloud';
		const queryVector: EmbeddingResponse = await ai.run('@cf/baai/bge-base-en-v1.5', {
			text: [userQuery],
		});

		let matches = await env.TEXT_EMBEDDINGS.query(queryVector.data[0], { topK: 1 });
		return Response.json({
			// We expect vector id: 1 to be our top match with a score of
			// ~0.896888444
			// We are using a cosine distance metric, where the closer to one,
			// the more similar.
			matches: matches,
		});
	},
};

상단의 코드는 다음 네 가지 작업을 수행합니다.

  1. 세 문장을 Workers AI의 텍스트 임베딩 모델(@cf/baai/bge-base-en-v1.5)에 전달하고 각 문장의 벡터 임베딩을 검색합니다.
  2. 이 벡터를 Vectorize 인덱스에 삽입합니다.
  3. 사용자 쿼리를 가져오고 동일한 Workers AI 모델을 통해 벡터 임베딩으로 변환합니다.
  4. 일치 항목을 Vectorize 인덱스에서 쿼리합니다.

이 예제는 “지나치게” 단순해 보일 수도 있지만, 실제 애플리케이션에서는 벡터를 한 번(또는 Cron Triggers를 통해 주기적으로) 삽입하고, 세 개의 예제 문장을 R2, D1 데이터베이스 또는 다른 스토리지 공급자에 저장된 실제 데이터로 교체하는 두 가지 작업만 변경하면 됩니다.

사실 이 작업은 Cloudflare Worker에 대한 질문에 답할 수 있는 AI 비서 Cursor를 실행하는 방식과 매우 유사합니다. 당사는 Workers AI와 Vectorize에서 실행할 수 있도록 Cursor를 마이그레이션했습니다. 개발자 설명서를 참고하여 내장된 텍스트 임베딩 모델을 사용하는 텍스트 임베딩을 생성하고, 이를 Vectorize 인덱스에 삽입하고, 동일한 모델을 통해 사용자 쿼리를 즉시 변환합니다.

가장 선호하는 AI API를 사용한 BYO 임베딩

Vectorize는 Workers AI에만 국한하지 않고, 필요한 모든 기능을 갖춘 독립형 벡터 데이터베이스입니다.

이미 OpenAI의 임베딩 API, Cohere의 다국어 모델 또는 다른 임베딩 API를 사용하고 있는 경우, Vectorize에 자체 벡터를 쉽게 가져올 수 있습니다.

동작 방식은 같습니다. 임베딩을 생성하고, Vectorize에 삽입하고, 모델을 통해 쿼리를 전달하고, 인덱스를 쿼리합니다. Vectorize에는 가장 인기 있는 임베딩 모델들에 대한 몇 가지 바로 가기가 포함되어 있습니다.

# Vectorize has ready-to-go presets that set the dimensions and distance metric for popular embeddings models
$ wrangler vectorize create openai-index-example --preset=openai-text-embedding-ada-002

이는 기존 임베딩 API에 대한 기존 워크플로우가 이미 있거나 사용 사례에 맞는 특정 멀티모달 또는 다국어 임베딩 모델을 검증한 경우 특히 유용할 수 있습니다.

AI의 비용 예측하기

AI와 ML에 대한 기대치는 매우 높지만, 규모가 큰 경우 실험하려면 비용이 너무 많이 들고 예측하기 어렵다는 우려도 존재합니다.

우리는 Vectorize를 통해 벡터 데이터베이스에 더 간단한 가격 모델을 도입하고 싶었습니다. 회사에서 개념 증명을 위한 좋은 생각이 있으신가요? 이는 무료 등급 제한에 적합해야 합니다. 성능과 정확성을 위해 임베딩 차원을 확장하고 최적화하고 계신가요? 이는 비용이 많이 들지 않아야 합니다.

중요한 것은 Vectorize는 예측 가능성을 목표로 한다는 점입니다. 즉, CPU와 메모리 소비량을 예측할 필요가 없습니다. 처음에는 어려울 수 있습니다. 새로운 사용 사례를 위한 프로덕션 환경에서 피크 타임과 한산한 시간에 대비한 계획을 세울 때는 더욱 그렇습니다. 대신, 월별 요금은 저장되는 총 벡터 차원 수와 해당 벡터에 대한 쿼리 수에 따라 책정됩니다. 쿼리 패턴에 맞춰 임베딩 차원을 확장하는 일은 당사의 몫입니다.

Vectorize의 요금은 다음과 같습니다. 현재 Workers 유료 요금제를 사용 중인 경우, 2024년까지 Vectorize를 완전히 무료로 사용할 수 있습니다.

Workers Free(출시 예정) Workers Paid(5달러/월)
쿼리한 벡터 차원 포함 총 3천만 개의 쿼리한 차원/월 총 5천만 개의 쿼리한 차원/월
저장된 벡터 차원 포함 5백만 개의 저장된 차원/월 1천만 개의 저장된 차원/월
추가 비용 0.04달러/ 1백만 개의 쿼리한 또는 저장된 벡터 차원 0.04달러/ 1백만 개의 쿼리한 또는 저장된 벡터 차원

가격은 저장 및 쿼리 규모에 따라 전적으로 달라집니다. 비용을 계산하는 공식은 '(쿼리 및 저장된 총 벡터 차원 수) * 벡터당 차원 수 * 단가'입니다. 쿼리가 더 많으면? 예측하기 쉽습니다. 최적화를 통해 벡터당 더 적은 수의 차원을 사용하여 속도를 개선하고 전체 대기 시간을 줄일 수 있으신가요? 비용이 절감됩니다. 새로운 사용 사례의 프로토타입을 제작하거나 실험하기 위한 몇 가지 인덱스가 있으신가요? 당사는 인덱스당 요금을 부과하지 않습니다.

새로운 아이디어의 프로토타입을 제작 및/또는 운영 환경과 개발 환경을 분리하는 데 필요한 만큼 인덱스를 만듭니다.

예를 들어, 10,000개의 Workers AI 벡터(벡터당 384개 차원)를 로드하고 인덱스에서 하루에 5,000개의 쿼리를 만드는 경우, 쿼리되는 벡터 차원 수는 총 4,900만 개지만 여전히 Workers 유료 요금제(월 5달러)에 포함된 벡터 차원 수에 맞출 수 있습니다. 더 좋은 점은 비활성 상태라고 해도 당사는 인덱스를 삭제하지 않습니다.

이 가격이 최종 가격은 아니지만 향후 거의 변동이 없을 것으로 예상됩니다. 플랫폼 구축, 코딩, 테스트, 기술적인 뉘앙스 학습에 시간을 투자한 그때서야 비용을 감당할 수 없다는 사실을 알게 되는 것보다 더 나쁜 일은 없습니다.

Vectorize!

유료 요금제를 사용하는 모든 Workers 개발자는 즉시 Vectorize를 사용할 수 있습니다. 지금 당장 오픈 베타를 사용할 수 있으며, 개발자 설명서를 참조하여 시작하실 수 있습니다.

이는 Cloudflare 벡터 데이터베이스 스토리의 시작에 불과합니다. 앞으로 몇 주 또는 몇 달에 걸쳐 쿼리 성능을 더 개선하고, 더 큰 규모의 인덱스를 지원하고, 하위 인덱스 필터링 기능 및 메타데이터 제한 증가, 인덱스별 분석 기능을 도입한 새로운 쿼리 엔진을 출시할 계획입니다.

무엇을 구축해야 할지 영감이 필요하다면, 문서 검색을 위해 Workers AI와 Vectorize를 결합한 시맨틱 검색 튜토리얼을 참조하세요. 시맨틱 검색 튜토리얼은 Cloudflare에서 전적으로 운영하고 있습니다. 또는 LLM에 더 많은 컨텍스트를 제공하여 답변의 정확도를 크게 향상시키는 OpenAI와 Vectorize를 결합하는 방법의 예시를 알아보세요.

또한 제품 및 엔지니어링 팀에서 Vectorize를 사용하는 방법에 대해 궁금한 사항이 있거나 Workers AI를 기반으로 개발하는 다른 개발자들과 아이디어를 공유하고 싶다면, 개발자 Discord의 #vectorize 및 #workers-ai 채널에 참여해 주세요.

Cloudflare에서는 전체 기업 네트워크를 보호하고, 고객이 인터넷 규모의 애플리케이션을 효과적으로 구축하도록 지원하며, 웹 사이트와 인터넷 애플리케이션을 가속화하고, DDoS 공격을 막으며, 해커를 막고, Zero Trust로 향하는 고객의 여정을 지원합니다.

어떤 장치로든 1.1.1.1에 방문해 인터넷을 더 빠르고 안전하게 만들어 주는 Cloudflare의 무료 앱을 사용해 보세요.

더 나은 인터넷을 만들기 위한 Cloudflare의 사명을 자세히 알아보려면 여기에서 시작하세요. 새로운 커리어 경로를 찾고 있다면 채용 공고를 확인해 보세요.
Birthday Week (KO)Vectorize (KO)Cloudflare Workers (KO)Developer Platform (KO)AI (KO)Database (KO)Product News (KO)한국어

X에서 팔로우하기

Matt Silverlock|@elithrar
Jérôme Schneider|@jeromeschneider
Cloudflare|@cloudflare

관련 게시물

2023년 10월 02일 오후 1:00

창립기념일 주간 요약: Cloudflare에서 발표한 모든 내용 및 스타트업을 위한 AI를 기반으로 한 기회

이번 주 창립 기념일 주간의 중요 뉴스를 모두 요약하거나 다시 확인할 필요가 있나요? 이 요약이 도움이 될 수 있습니다...

2023년 9월 29일 오후 1:00

Encrypted Client Hello - 개인정보 보호를 위한 마지막 퍼즐 조각

오늘 당사에서 모든 인터넷 사용자의 개인정보 보호를 강화하기 위한 방안을 발표하게 되어 기쁘게 생각합니다. 사용자가 방문하고 있는 웹 사이트를 누군가 염탐하지 못하도록 네트워크를 보호하는 새로운 기준인 암호화된 Client Hello가 이제 모든 Cloudflare 요금제에서 제공됩니다...