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

GO언어를 가볍게 쓰고 있는 입장에서 JetBrains의 GoLand(이전에는 GogLand)는 참 좋은 도구이다.

비용을 들이지 않고 쓰는 개인버전이라 새버전이 나오면 열심히 업버전을 해서 사용중이다.

이전과 마찬가지로 업버전을 하고 GoLand를 실행하니 기존 코드들에 모두 빨간줄이 그어진다.

분명 GOROOT, GOPATH등 여러 변수를 확인해 봤는데 문제가 없다.

빨간줄이 그어져 있지만 컴파일도 잘 된다.

여기저기 찾아봤는데 쉽게 답이 보이지 않았다. (무쓸모 검색능력... )

그러다가 검색한 스택오버플로의 글이 보였다. 물론 나와는 좀 다른 이슈였다.

No buildable go source files after update Goland to EAP 19


선택된 답은 Fil 메뉴 --> Invalidate Caches 를 선택하라.

그래서 나도 해 봤다.

빨간줄 사라졌다.

혹여 이런 문제가 있으신 분들에게 도움이 되길 바라면서 이만~ 


페이스북의 GOLang 커뮤니티에서 아래의 글이 링크되었다.

Deferred Funcs

해당 글은 defer에 대해 매우 쉽게 잘 설명해주고 있다.

내용중에 인상적인 부분은 Deferred closure 이다.

최근 텍스트파일을 읽어서 데이터 분석을 했었다.

분석시 해당 데이터 전체의 시작과 끝점을 기록할 필요가 있었다.

문제는 끝점이 정확히 검색이 되는 경우이지만 그렇지 않은 경우 여러 조건절을 통해 끝점을 찾아야 한다.

나쁜 경우는 내용전체를 돌면서 최종 기록을 끊임없이 반복하여 기존 변수에 할당해야 하는 것이다.


for err != io.EOF {
if xxxx { ...... ......
(CarsInfo[lID][dDate][rNo]).endTime = "20" + string(lines[11:23])
beforeSpeed = nowSpeed
}

n, err = rc.Read(lines) } // 마지막 라인 if xxx { (CarsInfo[lID][dDate][rNo]).endTime = "20" + string(lines[11:23]) }

이런 경우 deferred closure를 사용하면 성능을 좋게하면서 코드를 깔끔하게 할 수 있다.

위의 링크의 예제코드이다.

defer func() 내에서 조건문을 사용하여 작업을 할수도 있고 정해진 구조라면 마지막의 일부 데이터만 추출하여 사용할 수 있다.

만약  for 루프가 10000번 실행된다면 10000번의 할당작업의 성능을 아낄수 있다.


defer func () {

(CarsInfo[lID][dDate][rNo]).endTime = "20" + string(lines[11:23])

}() for err != io.EOF {
if xxxx { ...... ......
beforeSpeed = nowSpeed
}

n, err = rc.Read(lines) }


현재 개인적으로 개발한 분석 코드에서의 수정 결과를 체크해보자.

할당에 대한 CPU 타임은 크지 않으므로 아주 큰 효과는 없으리라 본다.


테스트 환경 / 결과

12코어(TOP 표기상 24코어)중 절반 사용

평소 100% idle (완벽히 노는 서버)

worker 20개로 버퍼채널 이용

20398개의 ZIP 파일을 zip package를 이용해서 압축을 풀면서 분석

캐쉬 상황을 고려하여 번갈아가며 3회 반복(압축을 풀면서 작업하기에 캐쉬관련 이슈는 없지 싶지만서도...)

평균 15초 이상 줄어든다

기존 코드 결과

job ends. execute time: 3m14.353966626s

job ends. execute time: 3m16.453676824s

job ends. execute time: 3m15.367271724s

deferred closure 사용 결과

job ends. execute time: 3m0.899630867s

job ends. execute time: 2m59.410079684s

job ends. execute time: 2m52.597714407s


결론

꽤 괜찮다. 적극적으로 사용할만한 패턴이라 생각된다.

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




아주 작은 프로젝트가 하나 생겼다.

파일에서 특정 데이터를 추출해내는 것이다.

기본적으로는 mpeg의 기본 구조를 따르는 것으로 보인다

go lang으로 작업을 진행하면서 보이는 자잘한 문제를 기술한다.


첫번째는 binary file에서 binary 데이터를 읽어 struct에 넣는 것이다

아래 코드와 같이 코딩하고 진행을 하면 이 지점에서 panic이 발생한다

err = binary.Read(buf, binary.LittleEndian, &riffHeader)

아래 코드중에 Header struct를 int32의 변수 세개(A,B,C)로 변경하면 정상 작동한다.
4 byte의 array로 read하는 부분에서 문제를 발생시키는 것으로 보인다.

결론은 역시...  stack overflow에서 찾았다


일단 소문자로 했을때는 패닉의 내용이 아래와 같다
using value obtained using unexported field

unexported 필드를 이용해서 값을 얻었다는 것이다.
패닉의 내용을 잘 이해했으면 더 빨리 풀었을지도 모를 일이다.

결국 header의 필드가 소문자로 시작하는 private였기 때문에 해당 값을 설정하지 못한것으로 이해된다.
해당 필드값들을 시작할때 대문자로 시작하면 문제가 해결된다.
다시 한번 느끼지만 에러문자열을 잘 읽어보자. ^^

package main

import (
	"os"
	"bytes"
	"encoding/binary"
	"fmt"
)

type Header struct {
	//A int32
	//B int32
	//C int32
	sID   [4]byte
	size  uint32
	stype [4]byte
}

func readFile(filename string) {
	f, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	b := make([]byte, 12)
	readsize, err := f.Read(b)
	if ( len(b) != readsize || err != nil) {
		panic(err)
	}

	riffHeader := Header{}
	buf := bytes.NewBuffer(b)
	err = binary.Read(buf, binary.LittleEndian, &riffHeader)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
}

func main() {
	readFile("e:/temp/20161101_105118_GE_2.avi")
}