이미 눈치채신 분들도 계실 겁니다. 오늘 출시된 Couchbase 1.8와 다양한 언어를 지원하는 새로운 스마트 클라이언트 세트를 출시했습니다. 개인적으로 이것은 매우 중요한 이정표입니다. libcouchbase 는 이제 C 언어에 대해 지원되는 클라이언트입니다.
그렇다면 왜 제가 그것에 관심을 가질까요? 글쎄요, 서버의 다양한 컴포넌트를 쉽게 테스트하고 싶다는 필요성에서 libcouchbase가 시작되었습니다. 저는 서버의 컴포넌트 개발을 대부분 C로 구현했기 때문에 테스트에 C를 사용하는 것이 당연했습니다.
멀티 스레드 컨텍스트에서 libcouchbase가 어떻게 작동하는지에 대한 몇 가지 질문을 받았기 때문에 먼저 명확히 해야 할 것 같습니다. any 형태의 잠금을 사용하여 내부 데이터 구조를 보호할 수 있지만, 그렇다고 해서 멀티스레드 프로그램에서 libcouchbase를 사용할 수 없다는 의미는 아닙니다. 즉, 클라이언트 사용자는 잠금 기능을 사용하여 여러 스레드에서 동시에 라이브러리카우치베이스 인스턴스에 액세스하지 못하도록 보호하거나 각 스레드가 자체 라이브러리카우치베이스 인스턴스에서만 작동하도록 해야 한다는 의미일 뿐입니다. 이 문제를 해결하는 한 가지 쉬운 방법은 각 스레드가 Couchbase 서버에 액세스해야 할 때마다 해당 인스턴스를 푸시하는 libcouchbase 인스턴스 '풀'을 만드는 것입니다. 이 풀에 대한 액세스는 잠금으로 보호되어야 합니다(하지만 이미 알고 계시겠죠 ;-).
이 블로그 게시물에서는 Couchbase 서버에 JSON 문서를 업로드하는 데 사용할 수 있는 데모 프로그램을 만들어 보겠습니다. 전체 소스는 다음 링크에서 찾을 수 있습니다. https://github.com/trondn/vacuum 예제를 사용해보고 싶으시다면
이 프로그램의 아이디어는 디렉토리를 "모니터링"하고 거기에 나타나는 모든 파일을 Couchbase 클러스터에 업로드하는 것입니다. 아마 여러분 대부분은 이렇게 생각하실 겁니다: "어떻게 이식 가능한 방식으로 할 수 있을까?"라고 생각하실 것입니다. 쉬운 일이 아니므로 저는 그렇게 하려고 시도조차 하지 않겠습니다. 대신 반휴대용 방식으로 구현하여 다른 플랫폼에서 구현하기가 어렵지 않아야 합니다. 즉, 다음과 같은 제한 사항을 사용하고 있습니다:
- 사용 중 열기 그리고 readdir 를 사용하여 디렉토리를 탐색합니다. 이는 다음을 사용하여 쉽게 다시 구현할 수 있습니다. FindFirst 그리고 다음 찾기 를 클릭합니다.
- 디렉터리 모니터링은 디렉터리를 스캔한 다음 지정된 시간 동안 절전 모드로 전환한 후 다른 스캔을 실행한다는 의미입니다. 일부 플랫폼에서는 파일 시스템의 변경 사항을 구독하는 기능을 지원한다는 것을 알고 있지만, 적어도 지금 당장은 그렇게 할 생각은 없습니다(-)).
- 다른 사람이 파일을 쓰는 동안 파일이 잠기거나 파일에 액세스하는 것을 방지하려면 클라이언트는 파일 이름 앞에 '점'이 있는 디렉터리에 파일을 쓴 다음 완료되면 파일 이름을 바꿔야 합니다. 프로그램은 점으로 시작하는 모든 파일을 무시합니다.
이제 코드로 넘어가 보겠습니다. 가장 먼저 살펴볼 수 있는 코드는 main() 함수에서 libcouchbase 인스턴스를 생성하는 부분입니다:
if (인스턴스 == NULL) {
fprintf(stderr, "카우치베이스 인스턴스를 만들지 못했습니다.");
exit(EXIT_FAILURE);
}
위의 코드 스니펫은 libcouchbase 인스턴스를 생성합니다. 정적 구조를 사용하면 바이너리 호환성을 유지하기가 매우 어렵기 때문에 이를 위해 정적 구조를 사용할 수 없습니다. 저는 라이브러리 내에서 버그를 수정하고 프로그램을 다시 컴파일하지 않고도 사용할 수 있는 새 버전을 출시할 수 있고, 클라이언트에서 내부 데이터 구조를 숨기면 클라이언트의 크기에 의존하지 않는 것이 더 쉬워진다는 점을 좋아합니다. 첫 번째 매개변수는 libcouchbase_create 는 카우치베이스 서버의 REST 포트 이름(및 포트)입니다(기본값: localhost:8091). 두 번째 및 세 번째 매개변수는 풀 정보를 얻기 위해 REST 포트에 연결할 때 사용할 자격 증명입니다(기본값은 인증하지 않음). 네 번째 매개 변수는 연결하려는 버킷이며, 버킷을 지정하지 않으면 '기본 버킷'으로 연결됩니다. 다섯 번째 인수는 libcouchbase의 "고급" 기능을 사용하려는 경우 사용할 수 있는 특수 객체입니다. 대부분의 사용자는 기본값을 사용하고 여기에 NULL을 전달할 것입니다.
다음으로 해야 할 일은 어떤 일이 일어나는지 파악할 수 있도록 몇 가지 콜백 핸들러를 설정하는 것입니다. 이 예제에서는 하나의 작업(캐시에 데이터를 로드하는 작업)만 사용하므로 스토리지 작업의 결과를 포착하는 핸들러를 설정해야 합니다. 불행히도 문제가 발생할 수도 있으므로 오류 처리기를 설정해야 합니다(잠시 후에 다시 설명하겠습니다).
libcouchbase_set_error_callback(인스턴스, 오류_콜백);
이제 인스턴스를 생성하고 초기화했으므로 Couchbase 클러스터에 연결을 시도해야 합니다:
if (ret != LIBCOUCHBASE_SUCCESS) {
fprintf(stderr, "연결에 실패했습니다: %sn",
libcouchbase_error(instance, ret));
exit(EXIT_FAILURE);
}
libcouchbase는 완전히 비동기식이기 때문에 위에서 일어난 일은 연결이 시작되었다는 것뿐입니다. 즉, 다음을 수행해야 합니다. wait 를 실행하여 서버가 Couchbase 클러스터에 연결되고 올바른 버킷에 연결되도록 합니다. 프로그램이 다른 작업을 수행해야 한다면 지금이 바로 해야 할 때이지만, 다른 초기화 작업이 없으므로 완료될 때까지 기다리면 됩니다:
내부 통계 인터페이스를 제공하므로 다음 스니펫을 사용하여 작업의 타이밍 정보를 수집하도록 지시할 수 있다는 점이 libcouchbase의 '멋진' 기능 중 하나입니다:
if ((ret = libcouchbase_enable_timings(instance) != LIBCOUCHBASE_SUCCESS)) {
fprintf(stderr, "타이밍 활성화에 실패했습니다: %sn",
libcouchbase_error(instance, ret));
}
이제 프로그램이 완전히 초기화되었으며 다음과 같은 메인 루프에 들어갈 수 있습니다:
{
프로세스_파일();
sleep(nsec);
}
그렇다면 프로세스_파일() 처럼 보이나요? 예제를 너무 크게 만들어서 모두 붙여넣지는 않겠지만, 첫 번째 예제는 이렇게 생겼습니다:
if (strcmp(de->d_name, ".dump_stats") == 0) {
fprintf(stdout, "통계 덤핑:n");
libcouchbase_get_timings(인스턴스, 스탯아웃, 타이밍_콜백);
fprintf(stdout, "--n");
remove(de->d_name);<
}
계속합니다;
}
위의 코드 스니펫에서 볼 수 있듯이 ''로 시작하는 모든 파일은 무시합니다..' 파일을 제외하고는 ".dump_stats". 해당 파일을 볼 때마다 내부 통계 타이밍을 덤프하기 위해 타이밍_콜백 (이 부분은 나중에 다시 설명하겠습니다).
다음으로 할 일은 파일을 메모리로 읽고 JSON을 디코딩한 후 "_id" 필드를 키로 사용하도록 설정합니다. 이 모든 작업이 성공하면 데이터를 Coucbase에 저장하려고 시도합니다:
ret = libcouchbase_store(instance, &error, LIBCOUCHBASE_SET,
ID->값 문자열, 스트렌(ID->값 문자열),
ptr, size, 0, 0, 0);
if (ret == LIBCOUCHBASE_SUCCESS) {
libcouchbase_wait(인스턴스);
} else {
오류 = 1;
}
여기서 &error 부분은 꽤 흥미롭습니다. 콜백에 전달되는 '쿠키'로, 문제가 발생했는지 여부를 알 수 있습니다. 제가 이 쿠키를 어떻게 사용하는지 설명할 때 storage_callback 아래에 있습니다.
이것이 기본적으로 이 예제에서 중요한 로직의 전부입니다. 다른 콜백으로 돌아가겠다고 약속했으니 오류 콜백부터 살펴보겠습니다:
libcouchbase_error_t 오류,
const char *errinfo)
{
/* 시간 초과 무시... */
if (error != LIBCOUCHBASE_ETIMEDOUT) {
fprintf(stderr, "rFATAL ERROR: %sn",
libcouchbase_error(인스턴스, 오류));
if (errinfo && strlen(errinfo) != 0) {
fprintf(stderr, "t"%s"n", errinfo);
}
exit(EXIT_FAILURE);
}
}
위 스니펫에서 볼 수 있듯이 libcouchbase는 오류_콜백 을 호출하여 시간 초과가 발생하지만 작업을 다시 시도하려고 합니다. 실제 오류가 발생하면 오류 메시지를 출력하고 프로그램을 종료합니다.
다음으로 사용하는 콜백은 storage_callback. 저장 작업이 완료되면 호출되므로 데이터를 저장하는 동안 오류가 발생했는지 파악하기에 적합한 곳입니다. 콜백은 다음과 같습니다:
const void *쿠키,
libcouchbase_storage_t 작업,
libcouchbase_error_t 오류,
const void *key, size_t nkey,
UINT64_t CAS)
{
int *error = (void*)cookie;
if (err == LIBCOUCHBASE_SUCCESS) {
*오류 = 0;
} else {
*오류 = 1;
fprintf(stderr, "저장에 실패했습니다.");
fwrite(key, 1, nkey, stderr);
fprintf(stderr, "": %sn",
libcouchbase_error(instance, err));
fflush(stderr);
}
}
보시다시피 쿠키로 전달된 정수에 연산 결과를 저장하고 있습니다. 주의 깊은 독자라면 파일을 연결 해제하고 콜백 내에서 메모리를 제거할 수도 있다는 것을 알 수 있습니다(대신 해당 정보를 쿠키로 제공했다면 ;)).
마지막으로 다룰 콜백은 타이밍 통계를 덤프하는 데 사용하는 타이밍 콜백입니다.
static void timings_callback(libcouchbase_t 인스턴스, const void *쿠키,
라이브러리카우치베이스_시간단위_t 시간단위,
UINT32_t 최소, UINT32_t 최대,
UINT32_t 합계, UINT32_t 최대합계)
{
문자 버퍼[1024];
int offset = sprintf(buffer, "[%3u - %3u]", min, max);
스위치 (시간 단위) {
case LIBCOUCHBASE_TIMEUNIT_NSEC:
오프셋 += sprintf(버퍼 + 오프셋, "ns");
break;
case LIBCOUCHBASE_TIMEUNIT_USEC:
오프셋 += sprintf(버퍼 + 오프셋, "us");
break;
case LIBCOUCHBASE_TIMEUNIT_MSEC:
오프셋 += sprintf(버퍼 + 오프셋, "ms");
break;
case LIBCOUCHBASE_TIMEUNIT_SEC:
오프셋 += sprintf(버퍼 + 오프셋, "s");
break;
기본값입니다:
;
}
int num = (float)40.0 * (float)total / (float)maxtotal;
오프셋 += sprintf(버퍼 + 오프셋, " |");
for (int ii = 0; ii < num; ++ii) {
오프셋 += sprintf(버퍼 + 오프셋, "#");
}
오프셋 += sprintf(버퍼 + 오프셋, " - %un", total);
fputs(버퍼, (FILE*)쿠키);
}
libcouchbase에서 타이밍을 요청하면 타이밍 콜백을 호출하여 수집한 모든 타이밍 메트릭을 보고합니다. API에서 볼 수 있듯이 범위의 최소값, 최대값, 해당 범위 내에서 수행된 작업 수를 얻을 수 있습니다. 이러한 메트릭은 작업을 호출한 시점부터 작업이 완료될 때까지 클라이언트 코드에서 수행한 작업에 따라 달라지므로 정확한 수치로 간주해서는 안 됩니다(예: libcouchbase_wait을 호출하여 작업이 완료될 때까지).
이제 프로그램을 실행해 보겠습니다. 다음을 미리 채웠습니다. /var/spool/vacuum 를 여러 개의 JSON 파일과 함께 사용하여 프로그램이 어떤 작업을 수행하도록 할 수 있습니다.
다시 시도하기 전에 3초 동안 잠자기...
다른 위도우에서 명령을 실행합니다:
그리고 첫 번째 창에서 타이머가 만료되면 인쇄됩니다:
[60 - 69]미국 |######################################## - 18
[70 - 79]미국 |## - 1
[240 - 249]미국 |## - 1
—-
다시 시도하기 전에 3초 동안 잠자기...
이 블로그를 통해 libcouchbase를 사용해 Couchbase 클러스터와 통신하는 것이 얼마나 쉬운지 알게 되셨길 바랍니다. libcouchbase를 기반으로 PHP, Ruby 등 다른 프로그래밍 언어를 위한 다양한 클라이언트가 구축되어 있으므로 앞으로 더 많은 기능이 추가될 것입니다!