#include "ThreadTargetSelect.h" #include "Logger.h" #include "ProcessConfig.h" #include #include #include #include // 매 Loop 마다 Sleep 시간 정보 정의 ( sec 단위) #define SLEEP_TIME_DEFAULT 5 #define SLEEP_TIME_MAX 10 // DB 조회시 SELECT limit count // - queue 의 최소 크기가 2048 이므르.. DB 조회시 2000 씩만 조회하도록 한다. #define DB_SELECT_LIMIT 2000 // Content select query 수행 시간이 지정된 시간 이상일 경우 로깅 처리 (sec) #define LOGGING_MAX_TIME_QUERY_EXEC 30 CThreadTargetSelect::CThreadTargetSelect( CDataQueue * const pQueue, unsigned int uTargetService ) : m_pQueue(pQueue), m_uTargetService(uTargetService) { // 멤버변수 초기화 m_threadHandle = 0; m_uSleep = SLEEP_TIME_DEFAULT; m_ullResourceIdMin = 0; m_ullResourceIdMax = 0; m_ullResourceIdStart = 0; } CThreadTargetSelect::~CThreadTargetSelect() { // 내부 therad 가 동작 중인 경우... 중지 처리 if( m_threadHandle != 0 ) { pthread_cancel( m_threadHandle ); } sleep( 1 ); // thread 종료 잠시 대기 // 혹시나 해서 정석대로 내부 변수 NLLL 처리 if( m_pQueue != NULL ) m_pQueue = NULL; } ///////////////////////////////////////////////// bool CThreadTargetSelect::Start( void ) { // 멤버 변수 유효성 check if( m_pQueue == NULL ) { LOG( LERR, "ThreadTargetSelect: content queue pointer is null." ); return false; } // RCDB 접속 정보 load m_rcdbInfo.Load(); // 작업 thread 생성 int nResult = pthread_create( &m_threadHandle, NULL, CThreadTargetSelect::threadFunc, this ); if( nResult != 0 ) { // thread 생성 실패시 int errorNum = errno; LOG( LERR, "ThreadTargetSelect: thread create failed. [%d: %s]", errorNum, strerror( errorNum ) ); return false; } return true; } /////////////////////////////////////////////// void * CThreadTargetSelect::threadFunc( void * arg ) { CThreadTargetSelect * pObject = reinterpret_cast ( arg ); pthread_detach( pthread_self() ); while( 1 ) { pthread_testcancel(); pObject->Execute(); pthread_testcancel(); // 지정된 주기로 실행 sleep( pObject->m_uSleep ); } // thread 종료시 pObject->m_threadHandle = 0; return NULL; } void CThreadTargetSelect::Execute( void ) { // sleep time 조정 처리 m_uSleep = SLEEP_TIME_DEFAULT; // 전체 구간 scan 이 완료된 경우... if( m_ullResourceIdStart > m_ullResourceIdMax ) { m_uSleep = SLEEP_TIME_MAX; _LOG( LINF, "[SELECT ] content select completed. max sleep[%u]. resource_id min:%llu max:%llu next:%llu" , m_uSleep, m_ullResourceIdMin, m_ullResourceIdMax, m_ullResourceIdStart ); return; } // 조회 queue 상에 저장 가능 공간이 있는지 확인 if( m_pQueue->GetFreeSize() < DB_SELECT_LIMIT ) { m_uSleep = SLEEP_TIME_MAX; _LOG( LINF, "[SELECT ] queue free size[%u / %u] too lower. select job skip and max sleep %u sec." , m_pQueue->GetFreeSize(), m_pQueue->GetMaxSize(), m_uSleep ); return; } _LOG( LINF, "[SELECT ] start with target svc[%u], queue_free[%u]" , m_uTargetService, m_pQueue->GetFreeSize() ); // DB 처리를 수행할 객체 생성 // DB 와 연결은 필요시마다 연결 후 해제 처리한다. (세션 유지 않도록...) DataBase db; // DB 와 연결을 시도한다. if( db.PgOpenDB( m_rcdbInfo.m_strRcdbIp, m_rcdbInfo.m_nRcdbPort, m_rcdbInfo.m_strRcdbName , m_rcdbInfo.m_strRcdbAcct, m_rcdbInfo.m_strRcdbAcctPw ) == NULL ) { // 연결 실패시... m_uSleep = SLEEP_TIME_DEFAULT; LOG( LERR, "[SELECT ] db connection failed. sleep %u sec.", m_uSleep ); return; } // resource_id 정보를 추출한다. if( m_ullResourceIdMin == 0 && m_ullResourceIdMax == 0 ) { char szQuery[4096]; int nResult = 0; // resource_id 정보 조회 snprintf( szQuery, 4096 - 1 , "SELECT MIN(resource_id), MAX(resource_id) FROM t_access_%u " "WHERE resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' ; " , m_uTargetService ); // Query 실행 db.PgDoExec( szQuery ); nResult = db.PgResult( DataBase::NOT_CLEAR ); if( nResult < 0 ) { // query 수행 실패시 LOG( LERR, "[SELECT ] service[%u] resource_id min/max select error. [%d][%s][%s]" , m_uTargetService, nResult, db.GetErrorMessage().c_str(), szQuery ); // DB 연결 해제 db.PgCloseDB(); // 대부분 t_access_xxx table 이 존재하지 않는 경우 이므로... // 재시도 시간을 최대한 지연시킨다. m_uSleep = SLEEP_TIME_MAX; return; } // 추출된 정보 저장 m_ullResourceIdMin = atoll( db.GetValue( 0, 0 ) ); m_ullResourceIdMax = atoll( db.GetValue( 0, 1 ) ); // resource_id 정보 최초 추출시.. start 시점은 최소값으로 설정 처리 m_ullResourceIdStart = m_ullResourceIdMin; // db resultset clear. db.PgClear(); _LOG( LINF, "[SELECT ] service[%u] resource_id min: %llu max: %llu" , m_uTargetService, m_ullResourceIdMin, m_ullResourceIdMax ); } // DB 에서 access time 추출 대상 content 목록을 조회하여 queue 에 저장 처리한다. SelectContent( db ); // DB 와의 연결을 명시적으로 해제 처리 db.PgCloseDB(); return; } bool CThreadTargetSelect::SelectContent( DataBase & db ) { // Query 수행 시간 check 를 위한 변수 struct timespec timeStart, timeNow; double timeElapsed = 0.0; memset( &timeStart, 0x00, sizeof( struct timespec ) ); memset( &timeNow, 0x00, sizeof( struct timespec ) ); clock_gettime( CLOCK_MONOTONIC, &timeStart ); _LOG( LINF, "[SELECT ] service[%u] content select start with start resource_id: %llu" , m_uTargetService, m_ullResourceIdStart ); // DB 연결은 이미 되어 있는 상태 char szQuery[4096]; int nResult = 0; // 대상 content 조회 snprintf( szQuery, 4096 - 1 , "SELECT resource_id, uri, host_name, filename_hash " "FROM t_access_%u " "WHERE resource_id >= %llu AND resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' AND file_lastaccess = 0 " "ORDER BY resource_id ASC LIMIT %d" , m_uTargetService , m_ullResourceIdStart , DB_SELECT_LIMIT ); // Query 실행 db.PgDoExec( szQuery ); nResult = db.PgResult( DataBase::NOT_CLEAR ); // Query 수행시간이 지정된 시간 이상 걸리면 로깅.... clock_gettime( CLOCK_MONOTONIC, &timeNow ); timeElapsed = (double)( ( timeNow.tv_sec - timeStart.tv_sec ) * 1.0e9 + ( timeNow.tv_nsec - timeStart.tv_nsec ) ) / 1.0e9; if( timeElapsed >= (double)LOGGING_MAX_TIME_QUERY_EXEC ) { _LOG( LWAR, "[SELECT ] query execute time too long [%.2lf sec]. result[%d] [%s]" , timeElapsed, nResult, szQuery ); } else { _LOG( LDBG, "[SELECT ] query execute [%.2lf sec]. result[%d] [%s]" , timeElapsed, nResult, szQuery ); } if( nResult < 0 ) { // query 수행 실패시 LOG( LERR, "[SELECT ] content select exec error. [%d][%s][%s]" , nResult, db.GetErrorMessage().c_str(), szQuery ); // 다음 Query 수행을 위해 조회 결과 set 을 clear 처리 db.PgClear(); // 재시도 시간 최대 설정 m_uSleep = SLEEP_TIME_MAX; return false; } unsigned int uPushTotalCount = 0; // 결과 Row 수 확인 nResult = db.GetNoTuples(); _LOG( LINF, "[SELECT ] content selected. [svc:%u select_count:%d ]" , m_uTargetService, nResult ); if( nResult < 0 ) { // query 수행 실패시 LOG( LERR, "[SELECT ] content select tuple error. [%d][%s][%s]" , nResult, db.GetErrorMessage().c_str(), szQuery ); // 다음 Query 수행을 위해 조회 결과 set 을 clear 처리 db.PgClear(); // 재시도 시간 최대 설정 m_uSleep = SLEEP_TIME_MAX; return false; } else if( nResult == 0 ) { // 적합한 Content 가 없는 경우.. LOG( LWAR, "[SELECT ] content select not found. [%d][svc:%u][%s]" , nResult, m_uTargetService, szQuery ); // 다음 Query 수행을 위해 조회 결과 set 을 clear 처리 db.PgClear(); // 재시도 시간 최대 설정 m_uSleep = SLEEP_TIME_MAX; // 더 이상 작업할 contnet 가 없으므로.. 더 이상 DB 조회 발생하지 않도록 처리 m_ullResourceIdStart = m_ullResourceIdMax + 1; return false; } else { // N 개의 조회 결과가 존재하는 경우 // - 정해진 용량까지 Queue 에 push 처리한다. CDataAccess data; for( int i = 0; i < nResult; i++ ) { // 0: resouce_id // 1: uri // 2: host_name // 3: filename_hash // 확인 결과 resouce_id 의 최대값은 9223372036854775807 // long long int 의 최대값은 9223372036854775807 으로 resouce_id 값과 동일 // 실제 변환 테스트해 보니 최대값까지 정상 변환 확인함. data.m_uResourceId = atoll( db.GetValue( i, 0 ) ); data.m_strUri = db.GetValue( i, 1 ); data.m_strHostname = db.GetValue( i, 2 ); data.m_strFilenamehash = db.GetValue( i, 3 ); // push 처리 if( m_pQueue->Push( data ) == false ) { // push 처리 실패시... // 2 sec 후 재시도해서 실패하면.. queue full 가능성이 있으므로... // 버리고.. loop 중지 sleep( 2 ); if( m_pQueue->Push( data ) == false ) { LOG( LWAR, "[SELECT ] content select ok. but queue push fail. [svc:%u resource_id:%llu]" , m_uTargetService, data.m_uResourceId ); break; } } // total push count 1 증가 uPushTotalCount++; // 로깅 for debug //_LOG( LINF, "[SELECT ] content select, push ok. [svc:%u resource_id:%llu]", m_uTargetService, data.m_uResourceId); } // 다음 조회 start resouce_id 정보 설정 m_ullResourceIdStart = data.m_uResourceId + 1; } db.PgClear(); // Content 선택 결과 로깅 _LOG( LINF, "[SELECT ] content push end. service[%u] select[%d][%.2lf sec] push[%u] netxt start resource_id[%llu]" , m_uTargetService, nResult, timeElapsed, uPushTotalCount, m_ullResourceIdStart ); return true; }