흐르는 시간의 블로그...

AWS S3에 Object Upload를 테스트 하는 업무가 있다.

이를 위해 RESTful API, C++ SDK, Go SDK를 사용하여 테스트를 진행하였다.


각각의 SDK는 결국 RESTful API를 wrapping해서 사용하는 것이었다.

내부 코드를 보면 s3 manager 혹은 s3 client 객체를 생성하여 진행한다.


Object를 업로드할때 일정 이상의 용량일때는 multipart 업로드가 유리하다.


다음은 multipart upload에 대한 AWS의 공식 문서와 이를 해 잘 설명한 블로그다.

멀티파트 업로드 개요 - AWS

Amazon S3 Parallel MultiPart File Upload - BLOG(java)


개인적으로 잘 사용하는 C++과 Go lang용 SDK는 다음의 링크에서 확인할 수 있다

AWS SDK C++ - download(github)

AWS SDK GO - download(github)


다음의 링크는 C++ SDK의 통합 테스트코드로 상세하고 다양한 코드샘플을 찾을 수 있다.

https://github.com/aws/aws-sdk-cpp/blob/master/aws-cpp-sdk-s3-integration-tests/BucketAndObjectOperationTest.cpp


C++ SDK에는 putobject와 multipart upload가 별도로 구분되어 있다.

위의 테스트 코드에서 PutObjectRequest 를 검색하면 기본 업로드 코드를, CreateMultipartUploadRequest 를 검색하면 multipart upload 코드를 찾을 수 있다.


아래 주소에 가면 cpp, go, .net, java, javascript, php, python, ruby의 예제 코드를 확인할 수 있다.

환경 설정이 잘 되어 있으면 즉각 컴파일과 실행이 가능하다.


GO Lang SDK의 sample 코드는 오픈소스라 더 쉽게 내용을 확인할 수 있다.

패키지 s3manager의 Upload를 확인하면 Upload() 함수가 있다.

이 함수를 사용하면 uploader의 PartSize가 s3manager.MinUploadPartSize인 5Mbytes보다 작으면 에러를 발생시킨다.

이후 PartSize 단위로 읽어들여 하나 이하의 chunk가 발생하면 single part upload를 진행한다.

chunk가 둘 이상이면 mulpartuploader 객체를 통해 업로드를 진행한다.



위의 SDK를 실행하려면 기본 환경설정을 해야 한다.

AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 를 설정해야한다.

AWS_SESSION_TOKEN 의 경우 옵션이다.


SDK Configuration - GO Lang


CPP SDK의 경우 CMake를 활용하며 c++ version이 최소 C++11를 지원해야 한다.

SDK 설치시 C++11 혹은 C++14를 지정할 수 있다.

이와 함께 CMake를 사용한다. 

사용하는 linux의 기존 CMake는 버전이 낮아 호환되지 않았다.

yum을 통해 cmake3를 설치하여 사용하였다.

Configuring the SDK - CPP


위의 링크들의 문서와 예제 코드를 사용하면 SDK를 활용하여 AWS를 사용하는 것이 크게 어렵지 않을 것이다.




위의 과정들을 통해 파일 업로드 속도를 간단히 체크한 결과이다.

테스트는 IDC의 linux 서버에서 진행하였으며 multipart upload의 경우 사무실의 개발PC에서 실행하여 체크한 시간이다.



S3 File upload time check - linux server, AWS CLI






시작(ms)

종료(ms)

시간차(ms)

1회

1494548006096

1494548043927

37831

2회

1494548050272

1494548072718

22446

3회

1494548094591

1494548148717

54126

4회

1494548161326

1494548217320

55994

5회

1494548227645

1494548275162

47517

6회

1494548306828

1494548329881

23053

7회

1494548352251

1494548364362

12111

8회

1494548382826

1494548395007

12181

9회

1494548401111

1494548453868

52757

10회

1494548459801

1494548475682

15881

합산



333897

평균



33389.7




S3 File upload time check - linux server, c++ sdk, putobject(single), file size:25826362


소요시간(seconds) - 1회

소요시간(seconds) - 2회

1회

8.70795

18.9716

2회

21.2515

12.0308

3회

34.9919

13.7737

4회

29.1713

6.39977

5회

9.98919

7.33901

6회

15.6894

16.913

7회

29.9045

20.028

8회

53.3282

25.8403

9회

32.0113

42.0073

10회

10.8376

33.4675

합산

245.883

196.771

평균

24.5883

19.6771



S3 File upload time check - linux server, go sdk, Upload(auto multiparts), file size:26189716


소요시간(seconds) - 1회

소요시간(seconds) - 2회

1회

4.6583578

4.6088254

2회

3.9769095

4.1344735

3회

4.0830212

4.2095661

4회

4.7418807

4.1589274

5회

4.1057907

4.6452324

6회

4.2522046

4.0686392

7회

4.5779243

4.0981387

8회

4.6437622

5.8632637

9회

4.2051021

6.0630964

10회

4.1674061

6.753556

합산

43.412359

48.60372

평균

4.3412359

4.860372




현재 운영중인 시스템에서 poco library의 log시스템을 사용한다.

c++14를 사용하기 위해 최근 GCC를 5.4버전으로 올렸다.

그 과정의 수많은 삽질이 있었다. Linux를 잘아는 천과장이 아니었으면 지금도 미궁을 헤메고 있었을듯... :(


우쨋거나 GCC 5.4 업버전 이후 또 다른 문제에 봉착했다.

poco library가 정상 작동을 하지 않았다.

혹시 하는 생각에 poco-1.7.4로 업버전 하여 컴파일하여 install 하였다.

여전히 안된다.


configure를 살펴봐도 gcc 관련 내용은 없다.

여기저기 뒤져보다가 make file을 살펴보니 다름과 같은 라인이 보인다.


include $(POCO_BASE)/build/config/$(POCO_CONFIG)


시스템상 관련 파일은 ./build/config/Linux 이다

해당 부분에서 현재의 gcc5.4의 관련 부분으로 수정하여 컴파일 했더니 문제 없이 넘어가게 되었다.


#
# $Id: //poco/1.4/build/config/Linux#2 $
#
# Linux
#
# Make settings for Linux 2.6/gcc 3.3
#
#

#
# General Settings
#
LINKMODE ?= SHARED

#
# Define Tools
#
#CC      = ${CROSS_COMPILE}gcc
#CXX     = ${CROSS_COMPILE}g++
CC      = /opt/rh/gcc-5.4.0/bin/gcc
CXX     = /opt/rh/gcc-5.4.0/bin/g++


원래 나던 링크 에러 문제...


ubirloader.cpp:(.text+0x20a): 
undefined reference to `Poco::FileChannel::setProperty(std::__cxx11::basic_string<char, std::char_traits<char>, 
std::allocator<char> >  std::char_traits<char>, std::allocator<char> > const&)'
collect2: error: ld returned 1 exit status


일전에 pcqueue를 간단히 만들었었다.
창피해서 코드를 올리지 말까 하다가 올려본다.
실제 사용중에 있다.
허접한 코드를 개선해서 사용해도 좋다.
요기 출처를 달아주고 개선된 부분이 있으면 공유해 주면 더 바랄게 없다.
허접하다고 너무 심하게 구박하지 말아줬으면 좋겠다.

아래는 해당 pcqueue의 템플릿 코드이다.
실제 호출은 템픗릿 소스의 아래에 올려 놓겠다.

//---------------------------------------------------------------------------------------------------------------------
// pcqueue.hpp
//
// Asker's product consumer queue
//---------------------------------------------------------------------------------------------------------------------
#pragma once

#include <iostream>
#include <vector>
#include <pthread.h>

#include "errno.h"

#include "common.hpp"

using namespace std;
//---------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------------------
template <class T>
class CPCQueue
{
private:
	pthread_mutex_t ProducerLock;
	pthread_mutex_t ConsumerLock;

	vector<T*> ProducerQueue;
	vector<T*> ConsumerQueue;

private:
	bool LockProducer()
	{
		int _nError = pthread_mutex_lock( &ProducerLock );
		if ( _nError )
		{
			cout << "error in lock producer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in lock producer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}
	
		return true;
	}

	bool UnlockProducer()
	{
		int _nError = pthread_mutex_unlock( &ProducerLock );
		if ( _nError )
		{
			cout << "error in unlock producer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in unlock producer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}
	
		return true;
	}

	bool LockConsumer()
	{
		int _nError = pthread_mutex_lock( &ConsumerLock );
		if ( _nError )
		{
			cout << "error in lock consumer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in lock consumer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}

		return true;
	}

	bool UnlockConsumer()
	{
		int _nError = pthread_mutex_unlock( &ConsumerLock );
		if ( _nError )
		{
			cout << "error in unlock consumer. return : " << _nError << ", err :" << strerror(errno) << endl;
			formatlog( LOG_CRIT, "error in unlock consumer. return(%d) ErrNo(%d) ErrMsg(%s)", _nError, errno, strerror(errno));
			return false;
		}

		return true;
	}


public:
	CPCQueue(){}
	virtual ~CPCQueue()
	{
		for ( class vector<T*>::iterator _iter = ProducerQueue.begin(); _iter != ProducerQueue.end(); _iter++)
			delete *_iter;

		for ( class vector<T*>::iterator _iter = ConsumerQueue.begin(); _iter != ConsumerQueue.end(); _iter++)
			delete *_iter;

		ProducerQueue.clear();
		ConsumerQueue.clear();
	}

	bool Init()
	{
		int _ErrorNo = pthread_mutex_init( &ProducerLock, NULL);
		if (_ErrorNo)
			return false;
	
		_ErrorNo = pthread_mutex_init( &ConsumerLock, NULL);
		if (_ErrorNo)
			return false;
	
		return true;
	}

	// Product에 하나를 추가한다
	bool AddProduct( T* _Job )
	{
		if ( false == LockProducer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		ProducerQueue.push_back(_Job);
	 
		if ( false == UnlockProducer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		return true;
	}

	// Consumer 쪽에서 하나씩 빼낸다.
	bool PopFromConsumer(T** _Value)
	{
		*_Value = NULL;

		if ( false == LockConsumer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		if (!ConsumerQueue.empty())
		{
			*_Value = ConsumerQueue.front();
			ConsumerQueue.erase(ConsumerQueue.begin());
		}

		if ( false == UnlockConsumer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		return true;
	}

	// 합할 것이 하나도 없는 경우 false를 반환한다
	bool MoveProducerToConsumer(int& _MoveCount)
	{
		if ( false == LockConsumer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		if ( false == LockProducer())
		{
			// lock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Lock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		_MoveCount = ProducerQueue.size();
		if (!ProducerQueue.empty())
		{
			for ( class vector<T*>::const_iterator _iter = ProducerQueue.begin(); _iter != ProducerQueue.end(); _iter++)
				ConsumerQueue.push_back(*_iter);
	
			ProducerQueue.clear();
		}

		if ( false == UnlockProducer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		if ( false == UnlockConsumer())
		{
			// unlock 실패
			formatlog( LOG_CRIT, "CPCQueue::%s(%d) Unlock Fail. fatal issue.", __func__, __LINE__);
			return false;
		}

		return true;
	}

	int GetConsumerSize() const
	{
		return ConsumerQueue.size();
	}

};
//---------------------------------------------------------------------------------------------------------------------



class CDataThread: public CUbiThread
{
private:
	CPCQueue<COnePacket> PCQueue;
	int ThreadIndex;

	CURL* ctx;

public:
	bool Terminated;

public:
	CDataThread();
	virtual ~CDataThread();

	bool init();
	void SetIndex(int _Index);
	bool AddProduct( COnePacket* _OnePacket);

	virtual void Run();

private:
	bool DoPacketProcess(COnePacket* _OnePacket);
	CUbiParser* MakeParser(COnePacket* _OnePacket);
}

//---------------------------------------------------------------------------------------------------------------------
CDataThread::CDataThread()
{
}
//---------------------------------------------------------------------------------------------------------------------
CDataThread::~CDataThread()
{
}
//---------------------------------------------------------------------------------------------------------------------
bool CDataThread::init()
{
	return PCQueue.Init();
}
//---------------------------------------------------------------------------------------------------------------------
void CDataThread::SetIndex(int _Index)
{
	ThreadIndex = _Index;
}
//---------------------------------------------------------------------------------------------------------------------
bool CDataThread::AddProduct( COnePacket* _OnePacket )
{
	return PCQueue.AddProduct(_OnePacket);
}
//---------------------------------------------------------------------------------------------------------------------
void CDataThread::Run()
{
	int _Count = 0;
	int _JobCount = 0;
	int _SleepMicro;
	COnePacket* _OnePacket = NULL;

	Terminated = false;
	while(!Terminated)
	{
		try
		{
			// Customer Queue에 Producer Queue의 내용을 가져오게 한다
			if (!PCQueue.MoveProducerToConsumer(_Count))
				formatlog( LOG_FATAL, "CDataThread::%s(%d) %10d Thread Error in Move PCQueue data", __func__, __LINE__, ThreadIndex); //, typeid(T).name());
			else
				formatlog( LOG_INFO, "CDataThread::%s(%d) %10d Thread %2d %3d remain.", __func__, __LINE__, ThreadIndex, _Count, PCQueue.GetConsumerSize());
		}
		catch(exception &_innerException)
		{
			formatlog( LOG_CRIT, "Exception on CDataThread::%s(%d). %2d Thread what(%s)", __func__, __LINE__, ThreadIndex, _innerException.what());
		}

		// Customer Queue를 비울때 까지 작업을 처리 한다
		_JobCount = 0;
		while(true)
		{
			_OnePacket = NULL;

			// 처리 간격에 일정 시간을 둔다
			if (!PCQueue.PopFromConsumer(&_OnePacket))
			{
				formatlog( LOG_FATAL, "CDataThread::%s(%d). %2d Thread Error in Get Data From PCQueue", __func__, __LINE__, ThreadIndex);
				break;
			}

			if ( NULL == _OnePacket)
				break;

			//1 실질적인 패킷 처리!!
			DoPacketProcess(_OnePacket);
			_JobCount++;

			delete _OnePacket;
		}

		usleep(500000 );
	}
}


C++11 STL 프로그래밍

2015. 12. 11. 11:26

최근 Effective Modern C++이란 책을 구매해서 봤다.

결국 중간에 접었다.

어려웠다.


다시 이전 단계로 갔다.

C++11 STL 프로그래밍...

문법 보다는 사용예를 위주로 책을 구성하였다.

일주일도 안되는 시간에 몇몇 예제를 돌려보며 책을 다 보았다.


일관된 방식으로 빠르게 볼 수 있게 구성된 것이 장점이다.

그러나 개정2판임에도 몇몇의 오자와 함께 예제가 통채로 뒤바뀐 경우도 있고

대소문자가 다르게 나온 경우도 있다.


그럼에도 불구하고 빠른 시간내에 C++11에 대해 접하기에는 나쁘지 않다는 생각이다.


이 예제 테스트를 위해 VC++ 2015(Professrional)를 깔았다.

예전과 다르게 MS 홈페이지에서 직접 다운로드 하여 깔 수 있다.

(혼자 공부하는 것이니 아마도 라이센스에 걸리지는 않을 듯하다)


예전 디지털방송쪽 일을 할때 보면 문서가 두벌이 세트이다.

규격과 Guide...

이번에 C++11을 보면서도 그 지점을 느꼈다...

C++11 STL 프로그래밍은 "규격"을 보여준다면 Effective Modern C++은 Guide를 제공한다.

규격이 훌륭하더라도 그 제약점과 한계를 모르면 일을 할 수 없다.

여기서 좀 더 발전하면 How를 제공하는 패턴과 같은 쪽으로 갈 수 있을 것이다.


일단 읽은 기념으로 표지라도 붙인다.