<< 품질관리의 예방과 방어 전략에 대한 생각 | Home | 인터넷의 사회적 책임 >>

MySQL 내부 프로세스 - Connection/Thread Manager

먼저 커넥션 메니저와 쓰레드 메니저는 튜닝 포인트도 되고 해서 MySQL의 내부 소스 흐름을 한번 분석해 보았습니다.
소스는 doxygen으로 보고 있습니다. ^^
주로 튜닝 포인트는 max_connections, thread_cache 두 매개변수로 대변되는 설정값입니다. 튜닝 포인트 설명은 다음 포스트로 하고 본 포스트는 내부 흐름에 대해서 먼저 살펴보도록 하겠습니다.
틀린 부분이 있으면 댓글을 통해 주시면 검토해 반영토록 하겠습니다.

Connection Manager 프로세스

1) 연결 방식 결정
서버 구동기 매개 변수 설정을 통해 단일 쓰레드인지 멀티 쓰레드 방식인지 결정한다. 이는 sql/mysqld.cc의 get_options()함수에서 처리된다.
#ifdef EMBEDDED_LIBRARY//임베드 라이브러리용
  one_thread_scheduler();
#else
  // 커넥션당 단일 쓰레드(멀티 쓰레드)
  if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION)
    one_thread_per_connection_scheduler();
  else /* thread_handling == SCHEDULER_NO_THREADS) */
    one_thread_scheduler();// 단일 쓰레드
#endif

그리고 sql/scheduler.cc의 one_thread_per_connection_scheduler() 함수를 살펴보면 스케쥴러 초기화와 max_connections과 thread_scheduler를 설정한다.
void one_thread_per_connection_scheduler()
{
  scheduler_init();
  one_thread_per_connection_scheduler_functions.max_threads= max_connections;
  thread_scheduler= &one_thread_per_connection_scheduler_functions;
}

2) 네트워크 초기화
네트워크 구성은 비교적 간단하며, 수신 대기 포트를 바인딩할 소켓을 만들어 포트를 설정하는 것이다. 처리는 sql/mysqld.cc의 network_init() 함수이다.

3) 소켓 관리
클라이언트로 부터 요청이 오면 thread_cache에 남아있는(유휴) 연결이 있으면 재사용하고 없다면 새로 쓰레드를 생성한다. 처리는 handle_connections_sockets() 함수가 한다.
handle_connections_sockets()
{
  // Max Connection 제한
  if (!(thd= new THD)) {//새로운 THD 생성
    ..
  }
/* Open the socket or TCP/IP connection and read the fnctl() status */
  if (!(vio_tmp=vio_new(new_sock,
             sock == unix_sock ? VIO_TYPE_SOCKET :
             VIO_TYPE_TCPIP,
             sock == unix_sock ? VIO_LOCALHOST: 0)) ||
    my_net_init(&thd->net,vio_tmp))//THD 네트워크 초기화
  {
  }
  // 새로운 스레드 생성
  create_new_thread(thd);
}

create_new_thread() 함수를 살펴보면
static void create_new_thread(THD *thd)
{
 //내부적으로 max_connections+1해서 연결 수 체크하고
 // +1 하는 이유는 슈퍼 관리자의 접속을 위해서
 if (connection_count >= max_connections + 1 || abort_loop){
  ...
 }
 thread_count++;//쓰레드 카운트 증가시키고
 MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));
}

MYSQL_CALLBACK 함수를 통해 one_thread_scheduler가 아닌 초기 설정값인 one_thread_per_connection_scheduler에 설정된 add_connection 기능을 호출하게 된다. 이 함수는 create_thread_to_handle_connection()이 된다. 이유는 sql/scheduler.cc의 one_thread_per_connection_scheduler() 함수에서 설정한 정보를 보면 안다.
static scheduler_functions one_thread_per_connection_scheduler_functions=
{
  0,                                 // max_threads
  NULL,                              // init
  init_new_connection_handler_thread,// init_new_connection_thread
  create_thread_to_handle_connection,// add_connection
  NULL,                              // thd_wait_begin
  NULL,                              // thd_wait_end
  NULL,                              // post_kill_notification
  one_thread_per_connection_end,     // end_thread
  NULL,                              // end
};

이제 마지막으로 sql/mysqld.cc 소스의 create_thread_to_handle_connection() 함수를 살펴 보기로 한다.
void create_thread_to_handle_connection(THD *thd)
{
    thread_created++;
    // 쓰레드 단위의 번호를 생성하고 스레드에 추가할 목록을 연결
    threads.append(thd);
    // 쓰레드 생성
    if ((error= mysql_thread_create(key_thread_one_connection,
              &thd->real_id, &connection_attrib,
              handle_one_connection,
              (void*) thd)))   
}

쓰레드 (재)사용을 위한 Thread Manager

쓰레드 생성기인 mysql_thread_create()함수는 구동되면서 sql/sql_connect.cc의 handle_one_connection() 함수를 호출하게 된다. 이 함수를 살펴보자.
pthread_handler_t handle_one_connection(void *arg)
{
  THD *thd= (THD*) arg;
  mysql_thread_set_psi_id(thd->thread_id);

  do_handle_one_connection(thd);//연결 처리
  ...
  do_command();// 요청 받은 커맨드 처리
  end_connection(thd); //재사용 연결이 아니면 커넥션 종료
  return 0;
}

내부적으로 sql/sql_connect.cc의 do_handle_one_connection() 함수를 호출하는데..
void do_handle_one_connection(THD *thd_arg)
{
  ...
  if (MYSQL_CALLBACK_ELSE(thread_scheduler, 
    init_new_connection_thread, (), 0))
  {
    close_connection(thd, ER_OUT_OF_RESOURCES);
    statistic_increment(aborted_connects,&LOCK_status);
    // 커넥션 IDLE인 연결 재사용 처리
    MYSQL_CALLBACK(thread_scheduler, end_thread, (thd, 0));
    return;
  }
  ...
}

위의 소스에서 init_new_connection_thread()가 0이 아니면, MYSQL_CALLBACK의 end_thread는 one_thread_per_connection_end() 함수를 호출하게 되어 쓰레드를 재사용하게 된다.
bool one_thread_per_connection_end(THD *thd, bool put_in_cache)
{

  unlink_thd(thd);
  if (put_in_cache)
     put_in_cache= cache_thread();

}
...
static bool cache_thread()
{
  mysql_mutex_assert_owner(&LOCK_thread_count);
  if (cached_thread_count < thread_cache_size &&
      ! abort_loop && !kill_cached_threads)
  {
    /* Don't kill the thread, just put it in cache for reuse */
    DBUG_PRINT("info", ("Adding thread to cache"));
    cached_thread_count++;
    ...
    if (wake_thread)
    {
      THD *thd;
      wake_thread--;
      thd= thread_cache.get();
      thd->thread_stack= (char*) &thd;          // For store_globals
      (void) thd->store_globals();//쓰레드 정보 재설정
      ...
      threads.append(thd);
    }
  }
}

디버그 모드로 소스분석 검증은..

MySQL 컴파일할 때 -DWITH_DEBUG=1를 넣어서 컴파일하면 디버그 로그를 추적할 수 있다. 그리고 MySQL을 구동할때는 --debug --log 옵션을 주면 된다. 전체 구동 스크립트를 보면..
bin/mysqld_safe --defaults-file=/etc/mysql-5.5.25/my.cnf \
 --datadir=/database/data2 --user=mysql --debug --log &
이렇게 구동되면, /tmp/mysqld.trace 파일에 디버깅용 로그가 쌓이게 된다. 이 trace 로그를 보면서 소스분석을 하면 도움을 받을 수 있다.
참고로, trace 로그를 읽는 방법은 "|"는 동일한 레벨, ">"는 함수 IN, "<"로 함수 OUT을 나타낸다.
그외, strace로 시스템 호출 내용을 볼 수도 있다. 사용 방법은 아래와 같다.
strace -o /tmp/strace /database/server/mysql-5.5.25/bin/mysql

이로써, MySQL의 클라이언트에서 접근했을 때 MySQL 서버가 어떻게 동작하는지 살펴봤습니다. 좀 더 도움을 받고자 한다면 MySQL Internals이라는 책을 보면서 소스를 분석해 보신다면 더 빨리 이해도를 높일 수 있지 않을까 생각합니다.
Tags : , , , ,


Re: MySQL 내부 프로세스 - Connection/Thread Manager

한국에도 이런(소스분석)  글이 올라오기 시작하는군요. ^^


Add a comment Send a TrackBack