#include "TargetSelectThread.h"

#include "Logger.h"
#include "Common.h"
#include "ProcessConfig.h"

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


// 매 Loop 마다 Sleep 시간 정보 정의 ( sec 단위)
#define SLEEP_TIME_DEFAULT		30 
#define SLEEP_TIME_MAX			60


// Content 조회시 최대 조회 limit
#define CONTENT_SELECT_MAX_LIMIT_COUNT		100


// Content select query 수행 시간이 지정된 시간 이상일 경우 로깅 처리 (sec)
#define LOGGING_MAX_TIME_QUERY_EXEC			60


// m_pQueueMoveStandby queue 에 추가 push 를 방지를 제한값.
// - Max Copy Thread 값에 2배수까지 허용한다.
#define MOVE_STANDBY_QUEUE_PUSH_HIGH_LIMIT  (MAX_COPY_THREAD_COUNT * 2)



CTargetSelectThread::CTargetSelectThread( CDataQueue<CRebalanceData> * const pQueueMoveStandby
	, CShmInfoThread * const pShmInfoThread )
	: m_pQueueMoveStandby( pQueueMoveStandby), m_pShmInfoThread( pShmInfoThread )
{
	// 멤버변수 초기화

	m_threadHandle = 0;		// thread handle 초기화

	m_uSleep = SLEEP_TIME_DEFAULT;

	m_listCompleteFhs.clear();

	m_uTimeoutFhsReselection = 0;
	m_uMaxTransferlimit = 0;
}

CTargetSelectThread::~CTargetSelectThread()
{
	// thread 가 동작 중인 경우.. thread 중지 처리
	if( m_threadHandle != 0 )
	{
		pthread_cancel( m_threadHandle );
	}

	sleep( 1 );	// thread 종료 잠시 대기

	m_listCompleteFhs.clear();

	// 혹시나 해서 정석대로 NULL 처리 추가함.
	if( m_pQueueMoveStandby != NULL )
		m_pQueueMoveStandby = NULL;
	if( m_pShmInfoThread != NULL )
		m_pShmInfoThread = NULL;

}


///////////////////////////////////////////////////

bool CTargetSelectThread::Start()
{
	// 멤버 변수 유효성 check
	if( m_pQueueMoveStandby == NULL )
	{
		LOG( LERR, "TargetSelectThread: queue object pointer not valid." );
		return false;
	}

	// 전송 처리 관련 내부 변수 계산.
	CalcInternalLimit();

	// RCDB 접속 정보 load
	m_rcdbInfo.Load();

	// 작업 thread 생성
	int nResult = pthread_create( &m_threadHandle, NULL, CTargetSelectThread::threadFunc, this );
	if( nResult != 0 )
	{
		// thread 생성 실패
		int errorNum = errno;
		LOG( LERR, "TargetSelectThread: Thread create failed.[%d][%s]", errorNum, strerror( errorNum ) );

		return false;
	}

	return true;
}

///////////////////////////////////////////////////

void * CTargetSelectThread::threadFunc( void * arg )
{
	CTargetSelectThread * pObject = reinterpret_cast<CTargetSelectThread *>(arg);
	pthread_detach( pthread_self() );

	while( 1 )
	{
		pthread_testcancel();
		pObject->Execute();
		pthread_testcancel();

		// 지정된 주기로 실행
		sleep( pObject->m_uSleep );
	}

	// thread 종료시 handle 정보 초기화
	pObject->m_threadHandle = 0;

	return NULL;
}

void CTargetSelectThread::Execute()
{
	// sleep time 조정 처리
	m_uSleep = SLEEP_TIME_DEFAULT;

	// rc_mond 가 기동 중인지 check
	// - 미기동 상태이면... 기동시까지 대기.
	if( IsRunRcMonD() == false )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LERR, "[SELECT ] %s process not running. max sleep %u sec.", PROCESS_NAME_RC_MONITORING_DAEMON, m_uSleep );
		return;
	}

	// MOVE 처리 가능한 FHS 존재 여부 check
	// - 존재하지 않는 경우.. content select 해서 push 해 놓기만 하다보면....
	// - 결국 중복 content push 되는 상황 발생할 수 있으므로....
	// - MOVE 수신 가능한 FHS 가 존재하지 않은 경우... content select 되지 않도록 한다.
	if( m_pShmInfoThread->IsValidCopyTargetFhs() == false )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LINF, "[SELECT ] valid copy target fhs not exist. max sleep %u sec.", m_uSleep );
		return;
	}
	
	// Move 처리 대상 content 정보를 저장하는 queue 가 특정값 이상이면
	// sleep time 을 조정하고.. 이번 에는 작업을 수행하지 않는다.
	// copy 처리 thread 수가 작아서... 병목 구간
	// 이 때 많은 양을 push 처리하며... 중복 발생 가능성이 높아짐
	// 따라서 해당 queue 가 지정된 값 이하인 경우에만 rebalance 대상을 선택 처리한다.

	if( m_pQueueMoveStandby->GetSize() > MOVE_STANDBY_QUEUE_PUSH_HIGH_LIMIT )
	{
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LINF, "[SELECT ] move queue count high [%u / %d]. job skip. max sleep %u sec."
			, m_pQueueMoveStandby->GetSize(), MOVE_STANDBY_QUEUE_PUSH_HIGH_LIMIT, m_uSleep );
		return;
	}

	// 이미 선택되어 처리된 FHS 목록 중 expire 된 항목 제거하여 갱신 처리
	UpdateCompleteFhs();

	// 재선택 방지를 위한 ignore fhs list 생성
	std::list<std::string> listIgnoreFHS;
	std::list<CCompleteFhsInfo>::iterator it;

	listIgnoreFHS.clear();

	for( it = m_listCompleteFhs.begin(); it != m_listCompleteFhs.end(); ++it )  
	{ 
		listIgnoreFHS.push_back( it->m_szHostName );
	}


	// m_pShmInfoThread 객체를 이용하여 rebalance 처리할 FHS 목록을 가져온다.
	std::string szRebalanceHost;
	if( m_pShmInfoThread->GetRebalanceTargetFhs( szRebalanceHost, listIgnoreFHS ) == false )
	{
		// rebalance 대상 host 를 선정하지 못한 경우...
		m_uSleep = SLEEP_TIME_MAX;

		_LOG( LINF, "[SELECT ] reblance fhs not selected. max sleep %u sec.", m_uSleep );
		return;
	}

	// RCDB 처리를 수행할 객체 생성
	// RCDB 와 연결은 필요시마다 연결 후 해제 처리한다. (세션 유지 않도록...)		
	DataBase rcdb;

	// RCDB 와 연결을 시도한다.
	if( rcdb.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, "TargetSelectThread: RCDB connection failed. sleep %u sec.", m_uSleep );
		return;
	}

	// 최종 선정된 FHS 에 대해 MOVE 처리할 최종 서비스를 선택한다.
	std::string szService;
	if( SelectService( rcdb, szService ) == false )
	{
		// RCDB 와의 연결을 명시적으로 해제 처리
		rcdb.PgCloseDB();

		// 서비스 선택 실패시...
		m_uSleep = SLEEP_TIME_MAX;
		LOG( LERR, "TargetSelectThread: Target service select failed. sleep %u sec.", m_uSleep );
		return;

	}

	_LOG( LINF, "[SELECT ] start with host[%s], svc[%s]", szRebalanceHost.c_str(), szService.c_str() );

	// 최종 선택된 FHS 를 작업 처리 list 에 등록처리한다
	InsertCompleteFhs( szRebalanceHost );

	// 최종 선정된 FHS, 서비스에 대해 MOVE 처리할 content 정보를 조회하여...
	// Move 처리 대상 content 정보를 저장하는 queue 에 push_back 시킨다.
	SelectContent( rcdb, szRebalanceHost, szService );

	// RCDB 와의 연결을 명시적으로 해제 처리
	rcdb.PgCloseDB();

	return;
}

bool CTargetSelectThread::CalcInternalLimit( void )
{
	// FHS 의 재선택 방지 관련 time limit 값을 sec 단위로 계산한다.
	// 300 ~ 86400
	m_uTimeoutFhsReselection = (CProcessConfig::GetInstance()->GetFhsReselectionTimeLimit()) * 60;

	// rebalance 대상 장비에서 1회 Move 처리시킬 Content 의 최대 크기 계산
	// 1. 각 copy thread 당 100Mbps 전송으로 계산 : 100Mbps -> 12 Mbyte / sec -> 12000000ULL
	// 2. MAX_COPY_THREAD_COUNT 정의된 Copy thread count
	// 3. 전송 시간
	//    - 일반 상황 : FHS 재선택 방지시간
	//    - full hash : FHS 재선택 방지시간 / 2
	// 4. 안전계수
	//    - 0.7

	// full hash check 선택된 경우
	if( CProcessConfig::GetInstance()->GetHashCheckLevel() == 2 )
	{
		// Copy Thread 가 1개인 경우, N 개인 경우 본 값에 곱하면 됨.
		// - 재선택 방지 시간이 30 min =   7559999999  =  7.5 GB 
		// - 재선택 방지 시간이 20 min =   5040000000  =  5.0 GB
		// - 재선택 방지 시간이 60 min =  15119999999  = 15.1 GB
		// - 재선택 방지 시간이 1 day  = 362880000000  = 362  GB

		m_uMaxTransferlimit = (unsigned long long)(12000000ULL * MAX_COPY_THREAD_COUNT * m_uTimeoutFhsReselection / 2 * 7 / 10);
	}
	else // full hash 가 아닌.. No check 또는 앞뒤 1MByte check 인 경우.
	{
		// Copy Thread 가 1개인 경우, N 개인 경우 본 값에 곱하면 됨.
		// - 재선택 방지 시간이 30 min =  15119999999 =  15 GB 
		// - 재선택 방지 시간이 20 min =  10080000000 =  10 GB
		// - 재선택 방지 시간이 60 min =  30239999999 =  30 GB
		// - 재선택 방지 시간이  1 day = 725760000000 = 725 GB

		m_uMaxTransferlimit = (unsigned long long)(12000000ULL * MAX_COPY_THREAD_COUNT * m_uTimeoutFhsReselection * 7 / 10);
	}

	return true;
}


void CTargetSelectThread::UpdateCompleteFhs( )
{
	time_t timeNow;
	time( &timeNow );

	std::list<CCompleteFhsInfo>::iterator it;

	// list 의 erase() 함수 사용시 문제를 해결하기 위해 아래와 같이 처리함.
	it = m_listCompleteFhs.begin();
	while( it != m_listCompleteFhs.end() )
	{
		if( (timeNow - it->m_timeSelect) > m_uTimeoutFhsReselection )
		{
			// expire 시간을 넘긴 경우.. 해당 FHS 삭제 처리
			_LOG( LINF, "[SELECT ] fhs %s re-select limit expire.", it->m_szHostName.c_str() );

			// list 삭제시 다음 원소 위치 접근 실패 오류를 해결하기 위해 반환값을 it 에 설정 처리.
			it = m_listCompleteFhs.erase( it);
		}
		else
		{
			// expire 시간을 넘기지 않은 경우...
			// queue  개념의 list 라서 다음 FHS 도 expire 시간이 되지 않았으므로... 더이상 check 하지 않는다.
			// it++;
			break;
		}
	}

	return;
}

void CTargetSelectThread::InsertCompleteFhs( std::string & szHost )
{
	CCompleteFhsInfo fhs;
	
	fhs.m_szHostName = szHost;
	// 시간은 CCompleteFhsInfo 객체 생성시 자동으로 생성됨

	// list push.
	m_listCompleteFhs.push_back( fhs );

	_LOG( LINF, "[SELECT ] fhs re-select limit list push. [%s current:%ld expire:+%u sec]"
		, fhs.m_szHostName.c_str(), fhs.m_timeSelect, m_uTimeoutFhsReselection );

	return;
}


bool CTargetSelectThread::SelectService( DataBase & rcdb, std::string & szService )
{
	// MOVE 처리를 수행할 서비스를 RCDB 에서 선택한다.

	// RCDB 연결은 이미 되어 있는 상태
	char szQuery[4096];
	int nResult = 0;

	// 1. 서비스 실제 사용량이 1TB 이상인 서비스를 선택
	snprintf( szQuery, 4096 - 1
		, "SELECT sp_svc_tran_id FROM t_dav_use_dir WHERE depth=0 AND dir_used >= 1000000000000; " 
	);

	rcdb.PgDoExec( szQuery );
	nResult = rcdb.PgResult( DataBase::NOT_CLEAR );

	if( nResult < 0 )
	{
		// query 수행 실패시
		LOG( LERR, "[SELECT ] service select exec error. [%d][%s][%s]"
			, nResult, rcdb.GetErrorMessage().c_str(), szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		rcdb.PgClear();
		return false;
	}

	// 결과 Row 수 확인
	nResult = rcdb.GetNoTuples();

	if( nResult < 0 )
	{
		// query 수행 실패시
		LOG( LERR, "[SELECT ] service select tuple error. [%d][%s][%s]"
			, nResult, rcdb.GetErrorMessage().c_str(), szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		rcdb.PgClear();
		return false;
	}
	else if( nResult == 0 )
	{
		// 적합한 서비스가 없는 경우.. 
		LOG( LWAR, "[SELECT ] service select not found. [%d][%s]"
			, nResult, szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		rcdb.PgClear();
		return false;
	}
	else if( nResult == 1 )
	{
		// 조회 결과가 1개인 경우...
		szService = rcdb.GetValue( 0, 0 );
	}
	else
	{
		// N 개의 조회 결과가 존재하는 경우
		// random 처리하여 1개를 선택한다.

		// random 선택
		unsigned int  uRandom = 0;
		int fd = open( "/dev/urandom", O_RDONLY );		
		read( fd, &uRandom, sizeof( uRandom ) );
		close( fd );
	
		szService = rcdb.GetValue( uRandom % nResult, 0 );
	}
	
	rcdb.PgClear();

	// 적합한 서비스 정보 결과 로깅
	_LOG( LINF, "[SELECT ] service select ok. selected svc_seq [%s]. total svc count [%d]"
		, szService.c_str() , nResult );

	return true;
}

bool CTargetSelectThread::SelectContent( DataBase & rcdb, std::string & szRebalanceHost, std::string & szService )
{
	// RCDB 에서 해당 host, service 에 해당하는 content 를 조회하여 멤버변수인 queue 에 push 처리한다.


	// 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 );

	// RCDB 연결은 이미 되어 있는 상태
	char szQuery[4096];
	int nResult = 0;

	// 대상 content 조회
	// - 삭제되지 않았고...
	// - 원본, 복제본 만.. cache 는 포함하지 않는다.
	// - 0 파일 제외하고 최소 3 MB 이상 파일
	// - 최종 수정일이 15일 이상된 content
	// - 가장 용량이 큰 순서부터 
	//   1. 최소한의 이동으로 FHS 간 균형을 빨리 맞추기 위함
	//   2. 작은 파일 많이 이동시키면.. RCDB blot 되므로... 용량 큰 파일이 유리
	//   3. 오래된 파일부터 이동시키면... 실패시에도 계속 오래된 파일부터 시도하므로...
	//   4. 용량은 m_uMaxTransferlimit 값 이상은 옮길 수 없으므로.. 해당 크기 이하로 설정한다.

	// 기능 수정
	// - 제한크기 값에 해당하는 content 1개만 선택되었지만.. 해당 content 가 이미 copy 대상 FHS 에 존재하는 경우
	// - 해당 FHS 는 매번 조회해도 해당 content 만 나와.. 실패처리되어.. rebalance 안되는 상황 발생
	// - 이를 해결하고자.... 조회시 크기 제한 값을 1GB ~ 전송 제한값 수준까지 GB 단위로 random으로 계산하여 조회되도록 하여
	// - 문제 content 조회 되지 않을 가능성이 존재하도록 처리.

	// 최대 전송 제한값에서 GB 단위 값을 추출
	// - 1GB 이하이면 1 로 처리.
	unsigned long long uSelectSizeLimit = m_uMaxTransferlimit / 1000000000ULL;
	if( uSelectSizeLimit == 0 )
		uSelectSizeLimit = 1;

	// nano 시간값을 1~ 전송제한크기 GB 단위로 처리하여 GB 단위 random 값을 얻은 후
	// 실제 전송 제한값에서 해당 크기만큼 빼서
	// 조회시 사용할 GB 단위의 크기값을 생성.
	uSelectSizeLimit = m_uMaxTransferlimit - ((timeStart.tv_nsec % uSelectSizeLimit) * 1000000000ULL);
	_LOG( LINF, "[SELECT ] select content size limit. [%llu GB]", uSelectSizeLimit/1000/1000/1000 );


	// 2021-01-05 NEW huibong 이동 대상 content 선택 관련 mode 기능 추가 (#33306)
	// - PC-71 에서 조회 후 선택된 content 가 이미 대상 장비에 복제본 등이 존재하여 이동이 실패할 경우....
	// - 다음 조회시에도 계속 같은 content 가 다시 선택되는 문제가 발생함.
	// - 따라서 조회시 order by 를 content length 가 아닌 random 으로 처리하는 기능 추가

	// select mode 사용 전 기존 mode 를 timeout 처리해야 하는지 확인 처리
	CProcessConfig::GetInstance()->SetContentSelectModeToDefault();

	if( CProcessConfig::GetInstance()->GetContentSelectMode() == SELECT_MODE_RANDOM )
	{
		_LOG( LINF, "[SELECT ] content select mode : RANDOM " );

		snprintf( szQuery, 4096 - 1
			, "SELECT resource_id, uri, sp_svc_tran_id, host_name, filename_hash, get_content_length "
			"FROM t_meta_%s "
			"WHERE resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' "
			"AND host_name = '%s' "
			"AND get_content_length >= 3000000  AND get_content_length < %llu "
			"AND get_lastmodified < (SELECT date_part( 'epoch', CURRENT_TIMESTAMP )::int - 1296000 || '000000')::bigint "
			"ORDER BY random() LIMIT %d;"
			, szService.c_str()
			, szRebalanceHost.c_str()
			, uSelectSizeLimit
			, CONTENT_SELECT_MAX_LIMIT_COUNT
		);
	}
	else
	{
		// default 상황인 경우.. 큰 파일을 우선으로 이동시킨다.
		snprintf( szQuery, 4096 - 1
			, "SELECT resource_id, uri, sp_svc_tran_id, host_name, filename_hash, get_content_length "
			"FROM t_meta_%s "
			"WHERE resource_type = 0 AND deleted_yn = 'N' AND is_cache != 'Y' "
			"AND host_name = '%s' "
			"AND get_content_length >= 3000000  AND get_content_length < %llu "
			"AND get_lastmodified < (SELECT date_part( 'epoch', CURRENT_TIMESTAMP )::int - 1296000 || '000000')::bigint "
			"ORDER BY get_content_length DESC LIMIT %d;"
			, szService.c_str()
			, szRebalanceHost.c_str()
			, uSelectSizeLimit
			, CONTENT_SELECT_MAX_LIMIT_COUNT
		);
	}

	// Query 실행
	rcdb.PgDoExec( szQuery );
	nResult = rcdb.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, rcdb.GetErrorMessage().c_str(), szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		rcdb.PgClear();
		return false;
	}
	
	unsigned int uPushTotalCount = 0;

	// 결과 Row 수 확인
	nResult = rcdb.GetNoTuples();

	_LOG( LDBG, "[SELECT ] content selected. [%s svc:%s select_count:%d ]"
		, szRebalanceHost.c_str(), szService.c_str(), nResult );

	if( nResult < 0 )
	{
		// query 수행 실패시
		LOG( LERR, "[SELECT ] content select tuple error. [%d][%s][%s]"
			, nResult, rcdb.GetErrorMessage().c_str(), szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		rcdb.PgClear();
		return false;
	}
	else if( nResult == 0 )
	{
		// 적합한 Content 가  없는 경우.. 
		LOG( LWAR, "[SELECT ] content select not found. [%d][svc:%s][%s][%s]"
			, nResult, szService.c_str(), szRebalanceHost.c_str(), szQuery );

		// 다음 Query 수행을 위해 조회 결과 set 을 clear 처리
		rcdb.PgClear();
		return false;
	}
	else
	{

		// N 개의 조회 결과가 존재하는 경우
		// - 정해진 용량까지 Queue 에 push 처리한다.
		CRebalanceData data;
		unsigned long long uPushSum = 0;

		for( int i = 0; i < nResult; i++ )
		{
			// 0: resouce_id
			// 1: uri
			// 2: svc seq
			// 3: host_name
			// 4: filename_hash
			// 5: get_content_legnth
			data.m_uOriginContentLength = atoll( rcdb.GetValue( i, 5 ) );

			if( uPushSum + data.m_uOriginContentLength > m_uMaxTransferlimit )
			{
				// 더 이상 push 처리하지 못하도록 한다.
				_LOG( LINF, "[SELECT ] content push end by total size %llu GB limit. [%s svc:%s select:%d, push:%u]"
					, m_uMaxTransferlimit /1000/1000/1000
					, szRebalanceHost.c_str(), szService.c_str()
					, nResult, uPushTotalCount );
				break;
			}

			uPushSum += data.m_uOriginContentLength;

			// 확인 결과 resouce_id 의 최대값은 9223372036854775807
			// long long int 의 최대값은 9223372036854775807 으로 resouce_id 값과 동일
			// 실제 변환 테스트해 보니 최대값까지 정상 변환 확인함.
			data.m_uResourceId = atoll( rcdb.GetValue( i, 0 ) );
			data.m_szURI = rcdb.GetValue( i, 1 );
			data.m_szServiceSeq = rcdb.GetValue( i, 2 );
			data.m_szOriginHost = rcdb.GetValue( i, 3 );
			data.m_szOriginFileNameHash = rcdb.GetValue( i, 4 );

			// push 처리
			if( m_pQueueMoveStandby->Push( data ) == false )
			{
				// push 처리 실패시...
				// 2 sec 후 재시도해서 실패하면.. queue full 가능성이 있으므로...
				// 버리고.. loop 중지

				sleep( 2 );
				if( m_pQueueMoveStandby->Push( data ) == false )
				{
					LOG( LWAR, "[SELECT ] content select ok. but queue push fail. [%s] [svc:%s id:%llu size:%llu]"
						, szRebalanceHost.c_str(), szService.c_str()
						, data.m_uResourceId, data.m_uOriginContentLength );

					break;
				}
			}

			// total push count 1 증가
			uPushTotalCount++;

			// 로깅
			_LOG( LINF, "[SELECT ] content select, push ok. [%s] [svc:%s id:%llu size:%llu]"
				, szRebalanceHost.c_str(), szService.c_str()
				, data.m_uResourceId, data.m_uOriginContentLength );
		}
	}

	rcdb.PgClear();

	// Content 선택 결과 로깅
	_LOG( LINF, "[SELECT ] content push end. [%s svc:%s select:%d, push:%u]"
		, szRebalanceHost.c_str(), szService.c_str()
		, nResult, uPushTotalCount );

	return true;
}
