분류 전체보기 (356)

💻 Programming

AWS ES에 logback으로 로깅하기 (커스텀 어펜더)

엘라스틱서치 RestHighLevelClient를 이용한 로그백 커스텀 appender 만들기

AWS에서 제공하는 ES를 상용애플리케이션 로그시스템으로 활용하고 있는데 최근 코로나 덕분에 재택근무를 하다보니 인프라팀에서 키바나 접속을 제한하면서 재택근무 하는 날 상용서비스에 대한 로그확인이 불가능해졌다. 그래서 키바나에 Cognito서비스를 적용하여 인증을 통해서 키바나 접근이 가능하도록 설정을 하긴 했는데.....로그가 기록이 안되는 문제가 발생했다. 기존에 사용하던 라이브러리는 internetitem/logback-elasticsearch-appender 였는데 기본적인 rest api를 통한 인덱싱만 가능했고 ES의 access policy를 role 기반으로 cognito를 설정하면서 인증문제로 인해 ES로깅을 못하게 된 것이다. 

그럼 이 문제를 어떻게 해결해야 할까 하다가 내린 결론은 "Elasticsearch에서 제공하는 RestHighLevelClient를 이용해서 logback appender를 직접 만들어서 써보지 뭐~" 였다.

우선 RestHighLevelClient를 사용할 수 있으려면 아래 라이브러리들을 추가해야한다. 

compile 'org.elasticsearch:elasticsearch:7.7.1' 
compile 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.7.1' 
compile 'com.github.awslabs:aws-request-signing-apache-interceptor:deb7941e85' 
compile 'org.elasticsearch:elasticsearch-x-content:7.7.1'

이것도 공식 엘라스틱서치 문서가 제대로 업데이트 되어있지 않거나 불명확해서 런타임시에 자꾸 에러나서 구글링 열심히 해서 찾아냈다

자, 이제 AWS Elasticsearch에 access policy를 어떻게 설정했는지 한번 보자.

{
   "Version":"2016-10-17",
   "Statement":[
       {"Effect":"Allow",
         "Principal":{
         "AWS":"arn:aws:iam::77345249:role/my-iam-role"},
         "Action":"es:*",
         "Resource":"arn:aws:es:ap-northeast-2:77345249:domain/my-application/*"},
      {"Effect":"Allow",
         "Principal":{
            "AWS":"arn:aws:sts::77345249:assumed-role/Cognito_Role/CognitoIdentityCredentials"},
         "Action":"es:*",
         "Resource":"arn:aws:es:ap-northeast-2:77345249:domain/my-application/*"  }
    ]
}

위 처럼 설정을 하면 기존에 잘 동작하던 ElasticsearchAppender가 로그를 제대로 남기지 못하게 된다. (my-iam-role을 기반으로 인증을 해야 정상적으로 로깅을 할 수 있다) 그래서 internetitem/logback-elasticsearch-appender 소스를 좀 까 보았더니 나름 AWSAuthentication이라는 인증 관련 클래스를 가지고 있었고 logback 설정으로 BasicAuthentication 대신에 해당 인증 방식을 사용할 수 있을 것 같아서 logback 설정에 AWSAuthentication을 설정해주었지만 region을 제대로 가져오지 못하는 버그와 함께 CredentialChain을 Default어쩌구 체인을 사용하고있었고 role기반 인증을 하도록 설정을 할 수가 없었다. 어떻게든 기존 라이브러리를 사용해보려고 해당 인증클래스를 상속해서 별도의 인증 클래스를 작성한 뒤 해당 인증서비스를 사용할 수 있도록 해보려고 시도해보았으나 결국 signed request를 날리지 못하고 인증오류로 ES에서 400 에러만 주구장창 받았다.

그래서 RestHighLevelClient를 이용해서 인덱싱부터 해보고 그것을 기반으로 logback custom appender를 만들어 보기로 했다. 우선 인덱싱하는 것은 엘라스틱서치 문서에 친절하게 나와있긴 하였으나 예제가 최신화가 안되어있어 라이브러리 추가하는데만도 시간을 낭비했다. 그리고 코드를 작성하여 ES에 요청하는 순간!!! cognito가 적용되어있어 인증 실패!! 그래서 위 ES access policy statement의 마지막 항목으로 아래와 같이 정책을 추가해서 임시로 허용해준다.

,
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "es:*",
      "Resource": "arn:aws:es:ap-northeast-2:72345249:domain/my-application/*"
    }

해당 도메인에서 ES로 들어오는 모든 요청을 허용해준다는 의미다. 

여기까지 성공했는데 인증을 도대체 어떤 credential을 이용해야할지 너무 난감했다. 기존에 경력 20년가까이 된 개발자가 작성해놓은 코드가 정상적으로 돌아가지 않았기 때문에 처음부터 하는 심정으로 구글링을 하기 시작했다. 동시에 aws sdk의 인증관련 클래스들을 하나하나 보기 시작했다. 그러다가 눈에 들어온 녀석. 바로 STSAssumeRoleSessionCredentialsProvider 이다. 이 녀석을 이용해서 아래와 같이 credential을 받아오는 코드를 작성하고 해당 credential을 이용해서 인증에 성공했다. 

  private AWSCredentialsProvider awsS3CredentialsProvider() {
      List<AWSCredentialsProvider> credentialsProviders = new ArrayList<>();

      String roleSessionName = "role-session-name-" + System.currentTimeMillis();

      credentialsProviders.add(new STSAssumeRoleSessionCredentialsProvider.Builder(roleArn, roleSessionName)
              .withStsClient(AWSSecurityTokenServiceClientBuilder.defaultClient())
              .build());

      return new AWSCredentialsProviderChain(credentialsProviders);
  }

여기서 roleArn 정보만 있으면 나머지는 aws sdk에서 알아서 정보를 가지고 와서 잘 처리해준다. 유후~~ 인증에 성공했으니 위에서 access policy에 마지막에 추가했던 항목을 다시 지워도 된다.

하지만 위 코드를 얻기까지 아래 오류메시지를 수도 없이 보았다.

Failed to send events to Elasticsearch: Got response code [403] from server with data {"message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method.

Failed to send events to Elasticsearch: Got response code [403] from server with data {"message":"The security token included in the request is expired"}

그리고 ES에 max 샤드 개수가 꽉차서 로깅시 신규 인덱스를 만들 수 없는 상황도 있었는데 이때는 아래와 같은 오류 메시지가 떴다.

Suppressed: org.elasticsearch.client.ResponseException: method [PUT], host [https://vpc-my-application-ig5szafi26tsfulustdplosdu3f4.ap-northeast-2.es.amazonaws.com], URI [/index-2020-06-20/_doc/4ba14a8b96b242d0?timeout=1m], status line [HTTP/1.1 400 Bad Request] {"error":{"root_cause":[{"type":"index_creation_exception","reason":"failed to create index [api-2020-06-20]"}],"type":"validation_exception","reason":"Validation Failed: 1: this action would add [10] total shards, but this cluster currently has [1002]/[1000] maximum shards open;"},"status":400}

키바나에서 직접 PUT myindex-2020-06-20 명령으로 시도해도 실패했고, 위 에러메시지와 동일한 내용을 확인할 수 있었다. 위와 같은 에러메시지가 발생하면 max 샤드개수를 올려주던지 아니면 기존에 있는 인덱스들 중에서 불필요한 것들을 좀 날려주면 된다. 일부 인덱스를 삭제하니 로깅이 잘 되는 것을 확인할 수 있었다.

하지만 뭔가 이상했다. 테스트 API를 호출하면 로그가 3개가 찍혀야되는데 1개만 찍히고 있었다. 도대체 뭘까 어디가 문제일까 한참을 고민하고 이것저것 수정하면서 테스트를 했다.

처음 로그메시지 인덱싱을 위한 테스트코드는 아래처럼 작성했었다.

  IndexRequest request = new IndexRequest(indexPrefix + LocalDate.now().toString())
          .id(UuidUtil.getTrackingId())
          .source(document);

  Runnable r = () -> {
      try (RestHighLevelClient esClient = esClient()) {
          esClient.index(request, RequestOptions.DEFAULT);
      } catch (Exception e) {
          log.error("ES logging ERROR!!! e-msg:{}", e.getMessage(), e);
      }
  };
  r.run();

저렇게 작성하니 아무 문제없이 로깅을 했으나 로그가 3개중 1개만 기록이 되고있었다. 그래서 IndexRequest의 create설정(default:false)을 true로 추가해보았다. 왠지 덮어쓰는 것 같은 느낌이 들었기 때문이다. 그랬더니 아래 "오류 1" 메시지가 출력이 되었고, version 설정을 했더니 오류 2가 출력이 되었다. 

// 오류 1 : create(true) 추가했더니
status:CONFLICT, e-msg:Elasticsearch exception [type=version_conflict_engine_exception, reason=[bf0af4cfa5b64945]: version conflict, document already exists (current version [1])]

// 오류 2 : version 설정을 해줬더니
create operations do not support explicit versions. use index instead

// 오류 3 : create 설정을 다시 뺐더니
internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead

// 오류 4 : ifSeqNo만 설정했더니
ifSeqNo is set, but primary term is [0];

// 오류 5 : ifPrimaryTerm도 설정했더니
version conflict, required seqNo [329563504204790], primary term [1592662218337]. but no document was found]

 

하나 둘 오류 메시지를 확인하면서 코드를 수정하고 다시 테스트 하기를 몇 번 하고나니 동일한 버전으로 여러 도큐먼트를 인덱싱하려다가 오류가 발생(versioning문제)하는 것이라 추측할 수 있었고, IndexRequest에 세팅하는 값에 version이 있어서 그것 때문일 거라 생각했었으나 문제는 id였다. -_-;; 우선, 나의 목적은 모든 로그를 남기려는 거였고 도큐먼트를 버저닝해가면서 업데이트 할 일은 없는데 왜 자꾸 버저닝 관련 오류가 나는 것일까? 고민을 좀 하다가 발견한 사실! id값을 특정 프로세스를 트래킹하기위한 똑같은 uuid값을 넣고있었던 것이다. 그래서 id에 세팅하는 값을 아래처럼 나노시간으로 바꾸어주었다.

IndexRequest request = new IndexRequest(indexPrefix + LocalDate.now().toString())
                .id(String.valueOf(System.nanoTime()))
                .source(document);

그랬더니 더이상 오류도 나지 않고 인증도 잘하고 정상적으로 cognito 설정을 완료할 수 있었다.

하지만 실제 상용서비스에서 사용함에 있어서 버그가 없는지 또는 성능에 문제가 없을지는 부하테스트를 좀 해봐야겠다.

황금같은 주말에 아가랑 놀아주지도 못하고 새벽까지 이러고 있다니 ㅜㅜ

그래도 해결의 실마리를 찾아서 기쁘다. 씻고 자야겠다...

 

[6월 24일 업데이트]

부하테스트 결과 try with resource 구문형태로 index 메서드를 사용할 경우 BAD_REQUEST Unable to parse response body 와 같은 오류나 아니면 "You have exceeded the number of permissible concurrent requests with unique IAM Identities. Please retry." 와 같은 오류를 보게될 수 있다. 따라서 상용로그처럼 1초내에 수백~수천 건의 로그를 쌓아야 한다면 IndexRequest대신 BulkRequest를 bulk메서드로 호출하도록 하고, try with resource 구문이 아닌 client를 한번만 생성한 다음 계속 재사용하도록 해야한다. 관련된 내용은 아래 문서에서 확인할 수 있다.

docs.aws.amazon.com/IAM/latest/UserGuide/reference_iam-limits.html

www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html

 

[6월 30일 업데이트]

BulkRequest도 bulk 메서드를 사용할 경우 synchronous하게 동작하기 때문에 로깅하는 것이 기존 애플리케이션 로직의 소요시간에 영향을 주게 된다. 따라서 로깅을 매우 빈번하게 해야하는 경우에는 bulkAsync 메서드를 사용해야 한다.

테스트 결과 bulk 메서드는 bulkAsync 메서드보다 2배의 성능 저하 현상을 가져온다. 테스트 시 ES는 AWS ES 7.1 버전으로 테스트했다. 라이브러리도 페이지 첫 부분에 명시했듯이 7.7.1 버전을 사용했다.

 

[7월 7일 업데이트]

세 가지 이슈가 추가로 발견되었다.

 

첫 번째, 6월 24일 업데이트에 나왔던 문제가 또 나왔다. 

method [POST], host [https://vpc-elasticsearch.ap-northeast-2.es.amazonaws.com], URI [/_bulk?timeout=1m], status line [HTTP/1.1 400 Bad Request]
{"Message":"You have exceeded the number of permissible concurrent requests with unique IAM Identities. Please retry."}

이번에는 소스레벨에서 많은 클라이언트를 사용한게 아니었지만, AWS 콘솔에 접속하여 사용하는 사용자가 많을 경우 종종 발생한다. 이 부분을 어떻게 해결할 수 있을까 고민을 해보아도 이 부분까지 컨트롤 할 수는 없을 것 같다는 생각이 든다.

 

두 번째, 의도치 않게 클라이언트가 종료되는 현상이 개발서버에서 발견되었다.

IllegalStateExceptionRequest cannot be executed; I/O reactor status: STOPPED

이건 예외를 잡아서 새로운 클라이언트를 만들어 사용하도록 수정하긴 했으나....왜 클라이언트가 종료되었는지 알 수가 없어 답답하다.

 

세 번째, 부하를 많이 준 인스턴스에서 아래와 같은 오류 발생 (thread: I/O dispatcher 2)

Connection lease request time out:
at org.apache.http.nio.pool.AbstractNIOConnPool.processPendingRequest(AbstractNIOConnPool.java:411)
at org.apache.http.nio.pool.AbstractNIOConnPool.processNextPendingRequest(AbstractNIOConnPool.java:391)
at org.apache.http.nio.pool.AbstractNIOConnPool.release(AbstractNIOConnPool.java:355)
at org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager.releaseConnection(PoolingNHttpClientConnectionManager.java:364)
at org.apache.http.impl.nio.client.AbstractClientExchangeHandler.releaseConnection(AbstractClientExchangeHandler.java:245)
at org.apache.http.impl.nio.client.MainClientExec.responseCompleted(MainClientExec.java:387)
at org.apache.http.impl.nio.client.DefaultClientExchangeHandlerImpl.responseCompleted(DefaultClientExchangeHandlerImpl.java:172)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.processResponse(HttpAsyncRequestExecutor.java:448)
at org.apache.http.nio.protocol.HttpAsyncRequestExecutor.inputReady(HttpAsyncRequestExecutor.java:338)
at org.apache.http.impl.nio.client.InternalRequestExecutor.inputReady(InternalRequestExecutor.java:83)
at org.apache.http.impl.nio.DefaultNHttpClientConnection.consumeInput(DefaultNHttpClientConnection.java:265)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:81)
at org.apache.http.impl.nio.client.InternalIODispatch.onInputReady(InternalIODispatch.java:39)
at org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:121)
at org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)
at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)
at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)
at java.lang.Thread.run(Thread.java:748)

 

 

 

참고자료:

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high-document-index.html http://logback.qos.ch/manual/appenders.html

 

 

💻 Programming

[Kotlin/JVM] 코틀린에서 자주 사용하는 문법

잠이 깨서 새벽에 포스팅하기는 정말 오랜만이네요

이번에는 코틀린에서 자주 사용되는 문법에 대해서 알아봤습니다.

 

코틀린에서 자주 사용하는 문법들

1. 클래스 만들기

data class Customer(val name: String, val email: String)

코틀린에서는 이 한줄이면 간단히 클래스를 만들 수 있습니다. 또한, 아래 메서드들은 기본적으로 생성됩니다.

- getters, setters

- equals()

- hashCode()

- toString()

- copy() <-- 자바의 clone과 같은 녀석

- component1(), component2(), ... <--이건 나중에 클래스에 대해 자세히 보게될 때 알게될 것 같네요

 

2. 함수 파라미터의 기본 값 설정

함수 정의 시 전달받을 파라미터의 기본값을 아래와 같이 설정할 수 있다.

fun foo(a: Int = 0, b: String = "") { ... }

위 코드는 foo 함수 호출 시 Int 타입과 String 타입의 파라미터를 전달하지 않으면 기본값으로 0과 빈 스트링 ""을 이용하여 로직을 처리하게된다. 따라서 함수 호출 시 foo()라고 호출이 가능하다.

 

3. 리스트 필터링

val positives = list.filter { x -> x > 0 }

자바의 stream에서 필터링 하는 것처럼 람다식을 이용해서 리스트에서 특정 조건에 해당하는 것들만 선별할 수 있다.

또한 it 키워드를 이용해서 아래와 같이 더 간결하게 작성도 가능하다.

val positives = list.filter { it > 0 }

 

4. 컬렉션에서 특정 요소 존재유무 확인하기

// 특정 스트링이 리스트에 존재하는지 확인
if ("john@example.com" in emailsList) { ... }

// 특정 스트링이 리스트에 존재하지 않는지 확인
if ("jane@example.com" !in emailsList) { ... }

 

5. 스트링 포매팅 (String Interpolation)

var name = "황비홍"
println("Name $name")

스트링 내에 변수의 값을 넣을 수 있는 기능으로 자바의 스트링 포매터와 유사한 기능이다.

 

6. 인스턴스 검사 (Instance Check)

when (x) {
    is Foo -> ...
    is Bar -> ...
    else   -> ...
}

코틀린에서 when 은 자바의 switch 문과 유사한 기능으로 좀 더 간결하게 switch문을 표현할 수 있다는 장점이 있다.

 

7. Map 순회하기 (Traversing a map/list of pairs)

for ((k, v) in map) {
    println("$k -> $v")
}

자바에서는 keySet이나 values를 각각 가져와서 처리해야 했지만 코틀린에서는 key, value 를 동시에 가져와서 처리가 가능하다.

 

8. 범위 사용법

loop를 위해서는 특정 범위를 지정해줘야 하는 경우가 많다. 코틀린에서는 아래와 같이 다양한 범위지정법을 제공해주고 있다.

for (i in 1..100) { ... }  // closed range: 100 포함
for (i in 1 until 100) { ... } // half-open range: 100 미포함
for (x in 2..10 step 2) { ... }	// 2부터 2씩 증가하면서 10까지 ...
for (x in 10 downTo 1) { ... }	// 10부터 1씩 감소하면서 1까지 ...
if (x in 1..10) { ... }	// 만약 x가 1이상 10이하면 ...

 

9. Read-Only List/Map

val list = listOf("a", "b", "c")
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

listOf, mapOf 함수를 사용하여 읽기전용 리스트나 맵을 생성할 수 있다.

 

10. 키를 이용한 맵의 값 읽어오기

println(map["key"])
map["key"] = value

코틀린에서는 맵의 요소에 접근할 때 배열처럼 접근이 가능하다.

 

11. Lazy 속성

val p: String by lazy {
    // compute the string
}

 

12. 확장 함수 (Extension Functions)

fun String.spaceToCamelCase() { ... }

"Convert this to camelcase".spaceToCamelCase()

String의 확장함수를 임의로 추가정의하여 사용이 가능하다

 

13. 싱글턴 객체 생성하기

object Resource {
    val name = "Name"
}

 

14. if not null 을 간결하게 작성

val files = File("Test").listFiles()

println(files?.size)

files의 size를 호출할 때 nullable을 의미하는 ?를 붙여주면 not null일 경우에만 실행하게 된다. 만약 files가 null이라면 null이 출력된다.

 

15. if not null and else

val files = File("Test").listFiles()

println(files?.size ?: "empty")

이번에는 if not null 에 else 조건을 붙여 null이면 "empty" 를 출력하도록 변경하였다.

 

16. null일 경우 문(statement) 실행하기

val values = ...
val email = values["email"] ?: throw IllegalStateException("Email is missing!")

 

17. possibly empty collection에서 첫번째 요소 가져오기

val emails = ... // might be empty
val mainEmail = emails.firstOrNull() ?: ""

 

18. null이 아닌 경우 실행하기

val value = ...

value?.let {
    ... // 블럭내의 문장은 value가 not null일 경우에 실행한다
}

 

19. nullable Boolean

val b: Boolean? = ...
if (b == true) {
    ...
} else {
    // `b` is false or null
}

Boolean타입의 변수가 null인지 아닌지 별도로 검사할 필요가 없다.

 

20. 두 변수의 값 스왑하기

var a = 1
var b = 2
a = b.also { b = a }

자바에서는 두 변수의 값을 swap할 때 임시변수를 하나 추가로 만들어서 swap하는 것이 일반적이지만 코틀린에서는 그럴 필요가 없다.

 

이외에도 여러가지 자바보다 더 쉽고 간결해진 자주 사용되는 문법이 있으나 이 정도만 소개합니다.

나머지는 아래 출처에 언급한 코틀린 공식사이트에서 확인해보세요.

 

어느 정도 기본적인 문법에 익숙해지면 쉬운 알고리즘 문제풀이를 코틀린으로 하면서 빠르게 익혀볼 계회입니다.

 

 

 

출처 : https://kotlinlang.org/docs/reference/idioms.html

💻 Programming

[Kotlin/JVM] 코틀린 언어의 특징 (코틀린이란?)

지난 포스팅에서 간략하게 코틀린의 기본 문법이 어떻게 생겼는지에 대해서 알아보았는데, 자바를 많이 닮은 언어라고 느꼈으며 매우 오랜만에 새로운 언어를 공부하는 터라 새롭고 신기하고 흥미로움을 많이 느꼈다. 그래서 회사 도서관에서 코틀린 in action 책을 빌려서 읽어볼 예정이며, 책에서 배운 내용도 포스팅을 하려고 한다.

 

우선 깊이있게 문법공부를 시작하기 전에 코틀린이 어떤 언어인지에 대한 내용부터 좀 더 자세히 알아야 겠다는 생각이 들어 책에서 관련 부분을 읽어보았고 그 내용을 정리해본다.

 

코틀린의 주요 특성

1. 대상플랫폼: 서버, 안드로이드 등 JVM이 설치될 수 있는 모든 플랫폼

- 이 부분은 자바를 대체할 새로운 언어임을 확실히 보여주고 있습니다.

 

2. 정적 타입 언어

- 컴파일 시에 오류를 감지하여 배포 후 런타임에 발생할 오류를 미리 예방할 수 있습니다.

 

3. 함수형 프로그래밍 지원

- 함수형 프로그래밍의 핵심개념은 3가지가 있죠. 일급시민(first-class), 불변성(immutability), 그리고 부수효과(side-effect)가 없다는 것. 코드를 더 간결하게 작성할 수 있게 해주고, 다중 스레드를 사용해도 안전하며, 테스트가 쉽다고 얘기합니다. 특히 다중 스레드 사용이 안전하다는 것은 함수 내에서 외부 객체의 상태변경을 하지 않기 때문이라고 얘기합니다.

 

4. 무료 오픈소스

- 가장 중요한 특징 중 하나라고 할 수 있죠. 그동안 한국의 웹애플리케이션 개발에 큰 기여를 한 Java는 오라클의 손에 넘어간 뒤로 유료화가 진행되고 있기 때문에 한국 기업들에서는 자바를 대체할 언어를 찾으려고 할겁니다. 

 

5. 기존 Java 소스코드와의 연동

- 이건 내가 생각하는 코틀린의 정말 중요한 특성이다. 기존 자바소스 코드를 코틀린 프로젝트로 불러와서 사용이 가능하다는 점은 정말 훌륭한 특성이다. 

 

코틀린의 철학

1. 실용성 : 이건 뭐 말할 필요가 있을까, 단지 연구를 위한 목적으로 개발된 언어가 아니라는 점

2. 간결성 : 더이상 개발자가 getter, setter, toString 등의 코드를 작성할 필요없다. 기본적으로 제공한다. 

3. 안전성 : 정적 타입 언어이니 컴파일 시점에 오류를 찾아낸 다는 점, 그리고 자바에서 흔히 볼 수 있는 ClassCastException, NPE 등을 미리미리 감지할 수 있도록 설계되었다는 점 등이 있다.

4. 상호 운용성 : 자바 코드를 그대로 가져다 쓸 수 있고 자바 코드에서 코틀린 코드를 가져다 쓸 수 있다. 심지어 코틀린 코드에서 자바로 정의한 클래스를 상속/구현도 가능하다. 또한, 자바-코틀린 변환기를 이용하여 기존 자바코드를 코틀린 언어로 변경하는 것도 가능하다. 이는 인텔리J뿐만 아니라 이클립스나 웹에서도 가능하다고 한다.

 

 

다음은 코틀린 문법을 테스트 해볼 수 있는 놀이터이다. 각종 코틀린 코드 샘플 예제도 확인할 수 있으니 코틀린에 대해 더 공부해보고자 한다면 자주 들러 놀다가자.

 

코틀린 온라인 놀이터 : https://play.kotlinlang.org/

Try Kotlin

Try Kotlin right in the browser.

try.kotlinlang.org

 

회사에서 AWS Elasticsearch를 이용하여 상용서비스의 로그를 기록하고 있는데 그 용량이 좀 많아 10TB용량으로 1달 정도밖에 버티고 있지 못해 이런 저런 리서치를 좀 하다가 해당 데이터를 S3로 백업해보기로 하였고 그 과정을 기록해본다.

 

 

참고로 AWS SDK는 1.11.483 사용중이며, role 기반으로 사용하고 있어 access_key, secret_key등은 사용하지 않는다.

 

우선 아래 라이브러리를 추가해주었다.

// ES snapshot S3 저장을 위한 디펜던시 추가
compile 'org.elasticsearch:elasticsearch:7.1.1'
compile 'org.elasticsearch.client:elasticsearch-rest-high-level-client:7.1.1'

 

1. 스냅샷 리포지토리 등록

a. Kibana에서 직접 등록

PUT _snapshot/my-snapshot-repo
{
  "type": "s3",
  "settings": {
    "bucket": "버킷명, 예제에서는 my-bucket 사용",
    "base_path": "/로 시작하지 않는 폴더경로, 버킷 내 폴더 경로임, 예제에서는 base/path/to/save/backup/data 사용",
    "readonly" : "true",
    "region": "us-west-1",
    "role_arn": "arn:aws:iam::4234:my-role",
    "compress" : "true"
  }
}

만약 위 명령어 실행시 아래와 같이 오류가 발생한다면 role 설정에 추가해야할 사항이 있는데, 해당 포스트 최하단의 참고문헌들을 읽어보며 해결해보시길 바란다. 

{
  "Message": "User: anonymous is not authorized to perform: iam:PassRole on resource: arn:aws:iam::4234:my-role"
}

 

나는 아래 설명할 Java 코드를 이용하여 등록했다.

 

b. Java 코드 (1과 동일한 내용을 자바코드로 작성한 것이다)

    import com.amazonaws.auth.AWS4Signer;
    import com.amazonaws.auth.AWSCredentialsProvider;
    import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpHost;
    import org.apache.http.HttpRequestInterceptor;
    import org.apache.http.entity.ContentType;
    import org.apache.http.nio.entity.NStringEntity;
    import org.elasticsearch.action.get.GetRequest;
    import org.elasticsearch.action.get.GetResponse;
    import org.elasticsearch.client.*;
    import org.junit.Ignore;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.junit4.SpringRunner;

    import javax.ws.rs.HttpMethod;
    import java.io.IOException;

    @Slf4j
    @ActiveProfiles("${SPRING_PROFILES_ACTIVE:local}")
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class AWSESSnapshotTest {

        private String region = "us-west-1";
        private static String serviceName = "es";
        private static String aesEndpoint = "VPC Endpoint를 넣어주세요";
        private static String snapshotRepository = "/_snapshot/리포지토리명을 써주세요";
        private static String snapshotSettings = "{ \"type\": \"s3\", \"settings\": { \"bucket\": \"버킷명을 써주세요\", \"region\": \"리전을 명시해주세요, 예: us-west-1\", \"base_path\": \"스냅샷을 저장할 버킷내 폴더 경로\", "compress":"true", "readonly":"true", \"role_arn\": \"IAM Role을 적어주세요\" } }";

        private static final AWSCredentialsProvider credentialsProvider = new DefaultAWSCredentialsProviderChain();

        @Test
        public void AWS_ES_수동스냅샷_리포지토리_등록() throws IOException {
            RestClient esClient = esClient(serviceName, region);

            // Register a snapshot repository
            HttpEntity entity = new NStringEntity(snapshotSettings, ContentType.APPLICATION_JSON);
            Request request = new Request(HttpMethod.PUT, snapshotRepository);
            request.setEntity(entity);
            // request.addParameter(name, value); // optional parameters
            Response response = esClient.performRequest(request);
            System.out.println(response.toString());
        }

        // Adds the interceptor to the ES REST client
        public static RestHighLevelClient esClient2(String serviceName, String region) {
            AWS4Signer signer = new AWS4Signer();
            signer.setServiceName(serviceName);
            signer.setRegionName(region);

            // java.lang.NoClassDefFoundError: org/elasticsearch/common/xcontent/DeprecationHandler
            HttpRequestInterceptor interceptor = new AWSRequestSigningApacheInterceptor(serviceName, signer, credentialsProvider);

            return new RestHighLevelClient(RestClient.builder(HttpHost.create(aesEndpoint)).setHttpClientConfigCallback(hacb -> hacb.addInterceptorLast(interceptor)));
        }

    }

 

자바코드가 정상적으로 실행되면 ES에서 아래 명령어로 설정이 잘 생성되었는지 확인해보자.

GET _snapshot

"my-repo" : {
  "type" : "s3",
  "settings" : {
    "bucket" : "my-bucket",
    "base_path" : "base/path/to/save/backup/data",
    "readonly" : "false",
    "region" : "us-west-1",
    "role_arn" : "arn:aws:iam::4234:my-role",
    "compress" : "true"
  }
}

 

이렇게 잘 나왔다면 이제 인덱스 백업으로 넘어가자.

 

2. ES 인덱스를 S3로 백업

ES 인덱스를 S3로 백업할 때는 아래와 같이 할 수 있다.

    public void AWS_ES_수동스냅샷_S3_저장() throws IOException {

        RestClient esClient = esClient(serviceName, region);

        // Save indexes into S3 repository
        String takeSnapShot = "{\n  \"indices\": \"index-2020-04-*\",\n  \"ignore_unavailable\": true,\n  \"include_global_state\": false\n}";
        HttpEntity entity = new NStringEntity(takeSnapShot, ContentType.APPLICATION_JSON);
        Request request = new Request(HttpMethod.PUT, snapshotRepository + "/snapshot-test-2020-04");
        request.setEntity(entity);
        
        Response response = esClient.performRequest(request);
        
        System.out.println(response.toString());
    }
    
    
    출력결과 : 
    Response{requestLine=PUT /_snapshot/my-repo/snapshot-test-2020-04 HTTP/1.1, host=${VPC Endpoint}, response=HTTP/1.1 200 OK}

 

위 코드는 인덱스명이 "index-2020-04-"로 시작하는 모든 인덱스를 "snapshot-test-2020-04"라는 이름의 스냅샷으로 S3에 저장하고 있다.

그런데 실제로 S3의 해당 리포지토리로 가서 확인해보면 indices 디텍토리가 생성되어있고, 그 안에는 백업하려했던 인덱스의 개수만큼 uuid같은 이름의 디렉토리가 생성되어 있고 그 디렉토리 안에는 또 uuid같은 이름의 파일들이 잔뜩 생성되어있는 것을 확인 할 수 있었다. 따라서 S3 파일명만 보고서는 이게 어느 스냅샷에 대한 데이터파일인지 알 수가 없다 아놔 ㅜㅜ 추후 S3에서 해당 스냅샷에 대한 데이터를 삭제하고 싶어도 여기서(S3에서) 파일명만 보고는 처리할 수가 없다는 뜻이다. 

 

우선 백업된 수동 스냅샷을 다시 ES로 복원해서 잘 조회해올 수 있는지부터 확인해보자.

 

3. S3에 백업된 스냅샷을 ES로 복원하기

복원시에는 아래처럼 할 수 있다.

    public void AWS_ES_수동스냅샷_S3_복원_테스트() throws IOException {

        RestClient esClient = esClient(serviceName, region);

        // Restoring snapshot as ES indices
        String takeSnapShot = "{\n  \"indices\": \"index-2020-04-28\",\n  \"ignore_unavailable\": true,\n  \"include_global_state\": false\n}";
        HttpEntity entity = new NStringEntity(takeSnapShot, ContentType.APPLICATION_JSON);
        Request request = new Request(HttpMethod.POST, snapshotRepository + "/snapshot-test-2020-04/_restore");
        request.setEntity(entity);
        
        Response response = esClient.performRequest(request);
        
        System.out.println(response.toString());
    }
    
    출력결과:
    Response{requestLine=POST /_snapshot/my-repo/snapshot-test-2020-04/_restore HTTP/1.1, host=${VPC Endpoint}, response=HTTP/1.1 200 OK}

위 코드는 4월달 로그에 대해서 저장했던 스냅샷에서 특정일자의 인덱스 즉, index-2020-04-28 에 대해서만 복원을 진행한다.

위 코드가 정상적으로 실행되면 ES에 해당 인덱스가 생성되어 있는 것을 확인할 수 있다.

만약 복원하려는 인덱스 명이 존재한다면 아래와 같이 오류가 발생한다.

org.elasticsearch.client.ResponseException: method [POST], host [https://vpc-endpoint.amazonaws.com], URI [/_snapshot/my-repo/snapshot-test-2020-04/_restore], status line [HTTP/1.1 500 Server Error]
{"error":{"root_cause":[{"type":"snapshot_restore_exception","reason":"[my-repo:snapshot-test-2020-04/ECzLylZnTsGn8KfBqCvSEw] cannot restore index [index-2020-04-27] because an open index with same name already exists in the cluster. Either close or delete the existing index or restore the index under a different name by providing a rename pattern and replacement name"}],"type":"snapshot_restore_exception","reason":"[my-repo:snapshot-test-2020-04/ECzLylZnTsGn8KfBqCvSEw] cannot restore index [index-2020-04-27] because an open index with same name already exists in the cluster. Either close or delete the existing index or restore the index under a different name by providing a rename pattern and replacement name"},"status":500}

 

이럴 경우 아래 3가지 경우로 복원절차를 진행해야 한다.

 

첫째, 현재 존재하는 동일한 이름의 인덱스를 삭제하고 복원한다.

둘째, 복원할 때 인덱스명을 rename해서 복원한다.

셋째, 다른 ES도메인으로 복원한다 -> 이건 수동으로 스냅샷을 생성한 경우에만 가능한 방법이다.

 

나는 테스트 중이므로 간편하게 첫번째를 선택했다.

 

만약 스냅샷에 여러 인덱스에 대한 정보가 담겨있고 모든 정보를 복원하려고 할 때 단 하나의 인덱스라도 이름이 겹친다면 위 에러가 발생한다.

 

위 코드가 정상적으로 실행되면 ES에서 해당 인덱스의 정보를 조회해올 수 있다.

 

그리고 특정 인덱스가 아닌 여러 인덱스를 한꺼번에 복원하고자 할 때는 takeSnapShot의 indices 항목에 인덱스명을 나열해주면 된다. *도 사용할 수 있다는 건 ES 유저라면 당연히 아실테고..

"indices":"index-2020-04-*,index-2020-05-0*"

위처럼 해주면 4월달 모든 인덱스와 5월 1일~5월 9일까지의 인덱스를 복원하게 될 것이다.

 

자, 그럼 복원에 대해서는 이정도로 하고, 스냅샷도 너무 오래 보관하면 용량만 잡아먹을테니 삭제는 어떻게 할 수 있을지 보자.

 

4. S3에 백업한 ES 스냅샷 삭제하기

스냅샷을 삭제할 때는 아래 코드를 참고하면된다.

    public void AWS_ES_수동스냅샷_삭제_테스트() throws IOException {

        RestClient esClient = esClient(serviceName, region);

        Request request = new Request(HttpMethod.DELETE, snapshotRepository + "/snapshot-test-2020-04");

        Response response = esClient.performRequest(request);

        System.out.println(response.toString());
    }

이렇게 하면 S3에 생성되었던 파일들이 삭제되는 것을 확인할 수 있는데, indices 디렉토리는 완전히 삭제되고 스냅샷 리포지토리로 지정된 root 경로에는 몇몇 쓰레기(??) 파일들이 남아있는 것을 확인 할 수 있었다. 만약 2개 이상의 스냅샷을 저장했다면 indices 디렉토리는 모든 스냅샷에 대한 삭제요청을 하지 않는 이상 삭제되지 않는다. 또한, 스냅샷을 삭제할 때는 시간이 오래걸린다. sync 방식으로 처리하기에는 너무너무너무 오래 걸리기 때문에 상용에서 삭제기능을 사용하려면 async 방식으로 처리하길 권장한다. 위 코드대로 작성했다면 무조건 아래 소켓타임아웃 예외가 발생할 것이다.

java.net.SocketTimeoutException: 30,000 milliseconds timeout on connection http-outgoing-0 [ACTIVE]

 

만약 스냅샷 이름이 기억이 나지 않는다면 어떻게 해야 할까? S3에서 조회해봐야 파일명이 어떤 스냅샷인지 알 수 없도록 생성되어있으니 알 길이 없다. 키바나 dev tool에서 아래 명령어를 이용하여 어떤 스냅샷이 있는지 그 스냅샷에 어떤 인덱스들이 저장되어있는지를 확인할 수 있다.

GET _snapshot/my-repo/_all 	<- my-repo의 모든 스냅샷 조회하기



{
  "snapshots" : [ {
    "snapshot" : "snapshot-test-2020-04",
    "uuid" : "VPMGlnLTQlqIT7SxPaqCOg",
    "version_id" : 7016199,
    "version" : "7.1.1",
    "indices" : [ "index-2020-04-29", "index-2020-04-26", "index-2020-04-27", "index-2020-04-28" ],
    "include_global_state" : false,
    "state" : "SUCCESS",
    "start_time" : "2020-05-25T07:34:09.804Z",
    "start_time_in_millis" : 1590392049804,
    "end_time" : "2020-05-25T07:34:32.145Z",
    "end_time_in_millis" : 1590392072145,
    "duration_in_millis" : 22341,
    "failures" : [ ],
    "shards" : {
      "total" : 20,
      "failed" : 0,
      "successful" : 20
    }
  } ]
}

 

특정 스냅샷에 대한 정보를 확인하고 싶다면 아래 명령어를 이용하면 된다.

GET _snapshot/my-repo/snapshot-test-*

 

 

참고로 큐레이터를 이용하여 ES의 데이터를 rotate 시킬 수도 있습니다. 관련 링크는 참고문서에 넣어놓았으니 관심있으시면 읽어보세요.

 

그리고 실제로는 이 스냅샷 코드를 사용하지 않고 ES 7 이상에서 지원하는 UltraWarm 설정을 이용하기로 했습니다. ES인스턴스 사이즈도 warm 노드의 경우 핫노드와는 별개로 S3를 이용함으로써 비용을 많이 줄일 수 있고 저장기간 역시 원하는 만큼 늘릴 수 있었기 때문에 현재 상황에서 선택할 수 있는 최선이었습니다. UltraWarm이 뭔지에 대해서는 참고문서의 링크를 확인해보세요. 참고로 ES 버전 6.8이상에서만 사용가능합니다.

 

UltraWarm 으로 마이그레이션 할 때는 아래 명령어들을 참고하면 됩니다.

 

// 마이그레이션 작업 요청
POST _ultrawarm/migration/index-2020-02-09/_warm

 

// 마이그레이션 작업 중인 인덱스의 상태 확인
GET _ultrawarm/migration/index-2020-02-09/_status

-> 마이그레이션 작업중이 아닌경우 즉, 끝났거나 시작도 안한 상태면 오류메시지가 출력됩니다.

 

// 인덱스 패턴 목록 조회
GET _cat/indices/index-2020-02-0*?v&s=index


-> 이 명령은 마이그레이션 작업 진행 중에 인덱스의 사이즈가 어떻게 변하는지 확인하기 위해 사용했었다. 마이그레이션 작업이 시작되면 마이그레이션하려는 인덱스의 원래 사이즈보다 2배까지 hot data node의 용량을 차지하게된다. 여유분의 사이즈가 없다면 마이그레이션 작업 자체가 시작되지 않는다. (free size를 검사한 뒤 시작하도록 되어있다)

 

// 마이그레이션이 완료된 인덱스 상태 조회
GET index-2020-02-09/_settings


-> Ultrawarm 노드로 마이그레이션이 완료되었다면 위 명령어로 조회 시 box_type이 warm 으로 바뀌어 있는 것을 확인할 수 있다.

 

 

참고문서

https://www.elastic.co/guide/en/elasticsearch/plugins/current/repository-s3-repository.html

https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-managedomains-snapshots.html#es-managedomains-snapshot-prerequisites

https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-java

https://aws.amazon.com/ko/blogs/database/use-amazon-s3-to-store-a-single-amazon-elasticsearch-service-index/

https://www.elastic.co/guide/en/elasticsearch/reference/7.1/modules-snapshots.html

AWS 큐레이터 이용하기

AWS ES Ultrawarm

 

 

💻 Programming

[Kotlin/JVM] 코틀린 기본문법 (Basic Syntax)

예전에 취미로 안드로이드 공부해서 자바로 어플하나 만들어 올린 적이 있었다.

너무 업데이트를 안해서 구글정책에 위배되어 해당 어플이 내려가 있었는데 그것도 모르고 놔두고 있었다.

최근에 수정해서 다시 올리는 작업을 했는데, 추가로 개발하는 1인 프로젝트들은 코틀린으로 해보고 싶은 마음이 생겼다.

아직 안드로이드 개발의 메인언어는 자바인 것 같긴 한데, 코틀린은 IntelliJ IDE를 개발한 JetBrain사에서 자바의 단점을 보완하기 위해서 개발한 언어로 자바처럼 JVM위에서 돌아갈 수 있고, 안드로이드, 자바스크립트와도 쉽게 연동할 수 있고 네이티브 바이너리를 만들 수도 있도록 개발되었다고 한다. (더 자세한 정보는 코틀린 공식사이트 FAQ에서 확인할 수 있다.)

 

나는 우선 자바 개발자이기 때문에 Kotlin/JVM 으로 코틀린을 연습해보려 한다. (Kotlin/JS를 이용하면 자바스크립트로 개발도 가능하다)

코틀린 프로젝트를 만들어서 Hello World 를 출력하는 예제는 코틀린 공식사이트에서 확인하기 바란다.

 

그 첫 번째 시간으로 코틀린 파일의 기본 구조가 어떻게 생겼는지, 그리고 변수 정의는 어떻게 하는지 자바와 어떻게 다른지 살펴보았다.

 

1. 코틀린 파일 구조

코틀린 헬로월드

- 코틀린 파일은 .kt 확장자를 갖는다.

- 자바는 class 블럭 내에 main메서드가 존재하지만 코틀린의 메인함수는 클래스 블럭내에 존재하는 것이 아니다.

- package 선언부 및 import 문은 자바와 동일하다.

- 코틀린에서 함수는 function의 축약어인 fun 으로 시작하고 리턴타입은 함수명() 뒤쪽에 위치한다.

- 코틀린에서는 statement를 세미콜론으로 종료하지 않는다.

2. 변수의 타입, 정의 및 초기화

코틀린에서 변수의 타입은 자바의 그것과 동일하지만 정의하는 방법이 다르다.

데이터 타입에 관계없이 상수는 val, 변수는 var로 선언한다.

데이터 타입을 명시해주지 않아도 할당해주는 값에 따라 자동으로 결정된다.

코틀린에서는 val을 read-only variable로 설명을 한다.

일반적인 변수는 var를 이용하고 read-only 변수는 val을 사용한다

val은 자바의 final과 같기 때문에 다른 값을 재할당 하려할 경우 컴파일 오류가 발생한다.

val 키워드로 선언된 변수에 다른 값을 재할당 하려할 경우 컴파일 오류 발생

var 키워드로 선언한 변수의 경우 타입이 한번 정해지면 다른 타입을 할당할 수 없다.

타입이 정해진(Int) var 키워드로 정의한 변수에 다른 타입(String)의 값 재할당 시도 시 컴파일 오류 발생

 

2-1. 변수 정의

코틀린에서 변수의 정의(define)은 아래와 같이 한다. 초기화를 하지 않고 정의만 할 경우 타입을 필수로 명시해줘야 한다.

    fun main() {
        var i:Int
        var s:String
        var l:Long
        var f:Float
        var d:Double
    }

 

2-2. 변수 초기화

변수의 초기화는 각 변수의 타입에 맞는 값을 할당해주면 된다.

    fun main() {
        var i:Int
        var s:String
        var l:Long
        var f:Float
        var d:Double

        i = 10
        s = "string"
        l = 10L
        f = 10.2F
        d = 10.5

        var v = "string variable"	// String 타입 변수
        var n = 123		// Int 타입 변수
    }

 

 

3. 함수 정의

두 개의 Int타입 파라미터를 전달받아 Int 타입을 반환하는 함수

    fun sum(a: Int, b: Int): Int { return a + b}

 

직접 값을 할당해주는 함수 (위 함수와 동일한 함수이다)

    fun sum(a: Int, b: Int) = a + b

 

리턴값이 없는 함수는 Unit (자바의 void) 으로 정의

    fun printSum(a: Int, b: Int): Unit { println("sum of $a and $b is ${a + b}")}

 

Unit 리턴타입은 안써줘도 된다.

    fun printSum(a: Int, b: Int) { println("sum of $a and $b is ${a + b}")}

 

4. 스트링 템플릿

    var a = 1
    // simple name in template:
    val s1 = "a is $a" 

    a = 2
    // arbitrary expression in template:
    val s2 = "${s1.replace("is", "was")}, but now is $a"

자바에서 String 클래스가 갖고있는 format 메서드와 유사한 기능을 하는 것으로 String의 내용이 가변적일 때 사용할 수 있다.

 

5. 조건문

조건문 사용법은 자바와 다를게 없다.

    fun maxOf(a: Int, b: Int): Int {
        if (a > b) {
            return a
        } else {
            return b
        }
    }

하지만 코틀린에서는 if문을 하나의 표현식으로 사용이 가능하다는 점이 다르다.

    fun maxOf(a: Int, b: Int) = if (a > b) a else b

if문의 결과값이 maxOf함수의 리턴값이라고 정의한 것으로, 바로 위에 maxOf함수와 동일한 함수라고 볼 수 있다.

 

6. Null

코틀린에서 반환값이 nullable인 경우 아래와 같이 표현할 수 있다.

    fun parseInt(str: String): Int? {
        // 리턴타입이 기본적으로 Int이나 null일 수도 있는 함수
    }

그리고 위 함수를 사용하는 경우를 보면.

    fun printProduct(arg1: String, arg2: String) {
        val x = parseInt(arg1)
        val y = parseInt(arg2)

        // 여기서 `x * y` 연산을 바로 해버리면 x또는 y가 null일 경우 에러가 발생할 수 있다.
        // 따라서 null 체크를 먼저 해야한다.
        if (x != null && y != null) {
            // null 체크가 완료되면 x 와 y 는 자동으로 non-nullable로 캐스팅 된다 
            // (코틀린 공식 문서에 이렇게 나와있는데 non-nullable로 캐스팅 된다라는게 아직 잘 이해는 안된다. 그런 타입이 있다는건지..)
            println(x * y)
        }
        else {
            println("'$arg1' or '$arg2' is not a number")
        }    
    }

이렇게 null 체크를 하고나면 non-nullable로 캐스팅이 된다고 한다.

그리고 이는 일반 타입에서도 마찬가지이다.

 

7. Type Check and Automatic Casts

    fun getStringLength(obj: Any): Int? {
        if (obj is String) {
            // 이 블럭 내에서 `obj`는 자동으로 String 타입으로 변환되며 String에서 제공하는 기능사용이 가능
            return obj.length
        }

        // `obj`는 타입 체크 블럭 밖에서는 여전히 어떤 타입도 될 수 있는 상태이다
        return null
    }

타입 체크 시 반대로(not을 이용) 할 경우 

    fun getStringLength(obj: Any): Int? {
        if (obj !is String) return null

        // 위 if문에서 `obj`가 String이 아니면 null을 리턴하기 때문에, 여기로 내려왔다면 무조건 String일 수 밖에 없다.
        // 따라서 자동으로 String 타입으로 변환되며, String에서 제공하는 기능사용이 가능하다.
        return obj.length
    }

또는 이렇게도 사용가능하다.

    fun getStringLength(obj: Any): Int? {
        // `&&`연산의 오른쪽에서는 obj가 String이라는 것을 확인한 뒤이므로,
        // String 으로 자동 형변환되어 String 에서 제공하는 기능사용이 가능하다
        if (obj is String && obj.length > 0) {
            return obj.length
        }

        return null
    }

 

8. For-Loop, While-Loop

loop는 어느 프로그래밍 언어나 다 비슷하다. 코틀린에서도 자바의 foreach문 처럼 for-loop를 사용할 수 있다.

    val items = listOf("apple", "banana", "kiwifruit")
    for (item in items) {
        println(item)
    }

인덱스 정보를 이용해야 경우 아래처럼 사용가능하다.

    val items = listOf("apple", "banana", "kiwifruit")
    for (index in items.indices) {
        println("item at $index is ${items[index]}")
    }

while-loop는 아래처럼 사용할 수 있다.

    val items = listOf("apple", "banana", "kiwifruit")
    var index = 0
    while (index < items.size) {
        println("item at $index is ${items[index]}")
        index++
    }

 

9. when 표현식

when expression은 자바의 switch-case문과 유사한데, 함수형 프로그래밍에 맞게 변형시켜놓은 것 같다

    fun describe(obj: Any): String =
        when (obj) {
            1          -> "One"
            "Hello"    -> "Greeting"
            is Long    -> "Long"
            !is String -> "Not a string"
            else       -> "Unknown"
        }

 

10. Range (범위)

자바에서는 loop를 돌릴 때 인덱스를 이용해서 아래처럼 많이 사용한다

    for (int i = 0; i < x; i++) {
        // ...
    }

그리고 이를 코틀린으로 옮기면 아래와 같다

    for (z in 1..10) {
        println(z)
    }

그럼 만약 매 loop시마다 i값의 증분을 i++가 아니라 i=i+2 처럼 써야한다면??? 어떻게 쓸 수 있을까?

    for (x in 1..10 step 2) {
    	// 1부터 10까지 2씩 증가시키면서 출력
        // 결과: 1~10 중에서 홀수 출력 => 13579
        print(x)
    }
    println()
    for (x in 9 downTo 0 step 3) {
    	// 큰 숫자에서 작은 숫자로 내려가면서 loop를 도는 케이스에서는 downTo 키워드를 사용한다
        // 결과: 9부터 시작해서 3씩 내려가면서 x가 0보다 크거나 같을 때 까지 숫자 출력 => 9630
        print(x)
    }

이렇게 뒤에 step 키워드를 이용하여 얼마씩 증가시킬 것인지를 써주면 된다.

 

또한 인덱스가 특정 범위를 벗어났는지는 아래와 같이 ! 연산자를 in과 함께 사용하여 표현할 수 있다

    val list = listOf("a", "b", "c")

    if (-1 !in 0..list.lastIndex) {
        println("-1 is out of range")
    }
    if (list.size !in list.indices) {
        println("list size is out of valid list indices range, too")
    }

 

 

 

 

referene: 코틀린 공식문서 - 기본문법

 

Basic Syntax - Kotlin Programming Language

 

kotlinlang.org

 

 

자바 스트림을 이용한 유용한 변환 방법

해당 게시글은 java의 stream을 이용하여 데이터를 쉽게 가공할 수 있는 방법을 정리 해놓은 문서로

유용한 케이스를 발견할 때마다 계속 업데이트할 예정입니다.

 

 

1. String배열을 int배열로 변환하기

int[] intArray = Stream.of(stringArray).mapToInt(Integer::parseInt).toArray();

2. Integer배열을 String배열로 변환하기

String[] stringArray = Stream.of(arr).map(String::valueOf).toArray(String[]::new);

3. String배열을 String리스트로 변환하기

String[] stringArray = new String[]{"!", "@", "#"};
List<String> stringList = Stream.of(stringArray).collect(Collectors.toList());

4. String리스트를 String배열로 변환하기

List<String> stringList = new ArrayList<>(Arrays.asList("!","@","#"));
String[] stringArray = stringList.toArray(new String[0]);

5. stream().map()을 이용한 객체(object) 변환하기

// 변환 전 클래스
@Getter
class Soldier {
  String name;
  int age;

  public Soldier(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 변환 대상 클래스
@Getter
class King {
  String name;
  int age;
  public King(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 샘플 Soldier stream 생성
Stream<Soldier> s = Stream.of(new Soldier("A", 18), new Soldier("B", 22), new Soldier("C", 19));

// map()을 이용하여 변환하기
List<King> k = s.map(soldier -> new King(soldier.getName(), soldier.getAge()))
		.collect(Collectors.toList());

 

6. filter()를 이용하여 필터링하기

// 변환 전 클래스
@Getter
class Soldier {
  String name;
  int age;

  public Soldier(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 변환 대상 클래스
@Getter
class King {
  String name;
  int age;
  public King(String name, int age) {
    this.name = name;
    this.age = age;
  }
}

// 샘플 Soldier stream 생성
Stream<Soldier> s = Stream.of(new Soldier("Arthur", 18), new Soldier("Belisarius", 22), new Soldier("Caesar", 19));

// filter()를 이용하여 Arthur만 골라서 map()을 이용하여 King으로 변환하기
List<King> k = s.filter(soldier -> "Arthur".equals(soldier.getName()))
    .map(soldier -> new King(soldier.getName(), soldier.getAge()))
    .collect(Collectors.toList());

 

7. min() / max() 를 이용하여 comparator 기준 최소 / 최대 값을 갖는 객체 추출하기

    @Getter
    @Setter
    class Datapoint {
        private Double maximum;
    }
    
    public void sortStream() {
        List<Datapoint> datapoints = new ArrayList<>();
        int max = 10;
        datapoints.add(new Datapoint().withMaximum(Math.random() * max));
        datapoints.add(new Datapoint().withMaximum(Math.random() * max));
        datapoints.add(new Datapoint().withMaximum(Math.random() * max));
        log.debug("data points: {}", datapoints);

        // 객체 목록 중 특정 필드(maximum)값을 기준으로 최대값을 갖고 있는 객체 추출 (stream.max() 이용)
        Datapoint datapointMax = datapoints.stream()
                .max(Comparator.comparing(Datapoint::getMaximum))
                .orElse(new Datapoint().withMaximum(0D));
        log.debug("data point MAX: {}", datapointMax);

        // 객체 목록 중 특정 필드(maximum)값을 기준으로 최소값을 갖고 있는 객체 추출 (stream.min() 이용)
        Datapoint datapointMin = datapoints.stream()
                .min(Comparator.comparing(Datapoint::getMaximum))
                .orElse(new Datapoint().withMaximum(0D));
        log.debug("data point MIN: {}", datapointMin);
    }
    
    
    
    // 실행 로그
    data points: [{Maximum: 5.471390124016474,}, {Maximum: 2.7559360370945916,}, {Maximum: 0.5778257233234019,}]
    data point MAX: {Maximum: 5.471390124016474,}
    data point MIN: {Maximum: 0.5778257233234019,}

 

§ 쓰레드 기본 내용에 대해서는 [Java] 멀티쓰레드를 참고하세요.

 

ThreadPoolExecutor 를 이용하여 멀티쓰레드 구현하기

java.util.concurrent 패키지는 동시성 프로그래밍 관련하여 유용한 기능들을 모아둔 패키지입니다.

여기에는 ThreadPoolExecutor가 있는데 이것을 이용하면 손쉽게 멀티쓰레드를 구현할 수 있습니다.

우선 생성자에 어떤 것들이 있는지 확인해보겠습니다.

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)


기본적으로 corePoolSize, maximumPoolSize, keepAliveTime, timeUnit 그리고 workQueue를 입력받고 있네요.

각 항목이 의미하는 바가 뭔지 알아보겠습니다. java doc에는 아래와 같이 설명이 나와있습니다.

* @param corePoolSize the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set

* @param maximumPoolSize the maximum number of threads to allow in the pool

* @param keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.

* @param unit the time unit for the {@code keepAliveTime} argument

* @param workQueue the queue to use for holding tasks before they are executed.


간단히 말하면 corePoolSize는 쓰레드 풀의 기본 사이즈로 몇 개의 쓰레드를 pool에 가지고 있을지에 대한 값이고, maximumPoolSize는 쓰레드 풀의 max size로 이 사이즈를 넘어가면 RejectedExecutionException이 발생하게 됩니다. keepAliveTime은 idle 쓰레드의 keep alive time이며, timeUnit은 keepAliveTime의 시간단위를, 그리고 workQueue는 corePoolSize를 넘어서는 쓰레드들을 queueing 처리하기 위해 사용됩니다.

 

그럼 이제 간단한 예제 코드를 한번 보도록 하겠습니다.

    public static void main(String[] args) throws InterruptedException {

        BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(1);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100, 100, 10, TimeUnit.SECONDS, blockingQueue);

        Runnable task = new Task();
        for (int i = 0; i < 100; i++) {
            threadPoolExecutor.execute(task);
        }
        System.out.println("쓰레드 콜 종료");
    }
    
    static class Task implements Runnable {

        int num = 0;

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ", num=" + num++);
            }
        }
    }

 

위 코드에서 ThreadPoolExecutor의 corePoolSize와 maximumPoolSize를 각각 100, 100 으로 설정을 해주었습니다.

위 코드를 실행하면 100개의 쓰레드가 각각 task를 실행하면서 아래와 같이 출력을 하게 됩니다. (더보기 클릭해서 보세요)

더보기

pool-1-thread-3, num=1
pool-1-thread-3, num=4
pool-1-thread-4, num=3
pool-1-thread-2, num=0
pool-1-thread-2, num=8
pool-1-thread-2, num=9
pool-1-thread-1, num=2
pool-1-thread-6, num=11
pool-1-thread-6, num=13
pool-1-thread-6, num=14
pool-1-thread-7, num=15
pool-1-thread-7, num=17
pool-1-thread-7, num=18
pool-1-thread-7, num=19
pool-1-thread-7, num=20
pool-1-thread-7, num=21
pool-1-thread-2, num=10
pool-1-thread-2, num=24
pool-1-thread-2, num=25
pool-1-thread-2, num=26
pool-1-thread-2, num=27
pool-1-thread-2, num=28
pool-1-thread-2, num=29
pool-1-thread-4, num=7
pool-1-thread-4, num=31
pool-1-thread-4, num=32
pool-1-thread-4, num=33
pool-1-thread-4, num=34
pool-1-thread-4, num=35
pool-1-thread-4, num=36
pool-1-thread-4, num=37
pool-1-thread-4, num=38
pool-1-thread-3, num=6
pool-1-thread-3, num=39
pool-1-thread-3, num=40
pool-1-thread-3, num=41
pool-1-thread-3, num=42
pool-1-thread-3, num=43
pool-1-thread-3, num=44
pool-1-thread-3, num=45
pool-1-thread-10, num=46
pool-1-thread-10, num=47
pool-1-thread-5, num=5
pool-1-thread-5, num=49
pool-1-thread-11, num=50
pool-1-thread-11, num=52
pool-1-thread-11, num=53
pool-1-thread-11, num=54
pool-1-thread-11, num=56
pool-1-thread-11, num=57
pool-1-thread-11, num=58
pool-1-thread-11, num=59
pool-1-thread-11, num=60
pool-1-thread-11, num=61
pool-1-thread-13, num=62
pool-1-thread-13, num=63
pool-1-thread-13, num=64
pool-1-thread-14, num=65
pool-1-thread-14, num=67
pool-1-thread-10, num=48
pool-1-thread-15, num=69
pool-1-thread-15, num=71
pool-1-thread-15, num=72
pool-1-thread-15, num=73
pool-1-thread-15, num=74
pool-1-thread-15, num=75
pool-1-thread-15, num=76
pool-1-thread-9, num=30
pool-1-thread-9, num=79
pool-1-thread-9, num=80
pool-1-thread-9, num=81
pool-1-thread-9, num=82
pool-1-thread-9, num=83
pool-1-thread-9, num=84
pool-1-thread-9, num=85
pool-1-thread-9, num=86
pool-1-thread-9, num=87
pool-1-thread-7, num=23
pool-1-thread-7, num=89
pool-1-thread-7, num=90
pool-1-thread-7, num=91
pool-1-thread-8, num=22
pool-1-thread-8, num=92
pool-1-thread-8, num=93
pool-1-thread-8, num=94
pool-1-thread-8, num=96
pool-1-thread-8, num=97
pool-1-thread-8, num=98
pool-1-thread-8, num=99
pool-1-thread-8, num=100
pool-1-thread-6, num=16
pool-1-thread-1, num=12
pool-1-thread-1, num=104
pool-1-thread-1, num=105
pool-1-thread-1, num=106
pool-1-thread-1, num=108
pool-1-thread-1, num=109
pool-1-thread-1, num=110
pool-1-thread-1, num=111
pool-1-thread-6, num=103
pool-1-thread-6, num=113
pool-1-thread-6, num=114
pool-1-thread-6, num=115
pool-1-thread-6, num=116
pool-1-thread-6, num=117
pool-1-thread-8, num=102
pool-1-thread-19, num=101
pool-1-thread-19, num=120
pool-1-thread-18, num=95
pool-1-thread-18, num=123
pool-1-thread-18, num=124
pool-1-thread-18, num=125
pool-1-thread-18, num=126
pool-1-thread-18, num=127
pool-1-thread-18, num=128
pool-1-thread-18, num=129
pool-1-thread-18, num=130
pool-1-thread-18, num=131
pool-1-thread-17, num=88
pool-1-thread-25, num=133
pool-1-thread-25, num=135
pool-1-thread-25, num=136
pool-1-thread-25, num=137
pool-1-thread-26, num=138
pool-1-thread-26, num=140
pool-1-thread-26, num=141
pool-1-thread-15, num=78
pool-1-thread-15, num=143
pool-1-thread-15, num=144
pool-1-thread-27, num=145
pool-1-thread-27, num=146
pool-1-thread-16, num=77
pool-1-thread-10, num=70
pool-1-thread-10, num=150
pool-1-thread-10, num=151
pool-1-thread-10, num=152
pool-1-thread-10, num=154
pool-1-thread-10, num=155
pool-1-thread-10, num=156
pool-1-thread-14, num=68
pool-1-thread-14, num=157
pool-1-thread-14, num=159
pool-1-thread-14, num=160
pool-1-thread-14, num=161
pool-1-thread-14, num=162
pool-1-thread-13, num=66
pool-1-thread-13, num=164
pool-1-thread-13, num=165
pool-1-thread-32, num=166
pool-1-thread-12, num=55
pool-1-thread-33, num=169
pool-1-thread-5, num=51
pool-1-thread-5, num=172
pool-1-thread-5, num=174
pool-1-thread-5, num=175
pool-1-thread-5, num=176
pool-1-thread-5, num=177
pool-1-thread-5, num=179
pool-1-thread-5, num=180
pool-1-thread-33, num=171
pool-1-thread-33, num=181
pool-1-thread-33, num=182
pool-1-thread-33, num=183
pool-1-thread-12, num=170
pool-1-thread-12, num=185
pool-1-thread-12, num=186
pool-1-thread-12, num=188
pool-1-thread-12, num=189
pool-1-thread-12, num=190
pool-1-thread-12, num=191
pool-1-thread-12, num=192
pool-1-thread-12, num=194
pool-1-thread-38, num=195
pool-1-thread-32, num=168
pool-1-thread-32, num=197
pool-1-thread-32, num=198
pool-1-thread-32, num=199
pool-1-thread-32, num=201
pool-1-thread-32, num=202
pool-1-thread-32, num=203
pool-1-thread-13, num=167
pool-1-thread-40, num=205
pool-1-thread-40, num=207
pool-1-thread-40, num=208
pool-1-thread-40, num=209
pool-1-thread-40, num=210
pool-1-thread-40, num=212
pool-1-thread-40, num=213
pool-1-thread-40, num=214
pool-1-thread-14, num=163
pool-1-thread-14, num=217
pool-1-thread-43, num=218
pool-1-thread-43, num=219
pool-1-thread-43, num=220
pool-1-thread-43, num=221
pool-1-thread-43, num=222
pool-1-thread-31, num=162
pool-1-thread-30, num=158
pool-1-thread-30, num=226
pool-1-thread-30, num=227
pool-1-thread-30, num=228
pool-1-thread-30, num=229
pool-1-thread-45, num=230
pool-1-thread-45, num=232
pool-1-thread-45, num=233
pool-1-thread-45, num=234
pool-1-thread-45, num=235
pool-1-thread-45, num=236
pool-1-thread-45, num=237
pool-1-thread-45, num=239
pool-1-thread-45, num=240
pool-1-thread-29, num=153
pool-1-thread-29, num=242
pool-1-thread-29, num=243
pool-1-thread-29, num=244
pool-1-thread-29, num=245
pool-1-thread-29, num=247
pool-1-thread-29, num=248
pool-1-thread-29, num=249
pool-1-thread-29, num=250
pool-1-thread-29, num=251
pool-1-thread-16, num=149
pool-1-thread-16, num=252
pool-1-thread-16, num=253
pool-1-thread-16, num=254
pool-1-thread-16, num=255
pool-1-thread-16, num=256
pool-1-thread-16, num=257
pool-1-thread-16, num=258
pool-1-thread-16, num=259
pool-1-thread-48, num=260
pool-1-thread-27, num=148
pool-1-thread-27, num=262
pool-1-thread-27, num=263
pool-1-thread-27, num=264
pool-1-thread-27, num=265
pool-1-thread-27, num=266
pool-1-thread-27, num=267
pool-1-thread-27, num=268
pool-1-thread-28, num=147
pool-1-thread-28, num=269
pool-1-thread-28, num=270
pool-1-thread-28, num=271
pool-1-thread-28, num=272
pool-1-thread-28, num=273
pool-1-thread-28, num=274
pool-1-thread-28, num=275
pool-1-thread-28, num=276
pool-1-thread-28, num=278
pool-1-thread-26, num=142
pool-1-thread-26, num=279
pool-1-thread-26, num=280
pool-1-thread-26, num=281
pool-1-thread-26, num=282
pool-1-thread-25, num=139
pool-1-thread-25, num=284
pool-1-thread-25, num=286
pool-1-thread-25, num=287
pool-1-thread-25, num=288
pool-1-thread-25, num=290
pool-1-thread-17, num=134
pool-1-thread-52, num=291
pool-1-thread-52, num=293
pool-1-thread-24, num=132
pool-1-thread-53, num=295
pool-1-thread-53, num=297
pool-1-thread-53, num=298
pool-1-thread-53, num=299
pool-1-thread-53, num=300
pool-1-thread-53, num=302
pool-1-thread-53, num=303
pool-1-thread-53, num=304
pool-1-thread-53, num=305
pool-1-thread-53, num=306
pool-1-thread-55, num=307
pool-1-thread-55, num=308
pool-1-thread-55, num=309
pool-1-thread-55, num=310
pool-1-thread-55, num=311
pool-1-thread-55, num=312
pool-1-thread-55, num=313
pool-1-thread-55, num=314
pool-1-thread-55, num=315
pool-1-thread-55, num=316
pool-1-thread-23, num=122
pool-1-thread-56, num=317
pool-1-thread-56, num=318
pool-1-thread-56, num=319
pool-1-thread-56, num=320
pool-1-thread-56, num=321
pool-1-thread-23, num=322
pool-1-thread-23, num=324
pool-1-thread-23, num=325
pool-1-thread-23, num=326
pool-1-thread-19, num=121
pool-1-thread-19, num=328
pool-1-thread-19, num=329
pool-1-thread-19, num=330
pool-1-thread-19, num=332
pool-1-thread-19, num=333
pool-1-thread-19, num=334
pool-1-thread-19, num=335
pool-1-thread-22, num=119
pool-1-thread-22, num=336
pool-1-thread-22, num=337
pool-1-thread-21, num=118
pool-1-thread-21, num=340
pool-1-thread-21, num=341
pool-1-thread-21, num=342
pool-1-thread-21, num=343
pool-1-thread-1, num=112
pool-1-thread-59, num=345
pool-1-thread-20, num=107
pool-1-thread-59, num=346
pool-1-thread-59, num=348
pool-1-thread-59, num=349
pool-1-thread-59, num=350
pool-1-thread-21, num=344
pool-1-thread-21, num=353
pool-1-thread-21, num=354
pool-1-thread-21, num=355
pool-1-thread-21, num=356
pool-1-thread-22, num=339
pool-1-thread-22, num=358
pool-1-thread-58, num=338
pool-1-thread-58, num=360
pool-1-thread-58, num=361
pool-1-thread-58, num=362
pool-1-thread-58, num=363
pool-1-thread-58, num=364
pool-1-thread-62, num=365
pool-1-thread-57, num=331
pool-1-thread-57, num=368
pool-1-thread-57, num=369
pool-1-thread-57, num=371
pool-1-thread-57, num=372
pool-1-thread-57, num=373
pool-1-thread-57, num=375
pool-1-thread-57, num=376
pool-1-thread-57, num=377
pool-1-thread-57, num=378
pool-1-thread-23, num=327
pool-1-thread-65, num=379
pool-1-thread-65, num=380
pool-1-thread-65, num=382
pool-1-thread-65, num=383
pool-1-thread-65, num=384
pool-1-thread-56, num=323
pool-1-thread-56, num=387
pool-1-thread-56, num=388
pool-1-thread-56, num=389
pool-1-thread-56, num=390
pool-1-thread-54, num=301
pool-1-thread-54, num=391
pool-1-thread-54, num=392
pool-1-thread-54, num=394
pool-1-thread-54, num=395
pool-1-thread-54, num=396
pool-1-thread-54, num=397
pool-1-thread-54, num=398
pool-1-thread-54, num=399
pool-1-thread-54, num=401
pool-1-thread-24, num=296
pool-1-thread-24, num=403
pool-1-thread-52, num=294
pool-1-thread-52, num=406
pool-1-thread-52, num=407
pool-1-thread-52, num=408
pool-1-thread-52, num=410
pool-1-thread-52, num=411
pool-1-thread-52, num=412
pool-1-thread-52, num=413
pool-1-thread-17, num=292
pool-1-thread-17, num=415
pool-1-thread-17, num=416
pool-1-thread-17, num=418
pool-1-thread-74, num=419
pool-1-thread-50, num=289
pool-1-thread-50, num=422
pool-1-thread-75, num=423
pool-1-thread-75, num=425
pool-1-thread-75, num=426
pool-1-thread-51, num=285
pool-1-thread-51, num=428
pool-1-thread-51, num=431
pool-1-thread-51, num=432
pool-1-thread-77, num=430
pool-1-thread-77, num=434
pool-1-thread-77, num=435
pool-1-thread-77, num=436
pool-1-thread-77, num=437
pool-1-thread-77, num=439
pool-1-thread-77, num=440
pool-1-thread-26, num=283
pool-1-thread-79, num=442
pool-1-thread-79, num=444
pool-1-thread-49, num=277
pool-1-thread-49, num=446
pool-1-thread-49, num=447
pool-1-thread-49, num=448
pool-1-thread-49, num=449
pool-1-thread-49, num=450
pool-1-thread-49, num=451
pool-1-thread-49, num=452
pool-1-thread-48, num=261
pool-1-thread-47, num=246
pool-1-thread-47, num=456
pool-1-thread-47, num=457
pool-1-thread-47, num=459
pool-1-thread-82, num=460
pool-1-thread-45, num=241
pool-1-thread-83, num=463
pool-1-thread-83, num=464
pool-1-thread-46, num=238
pool-1-thread-83, num=465
pool-1-thread-83, num=466
pool-1-thread-83, num=467
pool-1-thread-83, num=469
pool-1-thread-83, num=471
pool-1-thread-83, num=472
pool-1-thread-83, num=473
pool-1-thread-83, num=474
pool-1-thread-30, num=231
pool-1-thread-30, num=475
pool-1-thread-30, num=476
pool-1-thread-30, num=477
pool-1-thread-30, num=478
pool-1-thread-31, num=225
pool-1-thread-43, num=224
pool-1-thread-43, num=481
pool-1-thread-43, num=482
pool-1-thread-43, num=483
pool-1-thread-43, num=484
pool-1-thread-86, num=485
pool-1-thread-86, num=486
pool-1-thread-86, num=487
pool-1-thread-86, num=488
pool-1-thread-86, num=489
pool-1-thread-86, num=490
pool-1-thread-86, num=491
pool-1-thread-86, num=492
pool-1-thread-86, num=493
pool-1-thread-86, num=494
pool-1-thread-44, num=223
pool-1-thread-44, num=495
pool-1-thread-44, num=496
pool-1-thread-44, num=497
pool-1-thread-44, num=498
pool-1-thread-44, num=499
pool-1-thread-44, num=500
pool-1-thread-44, num=501
pool-1-thread-44, num=502
pool-1-thread-44, num=503
pool-1-thread-42, num=216
pool-1-thread-42, num=504
pool-1-thread-42, num=505
pool-1-thread-42, num=506
pool-1-thread-42, num=507
pool-1-thread-42, num=508
pool-1-thread-42, num=509
pool-1-thread-42, num=510
pool-1-thread-42, num=511
pool-1-thread-42, num=512
pool-1-thread-40, num=215
pool-1-thread-40, num=514
pool-1-thread-41, num=211
pool-1-thread-41, num=515
pool-1-thread-41, num=516
pool-1-thread-41, num=517
pool-1-thread-41, num=519
pool-1-thread-41, num=520
pool-1-thread-41, num=521
pool-1-thread-41, num=522
pool-1-thread-41, num=523
pool-1-thread-41, num=524
pool-1-thread-13, num=206
pool-1-thread-13, num=525
pool-1-thread-13, num=526
pool-1-thread-32, num=204
pool-1-thread-32, num=527
pool-1-thread-89, num=528
pool-1-thread-89, num=529
pool-1-thread-89, num=530
pool-1-thread-89, num=531
pool-1-thread-89, num=532
pool-1-thread-89, num=533
pool-1-thread-89, num=534
pool-1-thread-89, num=535
pool-1-thread-89, num=536
pool-1-thread-89, num=537
pool-1-thread-39, num=200
pool-1-thread-39, num=538
pool-1-thread-39, num=539
pool-1-thread-39, num=540
pool-1-thread-39, num=541
pool-1-thread-39, num=542
pool-1-thread-39, num=543
pool-1-thread-39, num=544
pool-1-thread-39, num=545
pool-1-thread-39, num=546
pool-1-thread-38, num=196
pool-1-thread-38, num=547
pool-1-thread-38, num=548
pool-1-thread-38, num=549
pool-1-thread-38, num=550
pool-1-thread-38, num=551
pool-1-thread-38, num=552
pool-1-thread-90, num=553
pool-1-thread-90, num=555
pool-1-thread-90, num=556
pool-1-thread-90, num=557
pool-1-thread-90, num=558
pool-1-thread-90, num=559
pool-1-thread-90, num=560
pool-1-thread-90, num=561
pool-1-thread-90, num=562
pool-1-thread-90, num=563
pool-1-thread-37, num=193
pool-1-thread-37, num=564
pool-1-thread-37, num=565
pool-1-thread-37, num=566
pool-1-thread-37, num=567
pool-1-thread-37, num=568
pool-1-thread-37, num=569
pool-1-thread-37, num=570
pool-1-thread-37, num=571
pool-1-thread-37, num=572
pool-1-thread-36, num=187
pool-1-thread-36, num=573
pool-1-thread-36, num=574
pool-1-thread-36, num=575
pool-1-thread-36, num=576
pool-1-thread-36, num=577
pool-1-thread-36, num=578
pool-1-thread-36, num=579
pool-1-thread-36, num=580
pool-1-thread-36, num=581
pool-1-thread-91, num=582
pool-1-thread-91, num=583
pool-1-thread-91, num=584
pool-1-thread-91, num=585
pool-1-thread-91, num=586
pool-1-thread-91, num=587
pool-1-thread-91, num=588
pool-1-thread-91, num=589
pool-1-thread-91, num=590
pool-1-thread-91, num=591
pool-1-thread-33, num=184
pool-1-thread-33, num=592
pool-1-thread-33, num=593
pool-1-thread-33, num=594
pool-1-thread-33, num=595
pool-1-thread-35, num=178
pool-1-thread-35, num=596
pool-1-thread-35, num=597
pool-1-thread-35, num=598
pool-1-thread-35, num=599
pool-1-thread-35, num=600
pool-1-thread-35, num=601
pool-1-thread-35, num=602
pool-1-thread-35, num=603
pool-1-thread-35, num=604
pool-1-thread-92, num=605
pool-1-thread-92, num=606
pool-1-thread-92, num=607
pool-1-thread-92, num=608
pool-1-thread-92, num=609
pool-1-thread-92, num=610
pool-1-thread-92, num=611
pool-1-thread-92, num=612
pool-1-thread-92, num=613
pool-1-thread-92, num=614
pool-1-thread-34, num=173
pool-1-thread-34, num=615
pool-1-thread-34, num=616
pool-1-thread-34, num=617
pool-1-thread-34, num=618
pool-1-thread-34, num=619
pool-1-thread-34, num=620
pool-1-thread-34, num=621
pool-1-thread-34, num=622
pool-1-thread-34, num=623
pool-1-thread-38, num=554
pool-1-thread-38, num=624
pool-1-thread-88, num=518
pool-1-thread-88, num=625
pool-1-thread-88, num=626
pool-1-thread-88, num=627
pool-1-thread-88, num=628
pool-1-thread-88, num=629
pool-1-thread-88, num=630
pool-1-thread-88, num=631
pool-1-thread-88, num=632
pool-1-thread-88, num=633
pool-1-thread-93, num=634
pool-1-thread-93, num=635
pool-1-thread-93, num=636
pool-1-thread-93, num=637
pool-1-thread-93, num=638
pool-1-thread-93, num=639
pool-1-thread-93, num=640
pool-1-thread-93, num=641
pool-1-thread-93, num=642
pool-1-thread-93, num=643
pool-1-thread-87, num=513
pool-1-thread-87, num=644
pool-1-thread-87, num=645
pool-1-thread-87, num=646
pool-1-thread-87, num=647
pool-1-thread-87, num=648
pool-1-thread-87, num=649
pool-1-thread-87, num=650
pool-1-thread-87, num=651
pool-1-thread-87, num=652
pool-1-thread-31, num=480
pool-1-thread-31, num=653
pool-1-thread-31, num=654
pool-1-thread-31, num=655
pool-1-thread-31, num=656
pool-1-thread-31, num=657
pool-1-thread-31, num=658
pool-1-thread-31, num=659
pool-1-thread-94, num=660
pool-1-thread-94, num=661
pool-1-thread-94, num=662
pool-1-thread-94, num=663
pool-1-thread-94, num=664
pool-1-thread-94, num=665
pool-1-thread-94, num=666
pool-1-thread-94, num=667
pool-1-thread-94, num=668
pool-1-thread-94, num=669
pool-1-thread-85, num=479
pool-1-thread-85, num=670
pool-1-thread-85, num=671
pool-1-thread-85, num=672
pool-1-thread-85, num=673
pool-1-thread-85, num=674
pool-1-thread-95, num=675
pool-1-thread-95, num=676
pool-1-thread-95, num=677
pool-1-thread-95, num=678
pool-1-thread-95, num=679
pool-1-thread-95, num=680
pool-1-thread-95, num=681
pool-1-thread-95, num=682
pool-1-thread-95, num=683
pool-1-thread-95, num=684
pool-1-thread-46, num=470
pool-1-thread-46, num=686
pool-1-thread-46, num=687
pool-1-thread-46, num=688
pool-1-thread-46, num=689
pool-1-thread-46, num=690
pool-1-thread-46, num=691
pool-1-thread-46, num=692
pool-1-thread-46, num=693
pool-1-thread-84, num=468
pool-1-thread-84, num=694
pool-1-thread-84, num=695
pool-1-thread-84, num=696
pool-1-thread-84, num=697
pool-1-thread-84, num=698
pool-1-thread-84, num=699
pool-1-thread-84, num=700
pool-1-thread-82, num=462
pool-1-thread-82, num=703
pool-1-thread-47, num=461
pool-1-thread-47, num=705
pool-1-thread-47, num=706
pool-1-thread-47, num=707
pool-1-thread-47, num=708
pool-1-thread-47, num=709
pool-1-thread-81, num=458
pool-1-thread-81, num=710
pool-1-thread-81, num=712
pool-1-thread-81, num=713
pool-1-thread-81, num=714
pool-1-thread-81, num=715
pool-1-thread-81, num=716
pool-1-thread-48, num=455
pool-1-thread-49, num=454
pool-1-thread-49, num=720
pool-1-thread-80, num=453
pool-1-thread-80, num=721
pool-1-thread-80, num=722
pool-1-thread-80, num=723
pool-1-thread-80, num=724
pool-1-thread-80, num=725
pool-1-thread-80, num=726
pool-1-thread-80, num=727
pool-1-thread-80, num=728
pool-1-thread-79, num=445
pool-1-thread-79, num=731
pool-1-thread-26, num=443
pool-1-thread-77, num=441
pool-1-thread-77, num=733
pool-1-thread-77, num=734
pool-1-thread-78, num=438
pool-1-thread-78, num=735
pool-1-thread-78, num=737
pool-1-thread-78, num=738
pool-1-thread-51, num=433
pool-1-thread-51, num=740
pool-1-thread-51, num=741
pool-1-thread-51, num=742
pool-1-thread-51, num=743
pool-1-thread-51, num=744
pool-1-thread-76, num=429
pool-1-thread-76, num=745
pool-1-thread-76, num=746
pool-1-thread-76, num=747
pool-1-thread-75, num=427
pool-1-thread-75, num=749
pool-1-thread-75, num=750
pool-1-thread-75, num=751
pool-1-thread-75, num=752
pool-1-thread-75, num=753
pool-1-thread-50, num=424
pool-1-thread-74, num=421
pool-1-thread-17, num=420
pool-1-thread-17, num=757
pool-1-thread-17, num=758
pool-1-thread-17, num=759
pool-1-thread-73, num=417
pool-1-thread-73, num=760
pool-1-thread-73, num=761
pool-1-thread-73, num=762
pool-1-thread-73, num=763
pool-1-thread-73, num=764
pool-1-thread-73, num=765
pool-1-thread-73, num=766
pool-1-thread-73, num=767
pool-1-thread-73, num=768
pool-1-thread-72, num=414
pool-1-thread-72, num=769
pool-1-thread-72, num=770
pool-1-thread-72, num=771
pool-1-thread-72, num=772
pool-1-thread-72, num=773
pool-1-thread-72, num=774
pool-1-thread-72, num=775
pool-1-thread-72, num=776
pool-1-thread-72, num=777
pool-1-thread-71, num=409
pool-1-thread-71, num=778
pool-1-thread-71, num=779
pool-1-thread-71, num=780
pool-1-thread-24, num=405
pool-1-thread-24, num=782
pool-1-thread-24, num=783
pool-1-thread-24, num=784
pool-1-thread-24, num=785
pool-1-thread-24, num=786
pool-1-thread-24, num=787
pool-1-thread-70, num=404
pool-1-thread-70, num=788
pool-1-thread-70, num=789
pool-1-thread-70, num=790
pool-1-thread-70, num=791
pool-1-thread-70, num=792
pool-1-thread-70, num=793
pool-1-thread-70, num=794
pool-1-thread-70, num=795
pool-1-thread-69, num=402
pool-1-thread-69, num=797
pool-1-thread-68, num=400
pool-1-thread-68, num=799
pool-1-thread-68, num=800
pool-1-thread-68, num=801
pool-1-thread-68, num=802
pool-1-thread-68, num=803
pool-1-thread-68, num=804
pool-1-thread-67, num=393
pool-1-thread-67, num=806
pool-1-thread-67, num=807
pool-1-thread-67, num=808
pool-1-thread-65, num=386
pool-1-thread-67, num=809
pool-1-thread-67, num=810
pool-1-thread-67, num=811
pool-1-thread-67, num=812
pool-1-thread-67, num=813
pool-1-thread-67, num=814
pool-1-thread-65, num=815
pool-1-thread-65, num=816
pool-1-thread-65, num=817
pool-1-thread-65, num=818
pool-1-thread-66, num=385
pool-1-thread-23, num=381
pool-1-thread-66, num=819
pool-1-thread-66, num=820
pool-1-thread-66, num=821
pool-1-thread-66, num=822
pool-1-thread-66, num=823
pool-1-thread-66, num=824
pool-1-thread-66, num=825
pool-1-thread-66, num=826
pool-1-thread-66, num=827
pool-1-thread-23, num=828
pool-1-thread-23, num=829
pool-1-thread-23, num=830
pool-1-thread-64, num=374
pool-1-thread-63, num=370
pool-1-thread-64, num=831
pool-1-thread-64, num=832
pool-1-thread-64, num=833
pool-1-thread-64, num=834
pool-1-thread-64, num=835
pool-1-thread-64, num=836
pool-1-thread-64, num=837
pool-1-thread-64, num=838
pool-1-thread-64, num=839
pool-1-thread-63, num=840
pool-1-thread-63, num=841
pool-1-thread-63, num=842
pool-1-thread-63, num=843
pool-1-thread-63, num=844
pool-1-thread-63, num=845
pool-1-thread-63, num=846
pool-1-thread-63, num=847
pool-1-thread-63, num=848
pool-1-thread-62, num=367
pool-1-thread-58, num=366
pool-1-thread-62, num=849
pool-1-thread-62, num=850
pool-1-thread-62, num=851
pool-1-thread-62, num=852
pool-1-thread-62, num=853
pool-1-thread-62, num=854
pool-1-thread-62, num=855
pool-1-thread-62, num=856
pool-1-thread-58, num=857
pool-1-thread-58, num=858
pool-1-thread-58, num=859
pool-1-thread-22, num=359
pool-1-thread-61, num=357
pool-1-thread-22, num=860
pool-1-thread-22, num=861
pool-1-thread-22, num=862
pool-1-thread-22, num=863
pool-1-thread-61, num=864
pool-1-thread-61, num=865
pool-1-thread-61, num=866
pool-1-thread-61, num=867
pool-1-thread-61, num=868
pool-1-thread-61, num=869
pool-1-thread-61, num=870
pool-1-thread-61, num=871
pool-1-thread-61, num=872
pool-1-thread-59, num=352
pool-1-thread-60, num=351
pool-1-thread-59, num=873
pool-1-thread-59, num=874
pool-1-thread-59, num=875
pool-1-thread-59, num=876
pool-1-thread-60, num=877
pool-1-thread-60, num=878
pool-1-thread-60, num=879
pool-1-thread-60, num=880
pool-1-thread-60, num=881
pool-1-thread-60, num=882
pool-1-thread-60, num=883
pool-1-thread-60, num=884
pool-1-thread-60, num=885
pool-1-thread-20, num=347
pool-1-thread-20, num=886
pool-1-thread-20, num=887
pool-1-thread-20, num=888
pool-1-thread-20, num=889
pool-1-thread-20, num=890
pool-1-thread-20, num=891
pool-1-thread-20, num=892
pool-1-thread-20, num=893
pool-1-thread-68, num=805
pool-1-thread-69, num=798
pool-1-thread-68, num=894
pool-1-thread-68, num=895
pool-1-thread-69, num=896
pool-1-thread-69, num=897
pool-1-thread-69, num=898
pool-1-thread-69, num=899
pool-1-thread-70, num=796
pool-1-thread-71, num=781
pool-1-thread-71, num=901
pool-1-thread-71, num=902
pool-1-thread-71, num=903
pool-1-thread-71, num=904
pool-1-thread-71, num=905
pool-1-thread-74, num=756
pool-1-thread-74, num=906
pool-1-thread-74, num=907
pool-1-thread-74, num=908
pool-1-thread-74, num=909
pool-1-thread-74, num=910
pool-1-thread-74, num=911
pool-1-thread-74, num=912
pool-1-thread-50, num=755
pool-1-thread-50, num=913
pool-1-thread-50, num=914
pool-1-thread-50, num=915
pool-1-thread-50, num=916
pool-1-thread-50, num=917
pool-1-thread-50, num=918
pool-1-thread-75, num=754
pool-1-thread-76, num=748
pool-1-thread-76, num=919
pool-1-thread-76, num=920
pool-1-thread-76, num=921
pool-1-thread-76, num=922
pool-1-thread-76, num=923
pool-1-thread-78, num=739
pool-1-thread-100, num=736
pool-1-thread-100, num=925
pool-1-thread-100, num=926
pool-1-thread-100, num=927
pool-1-thread-100, num=928
pool-1-thread-100, num=929
pool-1-thread-100, num=930
pool-1-thread-100, num=931
pool-1-thread-100, num=932
pool-1-thread-100, num=933
pool-1-thread-79, num=732
쓰레드 콜 종료
pool-1-thread-79, num=934
pool-1-thread-79, num=935
pool-1-thread-79, num=936
pool-1-thread-79, num=937
pool-1-thread-79, num=938
pool-1-thread-80, num=730
pool-1-thread-99, num=729
pool-1-thread-99, num=939
pool-1-thread-99, num=940
pool-1-thread-99, num=941
pool-1-thread-99, num=942
pool-1-thread-99, num=943
pool-1-thread-99, num=944
pool-1-thread-99, num=945
pool-1-thread-99, num=946
pool-1-thread-99, num=947
pool-1-thread-48, num=719
pool-1-thread-48, num=948
pool-1-thread-48, num=949
pool-1-thread-48, num=950
pool-1-thread-48, num=951
pool-1-thread-48, num=952
pool-1-thread-48, num=953
pool-1-thread-81, num=717
pool-1-thread-81, num=954
pool-1-thread-81, num=955
pool-1-thread-98, num=718
pool-1-thread-98, num=956
pool-1-thread-98, num=957
pool-1-thread-98, num=958
pool-1-thread-98, num=959
pool-1-thread-98, num=960
pool-1-thread-98, num=961
pool-1-thread-98, num=962
pool-1-thread-98, num=963
pool-1-thread-98, num=964
pool-1-thread-97, num=711
pool-1-thread-97, num=965
pool-1-thread-97, num=966
pool-1-thread-97, num=967
pool-1-thread-97, num=968
pool-1-thread-97, num=969
pool-1-thread-97, num=970
pool-1-thread-97, num=971
pool-1-thread-97, num=972
pool-1-thread-97, num=973
pool-1-thread-82, num=704
pool-1-thread-82, num=974
pool-1-thread-82, num=975
pool-1-thread-82, num=976
pool-1-thread-82, num=977
pool-1-thread-82, num=978
pool-1-thread-82, num=979
pool-1-thread-84, num=702
pool-1-thread-84, num=980
pool-1-thread-96, num=701
pool-1-thread-96, num=981
pool-1-thread-96, num=982
pool-1-thread-96, num=983
pool-1-thread-96, num=984
pool-1-thread-96, num=985
pool-1-thread-96, num=986
pool-1-thread-96, num=987
pool-1-thread-96, num=988
pool-1-thread-96, num=989
pool-1-thread-85, num=685
pool-1-thread-85, num=990
pool-1-thread-85, num=991
pool-1-thread-85, num=992
pool-1-thread-78, num=924
pool-1-thread-78, num=993
pool-1-thread-78, num=994
pool-1-thread-78, num=995
pool-1-thread-78, num=996
pool-1-thread-69, num=900
pool-1-thread-69, num=997
pool-1-thread-69, num=998
executor has been terminated

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

 

위 출력 결과를 자세히 확인해보면 thread는 thread-1 ~ thread-100 까지 총 100개가 생성되어 실행이 된 것을 확인 할 수 있습니다. 

이번에는 corePoolSize를 1로 설정을 해서 실행해보겠습니다. (더보기 클릭해서 보세요)

더보기

pool-1-thread-2, num=0
pool-1-thread-3, num=0
pool-1-thread-1, num=1
pool-1-thread-3, num=3
pool-1-thread-3, num=5
pool-1-thread-2, num=2
pool-1-thread-2, num=7
pool-1-thread-3, num=6
pool-1-thread-1, num=4
pool-1-thread-3, num=9
pool-1-thread-2, num=8
pool-1-thread-3, num=11
pool-1-thread-1, num=10
pool-1-thread-3, num=13
pool-1-thread-2, num=12
pool-1-thread-3, num=15
pool-1-thread-1, num=14
pool-1-thread-1, num=18
pool-1-thread-3, num=17
pool-1-thread-2, num=16
pool-1-thread-3, num=20
pool-1-thread-1, num=19
pool-1-thread-2, num=21
pool-1-thread-3, num=23
pool-1-thread-1, num=22
pool-1-thread-1, num=26
pool-1-thread-3, num=25
pool-1-thread-2, num=24
pool-1-thread-2, num=29
pool-1-thread-2, num=30
pool-1-thread-3, num=28
pool-1-thread-1, num=27
pool-1-thread-3, num=31
pool-1-thread-3, num=33
pool-1-thread-3, num=34
pool-1-thread-3, num=35
pool-1-thread-3, num=36
pool-1-thread-1, num=32
pool-1-thread-3, num=37
pool-1-thread-3, num=38
pool-1-thread-4, num=39
pool-1-thread-4, num=40
pool-1-thread-4, num=41
pool-1-thread-4, num=42
pool-1-thread-4, num=43
pool-1-thread-4, num=44
pool-1-thread-4, num=45
pool-1-thread-4, num=46
pool-1-thread-4, num=47
pool-1-thread-4, num=48
pool-1-thread-4, num=49
pool-1-thread-4, num=50
pool-1-thread-2, num=51
pool-1-thread-4, num=52
pool-1-thread-4, num=54
pool-1-thread-2, num=53
pool-1-thread-4, num=55
pool-1-thread-4, num=57
pool-1-thread-4, num=58
pool-1-thread-2, num=56
pool-1-thread-4, num=59
pool-1-thread-2, num=60
pool-1-thread-4, num=61
pool-1-thread-2, num=62
pool-1-thread-2, num=64
pool-1-thread-4, num=63
pool-1-thread-2, num=65
pool-1-thread-2, num=66
pool-1-thread-2, num=67
pool-1-thread-2, num=68
pool-1-thread-5, num=69
pool-1-thread-5, num=70
pool-1-thread-5, num=71
pool-1-thread-5, num=72
pool-1-thread-5, num=73
pool-1-thread-5, num=74
pool-1-thread-5, num=75
pool-1-thread-5, num=76
pool-1-thread-5, num=77
pool-1-thread-5, num=78
pool-1-thread-5, num=79
pool-1-thread-5, num=80
pool-1-thread-5, num=81
pool-1-thread-5, num=82
pool-1-thread-5, num=83
pool-1-thread-3, num=84
pool-1-thread-5, num=85
pool-1-thread-3, num=86
pool-1-thread-5, num=87
pool-1-thread-3, num=88
pool-1-thread-3, num=90
pool-1-thread-5, num=89
pool-1-thread-3, num=91
pool-1-thread-5, num=92
pool-1-thread-3, num=93
pool-1-thread-5, num=94
pool-1-thread-3, num=95
pool-1-thread-3, num=96
pool-1-thread-3, num=97
pool-1-thread-3, num=98
pool-1-thread-6, num=99
pool-1-thread-6, num=100
pool-1-thread-6, num=101
pool-1-thread-6, num=102
pool-1-thread-6, num=103
pool-1-thread-6, num=104
pool-1-thread-6, num=105
pool-1-thread-6, num=106
pool-1-thread-6, num=107
pool-1-thread-6, num=108
pool-1-thread-4, num=109
pool-1-thread-4, num=110
pool-1-thread-4, num=111
pool-1-thread-4, num=112
pool-1-thread-4, num=113
pool-1-thread-4, num=114
pool-1-thread-4, num=115
pool-1-thread-4, num=116
pool-1-thread-4, num=117
pool-1-thread-4, num=118
pool-1-thread-2, num=119
pool-1-thread-2, num=120
pool-1-thread-2, num=121
pool-1-thread-2, num=122
pool-1-thread-2, num=123
pool-1-thread-2, num=124
pool-1-thread-2, num=125
pool-1-thread-2, num=126
pool-1-thread-2, num=127
pool-1-thread-2, num=128
pool-1-thread-1, num=129
pool-1-thread-1, num=130
pool-1-thread-1, num=131
pool-1-thread-1, num=132
pool-1-thread-1, num=133
pool-1-thread-1, num=134
pool-1-thread-1, num=135
pool-1-thread-1, num=136
pool-1-thread-1, num=137
pool-1-thread-1, num=138
pool-1-thread-7, num=139
pool-1-thread-7, num=140
pool-1-thread-7, num=141
pool-1-thread-7, num=142
pool-1-thread-7, num=143
pool-1-thread-7, num=144
pool-1-thread-7, num=145
pool-1-thread-7, num=146
pool-1-thread-7, num=147
pool-1-thread-7, num=148
pool-1-thread-5, num=149
pool-1-thread-5, num=150
pool-1-thread-5, num=151
pool-1-thread-5, num=152
pool-1-thread-5, num=153
pool-1-thread-5, num=154
pool-1-thread-5, num=155
pool-1-thread-5, num=156
pool-1-thread-5, num=157
pool-1-thread-5, num=158
pool-1-thread-3, num=159
pool-1-thread-3, num=160
pool-1-thread-3, num=161
pool-1-thread-3, num=162
pool-1-thread-3, num=163
pool-1-thread-3, num=164
pool-1-thread-3, num=165
pool-1-thread-3, num=166
pool-1-thread-3, num=167
pool-1-thread-3, num=168
pool-1-thread-8, num=169
pool-1-thread-8, num=170
pool-1-thread-8, num=171
pool-1-thread-8, num=172
pool-1-thread-8, num=173
pool-1-thread-8, num=174
pool-1-thread-8, num=175
pool-1-thread-8, num=176
pool-1-thread-8, num=177
pool-1-thread-8, num=178
pool-1-thread-6, num=179
pool-1-thread-6, num=180
pool-1-thread-6, num=181
pool-1-thread-6, num=182
pool-1-thread-6, num=183
pool-1-thread-6, num=184
pool-1-thread-6, num=185
pool-1-thread-6, num=186
pool-1-thread-6, num=187
pool-1-thread-6, num=188
pool-1-thread-4, num=189
pool-1-thread-9, num=190
pool-1-thread-9, num=192
pool-1-thread-9, num=193
pool-1-thread-9, num=194
pool-1-thread-4, num=191
pool-1-thread-9, num=195
pool-1-thread-4, num=196
pool-1-thread-9, num=197
pool-1-thread-4, num=198
pool-1-thread-4, num=200
pool-1-thread-9, num=199
pool-1-thread-9, num=202
pool-1-thread-4, num=201
pool-1-thread-9, num=203
pool-1-thread-9, num=205
pool-1-thread-4, num=204
pool-1-thread-4, num=206
pool-1-thread-4, num=207
pool-1-thread-4, num=208
pool-1-thread-2, num=209
pool-1-thread-2, num=210
pool-1-thread-10, num=211
pool-1-thread-10, num=213
pool-1-thread-10, num=214
pool-1-thread-10, num=215
pool-1-thread-10, num=216
pool-1-thread-2, num=212
pool-1-thread-10, num=217
pool-1-thread-2, num=218
pool-1-thread-2, num=220
pool-1-thread-10, num=219
pool-1-thread-2, num=221
pool-1-thread-10, num=222
pool-1-thread-10, num=224
pool-1-thread-2, num=223
pool-1-thread-2, num=226
pool-1-thread-10, num=225
pool-1-thread-2, num=227
pool-1-thread-2, num=228
pool-1-thread-11, num=229
pool-1-thread-11, num=230
pool-1-thread-11, num=231
pool-1-thread-11, num=232
pool-1-thread-11, num=233
pool-1-thread-11, num=234
pool-1-thread-11, num=235
pool-1-thread-1, num=236
pool-1-thread-11, num=237
pool-1-thread-1, num=238
pool-1-thread-11, num=239
pool-1-thread-1, num=240
pool-1-thread-1, num=241
pool-1-thread-1, num=242
pool-1-thread-1, num=243
pool-1-thread-1, num=244
pool-1-thread-1, num=245
pool-1-thread-1, num=246
pool-1-thread-1, num=247
pool-1-thread-11, num=248
pool-1-thread-7, num=249
pool-1-thread-7, num=250
pool-1-thread-7, num=251
pool-1-thread-12, num=252
pool-1-thread-7, num=253
pool-1-thread-12, num=254
pool-1-thread-12, num=257
pool-1-thread-12, num=258
pool-1-thread-12, num=259
pool-1-thread-12, num=260
pool-1-thread-5, num=256
pool-1-thread-7, num=255
pool-1-thread-5, num=262
pool-1-thread-5, num=264
pool-1-thread-5, num=265
pool-1-thread-12, num=261
pool-1-thread-5, num=266
pool-1-thread-5, num=268
pool-1-thread-5, num=269
pool-1-thread-5, num=270
pool-1-thread-5, num=271
pool-1-thread-5, num=272
pool-1-thread-7, num=263
pool-1-thread-7, num=273
pool-1-thread-12, num=267
pool-1-thread-12, num=275
pool-1-thread-12, num=276
pool-1-thread-7, num=274
pool-1-thread-7, num=277
pool-1-thread-7, num=278
pool-1-thread-3, num=279
pool-1-thread-3, num=280
pool-1-thread-3, num=281
pool-1-thread-3, num=282
pool-1-thread-3, num=283
pool-1-thread-3, num=284
pool-1-thread-3, num=285
pool-1-thread-3, num=286
pool-1-thread-3, num=287
pool-1-thread-3, num=288
pool-1-thread-8, num=289
pool-1-thread-8, num=290
pool-1-thread-8, num=291
pool-1-thread-8, num=292
pool-1-thread-8, num=293
pool-1-thread-8, num=294
pool-1-thread-8, num=295
pool-1-thread-8, num=296
pool-1-thread-8, num=297
pool-1-thread-8, num=298
pool-1-thread-6, num=299
pool-1-thread-6, num=300
pool-1-thread-6, num=301
pool-1-thread-6, num=302
pool-1-thread-6, num=303
pool-1-thread-6, num=304
pool-1-thread-6, num=305
pool-1-thread-6, num=306
pool-1-thread-6, num=307
pool-1-thread-6, num=308
pool-1-thread-13, num=309
pool-1-thread-13, num=310
pool-1-thread-13, num=311
pool-1-thread-13, num=312
pool-1-thread-13, num=313
pool-1-thread-13, num=314
pool-1-thread-13, num=315
pool-1-thread-13, num=316
pool-1-thread-13, num=317
pool-1-thread-13, num=318
pool-1-thread-9, num=319
pool-1-thread-9, num=320
pool-1-thread-9, num=321
pool-1-thread-9, num=322
pool-1-thread-9, num=323
pool-1-thread-9, num=324
pool-1-thread-9, num=325
pool-1-thread-9, num=326
pool-1-thread-9, num=327
pool-1-thread-9, num=328
pool-1-thread-14, num=329
pool-1-thread-14, num=330
pool-1-thread-14, num=331
pool-1-thread-14, num=332
pool-1-thread-14, num=333
pool-1-thread-14, num=334
pool-1-thread-14, num=335
pool-1-thread-14, num=336
pool-1-thread-14, num=337
pool-1-thread-14, num=338
pool-1-thread-4, num=339
pool-1-thread-4, num=340
pool-1-thread-4, num=341
pool-1-thread-4, num=342
pool-1-thread-4, num=343
pool-1-thread-4, num=344
pool-1-thread-4, num=345
pool-1-thread-4, num=346
pool-1-thread-4, num=347
pool-1-thread-4, num=348
pool-1-thread-10, num=349
pool-1-thread-10, num=350
pool-1-thread-10, num=351
pool-1-thread-10, num=352
pool-1-thread-10, num=353
pool-1-thread-10, num=354
pool-1-thread-10, num=355
pool-1-thread-10, num=356
pool-1-thread-10, num=357
pool-1-thread-10, num=358
pool-1-thread-2, num=359
pool-1-thread-2, num=360
pool-1-thread-2, num=361
pool-1-thread-2, num=362
pool-1-thread-2, num=363
pool-1-thread-2, num=364
pool-1-thread-2, num=365
pool-1-thread-2, num=366
pool-1-thread-2, num=367
pool-1-thread-2, num=368
pool-1-thread-1, num=369
pool-1-thread-1, num=370
pool-1-thread-1, num=371
pool-1-thread-1, num=372
pool-1-thread-1, num=373
pool-1-thread-1, num=374
pool-1-thread-1, num=375
pool-1-thread-1, num=376
pool-1-thread-1, num=377
pool-1-thread-1, num=378
pool-1-thread-15, num=379
pool-1-thread-15, num=380
pool-1-thread-15, num=381
pool-1-thread-15, num=382
pool-1-thread-15, num=383
pool-1-thread-15, num=384
pool-1-thread-15, num=385
pool-1-thread-15, num=386
pool-1-thread-15, num=387
pool-1-thread-15, num=388
pool-1-thread-11, num=389
pool-1-thread-11, num=390
pool-1-thread-11, num=391
pool-1-thread-11, num=392
pool-1-thread-11, num=393
pool-1-thread-11, num=394
pool-1-thread-11, num=395
pool-1-thread-11, num=396
pool-1-thread-11, num=397
pool-1-thread-11, num=398
pool-1-thread-5, num=399
pool-1-thread-5, num=400
pool-1-thread-5, num=401
pool-1-thread-5, num=402
pool-1-thread-5, num=403
pool-1-thread-5, num=404
pool-1-thread-5, num=405
pool-1-thread-5, num=406
pool-1-thread-5, num=407
pool-1-thread-5, num=408
pool-1-thread-12, num=409
pool-1-thread-12, num=410
pool-1-thread-12, num=411
pool-1-thread-12, num=412
pool-1-thread-12, num=413
pool-1-thread-12, num=414
pool-1-thread-12, num=415
pool-1-thread-12, num=416
pool-1-thread-12, num=417
pool-1-thread-12, num=418
pool-1-thread-7, num=419
pool-1-thread-7, num=420
pool-1-thread-7, num=421
pool-1-thread-7, num=422
pool-1-thread-7, num=423
pool-1-thread-7, num=424
pool-1-thread-7, num=425
pool-1-thread-7, num=426
pool-1-thread-7, num=427
pool-1-thread-7, num=428
pool-1-thread-16, num=429
pool-1-thread-16, num=430
pool-1-thread-16, num=431
pool-1-thread-16, num=432
pool-1-thread-16, num=433
pool-1-thread-16, num=434
pool-1-thread-16, num=435
pool-1-thread-16, num=436
pool-1-thread-16, num=437
pool-1-thread-16, num=438
pool-1-thread-3, num=439
pool-1-thread-3, num=440
pool-1-thread-3, num=441
pool-1-thread-3, num=442
pool-1-thread-3, num=443
pool-1-thread-3, num=444
pool-1-thread-3, num=445
pool-1-thread-3, num=446
pool-1-thread-3, num=447
pool-1-thread-3, num=448
pool-1-thread-8, num=449
pool-1-thread-8, num=450
pool-1-thread-8, num=451
pool-1-thread-8, num=452
pool-1-thread-8, num=453
pool-1-thread-8, num=454
pool-1-thread-8, num=455
pool-1-thread-8, num=456
pool-1-thread-8, num=457
pool-1-thread-8, num=458
pool-1-thread-6, num=459
pool-1-thread-6, num=460
pool-1-thread-6, num=461
pool-1-thread-6, num=462
pool-1-thread-6, num=463
pool-1-thread-6, num=464
pool-1-thread-6, num=465
pool-1-thread-6, num=466
pool-1-thread-6, num=467
pool-1-thread-6, num=468
pool-1-thread-6, num=469
pool-1-thread-6, num=470
pool-1-thread-6, num=471
pool-1-thread-6, num=472
pool-1-thread-6, num=473
pool-1-thread-6, num=474
pool-1-thread-6, num=475
pool-1-thread-6, num=476
pool-1-thread-6, num=477
pool-1-thread-6, num=478
pool-1-thread-9, num=479
pool-1-thread-9, num=480
pool-1-thread-9, num=481
pool-1-thread-9, num=482
pool-1-thread-9, num=483
pool-1-thread-9, num=484
pool-1-thread-9, num=485
pool-1-thread-9, num=486
pool-1-thread-9, num=487
pool-1-thread-9, num=488
pool-1-thread-17, num=489
pool-1-thread-17, num=490
pool-1-thread-17, num=491
pool-1-thread-17, num=492
pool-1-thread-17, num=493
pool-1-thread-17, num=494
pool-1-thread-17, num=495
pool-1-thread-17, num=496
pool-1-thread-17, num=497
pool-1-thread-17, num=498
pool-1-thread-14, num=499
pool-1-thread-14, num=500
pool-1-thread-14, num=501
pool-1-thread-14, num=502
pool-1-thread-14, num=503
pool-1-thread-14, num=504
pool-1-thread-14, num=505
pool-1-thread-14, num=506
pool-1-thread-14, num=507
pool-1-thread-14, num=508
pool-1-thread-4, num=509
pool-1-thread-4, num=510
pool-1-thread-4, num=511
pool-1-thread-4, num=512
pool-1-thread-4, num=513
pool-1-thread-4, num=514
pool-1-thread-4, num=515
pool-1-thread-4, num=516
pool-1-thread-4, num=517
pool-1-thread-4, num=518
pool-1-thread-10, num=519
pool-1-thread-10, num=520
pool-1-thread-10, num=521
pool-1-thread-10, num=522
pool-1-thread-10, num=523
pool-1-thread-10, num=524
pool-1-thread-10, num=525
pool-1-thread-10, num=526
pool-1-thread-10, num=527
pool-1-thread-10, num=528
pool-1-thread-2, num=529
pool-1-thread-2, num=530
pool-1-thread-2, num=531
pool-1-thread-2, num=532
pool-1-thread-2, num=533
pool-1-thread-2, num=534
pool-1-thread-2, num=535
pool-1-thread-2, num=536
pool-1-thread-2, num=537
pool-1-thread-2, num=538
pool-1-thread-1, num=539
pool-1-thread-1, num=540
pool-1-thread-1, num=541
pool-1-thread-1, num=542
pool-1-thread-1, num=543
pool-1-thread-1, num=544
pool-1-thread-1, num=545
pool-1-thread-1, num=546
pool-1-thread-1, num=547
pool-1-thread-1, num=548
pool-1-thread-15, num=549
pool-1-thread-15, num=550
pool-1-thread-15, num=551
pool-1-thread-15, num=552
pool-1-thread-15, num=553
pool-1-thread-15, num=554
pool-1-thread-15, num=555
pool-1-thread-15, num=556
pool-1-thread-15, num=557
pool-1-thread-15, num=558
pool-1-thread-18, num=559
pool-1-thread-18, num=560
pool-1-thread-18, num=561
pool-1-thread-18, num=562
pool-1-thread-18, num=563
pool-1-thread-18, num=564
pool-1-thread-18, num=565
pool-1-thread-18, num=566
pool-1-thread-18, num=567
pool-1-thread-18, num=568
pool-1-thread-11, num=569
pool-1-thread-11, num=570
pool-1-thread-11, num=571
pool-1-thread-11, num=572
pool-1-thread-11, num=573
pool-1-thread-11, num=574
pool-1-thread-11, num=575
pool-1-thread-11, num=576
pool-1-thread-11, num=577
pool-1-thread-11, num=578
pool-1-thread-5, num=579
pool-1-thread-5, num=580
pool-1-thread-5, num=581
pool-1-thread-5, num=582
pool-1-thread-5, num=583
pool-1-thread-5, num=584
pool-1-thread-5, num=585
pool-1-thread-5, num=586
pool-1-thread-5, num=587
pool-1-thread-5, num=588
pool-1-thread-12, num=589
pool-1-thread-12, num=590
pool-1-thread-12, num=591
pool-1-thread-12, num=592
pool-1-thread-12, num=593
pool-1-thread-12, num=594
pool-1-thread-12, num=595
pool-1-thread-12, num=596
pool-1-thread-12, num=597
pool-1-thread-12, num=598
pool-1-thread-7, num=599
pool-1-thread-7, num=600
pool-1-thread-7, num=601
pool-1-thread-7, num=602
pool-1-thread-7, num=603
pool-1-thread-7, num=604
pool-1-thread-7, num=605
pool-1-thread-7, num=606
pool-1-thread-7, num=607
pool-1-thread-7, num=608
pool-1-thread-19, num=609
pool-1-thread-19, num=610
pool-1-thread-19, num=611
pool-1-thread-19, num=612
pool-1-thread-19, num=613
pool-1-thread-19, num=614
pool-1-thread-19, num=615
pool-1-thread-19, num=616
pool-1-thread-19, num=617
pool-1-thread-19, num=618
pool-1-thread-16, num=619
pool-1-thread-16, num=620
pool-1-thread-16, num=621
pool-1-thread-16, num=622
pool-1-thread-16, num=623
pool-1-thread-16, num=624
pool-1-thread-16, num=625
pool-1-thread-16, num=626
pool-1-thread-16, num=627
pool-1-thread-16, num=628
pool-1-thread-3, num=629
pool-1-thread-3, num=630
pool-1-thread-3, num=631
pool-1-thread-3, num=632
pool-1-thread-3, num=633
pool-1-thread-3, num=634
pool-1-thread-3, num=635
pool-1-thread-3, num=636
pool-1-thread-3, num=637
pool-1-thread-3, num=638
pool-1-thread-8, num=639
pool-1-thread-8, num=640
pool-1-thread-8, num=641
pool-1-thread-8, num=642
pool-1-thread-8, num=643
pool-1-thread-8, num=644
pool-1-thread-8, num=645
pool-1-thread-8, num=646
pool-1-thread-8, num=647
pool-1-thread-8, num=648
pool-1-thread-6, num=649
pool-1-thread-6, num=650
pool-1-thread-6, num=651
pool-1-thread-6, num=652
pool-1-thread-6, num=653
pool-1-thread-6, num=654
pool-1-thread-6, num=655
pool-1-thread-6, num=656
pool-1-thread-6, num=657
pool-1-thread-6, num=658
pool-1-thread-13, num=659
pool-1-thread-13, num=660
pool-1-thread-13, num=661
pool-1-thread-13, num=662
pool-1-thread-13, num=663
pool-1-thread-13, num=664
pool-1-thread-13, num=665
pool-1-thread-13, num=666
pool-1-thread-13, num=667
pool-1-thread-13, num=668
pool-1-thread-9, num=669
pool-1-thread-9, num=670
pool-1-thread-9, num=671
pool-1-thread-9, num=672
pool-1-thread-9, num=673
pool-1-thread-9, num=674
pool-1-thread-9, num=675
pool-1-thread-9, num=676
pool-1-thread-9, num=677
pool-1-thread-9, num=678
pool-1-thread-20, num=679
pool-1-thread-20, num=680
pool-1-thread-20, num=681
pool-1-thread-20, num=682
pool-1-thread-20, num=683
pool-1-thread-20, num=684
pool-1-thread-20, num=685
pool-1-thread-20, num=686
pool-1-thread-20, num=687
pool-1-thread-20, num=688
pool-1-thread-17, num=689
pool-1-thread-17, num=690
pool-1-thread-17, num=691
pool-1-thread-17, num=692
pool-1-thread-17, num=693
pool-1-thread-17, num=694
pool-1-thread-17, num=695
pool-1-thread-17, num=696
pool-1-thread-17, num=697
pool-1-thread-17, num=698
pool-1-thread-14, num=699
pool-1-thread-14, num=700
pool-1-thread-14, num=701
pool-1-thread-14, num=702
pool-1-thread-14, num=703
pool-1-thread-14, num=704
pool-1-thread-14, num=705
pool-1-thread-14, num=706
pool-1-thread-14, num=707
pool-1-thread-14, num=708
pool-1-thread-4, num=709
pool-1-thread-4, num=710
pool-1-thread-4, num=711
pool-1-thread-4, num=712
pool-1-thread-4, num=713
pool-1-thread-4, num=714
pool-1-thread-4, num=715
pool-1-thread-4, num=716
pool-1-thread-4, num=717
pool-1-thread-4, num=718
pool-1-thread-10, num=719
pool-1-thread-10, num=720
pool-1-thread-10, num=721
pool-1-thread-10, num=722
pool-1-thread-10, num=723
pool-1-thread-10, num=724
pool-1-thread-10, num=725
pool-1-thread-10, num=726
pool-1-thread-10, num=727
pool-1-thread-10, num=728
pool-1-thread-2, num=729
pool-1-thread-2, num=730
pool-1-thread-2, num=731
pool-1-thread-2, num=732
pool-1-thread-2, num=733
pool-1-thread-2, num=734
pool-1-thread-2, num=735
pool-1-thread-2, num=736
pool-1-thread-2, num=737
pool-1-thread-2, num=738
pool-1-thread-1, num=739
pool-1-thread-1, num=740
pool-1-thread-1, num=741
pool-1-thread-1, num=742
pool-1-thread-1, num=743
pool-1-thread-1, num=744
pool-1-thread-1, num=745
pool-1-thread-1, num=746
pool-1-thread-1, num=747
pool-1-thread-1, num=748
pool-1-thread-21, num=749
pool-1-thread-21, num=750
pool-1-thread-21, num=751
pool-1-thread-21, num=752
pool-1-thread-21, num=753
pool-1-thread-21, num=754
pool-1-thread-21, num=755
pool-1-thread-21, num=756
pool-1-thread-21, num=757
pool-1-thread-21, num=758
pool-1-thread-15, num=759
pool-1-thread-15, num=760
pool-1-thread-15, num=761
pool-1-thread-15, num=762
pool-1-thread-15, num=763
pool-1-thread-15, num=764
pool-1-thread-15, num=765
pool-1-thread-15, num=766
pool-1-thread-15, num=767
pool-1-thread-15, num=768
pool-1-thread-18, num=769
pool-1-thread-18, num=770
pool-1-thread-18, num=771
pool-1-thread-18, num=772
pool-1-thread-18, num=773
pool-1-thread-18, num=774
pool-1-thread-18, num=775
pool-1-thread-18, num=776
pool-1-thread-18, num=777
pool-1-thread-18, num=778
pool-1-thread-11, num=779
pool-1-thread-11, num=780
pool-1-thread-11, num=781
pool-1-thread-11, num=782
pool-1-thread-11, num=783
pool-1-thread-11, num=784
pool-1-thread-11, num=785
pool-1-thread-11, num=786
pool-1-thread-11, num=787
pool-1-thread-11, num=788
pool-1-thread-22, num=789
pool-1-thread-22, num=790
pool-1-thread-22, num=791
pool-1-thread-22, num=792
pool-1-thread-22, num=793
pool-1-thread-22, num=794
pool-1-thread-22, num=795
pool-1-thread-22, num=796
pool-1-thread-22, num=797
pool-1-thread-22, num=798
pool-1-thread-5, num=799
pool-1-thread-5, num=800
pool-1-thread-5, num=801
pool-1-thread-5, num=802
pool-1-thread-5, num=803
pool-1-thread-5, num=804
pool-1-thread-5, num=805
pool-1-thread-5, num=806
pool-1-thread-5, num=807
pool-1-thread-5, num=808
pool-1-thread-23, num=809
pool-1-thread-23, num=810
pool-1-thread-23, num=811
pool-1-thread-23, num=812
pool-1-thread-23, num=813
pool-1-thread-23, num=814
pool-1-thread-23, num=815
pool-1-thread-23, num=816
pool-1-thread-23, num=817
pool-1-thread-23, num=818
pool-1-thread-12, num=819
pool-1-thread-12, num=820
pool-1-thread-12, num=821
pool-1-thread-12, num=822
pool-1-thread-12, num=823
pool-1-thread-12, num=824
pool-1-thread-12, num=825
pool-1-thread-12, num=826
pool-1-thread-12, num=827
pool-1-thread-12, num=828
pool-1-thread-7, num=829
pool-1-thread-7, num=830
pool-1-thread-7, num=831
pool-1-thread-7, num=832
pool-1-thread-7, num=833
pool-1-thread-7, num=834
pool-1-thread-7, num=835
pool-1-thread-7, num=836
pool-1-thread-7, num=837
pool-1-thread-7, num=838
pool-1-thread-24, num=839
pool-1-thread-24, num=840
pool-1-thread-24, num=841
pool-1-thread-24, num=842
pool-1-thread-24, num=843
pool-1-thread-24, num=844
pool-1-thread-24, num=845
pool-1-thread-24, num=846
pool-1-thread-24, num=847
pool-1-thread-24, num=848
pool-1-thread-24, num=849
pool-1-thread-24, num=850
pool-1-thread-24, num=851
pool-1-thread-24, num=852
pool-1-thread-24, num=853
pool-1-thread-24, num=854
pool-1-thread-24, num=855
pool-1-thread-24, num=856
pool-1-thread-24, num=857
pool-1-thread-24, num=858
pool-1-thread-19, num=859
pool-1-thread-19, num=860
pool-1-thread-19, num=861
pool-1-thread-19, num=862
pool-1-thread-19, num=863
pool-1-thread-19, num=864
pool-1-thread-19, num=865
pool-1-thread-19, num=866
pool-1-thread-19, num=867
pool-1-thread-19, num=868
pool-1-thread-3, num=869
pool-1-thread-3, num=870
pool-1-thread-3, num=871
pool-1-thread-3, num=872
pool-1-thread-3, num=873
pool-1-thread-3, num=874
pool-1-thread-3, num=875
pool-1-thread-3, num=876
pool-1-thread-3, num=877
pool-1-thread-3, num=878
pool-1-thread-25, num=879
pool-1-thread-25, num=880
pool-1-thread-25, num=881
pool-1-thread-25, num=882
pool-1-thread-25, num=883
pool-1-thread-25, num=884
pool-1-thread-25, num=885
pool-1-thread-25, num=886
pool-1-thread-25, num=887
pool-1-thread-25, num=888
pool-1-thread-8, num=889
pool-1-thread-8, num=890
pool-1-thread-8, num=891
pool-1-thread-8, num=892
pool-1-thread-8, num=893
pool-1-thread-8, num=894
pool-1-thread-8, num=895
pool-1-thread-8, num=896
pool-1-thread-8, num=897
pool-1-thread-8, num=898
pool-1-thread-6, num=899
pool-1-thread-6, num=900
pool-1-thread-6, num=901
pool-1-thread-6, num=902
pool-1-thread-6, num=903
pool-1-thread-6, num=904
pool-1-thread-6, num=905
pool-1-thread-6, num=906
pool-1-thread-6, num=907
pool-1-thread-6, num=908
pool-1-thread-13, num=909
pool-1-thread-13, num=910
pool-1-thread-13, num=911
pool-1-thread-26, num=912
pool-1-thread-26, num=914
pool-1-thread-26, num=915
pool-1-thread-26, num=916
pool-1-thread-26, num=917
pool-1-thread-26, num=918
pool-1-thread-26, num=919
pool-1-thread-26, num=920
pool-1-thread-26, num=921
pool-1-thread-26, num=922
pool-1-thread-13, num=913
pool-1-thread-13, num=923
pool-1-thread-13, num=924
pool-1-thread-13, num=925
pool-1-thread-13, num=926
pool-1-thread-13, num=927
pool-1-thread-13, num=928
pool-1-thread-9, num=929
pool-1-thread-9, num=930
pool-1-thread-9, num=931
pool-1-thread-9, num=932
pool-1-thread-9, num=933
pool-1-thread-9, num=934
pool-1-thread-9, num=935
pool-1-thread-9, num=936
pool-1-thread-9, num=937
pool-1-thread-9, num=938
pool-1-thread-20, num=939
pool-1-thread-20, num=940
pool-1-thread-20, num=941
pool-1-thread-20, num=942
pool-1-thread-20, num=943
pool-1-thread-20, num=944
pool-1-thread-20, num=945
pool-1-thread-20, num=946
pool-1-thread-20, num=947
pool-1-thread-20, num=948
pool-1-thread-17, num=949
pool-1-thread-17, num=950
pool-1-thread-17, num=951
pool-1-thread-17, num=952
pool-1-thread-17, num=953
pool-1-thread-17, num=954
pool-1-thread-17, num=955
pool-1-thread-17, num=956
pool-1-thread-17, num=957
pool-1-thread-17, num=958
pool-1-thread-27, num=959
pool-1-thread-27, num=960
pool-1-thread-27, num=961
pool-1-thread-27, num=962
pool-1-thread-27, num=963
pool-1-thread-27, num=964
pool-1-thread-27, num=965
pool-1-thread-27, num=966
pool-1-thread-27, num=967
pool-1-thread-27, num=968
pool-1-thread-14, num=969
pool-1-thread-14, num=970
pool-1-thread-14, num=971
pool-1-thread-14, num=972
pool-1-thread-14, num=973
pool-1-thread-14, num=974
pool-1-thread-14, num=975
pool-1-thread-14, num=976
pool-1-thread-14, num=977
pool-1-thread-14, num=978
pool-1-thread-4, num=979
pool-1-thread-4, num=980
pool-1-thread-4, num=981
pool-1-thread-4, num=982
pool-1-thread-4, num=983
pool-1-thread-4, num=984
pool-1-thread-4, num=985
pool-1-thread-4, num=986
pool-1-thread-4, num=987
pool-1-thread-4, num=988
pool-1-thread-10, num=989
pool-1-thread-10, num=990
pool-1-thread-10, num=991
pool-1-thread-10, num=992
pool-1-thread-10, num=993
pool-1-thread-10, num=994
pool-1-thread-10, num=995
pool-1-thread-10, num=996
pool-1-thread-10, num=997
pool-1-thread-10, num=998
쓰레드 콜 종료
executor has been terminated

BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed

 

thread가 1~27 까지 27개의 쓰레드만 실행이 되었습니다. 왜 이런 걸까요?

corePoolSize를 1로 주었기 때문에 쓰레드풀은 1개의 쓰레드로 시작을 하게되고 해당 쓰레드가 끝나기 전에 새로운 task의 실행 요청이 들어오면 새로운 쓰레드를 생성해서 처리하게 됩니다. 하지만 새로운 task의 실행 요청이 이전 task가 종료된 이후에 들어오게되면 새로운 쓰레드를 생성하지 않고 기존의 쓰레드를 재사용하게 됩니다. 이것이 바로 쓰레드 풀을 사용하는 장점이죠. 쓰레드 풀에 필요한 개수만큼의 쓰레드만 생성하고 재사용함으로써 불필요한 쓰레드를 만듬으로써 소모되는 메모리를 줄일 수 있습니다.

 

여기까지 ThreadPoolExecutor를 활용한 멀티쓰레드 구현에 대해 간략한 포스팅을 마칩니다.

 

Reference: 오라클 Java 8 공식 문서

 

추가로 현재 Thread 클래스를 상속한 Task에서 num 변수를 1씩 증가해서 출력하도록 하였습니다만, 실제로 마지막 숫자를 보면 999가 아닌 998이 출력되었습니다. 여러 번 실행해보면 가장 큰 숫자가 99X~999가 랜덤하게 출력되는 것을 확인 할 수 있습니다. 이것은 num이라는 변수를 모든 쓰레드가 공유하는데 값을 1씩 증가시키는 오퍼레이션이 thread safe하게 동작하고 있지 않음을 보여주고 있는 것입니다. 이것을 해결하려면 volatile키워드를 사용하면 됩니다.

💻 Programming/Java

[Java] 자바 멀티쓰레드 (Multi Thread)

자바 멀티쓰레드 구현하기

자바에서 쓰레드를 이용하여 비동기로 작업을 처리하는 방법에 대해서 간략하게 포스팅합니다.

 

자바에서 쓰레드를 생성하는 방법은 2가지가 있습니다.

첫 번째는 Runnable 인터페이스를 구현하여 Thread 생성자로 해당 구현체를 넘겨주는 방법이고,

두 번째는 직접 Thread 클래스를 상속하는 것입니다.

 

우선 첫 번째 방법으로 쓰레드를 생성하여  실행하는 코드를 보겠습니다.

    public static void main(String[] args) {
        Runnable task = new Task();
        Thread thread = new Thread(task);
        thread.start();
    }
    static class Task implements Runnable {

        int num = 0;

        @Override
        public void run() {
        	for(int i = 0; i < 10; i++) {
	            System.out.println(num++);
            }
        }
    }

 

위 코드를 실행하면 아래와 같이 출력이 됩니다.

	0
	1
	2
	3
	4
	5
	6
	7
	8
	9

 

이번에는 Thread 클래스를 상속하여 동일한 작업(task)을 하도록 해보겠습니다.

    public static void main(String[] args) {
        ThreadTask task = new ThreadTask();
        task.run();        
    }
    static class ThreadTask extends Thread {

        int num = 0;

        @Override
        public void run() {
        	for(int i = 0; i < 10; i++) {
	            System.out.println(num++);
    	    }
        }
    }

 

실행하면 Runnable로 구현했을 때와 동일하게 아래와 같이 출력이 됩니다.

	0
	1
	2
	3
	4
	5
	6
	7
	8
	9

 

참고로 실제 Thread 클래스를 열어보면 Runnable 인터페이스를 implement하고 있습니다.

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;
.
.
.

 

그럼 이번에는 쓰레드 두 개를 동시에 돌려보도록 하겠습니다.

    public static void main(String[] args) {
        Runnable task = new Task();

        Thread thread1 = new Thread(task);
        thread1.start();
        System.out.println("thread 1 executed.");

        Thread thread2 = new Thread(task);
        thread2.start();
        System.out.println("thread 2 executed.");

        System.out.println("쓰레드 콜 종료");
    }

    static class Task implements Runnable {

        int num = 0;

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + ", num=" + num++);
            }
        }
    }

 

중간중간에 어떻게 실행이 되는지 확인을 하기 위하여 System.out.println으로 로깅을 해봤습니다.

thread 1 executed.
Thread-0, num=0
Thread-0, num=1
Thread-0, num=2
Thread-0, num=3
Thread-0, num=4
Thread-0, num=5
thread 2 executed.
쓰레드 콜 종료
Thread-0, num=6
Thread-1, num=7
Thread-1, num=9
Thread-0, num=8
Thread-1, num=10
Thread-0, num=11
Thread-1, num=12
Thread-1, num=13
Thread-1, num=14
Thread-1, num=16
Thread-1, num=17
Thread-1, num=18
Thread-1, num=19
Thread-0, num=15

BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date

 

위 출력 결과를 보면 쓰레드가 먼저 시작되지만 "task1 executed"가 먼저 출력이 되었고 그 뒤에 첫 번째 쓰레드인 Thread-0이 출력을 시작하였습니다. 그리고 중간에 "task2 executed"가 출력된 걸 보니 두 번째 쓰레드가 실행이 된 것을 알 수 있죠. 두 번째 쓰레드는 Thread-1 이며 첫 번째 쓰레드가 일을 마치기 전에 시작되어 "Thread-1, num=0"을 출력한 것을 확인 할 수 있습니다.

이렇게 하나의 프로세스(main 프로세스) 안에서 여러 개의 작업을 동시에 나누어 실행 할 수 있도록 해주는 것이 바로 이 쓰레드입니다.

쓰레드로 작업을 하게 되면 아래와 같은 특징들이 있습니다.

  • 여러 작업을 동시에 처리할 수 있어 작업을 완료하는데 필요한 총 소요 시간이 줄어든다. (메인쓰레드 1개로 작업했을 때와 비교했을 때)
  • 먼저 시작한 쓰레드가 항상 먼저 일을 끝내지는 않는다. 따라서, 작업의 순서가 중요할 때에는 쓰레드로 나누어 처리하면 안된다.

 

자, 이제 싱글 쓰레드로 10시간 걸릴 일을 100개의 쓰레드를 돌려서 10분만에 끝내고 싶습니다. 어떻게 해야 할까요?

쓰레드를 100개를 만들어야 하는데 아래처럼 무식하게 만들어야 할까요?

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);
        Thread thread4 = new Thread(task);
        Thread thread5 = new Thread(task);
        .
        .
        .

 

아닙니다. 이렇게 해야되면 쓰레드 만들다가 퇴근해야됩니다.

자바에서는 java.util.concurrent패키지에 동시작업을 위해 유용한 클래스들을 모아놨는데 그중에 ThreadPoolExecutor라는 녀석이 있습니다. 이 녀석을 사용하면 두 세 줄이면 100개의 쓰레드를 돌릴 수 있는 코드를 작성할 수 있죠.

ThreadPoolExecutor에 대해 설명하려면 포스트가 길어지니 ThreadPoolExecutor를 이용한 멀티쓰레드 구현서 계속 이어가겠습니다.

 

💻 Programming/웹프로그래밍

[AWS] Beanstalk IP 주소 조회

AWS Beanstalk

AWS 빈스톡을 이용하여 인스턴스를 여러개 띄우고

분산시스템 환경을 만들어 개발을 할 때

빈스톡 인스턴스의 private IP가 필요할 경우 

AWS SDK를 이용하여 아래와 같이 구현을 할 수 있다.

public List<String> getBeanstalkIps() {
	return findIpsByIds(findIds(getBeanstalkResources()));
}

private DescribeEnvironmentResourcesResult getBeanstalkResources() {
	DescribeEnvironmentResourcesRequest request =
		new DescribeEnvironmentResourcesRequest()
        		.withEnvironmentName("my-beanstalk-app");
	return beanstalk.describeEnvironmentResources(request);
}

private List<String> findIds(DescribeEnvironmentResourcesResult resources) {
	return resources.getEnvironmentResources().getInstances().stream()
			.map(Instance::getId)
			.collect(toList());
}

private List<String> findIpsByIds(List<String> ids) {
	DescribeInstancesRequest request = 
    	new DescribeInstancesRequest().withInstanceIds(ids);
	DescribeInstancesResult result = ec2.describeInstances(request);
	return result.getReservations().stream()
			.flatMap(n -> n.getInstances().stream())
			.map(com.amazonaws.services.ec2.model.Instance::getPrivateIpAddress)
			.sorted()
			.collect(toList());
}

 

여기서 필요한 것은 "my-beanstalk-app"을 대체할 빈스톡 환경명인데

이 이름은 AWS Console > Elastic Beanstalk > (애플리케이션) > Environment 에서 확인이 가능하다. 

AWS SDK는 2018년 11월 2.x 버전이 나왔으나 아직 1.11 버전을 사용 중이다.

SDK 2.x로 구현시에는 아래와 같이 할 수 있다.

-- TODO --

비동기 처리시 로깅 uuid 설정

트래픽이 많은 환경에서 멀티쓰레드로 작업을 처리하고 있는데 각 쓰레드에 uuid 설정이 안되어 있어 로그상으로 확인이 불가한 경우가 생겼다. 기존에는 잘 되고 있었던 것이었고 뭔가 수정작업을 하면서 빼뜨린 것 같아 다른 쓰레드들의 설정을 보다가 TaskDecorator를 보게 되었다.

 

package com.keichee;

import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

import java.util.Map;

public class LoggingTaskDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable task) {
        Map<String, String> callerThreadContext = MDC.getCopyOfContextMap();
        return () -> {
            MDC.setContextMap(callerThreadContext);
            task.run();
        };
    }
}

 

LoggingTaskDecorator는 TaskDecorator를 구현하고 있는데 decorate(Runnable task) 메서드만 구현해주면 된다.

여기서 TaskDecorator는 어떤 task(작업)을 꾸며준다는 의미로 지어진 이름이며 스프링의 코어 패키지에 들어있을 만큼 자주 사용되는 녀석이라고 생각하면 된다. 스프링 공식 문서에 따르면 TaskDecorator는 아래와 같이 설명이 되어있다.

 

Functional Interface : This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.@FunctionalInterface public interface TaskDecorator

A callback interface for a decorator to be applied to any Runnable about to be executed.

Note that such a decorator is not necessarily being applied to the user-supplied Runnable/Callable but rather to the actual execution callback (which may be a wrapper around the user-supplied task).

The primary use case is to set some execution context around the task's invocation, or to provide some monitoring/statistics for task execution.

Since:4.3

 

자, 그럼 TaskDecorator를 구현한 이 LoggingTaskDecorator는 어떻게 사용할까?

간단하다. 스프링에서 제공하는 ThreadPoolTaskExecutor를 사용한다면 setTaskDecorator() 메서드를 이용해 세팅해주기만 하면 된다.

@Bean
public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor t = new ThreadPoolTaskExecutor();
        t.setTaskDecorator(new LoggingTaskDecorator());
        return t;
}

 

 

async(비동기) 처리를 위한 ThreadPoolTaskExecutor

ThreadPoolTaskExecutor를 이용하여 비동기처리하는 방법을 알아보겠습니다.

ThreadPoolTaskExecutor는 스프링에서 제공해주는 클래스로 org.springframework.scheduling.concurrent 패키지에 속해있습니다.

생성자도 기본생성자 하나만 존재합니다. 이름에서 알 수 있듯이 쓰레드풀을 이용하여 멀티쓰레드 구현을 쉽게 해주는 클래스입니다.

 

그럼 어떻게 사용하는지 살펴보겠습니다.

	public static void main(String[] args) {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.initialize();
	}

ThreadPoolTaskExecutor 를 생성하고 사용할 수 있도록 initialize()를 호출했습니다. 왜냐면 이니셜라이즈하기 전에는 executor를 사용을 할 수 없기 때문입니다. 만약 이니셜라이즈 하기 전에 사용하려고 한다면 아래와 같은 오류 메시지를 보게 됩니다.

 

Exception in thread "main" java.lang.IllegalStateException: ThreadPoolTaskExecutor not initialized

 

그럼 이제 아래처럼 코드를 좀 더 추가한 뒤 실제로 쓰레드를 실행시켜 보겠습니다.

	public static void main(String[] args) {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.initialize();
		
		log.info("executing threads....");
		Runnable r = () -> {
			try {
				log.info(Thread.currentThread().getName() + ", Now sleeping 10 seconds...");
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		};

		for (int i = 0; i < 10; i++) {
			executor.execute(r);
		}
	}

 

출력 결과를 한번 볼까요?

07:42:09.450 [main] INFO com.keichee.test.service.TestService - executing threads....
07:42:09.460 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:42:19.464 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:42:29.465 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:42:39.470 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:42:49.472 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:42:59.477 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:43:09.483 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:43:19.489 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:43:29.491 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
07:43:39.496 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...

로그가 출력된 시간을 보면 10초마다 출력이 되고있고 쓰레드도 ThreadPoolTaskExecutor-1 하나가 모두 처리한 것을 알 수 있습니다. 즉, 지금 위 코드는 멀티쓰레드로 돌아간게 아니란 얘기죠. ThreadPoolTaskExecutor는 몇 가지 설정값들을 가지고 있습니다. 그 중 corePoolSize 값이 동시에 실행할 쓰레드의 개수를 의미하는데 이 설정의 default 값이 1로 세팅되어 있기 때문에 위 처럼 corePoolSize 설정없이 그대로 사용하면 싱글쓰레드로 돌아가게 됩니다. 

 

설정값

그럼 설정값들에 어떤 것들이 있는지 그것들이 의미하는게 무엇인지 setter 메서드를 기준으로 중요한 값들만 한번 살펴보고 넘어가겠습니다.

 

메서드

설명

기본값

setCorePoolSize

corePoolSize 값을 설정함. corePoolSize는 동시에 실행시킬 쓰레드의 개수를 의미함

1

setAllowCoreThreadTimeOut

코어 쓰레드의 타임아웃을 허용할 것인지에 대한 세터 메서드. true로 설정할 경우 코어 쓰레드를 10으로 설정했어도 일정 시간(keepAliveSeconds)이 지나면 코어 쓰레드 개수가 줄어듦.

false

setKeepAliveSeconds

코어 쓰레드 타임아웃을 허용했을 경우 사용되는 설정값으로, 여기 설정된 시간이 지날 때까지 코어 쓰레드 풀의 쓰레드가 사용되지 않을 경우 해당 쓰레드는 terminate 된다.

60초

setMaxPoolSize

쓰레드 풀의 최대 사이즈

Integer.MAX

setQueueCapacity

쓰레드 풀 큐의 사이즈. corePoolSize 개수를 넘어서는 task가 들어왔을 때 queue에 해당 task들이 쌓이게 된다. 최대로 maxPoolSize 개수 만큼 쌓일 수 있다.

Integer.MAX

 

여기서 corePoolSize, maxPoolSize, queueCapacity 이 세 가지 설정값이 가장 중요합니다.

우선 위에서 봤던 첫 번째 예제에서는 이 세 가지 값에 대해서 별도로 설정을 하지 않았었습니다. 그래서 싱글 쓰레드로 작업이 이루어졌죠.

 

corePoolSize

이번에는 corePoolSize를 올려보겠습니다.

    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.initialize();

        log.info("executing threads....");
        Runnable r = () -> {
            try {
                log.info(Thread.currentThread().getName() + ", Now sleeping 10 seconds...");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 10; i++) {
            executor.execute(r);
        }
    }

 

corePoolSize를 5로 설정 후 실행해보았습니다. (queueCapacity와 maxPoolSize값은 현재 기본값인 Integer.MAX 입니다)

08:52:50.423 [main] INFO com.keichee.test.service.TestService - executing threads....
08:52:50.456 [ThreadPoolTaskExecutor-3] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-3, Now sleeping 10 seconds...
08:52:50.456 [ThreadPoolTaskExecutor-2] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-2, Now sleeping 10 seconds...
08:52:50.456 [ThreadPoolTaskExecutor-5] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-5, Now sleeping 10 seconds...
08:52:50.456 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
08:52:50.456 [ThreadPoolTaskExecutor-4] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-4, Now sleeping 10 seconds...
08:53:00.460 [ThreadPoolTaskExecutor-1] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-1, Now sleeping 10 seconds...
08:53:00.460 [ThreadPoolTaskExecutor-2] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-2, Now sleeping 10 seconds...
08:53:00.461 [ThreadPoolTaskExecutor-3] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-3, Now sleeping 10 seconds...
08:53:00.461 [ThreadPoolTaskExecutor-4] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-4, Now sleeping 10 seconds...
08:53:00.461 [ThreadPoolTaskExecutor-5] INFO com.keichee.test.service.TestService - ThreadPoolTaskExecutor-5, Now sleeping 10 seconds...

실행되자마자 5개의 쓰레드가 실행되고 10초 후에 또 다른 5개의 쓰레드가 실행된 것을 확인할 수 있습니다. 즉, queueCapacity와 maxPoolSize값을 기본값으로 해놓으면 corePoolSize의 값만큼 쓰레드가 동시에 실행되는 것을 알 수 있습니다.

 

corePoolSize와 queueCapacity

그럼 이번에는 corePoolSize는 default 값인 1로 놔두고 queueCapacity와 maxPoolSize 값을 5로 설정해보겠습니다. 그리고 10개의 task가 실행될 때 poolSize, activeSize, queueSize 가 어떻게 변하는지 확인할 수 있게 출력해보겠습니다. 또, 출력을 좀 짧게 하기위해서 쓰레드명 prefix를 "my-"로 변경했습니다.

    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("my-");
        executor.setQueueCapacity(5);
        executor.setMaxPoolSize(5);
        executor.initialize();

        log.info("executing threads....");
        Runnable r = () -> {
            try {
                log.info(Thread.currentThread().getName() + ", Now sleeping 10 seconds...");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 10; i++) {
            executor.execute(r);
            System.out.print("poolSize:" + executor.getPoolSize());
            System.out.print(", active:" + executor.getActiveCount());
            System.out.println(", queue:" + executor.getThreadPoolExecutor().getQueue().size());
        }
    }

 

어떤 결과가 나올 것 같은지 한번 생각을 해보고 아래 결과를 확인해보시기 바랍니다.

09:02:22.864 [main] INFO com.keichee.test.service.TestService - executing threads....
poolSize:1, active:1, queue:0
poolSize:1, active:1, queue:1
poolSize:1, active:1, queue:2
poolSize:1, active:1, queue:3
poolSize:1, active:1, queue:4
poolSize:1, active:1, queue:5
poolSize:2, active:2, queue:5
09:02:22.886 [my-1] INFO com.keichee.test.service.TestService - my-1, Now sleeping 10 seconds...
09:02:22.887 [my-2] INFO com.keichee.test.service.TestService - my-2, Now sleeping 10 seconds...
poolSize:3, active:3, queue:5
09:02:22.887 [my-3] INFO com.keichee.test.service.TestService - my-3, Now sleeping 10 seconds...
poolSize:4, active:4, queue:5
09:02:22.887 [my-4] INFO com.keichee.test.service.TestService - my-4, Now sleeping 10 seconds...
poolSize:5, active:5, queue:5
09:02:22.887 [my-5] INFO com.keichee.test.service.TestService - my-5, Now sleeping 10 seconds...
09:02:32.888 [my-1] INFO com.keichee.test.service.TestService - my-1, Now sleeping 10 seconds...
09:02:32.890 [my-2] INFO com.keichee.test.service.TestService - my-2, Now sleeping 10 seconds...
09:02:32.890 [my-4] INFO com.keichee.test.service.TestService - my-4, Now sleeping 10 seconds...
09:02:32.890 [my-5] INFO com.keichee.test.service.TestService - my-5, Now sleeping 10 seconds...
09:02:32.890 [my-3] INFO com.keichee.test.service.TestService - my-3, Now sleeping 10 seconds...

 

 

위 출력 결과를 보면 10개의 task를 실행할 때 queue 사이즈가 0에서 5까지 올라가고 그 이후에 poolSize와 active 사이즈가 증가하는 것을 알 수 있습니다. corePoolSize가 1 이라서 2번째 task부터 6번째 task까지는 queue에 들어가게 됩니다. 그리고 7번째 task부터 10번째 task까지 4개의 task는 maxPoolSize 를 넘어서지 않는 한 추가로 쓰레드를 생성하여 pool에 넣고 해당 쓰레드로 각 task를 실행하게 됩니다. 

그럼 만약 maxPoolSize를 넘어설 만큼 많은 양의 task들이 들어온다면 어떻게 될까요?

Exception in thread "main" org.springframework.core.task.TaskRejectedException: Executor [java.util.concurrent.ThreadPoolExecutor@32eff876[Running, pool size = 5, active threads = 5, queued tasks = 5, completed tasks = 0]] did not accept task: com.keichee.test.service.TestService$$Lambda$22/0x00000008000ce840@5e0826e7
	at org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.execute(ThreadPoolTaskExecutor.java:324)

TaskRejectedException 오류가 발생하게 됩니다. 

따라서 얼마나 많은 양의 task를 소화해야 하는지를 정확히 알고 corePoolSize, queueCapacity, maxPoolSize에 적절한 값을 세팅하여 사용해야 합니다. 기본 값으로 사용하면 TaskRejectedException을 볼 일은 거의 없겠지만 그 대신 queue에 어마어마한 양의 task가 쌓이겠죠. 그러다가 애플리케이션의 배포나 어떤 이유에 의해서 재기동이 필요하게 되면 해당 queue에 쌓여있던 task들은 사라지게 됩니다. 

 

스프링부트에서 사용하실 분들은 아래 포스팅을 참고하시면 좋습니다.

스프링 부트에서 ThreadPoolTaskExecutor를 설정 및 사용하는 방법

 

스프링 RestTemplate 타임아웃 설정을 하는데 타임아웃 시간이 설정한대로 적용되지 않는 듯 하여 테스트 해봄..

보통 HttpComponentsClientHttpRequestFactory 와 SimpleClientHttpRequestFactory 를 사용하여 설정을 함

스프링의 RestTemplate 기본적으로 SimpleClientHttpRequestFactory를 사용함

SimpleClientHttpRequestFactory를 이용해서 설정을 하면 정상적으로 세팅한 값에 타임아웃이 발생함.

하지만 HttpComponentsClientHttpRequestFactory 를 이용하면 설정한 시간보다 4배 긴 시간이 흐른 뒤에야 타임아웃이 발생하였음

@Test
public void 타임아웃_테스트() {

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout((int) TimeUnit.SECONDS.toMillis(10));
factory.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(5)); // 4배의 시간이 걸린 뒤에야 타임아웃 발생
factory.setConnectionRequestTimeout(5 * 1000);

// SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// factory.setReadTimeout((int) TimeUnit.SECONDS.toMillis(10));
// factory.setConnectTimeout((int) TimeUnit.SECONDS.toMillis(5)); // 세팅한 시간대로 타임아웃 발생

// RequestConfig config = RequestConfig.custom()
// .setSocketTimeout((int) TimeUnit.SECONDS.toMillis(1))
// .setConnectTimeout((int) TimeUnit.SECONDS.toMillis(5))
// .setConnectionRequestTimeout((int) TimeUnit.SECONDS.toMillis(10))
// .build();
// CloseableHttpClient client = HttpClientBuilder
// .create()
// .setDefaultRequestConfig(config)
// .build();
// HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);

RestTemplate restTemplate = new RestTemplate(factory);

// 1. connect timeout 테스트
long start = System.currentTimeMillis();
try {
ResponseEntity<String> result = restTemplate.getForEntity("https://abc.com:81/test", String.class);
} catch (ResourceAccessException e) {
log.error("타임아웃!! {}", TimeUtils.printDuration(System.currentTimeMillis() - start), e);
}

}


아직 원인은 확인하지 못함.


push까지 해버린 git commit 원복하는 방법


공동 repository에 push까지 해버린 commit을 되돌리는 방법은 경우에 따라 두 가지가 있는 것 같습니다.
커밋 개수가 한 두 건 일 때 git revert를 사용하는 방법이 있고, 커밋 개수가 너무 많아 하나씩 revert하기 힘든 경우가 있을 수 있습니다. 여기서는 git revert를 사용하지 않고 원복하는 방법을 알려드립니다.

1. commit log 확인

2. reset (원하는 시점으로 되돌아가기)

3. revert (특정 시점 이후의 변경사항 되돌리기)

4. force push (되돌린 내용을 공동 repo에 반영하기)


1. commit history에서 원복 시점의 커밋 확인

keichee$ git log -5 --pretty=format:"%h - %an, %ar : %s"

148444a6a - keichee, 4 days ago : Merge branch 'dev-sentry' into stage

eebdd9202 - keichee, 4 days ago : dev 환경 로깅 sentry 연동

1c74ca53e - john, 5 days ago : Merge pull request #1238 in test/repo from release/200202 to stage

6544cd10a - john, 5 days ago : Merge pull request #1237 in test/repo from feature/200202 to release

754046d47 - tom, 5 days ago : test commit 1


git log 명령어를 이용해서 commit 히스토리를 확인 할 수 있습니다.

이때 옵션으로 -5 를 입력해주면 최신순으로 5개 까지만 조회를 할 수 있고 

옵션을 주지 않으면 페이징 처리되어 계속 조회할 수 있습니다.

뒤에 pretty 옵션을 주면 위 결과처럼 조회가 되는데 pretty 옵션을 안주면 commit 하나의 내용이 총 6줄에 걸쳐서 표시가 되기 때문에 한눈에 보기가 힘듧니다.


2. 원하는 시점으로 Reset 

위에서 조회한 5개의 커밋들 중에 john이 5일 전에 커밋한 내용까지만 적용하고 keichee가 커밋한 내용을 revert시켜보겠습니다.

commit it가 1c74ca53e인 것을 확인하고 아래와 같이 reset 합니다.

keichee$ git reset 1c74ca53e

Unstaged changes after reset:

M       src/main/resources/logback-spring.xml


3. Revert

reset을 하면 1c74ca53e 커밋까지 완료된 상태로 되돌아 갑니다.

그리고 그 이후의 커밋에 대해서는 수정상태로 변경됩니다.

이때 git status로 확인을 해보면 아래와 같이 확인 할 수 있습니다.

keichee$ git status

On branch test-revert

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)


        modified:   src/main/resources/logback-spring.xml

이제 이렇게 수정된 내용들에 대해서 원복해보겠습니다.

keichee$ git checkout -- src/main/resources/logback-spring.xml

파일들이 너무 많아 이렇게 하기가 불편하다면 아래와 같이 할 수도 있습니다.

# Revert changes to modified files.

git reset --hard


# Remove all untracked files and directories.

# '-f' is force, '-d' is remove directories.

git clean -fd

출처 : https://stackoverflow.com/questions/5807137/how-to-revert-uncommitted-changes-including-files-and-folders


4. Force push

keichee$ git push -f

Total 0 (delta 0), reused 0 (delta 0)

remote: 

remote: Create pull request for test-revert:

remote:   https://git.repository/compare/commits?sourceBranch=refs/heads/test-revert

remote: 

To https://git.repository

 + 61495b45b...1c74ca53e test-revert -> test-revert (forced update)



이상입니다.

오늘도 즐코딩하세요~


Converting timestamp to date time string format and vice versa

 

실제 업무에서 java로 프로그래밍 할 때 날짜/시간을 다뤄야 할 때가 참 많습니다.

데이터 업데이트 시간을 기록하거나 로깅을 하거나 등등 말이죠.

그래서 오늘은 시간 변환에 대해서 짧게 한가지만 알려드립니다.

자바 프로그래밍을 시작한지 얼마 안되었을 때 System.currentTimemillis()를 이용해서 내가 작성한 알고리즘이 얼마나 빨리 돌아가는지 확인을 했었는데요, 저 함수의 결과는 long형입니다. 

1970년 1월 1일 UTC 자정 이후로 몇 밀리초가 지났는지를 반환해주죠.

반환값에 대한 정확한 정의는 아래와 같습니다.

   * @return  the difference, measured in milliseconds, between
   *          the current time and midnight, January 1, 1970 UTC.

 

그런데 이 long형 숫자는 길이도 길고 이게 도대체 그래서 몇 일 몇 시 라는 건지를 알아보기가 힘들죠

그래서 이 long형 숫자를 가독성있게 우리가 일반적으로 사용하는 시간 포맷에 맞게 String 타입으로 변환하려면 어떻게 해야 할까요? 그리고 그렇게 얻은 String 타입 시간값을 다시 long형으로 변환하려면 또 어떻게 해야 할까요?

 

우선 long타입 시간을 원하는 일시 포맷으로 변경하는 것은 Date와 SimpleDateFormat을 이용하면 쉽게 변환할 수 있습니다. 아래와 같이 말이죠.

 public static void main(String[] args) {
     Date d = new Date(vv);
     DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
     String bb = format.format(d);
     System.out.println(bb);
 }

 

반대로 String으로 된 일시를 long으로 변환할 때는 아래와 같이 java.sql.Timestamp를 이용하면 쉽게 변환이 가능합니다.

public static void main(String[] args) {
    String prdt = "2020-01-10 10:38:09.419";
    long vv = Timestamp.valueOf(prdt).getTime();
    System.out.println(vv);
}

 

하지만 Timestamp.valueOf(String) 메서드는 "yyyy-mm-dd hh:mm:ss[.fffffffff]" 형태의 string만 입력 가능합니다.

일자만 있거나 시간값만 있으면 사용이 불가능 하다는 것 명심하세요.

만약 일시 포맷을 잘못 입력하면 아래와 같은 오류가 발생합니다.

Exception in thread "main" java.lang.IllegalArgumentException: 
		Timestamp format must be yyyy-mm-dd hh:mm:ss[.fffffffff]

 

java.sql.Timestamp 대신에 LocalDateTime을 이용할 수도 있습니다.

public static void main(String[] args) {

    String prdt2 = "2020-01-11 00:00:00";
    System.out.println(Timestamp.valueOf(prdt2).getTime());
    System.out.println(LocalDateTime.parse("2020-01-11T00:00:00")
        .atZone(ZoneId.of("Asia/Seoul"))
        .toInstant()
        .toEpochMilli());
}

---출력결과---
1578668400000
1578668400000

 

하지만 LocalDateTime의 경우 parse할 때 들어가는 string형태의 일시값에 'T'구분자를 포함해줘야 합니다.

그렇지 않을 경우 아래와 같이 파싱오류가 발생합니다.

Exception in thread "main" java.time.format.DateTimeParseException: 
		Text '2020-01-11 00:00:00' could not be parsed at index 10

 

 

Summary

이번 포스팅에서는 자바에서 long타입의 timestampe를 String 타입의 가독성 좋은 형태로 변환하는 작업과 그렇게 변환된 String을 다시 long으로 변경하는 부분에 대해서 알아보았습니다.

long -> String -> long 변경을 했을 때 동일한 값이 나오는지도 아래 테스트를 통해서 확인할 수 있었습니다.

public static void main(String[] args) {

    // String to long conversion 1
    String prdt = "2020-01-11 10:24:09.419";
    long l1 = Timestamp.valueOf(prdt).getTime();
    System.out.println(l1);

    // String to long conversion 2
    String prdt2 = "2020-01-11T10:24:09.419";
    long l2 = LocalDateTime.parse(prdt2)
        .atZone(ZoneId.of("Asia/Seoul"))
        .toInstant()
        .toEpochMilli();
    System.out.println(l2);

    // long to String conversion
    DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    System.out.println(format.format(new Date(l1)));
}

---출력결과---
1578705849419
1578705849419 <-- Timestamp를 이용해서 얻은 결과와 동일함을 확인 
2020-01-11 10:24:09.419 <-- long으로 변환했던 String 형태의 시간과 동일하게 출력되는지 확인

 

이상으로 Java에서 datetime을 long에서 String으로 다시 String에서 long으로 변환하는 방법에 대해서 알아보았습니다.

 

유용했다면 공감 꾹 부탁해요~

 

💻 Programming

[AWS DynamoDB] Conditional Check Failed Exception

현재 AWS 다이나모 DB를 이용해서 작업하는 부분이 있는데 

레가시 코드의 dynamoDB에 저장하는 부분에서 

ConditionalCheckFailedException 예외를 잡아서 debug 로깅을 하고 있는 부분이 있었다. 

 

이걸 왜 error 레벨로 로깅하지 않고 debug 레벨로 로깅을 하고 무시하고 있을까? 

실제로 얼마나 해당 로그가 남는지 확인해보니 매일 같이 10번 이상 발생하고 있었다. 

우선 throughput은 전혀 상관이 없어보였다.

stackoverflow를 찾아봐도 딱히 시원한 답을 얻을 수는 없었는데

그러다가 AWS 공식 문서를 찾게 되었다.

AWS DynamoDB Conditional Constraints

 

키포인트 한줄만 발췌해보면 아래와 같다.

You could specify a version attribute for your mapped objects, and the mapper would automatically apply conditional constraints to give you optimistic locking, but you couldn’t explicitly specify your own custom conditional constraints with the mapper.

 

위 페이지를 읽어보면 

dynamo DB는 기본적으로 version을 기준으로 logical condition을 검사한 뒤 

테이블에 데이터를 저장한다고 나와있다. 

실제 legacy코드에도 기존 데이터를 조회해서 version을 읽어와서 

새 데이터 입력할 때 세팅해주고 있었다. 

ok 그럼 뭔가 version충돌로 인해 데이터를 저장하지 못하고 있다는 추측을 해볼 수 있었다. 

왜 충돌이 날까? 

분산시스템에서 동일한 데이터의 업데이트 요청을 동시에 여러 개 받게 되면?

dynamo 테이블의 동일한 데이터를 동시에 업데이트를 하려고 시도를 하게 될 텐데,

실제로 업데이트 하기 전에 기존 version을 조회해와서 신규 데이터에 version 세팅을 해주고

업데이트를 시도하는데 처음 업데이트 시도는 성공! (이때 해당 데이터는 version이 올라가게 된다)

그 이후는 version충돌로 ConditionalCheckFailedException 예외가 발생하게 되는 것이다.

 

 

 

 

 

이클립스에서 프로젝트 import를 해보겠습니다.

import를 한다는 것은 불러오기를 한다는 것입니다.

당연히 기존에 생성했던 프로젝트나 파일이 있어야겠죠

이번 시연에 사용된 이클립스 버전은 oomph 2019-06 (4.12.0)입니다.

이클립스 버전


프로젝트 불러오기는 아래 두 가지 방법이 있습니다.

1. 이클립스 좌측의 package explorer에서 Import projects...를 선택

2. 이클립스 도구메뉴(상단메뉴)의 File > Import 를 선택

패키지탐색기

이클립스 파일 메뉴


어떤 방법을 선택하던지 동일한 아래 창이 뜨게 됩니다.

프로젝트 불러오기 팝업창 - 종류선택

위 창에서 내가 만들었던 프로젝트의 종류를 선택하면 됩니다.

여기서는 Existing Projects into Workspace를 선택하겠습니다.

이렇게 할 경우 현재 workspace가 아닌 

다른 경로에 위치한 프로젝트를 현재 workspace로 가져오게 됩니다.


프로젝트 불러오기 팝업창 - 경로탐색

여기서는 프로젝트의 경로를 선택하면 됩니다.


프로젝트 경로 선택창

import할 프로젝트의 경로를 찾아서 프로젝트 디렉토리를 선택해줍니다.

저는 SampleProject라는 프로젝트를 선택하고 Open을 클릭하였습니다.


프로젝트 불러오기 팝업창 3

프로젝트의 디렉토리 경로가 표시됨가 동시에 

Projects 목록에 SampleProject가 표시되었습니다.

이제 Finish 버튼을 클릭합니다.


프로젝트 불러오기 완료 모습

Package Explorer에 위와 같이 프로젝트가 import되었습니다.


이상으로 이클립스 IDE에서 프로젝트 불러오기를 해보았습니다.




스프링 프로젝트에서 logback을 이용하여 환경별로 로그 설정을 다르게 할 때 

<if>, <elseif> 등의 태그를 이용할 수 있는 줄 알고 아래처럼 설정을 해보았다.

<if condition="property('myproject.profile').equals('live')">
<logger name="com.myproject" level="debug">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.springframework" level="WARN"/>
<root level="info"/>
</if>
<elseif condition="property('myproject.profile').equals('dev')">
<logger name="com.myproject" level="debug">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.springframework" level="WARN"/>
<root level="info"/>
</elseif>
<else condition="property('myproject.profile').equals('local')">
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</else>

위 처럼 설정했는데 앱 기동 시 아래처럼 logback에서 에러로그를 찍었다.


14:46:24,662 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [STDOUT]

14:46:24,668 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - This appender no longer admits a layout as a sub-component, set an encoder instead.

14:46:24,668 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - To ensure compatibility, wrapping your layout in LayoutWrappingEncoder.

14:46:24,668 |-WARN in ch.qos.logback.core.ConsoleAppender[STDOUT] - See also http://logback.qos.ch/codes.html#layoutInsteadOfEncoder for details

14:46:24,673 |-ERROR in ch.qos.logback.core.joran.conditional.IfAction - Could not find Janino library on the class path. Skipping conditional processing.

14:46:24,673 |-ERROR in ch.qos.logback.core.joran.conditional.IfAction - See also http://logback.qos.ch/codes.html#ifJanino

14:46:24,673 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@31:64 - no applicable action for [logger], current ElementPath  is [[configuration][if][logger]]

14:46:24,674 |-ERROR in ch.qos.logback.core.joran.spi.Interpreter@32:42 - no applicable action for [appender-ref], current ElementPath  is [[configuration][if][logger][appender-ref]]


원인은 명확해보였다. Janino라는 라이브러리가 없어서였다.

<if>, <elseif> 등의 태그문법을 Janino 라이브러리에서 해석을 해주는데 해당 라이브러리가 없어서 logback 설정파일해석을 제대로 못한 상태였다.

그래서 현재일자 가장 최신버전으로 의존성을 추가했다.

<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.0.14</version>
</dependency>


그런데 또 오류가 났다 ㅠㅠ

 Failed to parse condition [property('myproject.profile').equals('live')] org.codehaus.commons.compiler.CompileException: Line 1, Column 45: Closing single quote missing

at org.codehaus.commons.compiler.CompileException: Line 1, Column 45: Closing single quote missing


조건으로 준게 파싱오류가 났단다. 그래서 컨디션을 아래처럼 바꾸었다.

<if condition='property("myproject.profile").equals("local")'>

쌍따옴표와 홑따옴표를 바꾼거다.

그리고 if-else if - else 를 하기위해선 nested if를 써야했다.

하지만 nested <if> 를 쓰기엔 안쪽으로 인덴트 되는 게 많아져서 가독성이 떨어진다.

어차피 조건을 equals로 비교하고 있으니 

결과적으로 if문만 써서 profile값을 비교해주어도 if-elseif의 효과는 볼 수 있으므로 아래처럼 바꿨다.

<if condition='property("myproject.profile").equals("live")'>
<then>
<logger name="com.myproject" level="debug">
<appender-ref ref="ELASTIC"/>
</logger>
<logger name="org.springframework" level="WARN"/>
<root level="info"/>
</then>
</if>
<if condition='property("myproject.profile").equals("dev")'>
<then>
<logger name="com.myproject" level="debug">
<appender-ref ref="ELASTIC"/>
</logger>
<logger name="org.springframework" level="WARN"/>
<root level="info"/>
</then>
</if>
<if condition='property("myproject.profile").equals("local")'>
<then>
<logger name="com.myproject" level="debug">
<appender-ref ref="STDOUT"/>
</logger>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</then>
</if>

이제 잘 동작한다.


java.lang.NoSuchMethodError: com.fasterxml.jackson.core.JsonStreamContext.<init>(II)V

위 오류는 jackson-core 버전과 jackson-bind 버전이 서로 다를 경우 발생할 가능성이 높다.

오류발생원인 

메이븐 프로젝트에서 일부 의존성(dependency)을 신규로 추가했을 때, 해당 dependency가 jackson-core의 2.8.0버전을 포함하고있었다. 기존에 jackson-core, jackson-databind 버전을 2.9.0 을 사용하도록 의존성을 관리하고 있었는데 신규 추가 의존성이 jackson-core 2.8.0 버전을 포함하고있어 앱 기동 시 2.8.0버전이 물려올라가면서 발생하였다.

오류해결방법

신규로 추가한 의존성에 아래와 같이 exclusion 처리함.

<dependency>
<groupId>com.internetitem</groupId>
<artifactId>logback-elasticsearch-appender</artifactId>
<version>1.6</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>


참고자료 : 깃헙 jackson-core 이슈

 

둥근 토글 버튼

본 포스팅에서는 html, css, javascript를 이용하여 둥근 toggle버튼을 만들고, 버튼의 상태가 변경될 때마다 상태를 출력하는 기능까지 만들어 봅니다. 본 보스팅에 사용되는 기본코드는 w3schools에서 가지고 온 것입니다. w3schools에서는 단순히 css를 이용해서 토글버튼처럼 보이는 것을 만드는 것 까지만 보여주었는데, 저는 그렇게 토글이 될 때마다 자바스크립트를 이용해서 어떤 기능이 실행되는 부분까지 확장해서 포스팅합니다.

 

우선 toggle.html 파일에 input tag를 이용해서 아래와 같이 작성해줍니다.

<!DOCTYPE html>
<html>

<head>
</head>

<body>
    <label class="switch">
        <input type="checkbox" />
        <span class="slider round"></span>
    </label>
</body>

</html>

 

그리고 toggle.css 파일을 하나 만들어서 그 안에 아래와 같이 작성을 해줍니다.

/* 슬라이더 외부를 감싸는 라벨에 대한 스타일 */
.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

/* HTML 기본 체크박스 숨기기 */
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

/* 슬라이더 - 실제로 토글될 부분 */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: .4s;
  transition: .4s;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: .4s;
  transition: .4s;
}

input:checked + .slider {
  background-color: #2196F3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196F3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

/* 슬라이더를 동그랗게 보여주기 위한 부분 */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

 

이제 HTML 파일의 <head></head> 부분에 위 css 파일을 링크시켜줍니다.

<head>
	<link rel="stylesheet" type="text/css" href="toggle.css" />
</head>

 

이제 toggle.html 파일을 더블클릭해서 브라우저에서 열어보면 아래 영상처럼 움직이는 것을 확인할 수 있습니다.

 

둥근 토글 버튼 움직이는 영상

 

자, 이제 여기에 자바스크립트를 이용하여 기능을 추가해보도록 할건데요,

이 토글버튼은 input 태그의 checkbox를 이용한 것이므로 토글이 될 때마다 checked 속성이 변경되도록 되어있습니다.

이 기본적인 내용을 기억하고 자바스크립트에서 토글버튼의 checked 속성을 기반으로 특정 문자열을 출력하도록 해보겠습니다.

자바스크립트 파일은 따로 만들지 않고 그냥 html 파일에 추가하도록 할게요.

우선, input 태그에 onclick 속성을 이용하여 toggle이라는 함수를 호출하도록 하고, toggle 함수를 구현하겠습니다.

아래처럼 input  태그의 onclick 속성을 넣어주세요. 

<label class="switch">
    <input type="checkbox" onclick="toggle(this)">
    <span class="slider round"></span>
</label>

이렇게 해주면 토글버튼(체크박스)가 클릭될 때마다 toggle이라는 함수를 호출하면서 자기자신을 파라미터로 넘겨주게 됩니다.

 

자, 이제 body 아래쪽에 자바스크립트를 이용하여 스크립트를 작성해보겠습니다.

<script>
    function toggle(element) {
        console.log(element.checked);
    }
</script>

위 코드는 <head></head> 에 위치해도 되고 <body></body> 사이에 위치해도 됩니다.

당연히 별도 파일로 분리하여 작성해도 됩니다.

분리하여 작성하는 것과 관련해서는 제가 작성한 다른 포스트에서 확인 가능합니다. -> https://keichee.tistory.com/356

저는 <body></body> 사이에 넣어놓았습니다.

<body>
    <label class="switch">
        <input type="checkbox" onclick="toggle(this)">
        <span class="slider round"></span>
    </label>

    <script>
        function toggle(element) {
            console.log(element.checked);
        }
    </script>
</body>

 

자, 여기까지 html 파일 전체 코드가 어떻게 되는지 다시 한번 보여드릴게요.

<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" type="text/css" href="toggle.css" />
</head>

<body>
    <label class="switch">
        <input type="checkbox" onclick="toggle(this)">
        <span class="slider round"></span>
    </label>

    <script>
        function toggle(element) {
            console.log(element.checked);
        }
    </script>
</body>

</html>

 

여기까지 완성하여 실행하면 체크박스(토글버튼)을 클릭할 때마다 toggle함수가 실행되면서 현재 체크박스의 상태가 어떻게 바뀌었는지를 출력하게 됩니다.

확인을 위해서 브라우저에서 toggle.html 파일을 새로고침하여 다시 열어주시고,

화면에서 우클릭 > 검사 (inspect)를 선택하여 개발자도구(developer tools)를 열어서 console탭을 열어주세요.

그리고 버튼을 누를때마다 어떤 값이 출력이 되는지 확인해보도록 하겠습니다.

 

토글 버튼 동작 영상

 

자, 이렇게 자바스크립트를 이용해서 토글버튼의 상태값을 출력해보았습니다.

어떤가요? 별로 어렵지 않죠? css 스타일을 해석하기 힘드실 순 있겠으나, 여기서 그런 부분을 자세하게 다루진 않겠습니다.

좀 더 나아가서 토글버튼의 상태값에 따라 ajax 요청을 서버에 날려 실제 DB를 업데이트하거나, 

또는 html의 특정 element를 hide하거나 show하는 등의 기능을 만들어 보세요.

 

그럼 오늘도 즐거운 코딩하시길 바래요 ^-^

 

- 깐깐한 개발자 -

웹사이트를 만들다보면 HTML파일 크기가 커지기 마련입니다.

웹사이트가 dynamic한 동적 사이트라면 더더욱 그럴 가능성이 커집니다.

이런 저런 기능을 자바스크립트로 구현을 하다보면 주체할 수 없이 커지는 html 파일을 볼 수도 있는데요

이럴 때는 자바스크립트 부분을 별도의 .js 파일로 분리한 뒤 html 파일에 링크를 걸어서

마치 html 파일에 자바스크립트를 직접 작성한 것처럼 사용할 수 있습니다.

 

방법도 매우 간단합니다.

아래 html 예제를 먼저 보도록 하겠습니다.

<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" type="text/css" href="toggle.css" />
</head>

<body>
    <label class="switch">
        <input type="checkbox" onclick="toggle(this)">
        <span class="slider round"></span>
    </label>

    <script>
        function toggle(element) {
            console.log(element.checked);
        }
    </script>
</body>

</html>

위 코드는 토글버튼 만들기 시간에 사용했던 코드입니다.

위 코드에서 <script> 태그로 감싼 부분을 .js 파일로 분리해내고 해당 파일을 html파일에 링크(import, include라고 얘기하기도 함)를 걸어서 기능이 정상적으로 돌아가도록 해보겠습니다.

우선 html 문서의 <script> 태그 내에 있던 내용을 복사하여 toggle.js 파일을 만들어 넣어줍니다.

function toggle(element) {
    console.log(element.checked);
}

이제 <head>태그 내에 아래와 같이 toggle.js 파일을 연결시켜주고, <body>안에 있던 script는 제거합니다.

<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" type="text/css" href="toggle.css" />
    <script src="toggle.js"></script>
</head>

<body>
    <label class="switch">
        <input type="checkbox" onclick="toggle(this)">
        <span class="slider round"></span>
    </label>
</body>

</html>

<script> 태그가 사라지니 화면에 그림을 그려주는 요소들만 <body>에 남아있게 되었습니다.

위 코드 자체가 워낙 작은 코드라 깔끔해졌다는 느낌을 받지 못할 수 있으나, 

위 처럼 짧은 html 문서는 테스트용밖에 없을 것입니다. 

개발자가 되고싶다면 항상 .js 파일을 분리하여 링크걸어 사용하길 추천합니다.

 

뭔가 길게 설명드렸지만, html파일에 자바스크립트 파일을 연결/링크/import/include 시키는 방법은 <head>태그 내에 아래와 같이 한 줄만 추가해주시면 됩니다.

<script src="toggle.js"></script>

여기서 .js 파일의 위치는 html 파일의 위치에서 상대경로로 지정해주시면 됩니다.

또는 웹상에 있는 파일일 경우 URL 주소가 들어올 수도 있으니 참고하시기 바랍니다.