#include "MetaCopy.h" #include #include #include #include #include #include #include "Logger.h" #include "RcdbInfo.h" #include "ProcessConfig.h" #define SQLITE_DB_LOCK_TIMEOUT 1000 // SQLITE_BUSY 상황 발생 문제 해결을 위해 DB Lock 관련 timeout 설정값. (1 sec) #define SQLITE_DB_LOCK_MAX_RETRY 60 // SQLITE_BUSY 발생시 재시도 최대 횟수 // Query 문장 저장을 위한 Buffer 크기 #define DEFAULT_QUERY_BUFFER_SIZE 2048 // 생성자 // - pLocalDb : sqlite memory db 객체에 대한 포인트 CMetaCopy::CMetaCopy( sqlite3 * pLocalDb ) : m_pLocalStmt(NULL) { m_pLocalDb = pLocalDb; m_ullTotalSelectCount = 0; } // 소멸자 CMetaCopy::~CMetaCopy() { // RCDB 와 연결 해제 처리. m_rcdb.PgClear(); m_rcdb.PgCloseDB(); // Local DB stmt clear 처리. // - local db 객체는 memory db 로서 전역 객체이므로.. close 하지 않는다. if( m_pLocalStmt != NULL ) { sqlite3_finalize( m_pLocalStmt ); m_pLocalStmt = NULL; } } // 객체 초기화 함수 // - RCDB 와 연결 생성 // - sqlite local memory db 관련 table, index 생성 및 insert stmt 생성 처리. bool CMetaCopy::Init() { // RCDB 접속 정보 저장 관련 객체 생성 및 초기화... CRcdbInfo rcdbInfo; rcdbInfo.Load(); // sqlite 를 이용하여 local 장비에 저장 목적 file DB 생성 처리 및 세션 연결. if( CreateLocalDb() == false ) return false; // RCDB 와 연결 생성. if( m_rcdb.PgOpenDB( rcdbInfo.m_strRcdbIp, rcdbInfo.m_nRcdbPort, rcdbInfo.m_strRcdbName, rcdbInfo.m_strRcdbAcct, rcdbInfo.m_strRcdbAcctPw ) == NULL ) { // 연결 실패시... LOG( LERR, "RCDB connection failed." ); return false; } else { _LOG( LINF, "RCDB connection success." ); } return true; } // sqlite 를 이용하여 local db 생성 작업을 수행. bool CMetaCopy::CreateLocalDb() { // sqlite local memory db 객체는 Main 모듈에서 생성 했으므로... // 그 외 작업을 수행한다. // 1. local db 상에 관련 table 생성 작업 수행 처리. int nResult = 0; std::string strQuery; strQuery = "CREATE TABLE meta( resource_id integer, filename_hash text, get_content_length integer, deleted_yn text )"; char * pStrDbError; int nRetryCount = 0; nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError ); while( nResult == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY ) { sqlite3_free( pStrDbError ); sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT ); ++nRetryCount; _LOG( LWAR, "Local db table create SQLITE_BUSY. Retry[%d]", nRetryCount ); nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError ); } // Table Create Query 가 최종 실패한 경우.... if( nResult != SQLITE_OK ) { LOG( LERR, "sqlite3 table create failed. [%d][%s]", nResult, pStrDbError ); sqlite3_free( pStrDbError ); return false; } else { _LOG( LINF, "Local db table create success."); } // 2. garbage clean 시 select 속도 향상을 위한 Index 생성 if( CProcessConfig::GetInstance()->IsRunGarbageClean() == true ) { strQuery = "CREATE INDEX index_filename_hash ON meta( filename_hash ) ;"; nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError ); while( nResult == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY ) { sqlite3_free( pStrDbError ); sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT ); ++nRetryCount; _LOG( LWAR, "Local db index create SQLITE_BUSY. Retry[%d]", nRetryCount ); nResult = sqlite3_exec( m_pLocalDb, strQuery.c_str(), NULL, NULL, &pStrDbError ); } // Index Create Query 가 최종 실패한 경우.... if( nResult != SQLITE_OK ) { LOG( LERR, "sqlite3 index create failed. [%d][%s]", nResult, pStrDbError ); sqlite3_free( pStrDbError ); return false; } else { _LOG( LINF, "Local db index create success." ); } } // 3. Meta 정보 조회 결과를 local db 에 저장 처리시 사용할 prepared 객체에 대한 초기화 수행. // - prepared sql 을 사용해야... insert 속도가 향상됨. strQuery = "INSERT INTO meta( resource_id, filename_hash, get_content_length, deleted_yn ) VALUES ( ?, ?, ?, ? );"; nResult = sqlite3_prepare( m_pLocalDb, strQuery.c_str(), strQuery.size(), &m_pLocalStmt, NULL ); // sqlite3_prepare() 함수 실행시 SQLITE_BUSY 오류가 발생할 수 있으므로.. 재시도 로직을 추가한다. while( nResult == SQLITE_BUSY && nRetryCount < SQLITE_DB_LOCK_MAX_RETRY ) { sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT ); ++nRetryCount; _LOG( LWAR, "Local db prepare sql SQLITE_BUSY. Retry[%d]", nRetryCount ); nResult = sqlite3_prepare( m_pLocalDb, strQuery.c_str(), strQuery.size(), &m_pLocalStmt, NULL ); } // 최종 실패시... if( nResult != SQLITE_OK ) { LOG( LERR, "sqlite3 prepare sql stmt create failed. [%d][%s]", nResult, sqlite3_errmsg(m_pLocalDb) ); m_pLocalStmt = NULL; return false; } return true; } bool CMetaCopy::Start() { // 작업 소요 시간 정보 계산을 위한 변수. // - 시간 동기화로 인한 문제를 해결하기 위해 clock_gettime() 함수 사용. struct timespec timeStart; struct timespec timeEnd; double dTimeInterval = 0.0; memset( &timeStart, 0x00, sizeof( struct timespec ) ); memset( &timeEnd, 0x00, sizeof( struct timespec ) ); // 작업 시작 로깅 및 시작 시간 정보 추출. _LOG( LINF, "Meta copy start."); clock_gettime( CLOCK_MONOTONIC, &timeStart ); // Query 문장 저장을 위한 버퍼 생성 char szQuery[DEFAULT_QUERY_BUFFER_SIZE]; // RCDB Query 수행 결과값 저장을 위한 변수.. int nResult = 0; // 1. t_sms_sp_svc_product 에서 각 고객사 정보를 가져온다. snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1, "SELECT sp_svc_tran_id FROM t_sms_sp_svc_product ORDER BY sp_svc_tran_id ASC;"); // Query 실행 m_rcdb.PgDoExec( szQuery ); nResult = m_rcdb.PgResult( DataBase::NOT_CLEAR ); if( nResult < 0 ) { // t_sms_sp_svc_product 테이블 조회시 오류가 발생한 경우... // - 해당 table 또는 sp_svc_tran_id 컬럼 존재 안할 경우.. LOG( LERR, "RCDB t_sms_sp_svc_product table select failed. [%s][%s]", m_rcdb.GetErrorMessage().c_str(), szQuery ); m_rcdb.PgClear(); m_rcdb.PgCloseDB(); return false; } // 조회된 각 고객사 정보 확인 nResult = m_rcdb.GetNoTuples(); if( nResult <= 0 ) { // 작업을 수행할 고객사 정보가 RCDB 에 존재하지 않는 경우... // - 고객사가 존재하지 않는 빈깡통 RCDB 인 경우.. 오류일 가능성이 많으므로.. 오류 처리. LOG( LERR, "RCDB t_sms_sp_svc_product table empty. Not found service. Check." ); m_rcdb.PgClear(); m_rcdb.PgCloseDB(); return false; } // 루프를 돌면서.. 고객사 정보를 저장 처리. std::vector vecService; std::string strTemp; for( int i = 0; i < nResult; i++ ) { strTemp = m_rcdb.GetValue( i, 0 ); if( strTemp.size() > 0 ) vecService.push_back( strTemp ); } // Query Resultset Clear. m_rcdb.PgClear(); // 변수에 저장된 서비스 정보의 수를 최종 확인 if( vecService.empty() == true ) { LOG( LERR, "RCDB t_sms_sp_svc_product table not empty. but valid service not found. Check." ); m_rcdb.PgCloseDB(); return false; } // 사용자가 작업대상 경로를 지정한 경우... // /stg/node0 // - t_sms_sp_svc_product 테이블을 조회하여 서비스 목록 추출해야 함. // /stg/node0/346 // - 서비스 폴더를 지정했으므로.... 다른 서비스 table 까지 불필요하게 조회할 필요 없음. // - 따라서.. t_sms_sp_svc_product 조회 결과에서 // - 사용자가 지정한 서비스 seq 가 존재하는 경우.... 해당 table 만 조회하도록 처리. // - 사용자가 지정한 서비스 seq 가 존재하지 않는 경우... 서비스 폴더가 아닐 수 있으므로.. 전체 조회 처리. std::vector< std::string >::const_iterator it; // 사용자가 작업 대상 경로를 지정한 경우... if( CProcessConfig::GetInstance()->IsUserDefineJobRoot() == true ) { strTemp = CProcessConfig::GetInstance()->GetJobRoot(); // 맨 끝의 폴더명만을 추출. string::size_type pos = strTemp.rfind( '/' ); if( pos != string::npos ) { // 경로의 마지막 문자열 정보 추출. std::string strSvcSeq = strTemp.substr( pos + 1 ); // vecService 목록에 추출된 정보가 존재하는지 확인. bool bFound = false; for( it = vecService.begin(); it != vecService.end(); ++it ) { if( strcmp( it->c_str(), strSvcSeq.c_str() ) == 0 ) { bFound = true; break; } } if( bFound == true ) { // 기존 RCDB 에서 추출된 정보 삭제 vecService.clear(); // 사용자가 지정한 서비스 seq 만 조회되도록 처리. vecService.push_back( strSvcSeq ); _LOG( LINF, "meta copy : user path set [%s]. meta select only service [%s]", CProcessConfig::GetInstance()->GetJobRoot(), strSvcSeq.c_str() ); } } } // 2. 사용자 지정 옵션에 따른 조회 구문을 설정 처리한다. char szWhere[DEFAULT_QUERY_BUFFER_SIZE]; memset( szWhere, 0x00, sizeof( szWhere ) ); // directory 는 빼고 파일만 조회. strcat( szWhere, "AND resource_type = 0 " ); // integrity check 만 수행할 경우... // - 삭제된 content 는 조회하지 않는다. if( CProcessConfig::GetInstance()->IsRunIntegrityCheck() == true && CProcessConfig::GetInstance()->IsRunGarbageClean() == false ) { strcat( szWhere, "AND deleted_yn = 'N' " ); } // 사용자가 작업 대상 경로를 지정한 경우... if( CProcessConfig::GetInstance()->IsUserDefineJobRoot() == true ) { char tempBuffer[DEFAULT_QUERY_BUFFER_SIZE]; snprintf( tempBuffer, DEFAULT_QUERY_BUFFER_SIZE - 1, "AND filename_hash LIKE '%s/%%' " , CProcessConfig::GetInstance()->GetJobRoot() ); strcat( szWhere, tempBuffer ); } strcat( szWhere, " ;" ); // Local sqlite db 관련 변수 int nLocalDbResult = 0; int nLocalRetryCount = 0; // 3. 루프를 돌면서 // 각 서비스별 t_meta_xx 테이블에서 content 정보를 조회한다. //std::vector< std::string >::const_iterator it; for( it = vecService.begin(); it != vecService.end(); ++it ) { _LOG( LINF, "Service [%s] meta copy start.", it->c_str() ); snprintf( szQuery, DEFAULT_QUERY_BUFFER_SIZE - 1, "SELECT resource_id, filename_hash, get_content_length, deleted_yn FROM t_meta_%s " "WHERE host_name = '%s' %s" , it->c_str() , CProcessConfig::GetInstance()->GetHostname() , szWhere ); // Query 실행 m_rcdb.PgDoExec( szQuery ); nResult = m_rcdb.PgResult( DataBase::NOT_CLEAR ); if( nResult < 0 ) { // t_meta_xxx 테이블 조회시 오류가 발생한 경우... LOG( LERR, "RCDB t_meta_%s table select failed. [%s][%s]", it->c_str(), m_rcdb.GetErrorMessage().c_str(), szQuery ); // local db stmt clear 처리. sqlite3_finalize( m_pLocalStmt ); m_pLocalStmt = NULL; // RCDB 연결 해제 처리. m_rcdb.PgClear(); m_rcdb.PgCloseDB(); return false; } // 조회된 row 정보 확인 nResult = m_rcdb.GetNoTuples(); if( nResult <= 0 ) { // 작업 대상 content 가 존재하지 않는 경우.... _LOG( LINF, "Service [%s] meta content not exist.", it->c_str() ); } else { // sqlite 의 insert 속도 향상을 위해.. begin - commit 구문 사용.... // 본 구문을 사용하지 않을 경우.. 초당 180 건 정도 insert 처리됨. nLocalDbResult= sqlite3_exec( m_pLocalDb, "BEGIN;", NULL, NULL, NULL ); nLocalRetryCount = 0; while( nLocalDbResult == SQLITE_BUSY && nLocalRetryCount < SQLITE_DB_LOCK_MAX_RETRY ) { sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT ); ++nLocalRetryCount; _LOG( LWAR, "Local db BEGIN query SQLITE_BUSY. Retry[%d]", nLocalRetryCount ); nLocalDbResult = sqlite3_exec( m_pLocalDb, "BEGIN;", NULL, NULL, NULL ); } // 최종 실패시.... if( nLocalDbResult != SQLITE_OK ) { LOG( LERR, "sqlite3 BEGIN query failed. [%d][%s]", nLocalDbResult, sqlite3_errmsg( m_pLocalDb ) ); // local db stmt clear 처리. sqlite3_finalize( m_pLocalStmt ); m_pLocalStmt = NULL; // RCDB 연결 해제 처리. m_rcdb.PgClear(); m_rcdb.PgCloseDB(); return false; } // 루프를 돌면서 조회된 content 를 local db 에 저장 처리. for( int i = 0; i < nResult; i++ ) { // local table 생성 Query // strQuery = "CREATE TABLE meta( resource_id integer, filename_hash text, get_content_length integer, deleted_yn text )"; // 조회 Query // "SELECT resource_id, filename_hash, get_content_length, deleted_yn FROM t_meta_%s " // insert query 에 변수 bind 처리. ( bind 값을 1부터 시작 ) // resource_id sqlite3_bind_text( m_pLocalStmt, 1, m_rcdb.GetValue( i, 0 ), strlen( m_rcdb.GetValue( i, 0 ) ), SQLITE_STATIC ); // filename_hash sqlite3_bind_text( m_pLocalStmt, 2, m_rcdb.GetValue( i, 1 ), strlen( m_rcdb.GetValue( i, 1 ) ), SQLITE_STATIC ); // get_content_length sqlite3_bind_text( m_pLocalStmt, 3, m_rcdb.GetValue( i, 2 ), strlen( m_rcdb.GetValue( i, 2 ) ), SQLITE_STATIC ); // deleted_yn sqlite3_bind_text( m_pLocalStmt, 4, m_rcdb.GetValue( i, 3 ), strlen( m_rcdb.GetValue( i, 3 ) ), SQLITE_STATIC ); // insert query 수행. nLocalDbResult = sqlite3_step( m_pLocalStmt ); nLocalRetryCount = 0; // 만약 BUSY 상태로 인해 오류가 발생하면..지정된 횟수 만큼 재시도 while( nLocalDbResult == SQLITE_BUSY && nLocalRetryCount < SQLITE_DB_LOCK_MAX_RETRY ) { sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT ); ++nLocalRetryCount; _LOG( LWAR, "Meta insert to local db. SQLITE_BUSY. Retry[%d]", nLocalRetryCount ); // query 재시도 nLocalDbResult = sqlite3_step( m_pLocalStmt ); } if( nLocalDbResult != SQLITE_DONE ) { // INSERT 실패 발생시... LOG( LERR, "sqlite3 insert step error. [%d][%s]", nLocalDbResult, sqlite3_errmsg( m_pLocalDb ) ); // local db stmt clear 처리. sqlite3_finalize( m_pLocalStmt ); m_pLocalStmt = NULL; // RCDB 와의 연결까지 clear 처리한다. m_rcdb.PgClear(); m_rcdb.PgCloseDB(); return false; } else { // Query 수행된 경우.. prepared 문을 재사용하기 위해 reset 처리한다. sqlite3_reset( m_pLocalStmt ); } } // for // sqlite 의 insert 속도 향상을 위해.. begin - commit 구문 사용.... // 본 구문을 사용하지 않을 경우.. 초당 180 건 정도 insert 처리됨. nLocalDbResult = sqlite3_exec( m_pLocalDb, "COMMIT;", NULL, NULL, NULL ); nLocalRetryCount = 0; while( nLocalDbResult == SQLITE_BUSY && nLocalRetryCount < SQLITE_DB_LOCK_MAX_RETRY ) { sqlite3_busy_timeout( m_pLocalDb, SQLITE_DB_LOCK_TIMEOUT ); ++nLocalRetryCount; _LOG( LWAR, "Local db COMMIT query SQLITE_BUSY. Retry[%d]", nLocalRetryCount ); nLocalDbResult = sqlite3_exec( m_pLocalDb, "COMMIT;", NULL, NULL, NULL ); } // 최종 실패시.... if( nLocalDbResult != SQLITE_OK ) { LOG( LERR, "sqlite3 COMMIT query failed. [%d][%s]", nLocalDbResult, sqlite3_errmsg( m_pLocalDb ) ); // local db stmt clear 처리. sqlite3_finalize( m_pLocalStmt ); m_pLocalStmt = NULL; // RCDB 연결 해제 처리. m_rcdb.PgClear(); m_rcdb.PgCloseDB(); return false; } } // Query Resultset Clear. m_rcdb.PgClear(); // 최종 통계 처리를 위해 누적 처리. m_ullTotalSelectCount += nResult; _LOG( LINF, "Service [%s] meta count [%d] copy end.", it->c_str(), nResult ); } // for // 작업 완료 후... RCDB 와의 연결을 종료 처리한다. m_rcdb.PgClear(); m_rcdb.PgCloseDB(); // 작업 완료 후 Local DB stmt 객체 clear 처리한다. sqlite3_finalize( m_pLocalStmt ); m_pLocalStmt = NULL; // 작업 완료시 시간 정보 추출 후 소요시간 계산. clock_gettime( CLOCK_MONOTONIC, &timeEnd ); dTimeInterval = timeEnd.tv_sec - timeStart.tv_sec; dTimeInterval += ( timeEnd.tv_nsec - timeStart.tv_nsec ) * 1e-9; // memory db 사용으로 인해 // sqlite3_memory_used() 함수를 사용하여 memory 사용량을 로깅하려 했으나.. // FreeBSD 6.X 에 설치되는 sqlite 3.4.1 버전에서 sqlite3_memory_used, sqlite3_status 등의 함수를 지원 안함. // 이로 인해 적용 불가함. _LOG( LINF, "Meta copy complete. path[%s] total count[%llu], elapsed time: %.1lf sec." , CProcessConfig::GetInstance()->GetJobRoot(), m_ullTotalSelectCount, dTimeInterval ); return true; }