소개
카우치베이스 서버 8.0은 고객이 이벤트 함수 배포 또는 재개 중에 외부 변경 없이도 비즈니스 로직을 실행할 수 있는 새로운 이벤트 함수 핸들러인 OnDeploy를 도입했습니다.
이전에는 이벤트 기능을 배포하거나 재개하기 전에 로직을 실행해야 하는 사용 사례가 있는 고객에게는 다음과 같은 몇 가지 선택지가 있었습니다:
- 필요한 설정을 직접 수동으로 수행합니다.
- 이벤트 기능 배포 또는 재시작을 트리거하기 전에 외부 스크립트를 통해 설정을 자동화하세요.
이 두 가지 방법 모두 번거롭고 외부 또는 수동 개입에 의존합니다.
이벤트 함수 라이프사이클의 “배포” 및 “재개” 이벤트는 변이 처리를 시작하려는 시점을 표시합니다. 이렇게 하면 OnDeploy 핸들러는 다음 작업 중 하나를 수행해야 하는 로직을 삽입하는 데 적합합니다:
- 비행 전 점검을 수행하여 환경이 올바르게 구성되었는지 확인합니다.
- 캐시(예: 조회 테이블)를 설정하여 효율성을 개선하세요.
- 다양한 Couchbase 및 외부 서비스에서 데이터를 전송, 수집, 처리합니다.
- “소스 키 공간에서 하나 이상의 문서를 수정하여 배포/재시작한 후 이벤트 함수를 ”자체 트리거'합니다.
- 이 돌연변이는
온업데이트및/또는OnDelete핸들러. - 이것은 고급 사용 사례입니다.
OnDeploy왜냐하면 기존에는 이벤트 함수 실행이 이벤트 함수 자체 또는 타이머 만료가 아닌 다른 엔티티에 의해 소스 키공간에 변경이 발생할 때만 트리거되도록 제한되어 있었기 때문입니다.
- 이 돌연변이는
속도 제한기
이 게시물에서는 다음과 같이 강력한 속도 제한기를 구축하겠습니다. 토큰 버킷 알고리즘 와 Couchbase의 Eventing 서비스를 소개합니다. 이 과정에서 새로운 OnDeploy 핸들러를 직접 경험하고 Eventing이 다른 Couchbase 서비스와의 통합을 간소화하는 방법을 알아볼 수 있습니다.
높은 수준의 디자인
다차원 스케일링
6노드 클러스터에는 노드 간 매핑에 다음과 같은 서비스가 있어야 합니다:
| S.No. | 노드 번호 | 서비스(들) |
|---|---|---|
| 1. | 0 | 데이터 |
| 2. | 1 | 데이터 |
| 3. | 2 | 데이터 |
| 4. | 3 | 이벤트, 쿼리 |
| 5. | 4 | 이벤트, 쿼리 |
| 6. | 5 | 인덱싱 |
클러스터 설정에 대해 몇 가지 주의할 점이 있습니다:
- 데이터 복제를 통해 중복성을 보장하기 위해 3개의 데이터 서비스 노드를 사용합니다.
- 이벤트 기능의 병렬성을 높이기 위해 2개의 노드에서 이벤트 서비스를 실행합니다.
- 이는 이벤트 기능에 여러 명의 워커를 배치하는 것 외에도 수행됩니다.
- 데이터 및 이벤트와 같이 CPU를 많이 사용하는 서비스는 별도의 클러스터 노드에 보관해야 합니다.
- 키스페이스의 모든 문서를 삭제하는 등의 특정 작업은 쿼리 서비스를 통해 편리하게 수행할 수 있기 때문에 쿼리 서비스가 필요합니다.
- 임시 버킷에 대한 기본 인덱스를 생성하려면 인덱싱 서비스가 필요합니다.
키 스페이스
클러스터에는 다음과 같은 키 스페이스가 있어야 합니다:
| S.No. | 버킷 이름 | 버킷 유형 | 범위 | 컬렉션 | 설명 |
|---|---|---|---|---|---|
| 1 | 기본값 | 카우치베이스 | _기본값 | _기본값 |
|
| _system | _모바일 | - | |||
| _system | _query | - | |||
| 2 | 속도 제한기 | 임시 | _기본값 | _기본값 | - |
| _system | _모바일 | - | |||
| _system | _query | - | |||
| my-llm | 제한 | 티어 간 요금 한도 매핑이 포함된 문서를 저장합니다. | |||
| my-llm | 추적기 | 카운터 문서를 저장하여 개별 사용자의 사용량을 추적하세요. | |||
| 3 | my-llm | 카우치베이스 | _기본값 | _기본값 | - |
| _system | _모바일 | - | |||
| _system | _query | - | |||
| 사용자 | 계정 | “티어'를 포함한 사용자 계정 세부 정보를 저장합니다. | |||
| 사용자 | 이벤트 | 사용자의 “티어'에 따라 요금이 제한되어야 하는 사용자 이벤트를 저장합니다. |
참고:
- 그리고
속도 제한기버킷은임시데이터를 유지할 필요가 없기 때문입니다. 해당 버킷의 데이터를 사용하여 사용자별 요금 제한 사용량을 추적하고 계층 간 요금 제한 매핑을 캐시합니다.
외부 REST API 엔드포인트
이벤트 함수는 다음 기능을 제공하는 외부 API 엔드포인트와 상호 작용합니다:
- 최신 티어-요금 제한 매핑을 제공합니다.
- 티어-요금 한도 매핑에 대한 수정을 수락합니다.
- 이벤트 기능을 통해 사용자의 요금 한도 내에서 들어오는 요청을 수락합니다.
- 이 프로젝트에서 엔드포인트는 이러한 수신 요청의 수를 유지합니다.
이 카운트는 속도 제한기 애플리케이션이 예상대로 작동하는지 확인하는 데 도움이 됩니다.
- 이 프로젝트에서 엔드포인트는 이러한 수신 요청의 수를 유지합니다.
- 이벤트 기능에서 사용자의 요금 한도 내에 있는 것으로 간주한 수신 요청 수를 입력합니다.
위 API 엔드포인트의 OpenAPI 사양에 대한 링크는 다음에서 찾을 수 있습니다. 여기.
참고: 이러한 REST API 엔드포인트를 호스팅하는 Go 프로그램은 부록에 링크되어 있습니다.
로우 레벨 디자인
이벤트 기능 설정
아래는 이벤트 기능의 기본 설정에 변경해야 하는 모든 변경 사항 목록입니다.
키 스페이스
| S.No. | 필드 | 가치 |
|---|---|---|
| 1. | 기능 범위 | default._default |
| 2. | 소스 키스페이스 | my-llm.users.events |
| 3. | 이벤트 스토리지 키공간 | 기본값._기본값._기본값 |
설정
| S.No. | 필드 | 가치 |
|---|---|---|
| 1. | 이름 | my-llm-rate-limiter |
| 2. | 배포 피드 경계 | 지금부터 |
| 3. | 설명 | 이 이벤트 함수는 속도 제한기 역할을 합니다. |
| 4. | 작업자 | 10 |
버킷 바인딩
| S.No. | 버킷 Alias |
키 스페이스 | 액세스 | ||
|---|---|---|---|---|---|
| 버킷 | 범위 | 컬렉션 | |||
| 1. | 사용자 계정 | my-llm | 사용자 | 계정 | 읽기 전용 |
| 2. | rateLimiter | 속도 제한기 | my-llm | 추적기 | 읽기 및 쓰기 |
| 3. | 계층 제한 | 속도 제한기 | my-llm | 제한 | 읽기 및 쓰기 |
URL 바인딩
| S.No. | URL 별칭 | URL | Auth | 사용자 이름 | 비밀번호 |
|---|---|---|---|---|---|
| 1. | llmEndpoint | http://localhost:3054/my-llm | 기본 | 이벤트 | Eventing123 |
| 2. | 계층 엔드포인트 | http://localhost:3054/tiers |
참고: “쿠키 허용” 및 “SSL 인증서 유효성 검사” 옵션이 모두 비활성화됩니다.
전체 애플리케이션 흐름도

이 다이어그램은 토큰 버킷 알고리즘에 따라 속도 제한기로 작동하는 이벤트 함수 핸들러, 외부 REST API 엔드포인트 및 키 공간 간의 상호 작용을 보여줍니다.
다음 섹션에서는 속도 제한기를 단계별로 구현해 보겠습니다.
OnDeploy 설정
외부 REST API 엔드포인트에서 계층 가져오기 및 저장하기

언제 OnDeploy 핸들러가 실행되기 시작하면 먼저 외부 REST API 엔드포인트에서 계층 엔드포인트 URL 바인딩.
의 응답은 /티어 외부 REST API는 계층 이름에서 매핑을 포함하는 JSON 값( 문자열)를 시간당 허용되는 총 요청 수(즉, 총_요청_수)로 표시되는 시간당 요금 한도(유형 숫자).
티어-요금 제한 매핑은 요금 제한에 대한 매핑을 rate-limit.my-llm.limits 키스페이스.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
function OnDeploy(action) { // ... // GET the tiers from the `tiersEndpoint` const response = curl('GET', tiersEndpoint); if (response.status != 200) { throw new Error("Error(Cannot get tiers): " + JSON.stringify(response)); } const tiers = response.body; log("Successfully retrieved the tiers: " + JSON.stringify(tiers)); // Write the tiers to the `tierLimits` keyspace, in the document with ID `limits` tierLimits["limits"] = tiers; // ... // Create a timer to run every 24 hours to refresh the tiers let timeAfter24hours = new Date(); timeAfter24hours.setDate(timeAfter24hours.getDate() + 1); log("Time after 24 hours is: " + timeAfter24hours); createTimer(updateTierCallback, timeAfter24hours, "tier-updater", {}); // ... } // Function to update the user tiers every 24 hours function updateTierCallback(context) { log('From updateTierCallback: timer fired'); // GET the tiers from the `tiersEndpoint` const response = curl('GET', tiersEndpoint); if (response.status != 200) { log("Error(Cannot get tiers): " + JSON.stringify(response)); } else { const tiers = response.body; log("Successfully retrieved the tiers: " + JSON.stringify(tiers)); // Write the tiers to the `tierLimits` keyspace, in the document with ID `limits` tierLimits["limits"] = tiers; } // Create a timer to run every 24 hours to refresh the tiers let timeAfter24hours = new Date(); timeAfter24hours.setDate(timeAfter24hours.getDate() + 1); log("Time after 24 hours is: " + timeAfter24hours); createTimer(updateTierCallback, timeAfter24hours, "tier-updater", {}); } |
배포 시 모든 속도 제한 추적기 초기화
![]()
저희 애플리케이션에서는 이벤트 기능의 배포 중단을 하드 셧다운으로 모델링하여 배포하는 동안 사용자의 속도 제한 사용을 추적하는 모든 문서를 삭제합니다. 일시 중지는 속도 제한 활동의 일시적인 중단으로 모델링하므로 이벤트 기능이 재개될 경우 해당 문서를 지우지 않습니다.
배포와 재개 작업을 별도로 처리하는 방식이 궁금하신가요? OnDeploy 는 이러한 사용 사례를 가능하게 하는 이유는 이벤트가 이유 필드에 액션 객체에 대한 OnDeploy 핸들러를 사용하여 이벤트 함수의 배포 또는 재개 여부를 지정할 수 있습니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
function OnDeploy(action) { // ... // If we are deploying, then we should delete all the existing document in the keyspace `rateLimiter` if (action.reason === "deploy") { let results = N1QL("DELETE FROM `rate-limiter`.`my-llm`.tracker"); results.close(); log("Deleted all the documents in the `rate-limiter`.`my-llm`.tracker keyspace as we are deploying!"); } // ... } |
매 시간마다 사용자 요금 한도 재설정하기

토큰 버킷 알고리즘을 구현하고 있기 때문에 타이머를 사용하여 매시간 사용자의 요금 한도를 재설정하는데, 이는 저희 사용 사례에 매우 중요한 이벤트 기능입니다. 첫 번째 타이머는 OnDeploy 핸들러에 1시간 후에 실행되도록 설정합니다. 타이머 콜백이 실행되면 1시간 후에 실행될 새 타이머를 생성하는 식으로 이벤트 기능이 배포되어 있는 한 매 시간마다 실행되는 자체 반복 타이머를 생성합니다.
이 타이머를 생성하기 위해 이벤트 함수를 트리거하는 데 외부 변이가 필요하지 않은 것을 관찰하세요. 이 모든 작업은 배포/재시작 시 OnDeploy 핸들러.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function OnDeploy(action) { // ... // Create a timer to run every 1 hour to reset user rate limits let timeAfter1Hour = new Date(); timeAfter1Hour.setHours(timeAfter1Hour.getHours() + 1); log("Time after 1 hour is: " + timeAfter1Hour); createTimer(resetRateLimiter, timeAfter1Hour, "rate-limit-resetter", {}); // ... } // Function to reset the rate limits for all users every 1 hour function resetRateLimiter(context) { log('From resetRateLimiter: timer fired'); let results = N1QL("DELETE FROM `rate-limiter`.`my-llm`.tracker"); results.close(); // Create a timer to run every 1 hour to reset user rate limits let timeAfter1Hour = new Date(); timeAfter1Hour.setHours(timeAfter1Hour.getHours() + 1); log("Time after 1 hour is: " + timeAfter1Hour); createTimer(resetRateLimiter, timeAfter1Hour, "rate-limit-resetter", {}); } |
매일 티어 요금 한도 새로 고침

저희는 24시간마다 요금 제한을 변경할 수 있도록 애플리케이션을 모델링하므로, 이벤트 기능은 24시간마다 외부 REST API 엔드포인트에서 최신 계층-요금 제한 매핑을 가져와야 사용자에게 올바른 요금 제한이 적용될 수 있습니다.
다시 말하지만, 자동 반복 타이머를 사용하여 24시간마다 최신 티어-속도 제한 매핑을 가져옵니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
function OnDeploy(action) { // ... // Create a timer to run every 24 hours to refresh the tiers let timeAfter24hours = new Date(); timeAfter24hours.setDate(timeAfter24hours.getDate() + 1); log("Time after 24 hours is: " + timeAfter24hours); createTimer(updateTierCallback, timeAfter24hours, "tier-updater", {}); // ... } // Function to update the user tiers every 24 hours function updateTierCallback(context) { log('From updateTierCallback: timer fired'); // GET the tiers from the `tiersEndpoint` const response = curl('GET', tiersEndpoint); if (response.status != 200) { log("Error(Cannot get tiers): " + JSON.stringify(response)); } else { const tiers = response.body; log("Successfully retrieved the tiers: " + JSON.stringify(tiers)); // Write the tiers to the `tierLimits` keyspace, in the document with ID `limits` tierLimits["limits"] = tiers; } // Create a timer to run every 24 hours to refresh the tiers let timeAfter24hours = new Date(); timeAfter24hours.setDate(timeAfter24hours.getDate() + 1); log("Time after 24 hours is: " + timeAfter24hours); createTimer(updateTierCallback, timeAfter24hours, "tier-updater", {}); } |
온업데이트 설정
사용자 이벤트 처리

애플리케이션은 다음에서 들어오는 요청 문서를 수신합니다. my-llm.users.events 키스페이스. 이러한 문서에는 고유 ID가 있으며 해당 형식의 데이터가 포함되어 있습니다:
|
1 2 3 4 5 6 |
{ "user_id": String, "respond_to": String, "payload": String, "header": String } |
사용자의 요청이 전송률 한도 내에 있는 경우, 사용자_ID를 제외한 문서의 모든 데이터는 전송률 제한기로 보호되는 엔드포인트로 전송됩니다.
사용자 계층 읽기

언제 온업데이트 핸들러가 이전 단계에서 들어오는 사용자 이벤트 문서에 의해 트리거되면, 이전 단계의 user_id 필드로 이동합니다.
사용 user_id 필드에서 사용자의 계정 세부 정보 문서를 검색합니다. my-llm.users.accounts 키스페이스. 이 문서에서 이 문서에 있는 계층 필드에 입력합니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function OnUpdate(doc, meta, xattrs) { // ... const user_id = doc.user_id; let done = false; while (!done) { // Get the tier of the `user_id` let userAccountsMeta = { "id": user_id }; let userAccountsResult = couchbase.get(userAccounts, userAccountsMeta, { "cache": true }); if (!userAccountsResult.success) { throw new Error("Error(Unable to get the user's details): " + JSON.stringify(userAccountsResult)); } const tier = userAccountsResult.doc.tier; // ... } // ... } |
티어의 요금 한도 읽기

티어와 요금 한도 매핑이 포함된 문서에서 사용자의 티어에 대한 요금 한도를 가져옵니다. rate-limit.my-llm.limits 키스페이스.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function OnUpdate(doc, meta, xattrs) { // ... while (!done) { // ... // Get the rate limit for the tier let tierLimitsMeta = { "id": "limits" }; let tierLimitsResult = couchbase.get(tierLimits, tierLimitsMeta, { "cache": true }); if (!tierLimitsResult.success) { throw new Error("Error(Unable to get the tier limits): " + JSON.stringify(tierLimitsResult)); } const rateLimit = tierLimitsResult.doc[tier]; // ... } // ... } |
요청에 대한 요금 제한 여부를 결정하고 사용자의 요금 제한 사용량을 업데이트하세요.

이제 사용자의 요금 한도가 주어지면 현재 사용량을 확인하여 요청을 할 수 있는지 여부를 결정합니다. 요금 제한기는 각 사용자의 사용량을 카운터 문서로 추적하여 rate-limit.my-llm.tracker 키스페이스. 각 요청에 따라 이 카운터 문서를 생성합니다. user_id 를 사용하여 토큰 버킷 한도가 새로 고쳐지기 전에 현재 기간 동안 해당 사용자의 요청 횟수를 저장합니다. 사용자의 사용량이 해당 티어의 한도를 충족하거나 초과하면 요청을 차단합니다. 그렇지 않으면 보호된 엔드포인트로 요청을 전달합니다. 마지막으로, 사용자의 요금 한도 사용량을 해당 카운터 문서에서 업데이트합니다. rate-limit.my-llm.tracker 키스페이스.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
function OnUpdate(doc, meta, xattrs) { // ... while (!done) { // ... // Try to get the rate limit count for the `user_id` const userIDMeta = { "id": user_id }; const result = couchbase.get(rateLimiter, userIDMeta); // If the rate limit count for the `user_id` does not exist. Try to create it. while (!result.success) { couchbase.insert(rateLimiter, userIDMeta, { "count": 0 }); result = couchbase.get(rateLimiter, userIDMeta); } // Assign the counter document's `count` and `meta` to local variables for convenience const counterDocCount = result.doc.count; const counterDocMeta = result.meta; // Check if the counter has hit the rate limit // We use >= instead of == to handle the edge case where the tier limits have reduced // but the tier tracker documents have not yet been deleted. if (counterDocCount >= rateLimit) { log("User with ID '" + user_id + "' hit their rate limit of " + rateLimit + "!"); done = true; continue; } // Update the count in the document let res = couchbase.mutateIn(rateLimiter, counterDocMeta, [ couchbase.MutateInSpec.replace("count", counterDocCount + 1), ]); // ... } // ... } |
원하는 엔드포인트로 “한도 내” 요청 보내기

사용자 요청은 해당 티어의 요금 한도 내에서 요금 제한기로 보호되는 REST API 엔드포인트로 전송됩니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function OnUpdate(doc, meta, xattrs) { // ... while (!done) { // ... done = res.success; if (done) { // POST the request to the `llmEndpoint` delete doc.user_id; const response = curl('POST', llmEndpoint, doc); if (response.status != 200) { throw new Error("Error(MyLLM endpoint is not working): " + response.status); } } } // ... } |
애플리케이션 테스트
이제 속도 제한기를 구현했으니 이를 실행하고 테스트할 환경을 만들 수 있습니다:
- Go 프로그램을 실행하여 100명의 사용자로 구성된 샘플 세트를 로드합니다.
- Go 프로그램을 실행하여 이벤트 함수가 상호작용하는 외부 REST API를 제공하는 HTTP 서버를 시작합니다.
- 이벤트 기능을 배포합니다.
- 이벤트 기능을 트리거하려면 Go 프로그램을 실행하여 사용자 이벤트 문서를 소스 키 공간에 로드해야 합니다,
my-llm.users.events. - 속도 제한기로 보호되는 외부 REST API 엔드포인트에 도달하는 사용자 요청의 수를 확인하려면 다음을 전송해야 합니다.
GET요청에 대한/my-llm엔드포인트.
결론
이 게시물에서는 새로운 Couchbase Eventing 핸들러를 사용하는 방법을 보여드렸습니다, OnDeploy, 를 사용하여 토큰 버킷 비율 제한기를 구축하여 통합된 독립형 솔루션을 개발하는 데 있어 Couchbase Eventing의 강력한 성능과 유연성을 강조했습니다.
더 넓게는 데이터베이스 자체에서 애플리케이션을 구축하는 애플리케이션 개발의 변화를 보여줍니다. 이를 통해 다양한 요구사항에 대한 맞춤형 솔루션을 Couchbase 플랫폼 내에서 모두 구현할 수 있습니다.
부록
이벤트 코드를 작성합니다: 여기를 클릭하세요
서버 이동 코드: 여기를 클릭하세요
클라이언트 이동 코드: 여기를 클릭하세요
사용자 로더 이동 코드: 여기를 클릭하세요