Files
interactive/fhs/cls/src/MetaCopy.cpp
T
2026-08-07 17:38:18 +09:00

553 lines
17 KiB
C++

#include "MetaCopy.h"
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string>
#include <vector>
#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<std::string> 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;
}