naver / arcus-c-client Goto Github PK
View Code? Open in Web Editor NEWARCUS C client
Home Page: https://github.com/naver/arcus
License: Apache License 2.0
ARCUS C client
Home Page: https://github.com/naver/arcus
License: Apache License 2.0
아래와 같이 buffer 크기가 잘못된 경우가 있다.
그 외, 불필요하게 크게 지정된 부분과 일관성 없는 부분이 있음.
program core because memcache invoke abort()
arcus-c-client version: 1.13.0
(gdb) bt
#0 0x00000038e5a32495 in raise () from /lib64/libc.so.6
#1 0x00000038e5a33c75 in abort () from /lib64/libc.so.6
#2 0x00007f73a98b77ba in memcached_set_error (self=..., rc=MEMCACHED_ERRNO, at=0x7f73a98de53b "libmemcached/get.cc:205") at libmemcached/error.cc:316
#3 0x00007f73a98bbff8 in ascii_get_by_key (ptr=0x7f73a3ed1000, master_server_key=4294967295, is_group_key_set=false, key=0x7f7270678868 "***************", key_length=22)
at libmemcached/get.cc:205
#4 0x00007f73a98bcbab in memcached_get_by_key (ptr=0x7f73a3ed1000, group_key=0x0, group_key_length=0, key=0x7f7270678868 "**************", key_length=22,
value_length=0x7f738b7f5ed8, flags=0x7f738b7f5ee0, error=0x7f738b7f5ee4) at libmemcached/get.cc:541
#5 0x00007f73a98bd12c in memcached_get (ptr=0x7f73a3ed1000, key=0x7f7270678868 "****************", key_length=22, value_length=0x7f738b7f5ed8, flags=0x7f738b7f5ee0,
error=0x7f738b7f5ee4) at libmemcached/get.cc:643
Segmentation fault
(gdb) f 3
#3 0x00007f73a98bbff8 in ascii_get_by_key (ptr=0x7f73a3ed1000, master_server_key=4294967295, is_group_key_set=false, key=0x7f7270678868 "***************", key_length=22)
at libmemcached/get.cc:205
205 in libmemcached/get.cc
(gdb) p rc
$5 = MEMCACHED_ERRNO
(gdb) p server_key
$6 = 10
(gdb) p command
$7 = 0x7f73a98de533 "get "
(gdb) p vector
$8 = {{length = 4, buffer = 0x7f73a98de533}, {length = 0, buffer = 0x0}, {length = 22, buffer = 0x7f7270678868}, {length = 2, buffer = 0x7f73a98de538}}
(gdb) p *instance
$10 = {options = {is_allocated = false, is_initialized = true, is_shutting_down = false, is_dead = false}, number_of_hosts = 0, cursor_active = 0, port = 11247, fd = -1,
io_bytes_sent = 0, server_failure_counter = 1, server_failure_counter_query_id = 2, weight = 1, version = 11, state = MEMCACHED_SERVER_STATE_IN_TIMEOUT, io_wait_count = {read = 0,
write = 0}, major_version = 255 '\377', minor_version = 255 '\377', micro_version = 255 '\377', is_enterprise = false, type = MEMCACHED_CONNECTION_TCP, read_ptr = 0x7f728b4abfd8 "",
read_buffer_length = 0, read_data_length = 0, write_buffer_offset = 0, address_info = 0x7f72706f6300, address_info_next = 0x7f72706f6300, next_retry = 1, immediate_reconnect = false,
groupindex = -1, next = 0x0, root = 0x7f73a3ed1000, limit_maxbytes = 0, error_messages = 0x7f729906f800, read_buffer = '\000' <repeats 8195 times>,
write_buffer = "version\r\n", '\000' <repeats 8186 times>, hostname = "127.0.0.1", '\000' <repeats 1010 times>}
(gdb) p *(instance->error_messages)
$11 = {root = 0x7f73a3ed1000, query_id = 2, next = 0x0, rc = MEMCACHED_ERRNO, local_errno = 104, size = 148,
message = "[2021-07-06 11:36:14.528575] mc_id:27 mc_qid:2 er_qid:2 SYSTEM ERROR(Connection reset by peer), host: 127.0.0.1:11247 -> libmemcached/io.cc:501"}
static memcached_return_t ascii_get_by_key(memcached_st *ptr,
uint32_t master_server_key,
bool is_group_key_set,
const char *key,
const size_t key_length)
{
memcached_return_t rc;
memcached_server_write_instance_st instance;
uint32_t server_key;
if (is_group_key_set)
{
server_key= master_server_key;
}
else
{
server_key= memcached_generate_hash_with_redistribution(ptr, key, key_length);
}
instance= memcached_server_instance_fetch(ptr, server_key);
const char *command= (ptr->flags.support_cas ? "gets " : "get ");
struct libmemcached_io_vector_st vector[]=
{
{ strlen(command), command },
{ memcached_array_size(ptr->_namespace), memcached_array_string(ptr->_namespace) },
{ key_length, key },
{ 2, "\r\n" }
};
rc= memcached_vdo(instance, vector, 4, true);
if (memcached_failed(rc))
{
memcached_io_reset(instance);
memcached_set_error(*ptr, rc, MEMCACHED_AT); /******core at this line, because of rc is MEMCACHED_ERRNO*********/
}
return rc;
}
CLEANUP: enable optimize mget operation with instance version 커밋에 대한 리뷰 의견입니다.
이번에 다시 보니, 수정해야 할 큰 사항들이 보입니다.
첫째, 각 memcached server의 버전 정보를 얻는 방식에 관하여,
둘째, mget 연산의 최적화 처리
기존 io 모듈 코드의 readability 가 좋지 않으므로 최신 libmemcached 코드를 참고하여 개선하도록 합니다.
아래의 memcached_set_errno() 함수는 사용되지 않으므로 제거하도록 합시다.
memcached_return_t memcached_set_errno(memcached_st& memc, int local_errno, const char *at, const char *str, size_t length);
memcached_return_t memcached_set_errno(memcached_server_st&, int local_errno, const char *at, const char *str, size_t length);
아래와 같이 에러 메세지를 memcached_string_t 구조로 받는 다른 memcached_set_errno() 함수가 존재하므로, 위의 함수는 제거하여도 됩니다.
memcached_return_t memcached_set_errno(memcached_st& memc, int local_errno, const char *at, memcached_string_t& str);
memcached_return_t memcached_set_errno(memcached_server_st&, int local_errno, const char *at, memcached_string_t& str);
현재 collection 구조에서 사용하는
xxx_query_init() 함수의 API type은 LIBMEMCACHED_API 이다.
LIBMEMCACHED_API type은 application에서 호출 하는 type 이다.
application에서 호출하지 않는 type은 LIBMEMCACHED_LOCAL 이다.
xxx_query_init()을 application에서 호출하는 collection은 b+tree가 유일하다.
API type을 점검하고, 내부에서만 사용하는 API 는 LOCAL type으로 변경 한다.
arcus는 아래 세 가지의 서버 모델을 지원하고 있고 각각의 초기화 메소드를 제공하고 있다.
arcus_return_t arcus_connect(memcached_st *mc, const char *ensemble_list, const char *svc_code)
arcus_return_t arcus_pool_connect(memcached_pool_st *pool, const char *ensemble_list, const char *svc_code)
arcus_return_t arcus_proxy_create(memcached_st *mc, const char *ensemble_list, const char *svc_code)
위에 따라 각각 zookeeper와 연결을 각 메소드 내에서 진행하고 있는 상태이다.
(arcus_connect와 arcus_pool_connect는 내부에 같은 함수를 사용하여 공유한다.)
실제 내부 코드는 거의 같은 상태인데, 각각 연결을 wait하는 코드가 분리되어 있다.
관련부분을 검토하여 하나의 함수로 표현 가능하다면 하나의 함수로 제공하는게
코드 관리나 readability 관점에서 더 좋다고 판단된다.
참고.
arcus_return_t arcus_proxy_create()
{
/* Creates a new ZooKeeper client thread. */
rc= do_arcus_zk_connect(mc);
if (rc != ARCUS_SUCCESS) {
return ARCUS_ERROR;
}
arcus = static_cast<arcus_st *>(memcached_get_server_manager(mc));
if (arcus->is_initializing) {
struct timeval now;
struct timespec ts;
ZOO_LOG_WARN(("Waiting for the cache server list..."));
/* Wait for the cache list (timed out after 5 sec.) */
gettimeofday(&now, NULL);
ts.tv_sec= now.tv_sec + (ARCUS_ZK_SESSION_TIMEOUT_IN_MS / 1000 / 3);
ts.tv_nsec= now.tv_usec * 1000;
pthread_mutex_lock(&lock_arcus);
if (pthread_cond_timedwait(&cond_arcus, &lock_arcus, &ts)) {
ZOO_LOG_ERROR(("pthread_cond_timedwait failed. %s(%d)", strerror(errno), errno));
rc= ARCUS_ERROR;
}
pthread_mutex_unlock(&lock_arcus);
if (rc == ARCUS_SUCCESS) {
ZOO_LOG_WARN(("Done"));
}
}
return rc;
}
static inline arcus_return_t do_arcus_connect()
{
/* Creates a new ZooKeeper client thread. */
rc= do_arcus_zk_connect(mc);
if (rc != ARCUS_SUCCESS) {
return ARCUS_ERROR;
}
ZOO_LOG_WARN(("Waiting for the cache server list..."));
pthread_mutex_lock(&lock_arcus);
arcus= static_cast<arcus_st *>(memcached_get_server_manager(mc));
if (arcus->is_initializing) {
struct timeval now;
struct timespec ts;
/* Wait for the cache list (timed out after 5 sec.) */
gettimeofday(&now, NULL);
ts.tv_sec= now.tv_sec + (ARCUS_ZK_SESSION_TIMEOUT_IN_MS / 1000 / 3);
ts.tv_nsec= now.tv_usec * 1000;
if (pthread_cond_timedwait(&cond_arcus, &lock_arcus, &ts)) {
ZOO_LOG_ERROR(("pthread_cond_timedwait failed. %s(%d)", strerror(errno), errno));
rc= ARCUS_ERROR;
}
}
pthread_mutex_unlock(&lock_arcus);
if (rc == ARCUS_SUCCESS) {
ZOO_LOG_WARN(("Done"));
}
return rc;
}
memcached_get_by_key() 함수의 마지막에 아래와 같은 dummy fetch 코드가 있다.
필요성을 검토해 보고, 필요가 없다면 제거합시다.
size_t dummy_length;
uint32_t dummy_flags;
memcached_return_t dummy_error;
char *dummy_value= memcached_fetch(ptr, NULL, NULL,
&dummy_length, &dummy_flags,
&dummy_error);
assert_msg(dummy_value == 0, "memcached_fetch() returned additional values beyond the single get it expected");
assert_msg(dummy_length == 0, "memcached_fetch() returned additional values beyond the single get it expected");
assert_msg(ptr->query_id >= query_id +1, "Programmer error, the query_id was not incremented.");
//assert_msg(ptr->query_id == query_id +1, "Programmer error, the query_id was not incremented.");
여러 cache nodes로 부터 smget response를 받는 과정 중에 오류가 발생할 경우,
smget result는 제대로 된 결과를 가지고 있지 않은 상태이며,
그 상태에서 MEMCACHED_SUCCESS를 리턴하는 문제가 있다.
이번에 최신 libmemcached 코드를 보니, 기존 보다 최적화되어 있고 readability도 나은 것 같습니다.
만약, arcus-c-client가 기반으로 하는 libmemcached 코드를 최신 libmemcached로 이전한다면,
arcus-c-client에서 ARCUS 관련 코드에 대해 code tag 추가해 두는 것이 필요합니다.
내부 검토 후에 결정하도록 하고, 현재는 이슈로만 올려둡니다.
zk와 연결을 close하는 do_arcus_zk_close() 는 zookeeper_close()를 호출하여
zk client의 연결을 해제하고, arcus의 zk 관련 설정들을 초기화 한다.
이 중 arcus->is_initializing
변수는 arcus와 zk의 처음 연결 확인을 위한 변수로
zk close 하면 다음 zk connect를 위해 설정하여 connect 이후 wait 해야 함을
나타내야 한다.
관련 부분을 검토하고 반영한다.
github docs의 operation 매뉴얼을 보강한다.
arcus c client 매뉴얼은 주로 API에 대한 설명만 되어있는 상태로,
사용법 및 사용 후 코드 example에 대한 보강이 필요하다.
bop incr/decr pipe 연산을 추가한다.
c-client 에서 bop smget 의 key, count 제약을 cache server 지원과 동일하도록 수정을 검토한다.
/* In bop smget, max limit on the number of given keys */
#define MAX_SMGET_KEY_COUNT 10000
/* In bop smget, max limit on (offset+count) */
#define MAX_SMGET_REQ_COUNT 2000
#define MEMCACHED_COLL_MAX_BOP_SMGET_KEY_COUNT 2000
#define MEMCACHED_COLL_MAX_BOP_SMGET_ELEM_COUNT 1000
KEY_COUNT(2000) 초과 시 에러를 리턴함.
for (size_t i=0; i<number_of_keys; i++)
{
uint32_t serverkey= key_to_serverkey[i];
lenkeys[serverkey]+= (key_length[i] + 1); // +1 for the delimeter
numkeys[serverkey]+= 1;
if (numkeys[serverkey] > MEMCACHED_COLL_MAX_BOP_SMGET_KEY_COUNT)
{
DEALLOCATE_ARRAY(ptr, key_to_serverkey);
return memcached_set_error(*ptr, MEMCACHED_INVALID_ARGUMENTS, MEMCACHED_AT,
memcached_literal_param("key size for a server should be <= MEMCACHED_COLL_MAX_BOP_SMGET_KEY_COUNT"));
}
}
private static final int MAX_SMGET_COUNT = 1000; // server configuration is 2000.
/**
* Max smget key chunk size per request
*/
public static final int DEFAULT_MAX_SMGET_KEY_CHUNK_SIZE = 500;
KEY_COUNT(500) 초과 시 잘라서 보냄.
문제점
static memcached_return_t do_coll_delete(memcached_st *ptr,
const char *key, size_t key_length,
memcached_coll_query_st *query,
size_t count,
bool drop_if_empty,
memcached_coll_action_t verb)
{
...
/* Query header */
if (verb == LOP_DELETE_OP)
{
...
}
else if (verb == SOP_DELETE_OP)
{
...
}
else if (verb == MOP_DELETE_OP)
{
...
}
else if (verb == BOP_DELETE_OP)
{
...
}
}
수정방안
아래 2가지를 확인하고, 문제가 있으면 처리한다.
현재 response를 read하는 함수의 return으로,
MEMCACHED_UNKNOWN_READ_FAILURE or
MEMCACHED_PROTOCOL_ERROR or
MEMCACHED_CLIENT_ERROR or
MEMCACHED_MEMORY_ALLOCATION_FAILURE
에 대해 io reset을 진행 한다.
io reset은 현재 connection을 끊고, 2초 wait 뒤 reconnect 하는 과정을 거친다.
아래의 return에 대해서는 wait할 필요가 없지만 현재는 wait 하도록 되어 있다.
UNKNOWN_READ_FAILURE or
PROTOCOL_ERROR or
CLIENT_ERROR
에 대해서 즉시 reconnect 하도록 변경이 필요하다.
quit_server() 동작에서 몇가지 검토가 필요하다.
quit
명령으로 telnet을 종료 하는데, 위 동작의 필요성.검토 후 문제없이 동작 가능 하다면 즉시 reconnect 하도록 변경 한다.
mgets 를 지원하도록 합니다.
파이프 연산시, 응답으로 PIPE_ERROR와 CLIENT_ERROR를 리턴하는 케이스에 대한 테스트 코드를 추가한다.
memcached pool size가 큰 경우, 아래 작업은 pool의 모든 memcached에 대해 clone 작업으로 반영하며 pool size에 비례하여 긴 시간이 소요되며, 그 동안에 서비스가 불가한 상태가 된다.
Cache list 변경은 자주 발생할 수 있어서, memcached pool scalability 이슈가 실 사용에 문제 된다.
Cache list 변경에서 pool의 모든 memcached에 반영할 사항은 2가지가 있다.
위의 2가지 중에 hashring 문제는
pool에 하나의 shared hashring을 두고, 모든 memcached가 공유하여 사용하도록 수정하여
해결한 상태이다.
server list 문제는 남아 있는 상태이며,
현재 모든 server들을 close한 후에 새로운 server들을 생성하여 추가하는 방식으로 처리하고 있다.
이에 대해 아래 방식으로 개선이 필요하다.
캐시 서버가 mget, mgets 을 지원하는 버전인 경우에도 get, gets 명령으로 요청을 보낼 수 있는 버그가 있다.
아래 코드는 다수 아이템 조회 명령 처리하는 로직인데, enable_mget 이 true 인 경우에 mget, mgets 명령을 사용한다.
mget_command_is_supported()에서 캐시 서버 버전을 확인하고, 캐시 서버 버전 등록은 캐시 서버와 최초 연결할 때 수행한다. (in memcached_connect())
아직 memcached_connect() 호출하지 않은 캐시 서버는 버전이 등록되있지 않아 enable_mget 은 false 가 되어 해당 instance 에게 mget, mgets 명령을 사용하지 못한다.
for (uint32_t x= 0; x < number_of_keys; x++)
{
memcached_server_write_instance_st instance;
uint32_t server_key;
if (is_group_key_set)
{
server_key= master_server_key;
}
else
{
server_key= key_to_serverkey[x];
}
instance= memcached_server_instance_fetch(ptr, server_key);
bool enable_mget= mget_command_is_supported(ptr, instance);
if (enable_mget)
{
if (hosts_failed[server_key])
{
/* The command protocol for that instance is broken. */
continue;
}
}
struct libmemcached_io_vector_st vector[]=
{
{ 0, NULL },
{ memcached_array_size(ptr->_namespace), memcached_array_string(ptr->_namespace) },
{ key_length[x], keys[x] }
};
size_t veclen= 3;
if (memcached_server_response_count(instance) == 0)
{
rc= memcached_connect(instance);
if (memcached_failed(rc))
{
if (rc != MEMCACHED_ERRNO)
{
memcached_set_error(*instance, rc, MEMCACHED_AT);
}
hosts_failed[server_key]= true;
continue;
}
hosts_connected++;
if (enable_mget)
{
const char *command= (ptr->flags.support_cas ? "mgets" : "mget");
char command_buffer[80];
vector[0].length= snprintf(command_buffer, 80, "%s %u %u\r\n",
command,
lenkeys[server_key]-1,
numkeys[server_key]); // -1 for the space-less first key
vector[0].buffer= command_buffer;
/* Sending the request header */
write_result= memcached_io_writev(instance, vector, veclen, false);
}
else
{
const char *command= (ptr->flags.support_cas ? "gets " : "get ");
vector[0].length= strlen(command);
vector[0].buffer= command;
/* Sending the request header */
write_result= memcached_io_writev(instance, vector, veclen, false);
}
해결방안:
unit test 관련:
element를 일괄 삭제하는 element delete pipe 연산을 각 collection에 추가한다.
--disable-64bit CFLAGS="-O2 -march=i686"
이 옵션이 32bit 환경에서 빌드할때 필수인가요?
이 옵션을 넣지 않고 32bit 환경에서 빌드하면 정상적으로 동작하지 않나요?
do_coll_mget() 함수에 포함된 아래 코드를 보면,
filter_str
에 eflag filter의 command string 생성한 후에, if (query->eflag_filter)
{
const size_t filter_length= MEMCACHED_COLL_MAX_FILTER_STR_LENGTH+1;
char filter_str[filter_length];
if (memcached_coll_eflag_filter_to_str(query->eflag_filter, filter_str, filter_length) < 0)
{
return memcached_set_error(*ptr, MEMCACHED_MEMORY_ALLOCATION_FAILURE, MEMCACHED_AT,
memcached_literal_param("snprintf(MEMCACHED_COLL_MAX_FILTER_STR_LENGTH)"));
}
write_length+= snprintf(buffer+write_length, buffer_length-write_length, "%s", filter_str);
}
한 번에 command buffer에 copy하는 것이 유리하다.
arcus_connect() 함수 호출 시
아래와 같은 로그만 남기고 연결이 안됩니다.
WARN@do_arcus_zk_connect@616: Initiating zookeeper client
src/mt_adaptor.c:162: start_threads: Assertion `"pthread_create() failed for the IO thread"&&!rc' failed.
gdb로 문제가 발생한 지점에서 backtrace 해보니 아래와 같습니다. (주소와 포트는 보안상 한글로 수정)
Program received signal SIGABRT, Aborted.
0xffffe410 in __kernel_vsyscall ()
(gdb) bt full
#0 0xffffe410 in __kernel_vsyscall ()
No symbol table info available.
#1 0x00b19df0 in raise () from /lib/libc.so.6
No symbol table info available.
#2 0x00b1b701 in abort () from /lib/libc.so.6
No symbol table info available.
#3 0x00b1326b in __assert_fail () from /lib/libc.so.6
No symbol table info available.
#4 0x08226ff3 in start_threads (zh=0x97d0dd0) at src/mt_adaptor.c:162
rc = <value optimized out>
adaptor = 0x97d17f8
__func__ = "start_threads"
__PRETTY_FUNCTION__ = "start_threads"
#5 0x08227163 in adaptor_init (zh=0x97d0dd0) at src/mt_adaptor.c:203
recursive_mx_attr = {__size = "\001\000\000", __align = 1}
__func__ = "adaptor_init"
#6 0x0821ed1c in zookeeper_init (host=0x97d9764 "아커스서버주소:포트",
watcher=0x8203a9e <do_arcus_zk_watcher_global(zhandle_t*, int, int, char const*, void*)>, recv_timeout=15000, clientid=0x97d974c,
context=0x97ee538, flags=0) at src/zookeeper.c:793
errnosave = <value optimized out>
zh = 0x97d0dd0
index_chroot = 0x0
__func__ = "zookeeper_init"
#7 0x08202c45 in do_arcus_zk_connect (mc=0x97ee538) at libmemcached/arcus.cc:626
arcus = 0x97d9748
__func__ = "do_arcus_zk_connect"
#8 0x08202f31 in do_arcus_connect (mc=0x97ee538, pool=0x0, ensemble_list=0x97ce644 "아커스서버주소:포트", svc_code=0x97ce62c "SVC코드")
at libmemcached/arcus.cc:312
arcus = 0x97ee538
rc = ARCUS_SUCCESS
__func__ = "do_arcus_connect"
#9 0x082031f3 in arcus_connect (mc=0x97ee538, ensemble_list=0x97ce644 "아커스서버주소:포트", svc_code=0x97ce62c "SVC코드")
at libmemcached/arcus.cc:99
zookeeper에서 pthread_create() 실행시 반환한 rc 값이 비정상이라 assrt 실패 된걸로 보입니다.
void start_threads (zhandle_t * zh)
{
struct adaptor_threads* adaptor=zh->adaptor_priv;
pthread_cond_init(&adaptor->cond,0);
pthread_mutex_init(&adaptor->lock,0);
adaptor->threadsToWait=2; // wait for 2 threads before opening the barrier
// use api_prolog() to make sure zhandle doesn't get destroyed
// while initialization is in progress
api_prolog(zh);
LOG_DEBUG(("starting threads..."));
int rc=pthread_create(&adaptor->io, 0, do_io, zh);
assert("pthread_create() failed for the IO thread"&&!rc); // 이부분에서 에러
rc=pthread_create(&adaptor->completion, 0, do_completion, zh);
assert("pthread_create() failed for the completion thread"&&!rc);
wait_for_others(zh);
api_epilog(zh, 0);
}
memcached_bop_mget() 함수는 특이하게 결과를 받아오기 위한 result 인자가 없다.
result 인자의 추가 여부를 재검토하여 필요하면 수정한다.
오픈소스가 아닌 1.6.3 버전에서 arcus.h 에 선언된 arcus_connect() 함수의 인자는 아래처럼 3개이고 pool 정보를 인자로 받지 않습니다만,
arcus_return_t arcus_connect(memcached_st *mc, const char *ensemble_list, const char *svc_code)
오픈소스인 1.7 버전에서 arcus.h에 선언된 arcus_connect() 함수의 인자는 4개이고 pool 정보를 인자로 받습니다.
arcus_return_t arcus_connect(memcached_st *mc, memcached_pool_st *pool, const char *ensemble_list, const char *svc_code)
왜 1.7 부터는 single thread 용인 arcus_connect() 함수에 pool 정보를 인자로 받고 있는지 궁금하며 (pool은 multi threaded/process 환경용 아닌가요?)
single thread 용인 arcus_connect() 함수에서 pool을 사용하지 않으려면 어떻게 사용해야하는지 예시를 부탁드립니다.
max key size를 조정합니다.
bop smget 명령에 대한 매뉴얼 내용이 잘못되어 있으므로, 수정이 필요합니다.
예를 들어, 다음과 같은 함수가 변경되어야 합니다.
purge 실패는 io issue 와 관련된 것이므로, io_flush() 내에서 memcached_quit_server() 호출해 delayed reconnect 를 수행하도록 한다.
purge 함수에서 발생하는 에러 :
/* Force a flush of the buffer to ensure that we don't have the n-1 pending
requests buffered up.. */
if (memcached_io_write(ptr, NULL, 0, true) == -1)
{
memcached_set_purging(root, true);
return memcached_set_error(*ptr, MEMCACHED_WRITE_FAILURE, MEMCACHED_AT);
}
memcached_return_t rc= memcached_read_one_response(ptr, buffer,
sizeof (buffer),
result_ptr);
/*
* Purge doesn't care for what kind of command results that is received.
* The only kind of errors I care about if is I'm out of sync with the
* protocol or have problems reading data from the network..
*/
if (rc == MEMCACHED_PROTOCOL_ERROR || rc == MEMCACHED_UNKNOWN_READ_FAILURE)
{
WATCHPOINT_ERROR(rc);
ret= rc;
memcached_set_error(*ptr, rc, MEMCACHED_AT);
}
I was trying to compile the source code, which led me to running into some compiler errors like following:
./libtest/comparison.hpp:39:18: error: ISO C++ forbids comparison between pointer and integer [-fpermissive]
if (__expected == false)
~~~~~~~~~~~^~~~~~~~
make[1]: *** [Makefile:5419: libtest/libtest_unittest-unittest.o] Error 1
make[1]: Leaving directory '/home1/irteam/shawn/arcus-c-client'
make: *** [Makefile:1850: all] Error 2
I have made a quick fix and pushed it into this repository, which ended up in permission denial.
I would like you to apply this patch:
diff --git a/clients/memflush.cc b/clients/memflush.cc
index 81bb257..ac3e1d0 100644
--- a/clients/memflush.cc
+++ b/clients/memflush.cc
@@ -37,7 +37,7 @@ int main(int argc, char *argv[])
{
options_parse(argc, argv);
- if (opt_servers == false)
+ if (opt_servers == NULL)
{
char *temp;
diff --git a/clients/memstat.cc b/clients/memstat.cc
index 8036ea1..779b73f 100644
--- a/clients/memstat.cc
+++ b/clients/memstat.cc
@@ -86,7 +86,7 @@ int main(int argc, char *argv[])
options_parse(argc, argv);
initialize_sockets();
- if (opt_servers == false)
+ if (opt_servers == NULL)
{
char *temp;
if ((temp= getenv("MEMCACHED_SERVERS")))
diff --git a/libtest/comparison.hpp b/libtest/comparison.hpp
index eb240d4..7366f61 100644
--- a/libtest/comparison.hpp
+++ b/libtest/comparison.hpp
@@ -36,7 +36,7 @@ namespace libtest {
template <class T_comparable, class T_hint>
bool _compare_truth_hint(const char *file, int line, const char *func, T_comparable __expected, const char *assertation_label, T_hint __hint)
{
- if (__expected == false)
+ if (!__expected)
{
libtest::stream::make_cerr(file, line, func) << "Assertation \"" << assertation_label << "\" failed, hint: " << __hint;
return false;
c-client 는 synchronous io 방식이기 때문에 write 시에 timeout 발생은
한번에 socket buffer 를 넘는 command 를 보내지 않는 이상 발생할 확률이 거의 없겠지만,
code 안전성을 높이기 위해 handling 하도록 하고, 관련해서 아래 사항을 검토한다.
최종적으로는 timeout 발생 시에 send 한 데이터 유무에 관계없이 connection 을 다시 맺도록 해주는 것이 가장 나아 보여 delayed reconnect 처리할 것으로 생각된다.
Hash collision 발생 시의 owner 결정 방식으로
hostname 비교하여 작은 hostname의 cache node가 owner가 되도록 한다.
현재 cache server, java-client는 모두 반영된 상태이고,
c-client만 남아있는 상태이다.
수백개 이상의 캐시 인스턴스를 가지는 대규모 클러스터인 경우
ketama hash collision이 발생하므로,
이러한 대규모 클러스터에서 사용하기 위해서는 수정해야 한다.
Smget 수행에서 특정 cache node로의 요청이 operation timeout난 경우,
그 cache node로 보낸 key들을
(1) misssed keys로 처리하거나 (2) timeout keys로
그 결과를 응용에게 전달해 주는 것에 대해 검토가 필요함
Operation timeout 시의 리턴 값 및 처리를 명확하게 할 필요 있음
현재 arcus 구조체를 사용하는 대부분의 코드에서는 lock_arcus putex lock을 획득한 상태에서
mc의 server_manager()를 통해 가져오는 arcus에 대해 not arcus를 확인하고
not arcus인 경우 각 코드에 맞는 핸들링을 하고 있다.
c client 구동 중 arcus 구조체가 제거될 가능성이 있는 상황을 정리하고
제거될 가능성이 없는 부분 이라면 not arcus 확인과 handling 작업을 제거하는게
코드 관리와 향후 arcus 구조체를 이용한 코드 수정에 대한 복잡성을 낮출 수 있을 것으로 보인다.
memcached_vdo() 함수의 오류 처리 부분이 일관적인지 검토하고, 필요하면 수정한다.
현재 zookeeper와의 session event 처리를 위한 do_arcus_zk_watcher_global()
에서는
ZOO_CONNECTED_STATE 일 때 아래와 같이 항상 client_info 등록과 cachelist watcher 등록을 시도한다.
if (state == ZOO_CONNECTED_STATE)
{
ZOO_LOG_WARN(("SESSION_STATE=CONNECTED, to %s", arcus->zk.ensemble_list));
const clientid_t *id= zoo_client_id(zh);
if (arcus->zk.myid.client_id == 0 or arcus->zk.myid.client_id != id->client_id) {
ZOO_LOG_DEBUG(("Previous sessionid : 0x%llx", (long long) arcus->zk.myid.client_id));
arcus->zk.myid= *id;
ZOO_LOG_DEBUG(("Current sessionid : 0x%llx", (long long) arcus->zk.myid.client_id));
}
if (arcus->zk.is_initializing) {
if (do_arcus_cluster_validation_check(mc, arcus) < 0) {
pthread_mutex_unlock(&lock_arcus);
return;
}
}
#ifdef ARCUS_ZK_ADDING_CLEINT_INFO
do_add_client_info(arcus);
#endif
pthread_mutex_unlock(&lock_arcus);
do_arcus_zk_watch_and_update_cachelist(mc, do_arcus_zk_watcher_cachelist);
}
client info 등록은 zoo_exists()를 통해 znode가 없을 경우에만 zoo_create() 하도록 되어 있어
큰 문제는 없지만 cachelist watcher 등록은 항상 수행한다.
session이 expired 되기 전까지 session이 생성한 znode와 watcher는 유지됨이 보장되기 때문에
항상 수행할 필요가 없지만 현재는 항상 시도하는 상태이다.
zk의 session event는 zk client와 server 간 연결이 끊어질 경우 다음 zk server로 연결하는 과정에서CONNECTING -> CONNECTED 순서로 event가 발생할 수 있다.
따라서 arcus->zk 초기화 상태가 아니라면 client_info, cacheList watcher 등록 할 필요가 없다.
관련 부분을 검토하여 수정한다.
참고로, java zk의 경우 CONNECTING event 대신 Disconnected event가 발생한다.
arcus-java-client : Cache Manager
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.None) {
switch (event.getState()) {
case SyncConnected:
zkInitLatch.countDown();
getLogger().info("Connected to Arcus admin. (%s@%s)", serviceCode, hostPort);
if (cacheMonitor != null) {
getLogger().warn("Reconnected to the Arcus admin. " + getInfo());
} else {
getLogger().debug("cm is null, servicecode : %s, state:%s, type:%s",
serviceCode, event.getState(), event.getType());
}
break;
case Disconnected:
getLogger().warn("Disconnected from the Arcus admin. Trying to reconnect. " + getInfo());
break;
case Expired:
// If the session was expired, just shutdown this client to be re-initiated.
getLogger().warn("Session expired. Trying to reconnect to the Arcus admin." + getInfo());
if (cacheMonitor != null)
cacheMonitor.shutdown();
break;
}
}
}
document에 보면 아래와 같은 설명이 있습니다.
MEMCACHED_BEHAVIOR_RETRY_TIMEOUT 설정을 하면 timeout 시간 후에 재연결 시도를 하는 것으로 이해됩니다. 이때 재시도 횟수는 몇 회인가요?
별도로 설정이 가능한가요?
+#### TIMEOUT 발생 시 캐시 서버로의 재연결 설정
+
+캐시 명령 수행 시, timeout이 발생한 경우에는 MEMCACHED_SERVER_TEMPORARILY_DISABLED (“SERVER HAS FAILED AND IS DISABLED UNTIL TIMED RETRY”) 오류가 발생하고, RETRY_TIMEOUT 후에 해당 캐시 장비로 재연결을 시도한다. 0을 지정하면 timeout이 발생할 때마다 즉시 재연결을 시도하게 된다.
+
+c +mc = memcached_create(NULL); +memcached_behavior_set(mc, MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, (uint64_t)timeout); +
+
+timeout 시간은 초(s) 단위이며, 캐시 명령 실행 전에 지정하면 된다. 기본 값은 MEMCACHED_SERVER_FAILURE_RETRY_TIMEOUT (2초) 이다.
+
+libmemcached에서 timeout은 두 가지 형태의 응답코드로 나타난다.
+* MEMCACHED_TIMEOUT (“A TIMEOUT OCCURRED”) : 요청을 보내던 도중 타임아웃이 발생했다.
+* MEMCACHED_IN_PROGRESS (“OPERATION IN PROCESS”) : 요청을 성공적으로 보내고 응답을 받던 도중 타임아웃이 발생했다
+
+
bop mget 또는 bop smget 처리에서 cluster size는 200보다 작다는 가정하에 구현된 로직이 있다.
아래의 상수 값으로 배열 크기를 정하여 처리하는 부분이 있으므로, 이를 수정해야 합니다.
최근에 cluster size가 300 ~ 400인 경우가 존재하기 시작하므로,
이러한 규모의 cluster에서도 동작할 수 있어야 합니다.
memcached_io_get_readable_server() 함수에서
readable server를 return하는 경우는 두 가지가 있습니다.
위와 같이 서로 다르게 처리하고 있는 데,
후자의 방식으로 통일하는 게 좋지 않을 까 생각합니다.
java-client 는 long key (32000) support 를 하고 있는 상태이고, c-client 도 이를 적용하기 위한 이슈이다.
변경이 필요한 부분은 key 저장 방식, command response 모듈이다.
key 저장 방식
command request 모듈
command response 모듈
삼중화 기능이 추가됨에 따라 SWITCHOVER/REPL_SLAVE 응답에 switchover peer 정보가 포함되는데,
어떤 문제로 인해 memcached_rgroup_switchover() 에서 peer 정보를 찾지 못하여도
do_action 으로 goto 되어 계속 요청을 보내게 됩니다.
memcached_rgroup_switchover() 에서 peer 정보를 찾지 못하면 에러 메세지 출력 및 -1 를 리턴하고,
이 경우에 SWITCHOVER or REPL_SLAVE 에러코드를 응용에 리턴해주는 것이 안전해보입니다.
#ifdef ENABLE_REPLICATION
do_action:
#endif
/* Send command header */
rc= memcached_vdo(instance, vector, 6, to_write);
if (rc == MEMCACHED_SUCCESS)
{
if (to_write == false)
{
rc= MEMCACHED_BUFFERED;
}
else if (ptr->flags.no_reply or ptr->flags.piped)
{
rc= MEMCACHED_SUCCESS;
}
else
{
char result[MEMCACHED_DEFAULT_COMMAND_SIZE];
rc= memcached_coll_response(instance, result, MEMCACHED_DEFAULT_COMMAND_SIZE, NULL);
#ifdef ENABLE_REPLICATION
if (rc == MEMCACHED_SWITCHOVER or rc == MEMCACHED_REPL_SLAVE)
{
ZOO_LOG_INFO(("Switchover: hostname=%s port=%d error=%s",
instance->hostname, instance->port, memcached_strerror(ptr, rc)));
memcached_rgroup_switchover(ptr, instance);
instance= memcached_server_instance_fetch(ptr, server_key);
goto do_action;
}
#endif
memcached_set_last_response_code(ptr, rc);
내부에서 memory allocation 실패에 대한 오류 처리가 필요하다.
특히,
memcached_io_get_readable_server()에서 NULL 리턴하는 경우는 두 가지 입니다.
전자 경우는 코드의 구현 로직 상 발생하지 않아야 하는 합니다.
따라서, 전자는 assert()로 처리하는 것이 좋을 것 같습니다.
후자 경우는 결국 operation timeout 난 경우인 데,
이 경우, MEMCACHED_TIMEOUT 오류를 명시적으로 리턴하는 것이 좋을 것 같습니다.
어떤 query 수행 직전에,
cursor_active인 cache node로 부터 previous query의 response를 받아 버리도록 처리할 경우,
모든 명령의 response가 올 수 있음을 고려하여 처리하여야 한다.
현재 memcached_response() 또는 memcached_coll_response()를 호출하고 있으나,
memcached_all_response()를 구현하여 이를 호출할 필요가 있다.
fetch_value_header()는 response message를 receive하는 버퍼에서
아래 2가지 문자 중 하나가 나올 때가지의 연속된 문자열을 읽어 string으로 변환하여 가져오고
2가지를 구분하여 아래 값을 리턴한다.
예를 들어, b+tree element 값이 아래 response message로 리턴된다고 가정하자.
<bkey> <bytes> <data>\r\n
이 경우, 각 token string을 fetch_value_header() 함수로 얻을 떄 리턴되는 값은 아래와 같이 다르다.
MEMCACHED_SUCCESS가 리턴되어야 할 경우에서 MEMCACHED_END가 리턴되거나
MEMCACHED_END가 리턴되어야 할 경우에서 MEMCAHCED_SUCCESS가 리턴된다면,
이는 오류이다.
이러한 오류 상태를 검사하고 잘못된 결과가 리턴된다면
MEMCACHED_PROTOCOL_ERROR를 리턴하는 것이 올바를 처리이다.
관련 이슈 : #158
아직 캐시 서버와 연결되지 않은 상태에서 mop get 명령 요청 처리 시 서버 버전이 등록되있지 않아 실제로 서버가 space_separated_keys 를 지원하는 경우에도 comma_separated_keys 사용할 수 있는 문제가 있습니다.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.