[scala] retry

scala 2016. 10. 6. 19:20

spring4에서는 Retryable을 사용하면 retry 관련 코드가 짧아진다.


 @Retryable(maxAttempts = 2, backoff = @Backoff(delay = 0))
String call(String url);




scala에서는 아예 쉽게 retry를 쉽게 만들 수 있다. 


아래 링크를 참조하면 좋은 결과를 얻을 수 있다. 꼬리 재귀와 커링을 사용했다. 


http://stackoverflow.com/questions/7930814/whats-the-scala-way-to-implement-a-retry-able-call-like-this-one



object Main extends App {
@annotation.tailrec
def retry[T](n: Int)(fn: => T): T = {
util.Try { fn } match {
case util.Success(x) => x
case _ if n > 1 => retry(n - 1)(fn)
case util.Failure(e) => throw e
}
}

var i = 0;
retry(3) {
i += 1
test(i)
}

def test(i: Int): Unit = {
if (i == 1 || i == 2) throw new Exception("XXX")
if (i == 3) {
System.out.println(i)
}
}
}


결과는 3이다. 








Posted by '김용환'
,



deview 2016 발표자가 되었다면 풍부하고 실제적인 자료를 전달할 수 있을 터인데, 아깝게 채택되지 못했다.


새로운 것만이 좋은 주제가 아니라, 우리 개발자들이 잘 하고 있는 일을 더욱 개선하는 작업도 좋은 사례로 남기고 싶었다.



이 내용은 나 혼자한 것은 아니고 동료들과 함께 진행한 내용이며,  해당 내용을 간단하게 글로 정리한다. 


(Docker, Jenkins, Rspec에 대한 팁은 내 블로그에서 검색할 수 있을 것이다.)




Spec by Example이라 했지만, (자세한 내용은 https://en.wikipedia.org/wiki/Specification_by_example를 참고한다)

Acceptance Test(인수 테스트)라고 좋고, Integration Test, Functional Test도 괜찮다. 

격리된 환경에서 서비스를 예제로 테스트하는 테스트 환경(Spec by Example)을 Docker로 빠르게 처리하는 방법을 진행했고, 이를 간단히 소개한다.





나는 그동안 직장 생활을 하면서 애플리케이션(또는 플랫폼)을 Spec by Example를 진행한 서비스/플랫폼을 거의 본적이 없다. (간단한 test unit이나 ui 속도 체크, js 속도 체크 정도는 있었지만, 그 이상의 통합 테스트가 없었다)


그리고, 나는 테스트의 의미를 깊이 생각하지 못했다. 개발하기도 바쁜데, 어떻게 테스트 코드까지 짜야하나 하는 불편함이 가지고 있었다. SKP에서 SI업체에서 만든 코드(정리 안된 코드, 테스트 없는 코드)를 받으면서, 정말 어이가 없는 환경에 대한 깊은 고민이 있었다. 테스트 코드에 대한 중요성을 생각하게 되었고, ansible을 공부하게 되었다.(deview 2014 발표)






<거대한 공룡과 싸우기 위해 

필요한 다양한 방패와 무기가 있어야 한다는 그림인데,

여전히 그 때나 지금이나 현실은 조금도 변함이 없는 것 같다. 나에게는 언제나 개발을 대할 때는 이런 것 같다. 
특히 대용량 서비스는 더 장난 아니다.>

이미지 참조 : https://www.amazon.com/Compilers-Principles-Techniques-Alfred-Aho/dp/0201100886







반면, 상용 서비스에서 통합 테스트 환경은 네이버 뉴스에서의 javadoc을 이용한 Functional Test 정도인 듯 하다. (소문에 따르면 지금은 사라졌다고 들었다).

http://d2.naver.com/helloworld/87523



지금 회사에 와서 서비스 개발하면서 가장 큰 수확은 바로 Spec By Example( 통합 테스트 환경)이었다. 수십 개의 컴포넌트가 유기적으로 동작하기 위한 테스트 환경이 Jenkins + Rspec을 기반으로 이미 구축되어 있었다. 

(혹시,,

Rspec을 잘 모른다면, https://semaphoreci.com/community/tutorials/getting-started-with-rspec를 읽으면 좋은 시작점이 될 것이다. 

Jenkins + RSpec 연동을 위해서는 https://sephinrothcn.wordpress.com/2014/04/24/run-rspec-with-jenkins/을 참조하면 좋을 것 같다)


(참고로 Rspec 말고도 최근에는 여러 Spec By Example 프레임워크가 나오고 있다.)






Ruby를 전혀 몰랐지만, Rspec을 통한 테스트 환경이 얼마나 서비스를 강력하게 만드는지 깨닫게 되었다.

격리된 환경에서 수십 개의 컴포넌트(자바 서버, MariaDB, Redis, ES, Mongo 등등......)에 수천 개의 시나리오 테스트가 동작하는 구조로 튼튼한(robust)할 수 있었다.


- 최대한 상용 환경과 비슷하다. 

- 리눅스, Mac OS에서 잘 동작한다.

- 인수 인계 문서가 필요없다. 시나리오 코드가 결국 어떻게 동작되는지를 설명한다.

- 코드 리팩토링하더라도 Spec 이 깨지는 일을 찾아내 문제를 빨리 해결할 수 있다.

- API를 기반으로 하기 때문에 스토리지를 변경하는 마이그레이션(Migration)을 쉽게 할 수 있다.

- 버전 별 API가 진행된다. 

- 운영을 하지만, Spec이 있기 때문에 

- Continuous Deployment의 근간, Continous


이외에 수 많은 장점이 있었다.





수천 개의 테스트를 시퀀셜(sequencial)로 진행될 때 가상 장비에서 50분이 넘어가기 시작했다. 이를 위해 병렬로 테스트를 진행하려 했지만, Rspec의 이슈가 아니라 수 천개의 테스트를 돌리다보니 서비스의 제약 사항을 넘지 못해 병렬로 진행하는 것은 실패했다.


중형 장비를 사용해 50분의 소요시간을 20분대로 줄였다.


이후, 시간이 흘러 Spec By Example 코드가 많아지면서 중형 장비가 다시 57분이 되었다. 


이를 해결하기 위해 여러 개발자와 함께 Docker와 Redis를 준비했다.그래서 8~9분 대로 줄어들도록 진행했고, Continuous Delivery(테스트가 성공되면 자동 개발 서버 배포)를 진행시켰다. 만족도는 좋았다!!!

Devops의 시간을 절약했다!!






이제 그 얘기를 진행한다. 


보통 Continuous Integration, Continuous Delivery, Continous Deployment의 근간은 Spec By Example이라고 생각한다. 그리고, 절제된 아키텍처가 필요했다.


Docker를 활용했지만, Docker의 이미지에 모든 것을 넣지 않았다. 변경이 자주 될만한 컴포넌트와 변경이 자주 안될만한 컴포넌트로 나눴다. 



변경이 자주 되지 않은 이미지를 Docker 이미지로 만들었다. 무척 큰 용량의 이미지가 생성된다.


- Docker 이미지를 생성한다

- Docker 이미지를 registry에 업로드한다. 

- Docker 이미지를 테스트 중형 서버에 배포된다. 




Jenkins에서 Job이 돌면서 Docker에 추가될 부분을 추가하고 여러 중장비에 Docker 이미지를 잘 사용할 수 있도록 했다.(정확히 말하면 병목 지점을 최대한 병렬화했다)


- 소스 커밋이 되면, Jenkins job 실행한다. (multijob + multijob)

- Job 실행시 docker 이미지를 사용해서 여러 데몬을 띄워 최대한의 성능을 낸다.

- 결과를 취합한다. (rspec + redis)

(주의할 사항은 Docker가 메모리 사용량이 높기 때문에 메모리에 대한 관리가 특별히 필요하다)

- 수천 개의 테스트를 모두 통과(정상)하면 개발 서버에 자동으로 배포한다. - Continuous Delivery



그리고, 여러 대의 중장비에 수십 개의 테스트 환경으로 분할할 때는 제대로 만든 분배 알고리즘이 만들었고, 잘 동작했다.


 



이를 기반으로 QA없이 테스트를 진행하고 빠른 Spec By Example을 통해 배포 시간을 최대한 줄였다. 








배운점 : 

1. 병목부분을 정확하게 아는 것이 중요하다. 

2. Docker, RSpec, Jenkin가 무엇인지 아는 것은 쉽지만, 제대로 아는 것은 쉽지 않다. 

   Docker를 무조건 이미지로 만들고 다운로드하는 것은 Continuous Integration에 적합치 않을 수 있다. 

   효율적인 구조로 만드는 개선 노력이 필요하다.

3. Docker를 여러 대의 중장비에 수십 개의 테스트 환경(Spec By Example)으로 동작시킬 때는 

     중장비를 사용하는 것이 좋다. 






소감 : 외국의 모델을 따라하는 게 아니라, 세계에 없는 우리 만의 모델을 만들었다는 점에서 기분이 좋았고, DevOps 사례로 남을 듯 하다. 


다음 시도 : 일부러 프레임워크를 쓰지 않았다. robust한 환경을 위해 Mesos + Marathon 대신 Google의 Kubernetes를 적용해서 반-자동화를 테스트해보고 적용할 예정이다. 





Posted by '김용환'
,