<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>diane_at_work.log</title>
        <link>https://velog.io/</link>
        <description>일하며 하는 기록</description>
        <lastBuildDate>Mon, 10 Feb 2025 07:12:15 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. diane_at_work.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/diane_at_work" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[elasticsearch sockettimeout]]></title>
            <link>https://velog.io/@diane_at_work/elasticsearch-sockettimeout</link>
            <guid>https://velog.io/@diane_at_work/elasticsearch-sockettimeout</guid>
            <pubDate>Mon, 10 Feb 2025 07:12:15 GMT</pubDate>
            <description><![CDATA[<p>sockettimetout 30초짜리 나고,
node개수만큼 돌면서 계속 timeout 나는 현상</p>
<p>rest-cleint내에 node개수만큼 돌면서 쿼리를 날리고, 계속 sockettimeout발생하여 cpu증가하는 현상</p>
<p>=&gt; 결론은 sockettimeout과 search.timeout을 적절히 설정해야 함.
socketimeout &gt; search.timeout이고, search.timeout이 끝났다고 바로 응답을 받는것은 아님.
바로 응답을 보내려면 strict하게 적용해서 탐색할 문서 개수를 설정해야함.</p>
<p>-----------아래는 chat cpt 대화-----------</p>
<p>elasticsearch-rest-client에서 exception발생시 node를 순회하면서 조회하잖아
ChatGPT의 말:
맞습니다! Elasticsearch Low-Level REST Client (RestClient) 는 기본적으로 요청 실패 시 등록된 노드들을 순회하며 재시도합니다.</p>
<p>🔹 어떤 경우에 다른 노드로 재시도하나?</p>
<p>네트워크 장애 (IOException) 발생 시
노드 다운 (ConnectionRefusedException) 발생 시
소켓 타임아웃 (SocketTimeoutException) 발생 시
하지만 쿼리 타임아웃(예: search.timeout)이 발생하면 재시도하지 않습니다.</p>
<p>1️⃣ 노드 순회 방식 (Round Robin)
Elasticsearch Low-Level Client는 여러 노드를 등록하면, 요청이 실패할 경우 자동으로 다음 노드로 요청을 시도합니다.
즉, 라운드 로빈 방식으로 노드를 순회하면서 요청을 수행합니다.</p>
<pre><code class="language-java">RestClient restClient = RestClient.builder(
    new HttpHost(&quot;node1.example.com&quot;, 9200, &quot;http&quot;),
    new HttpHost(&quot;node2.example.com&quot;, 9200, &quot;http&quot;),
    new HttpHost(&quot;node3.example.com&quot;, 9200, &quot;http&quot;)
).build();</code></pre>
<p>위와 같이 여러 개의 노드를 등록하면, 한 노드에서 요청이 실패할 경우 다음 노드로 자동 재시도됩니다.</p>
<p>2️⃣ 재시도 동작을 커스텀하려면?
기본적으로 쿼리 타임아웃(서버가 응답을 안 주는 경우)에는 재시도하지 않으므로, 이를 변경하려면 수동으로 재시도를 구현해야 합니다.</p>
<p>✅ setFailureListener를 사용한 노드 실패 감지</p>
<pre><code class="language-java">RestClient restClient = RestClient.builder(
    new HttpHost(&quot;node1.example.com&quot;, 9200, &quot;http&quot;),
    new HttpHost(&quot;node2.example.com&quot;, 9200, &quot;http&quot;)
).setFailureListener(new RestClient.FailureListener() {
    @Override
    public void onFailure(Node node) {
        System.err.println(&quot;노드 실패: &quot; + node.getName());
    }
}).build();</code></pre>
<p>✔ 특정 노드에서 실패할 경우 로깅할 수 있습니다.</p>
<p>3️⃣ High-Level REST Client에서도 동일한 동작을 하는가?
아니요!</p>
<p>RestHighLevelClient 자체는 노드 자동 순회를 지원하지 않습니다.
하지만 내부적으로 RestClient를 사용하므로, Low-Level REST Client의 재시도 전략을 활용할 수 있습니다.</p>
<pre><code class="language-java">RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(
        new HttpHost(&quot;node1.example.com&quot;, 9200, &quot;http&quot;),
        new HttpHost(&quot;node2.example.com&quot;, 9200, &quot;http&quot;)
    )
);</code></pre>
<p>✔ Low-Level REST Client의 노드 순환 로직이 적용되므로, 네트워크 장애 발생 시 다른 노드로 재시도합니다.</p>
<p>4️⃣ 완전한 재시도 로직 구현 (쿼리 타임아웃 포함)
쿼리 타임아웃을 포함하여, 노드를 순회하며 재시도하려면 수동 재시도 로직을 추가해야 합니다.</p>
<pre><code class="language-java">public static SearchResponse executeWithRetry(RestHighLevelClient client, SearchRequest request, int maxRetries) {
    int attempt = 0;
    while (attempt &lt; maxRetries) {
        try {
            return client.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            System.err.println(&quot;요청 실패 (시도 &quot; + (attempt + 1) + &quot;): &quot; + e.getMessage());
            attempt++;
            if (attempt &gt;= maxRetries) {
                throw new RuntimeException(&quot;모든 재시도 실패&quot;, e);
            }
        }
    }
    throw new RuntimeException(&quot;예기치 않은 오류&quot;);
}</code></pre>
<p>✔ IOException 발생 시 최대 maxRetries번 노드를 순회하며 재시도합니다.
✔ 쿼리 타임아웃(search.timeout)이 발생한 경우에도 재시도하도록 설정할 수 있습니다.</p>
<p>✅ 정리
Elasticsearch Low-Level REST Client는 기본적으로 노드들을 순회하며 재시도합니다.
네트워크 장애(IOException)나 노드 다운 시에는 자동 재시도하지만, 쿼리 타임아웃 시에는 재시도하지 않습니다.
노드 장애 감지를 위해 setFailureListener를 사용할 수 있습니다.
쿼리 타임아웃도 포함한 완전한 재시도 로직이 필요하면, 수동으로 재시도를 구현해야 합니다. 🚀</p>
<p>나의 말:
elasticsaerch에서 sockettimeout의 설정은 기본 몇이야?
ChatGPT의 말:
Elasticsearch Low-Level REST Client (RestClient) 의 기본 socketTimeout 값은 30초(30,000ms) 입니다.</p>
<p>🔹 기본 설정 값
설정 옵션    기본값    설명
connectTimeout    1000ms (1초)    서버에 연결을 시도할 때의 최대 대기 시간
socketTimeout    30000ms (30초)    요청을 보낸 후 응답을 기다리는 최대 시간
🔹 socketTimeout을 변경하는 방법</p>
<pre><code class="language-java">RestClient restClient = RestClient.builder(new HttpHost(&quot;localhost&quot;, 9200, &quot;http&quot;))
    .setRequestConfigCallback(requestConfigBuilder -&gt; 
        requestConfigBuilder
            .setSocketTimeout(60000)  // 60초로 설정
            .setConnectTimeout(5000)   // 연결 타임아웃 5초로 설정
    )
    .build();
    ```
✔ setSocketTimeout(60000) → 60초 동안 응답을 기다림
✔ setConnectTimeout(5000) → 5초 내에 연결이 안 되면 실패

즉, 기본적으로 socketTimeout 값은 30초이며, 필요에 따라 조정할 수 있습니다. 🚀






나의 말:
sockettimeout과 쿼리타임아웃은 어떻게 달라?
ChatGPT의 말:
🔹 socketTimeout vs 쿼리 타임아웃(search timeout) 차이점
설정 옵션    의미
socketTimeout    네트워크 레벨의 타임아웃. 요청을 보낸 후 서버 응답을 기다리는 최대 시간.
쿼리 타임아웃 (search.timeout)    Elasticsearch 내부에서 실행되는 쿼리의 제한 시간. 지정된 시간 내에 완료되지 않으면 부분 결과를 반환하거나 실패.
🔹 1. socketTimeout (네트워크 레벨 타임아웃)
요청을 보낸 후 Elasticsearch 서버의 응답을 기다리는 최대 시간
네트워크 지연이나 서버 응답이 늦어질 경우 적용됨
기본값: 30초 (30,000ms)
설정 방법:
```java
RestClient restClient = RestClient.builder(new HttpHost(&quot;localhost&quot;, 9200, &quot;http&quot;))
    .setRequestConfigCallback(requestConfigBuilder -&gt; 
        requestConfigBuilder
            .setSocketTimeout(60000)  // 60초로 설정
            .setConnectTimeout(5000)  // 연결 타임아웃 5초로 설정
    )
    .build();
    ```
✅ 만약 socketTimeout을 초과하면 SocketTimeoutException이 발생하며, 클라이언트는 응답을 받지 못함.

🔹 2. search.timeout (쿼리 실행 타임아웃)
Elasticsearch 내부에서 실행되는 쿼리의 제한 시간
지정된 시간 내에 실행되지 않으면 쿼리가 중단될 수 있음
기본적으로 설정되지 않음 (무한정 실행 가능)
설정 방법:
```java
SearchRequest searchRequest = new SearchRequest(&quot;index_name&quot;);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
    .query(QueryBuilders.matchAllQuery())
    .timeout(TimeValue.timeValueSeconds(5)); // 5초 제한
searchRequest.source(searchSourceBuilder);</code></pre>
<p>✅ 쿼리가 search.timeout을 초과하면 실패하거나 부분 결과를 반환할 수도 있음.
✅ 하지만 search.timeout이 초과되더라도 클라이언트는 여전히 socketTimeout 내에서 응답을 기다릴 수 있음.</p>
<p>🔹 차이점 요약
구분    socketTimeout    search.timeout
설정 대상    클라이언트 ↔ 서버 간 네트워크 통신    Elasticsearch 내부에서 실행되는 쿼리
기본값    30초    설정하지 않으면 무제한
적용 시점    클라이언트가 응답을 기다리는 동안    쿼리가 실행될 때 내부적으로 적용
초과 시 결과    SocketTimeoutException 발생 (응답 없음)    부분 결과 반환 또는 실패
✔ 결론:</p>
<p>socketTimeout은 클라이언트가 서버 응답을 기다리는 시간을 의미하고,
search.timeout은 Elasticsearch가 쿼리를 실행하는 최대 시간을 의미함.
🚀 둘을 함께 조정하면 더 안정적인 Elasticsearch 쿼리 타임아웃 관리가 가능!</p>
<p>나의 말:
search.timeout이 초과되었는데 왜 client는 여전히 응답을 기다려? socketTimeout시간 내에 search.timeout이 초과되면 에러 메세지를 받는거 아냐?
ChatGPT의 말:
좋은 질문이에요! search.timeout이 초과되었는데도 클라이언트가 여전히 응답을 기다리는 이유는 Elasticsearch의 동작 방식 때문입니다.</p>
<p>🔹 search.timeout과 socketTimeout의 관계
search.timeout은 Elasticsearch 내부에서 실행되는 쿼리의 제한 시간입니다.
초과되면 쿼리를 중단할 수도 있고, 부분 결과를 반환할 수도 있으며, 경우에 따라 에러를 반환할 수도 있음
socketTimeout은 클라이언트가 서버 응답을 기다리는 최대 시간입니다.
socketTimeout이 초과되면 클라이언트는 SocketTimeoutException을 발생시킴
즉, search.timeout이 초과되었다고 해서 바로 클라이언트가 응답을 받는 게 아니라, Elasticsearch가 응답을 어떻게 처리하느냐에 따라 달라집니다.</p>
<p>🔹 search.timeout 초과 시 클라이언트가 기다리는 이유
Elasticsearch는 search.timeout을 &quot;강제 중단&quot;이 아니라 &quot;최대한 제한하는 옵션&quot;으로 사용함</p>
<p>만약 검색이 search.timeout을 초과하면, Elasticsearch는 요청을 완전히 중단하지 않을 수도 있음
대신, 부분적인 결과를 반환하거나, 내부적으로 추가 연산을 수행한 후 에러를 반환할 수 있음
이 과정에서 클라이언트는 응답을 기다릴 수 있음
Elasticsearch가 search.timeout 이후에도 연산을 계속 수행하는 경우</p>
<p>일부 복잡한 쿼리(예: aggregations, scroll 쿼리)는 search.timeout이 초과되어도 부분 데이터를 수집하려고 할 수 있음
이때 응답이 늦어지면 클라이언트는 여전히 socketTimeout 내에서 기다리게 됨
Elasticsearch가 search.timeout 초과 시 에러 응답을 반환하는 경우</p>
<p>만약 search.timeout이 초과되었고, Elasticsearch가 바로 에러를 반환하면 클라이언트는 즉시 응답을 받음
이 경우, 클라이언트가 socketTimeout 내에 응답을 받으므로 문제 없음
응답 예시:</p>
<pre><code class="language-json">{
  &quot;error&quot;: {
    &quot;type&quot;: &quot;search_phase_execution_exception&quot;,
    &quot;reason&quot;: &quot;all shards failed&quot;,
    &quot;caused_by&quot;: {
      &quot;type&quot;: &quot;timeout_exception&quot;,
      &quot;reason&quot;: &quot;timed out after 5s&quot;
    }
  },
  &quot;status&quot;: 504
}</code></pre>
<p>위처럼 에러가 반환되면 클라이언트는 socketTimeout 내에 응답을 받고 처리 가능
Elasticsearch가 search.timeout 초과 후에도 내부적으로 작업을 마무리하는 경우</p>
<p>일부 경우, 노드 간 데이터 이동이나 리덕션 작업이 남아 있으면, search.timeout이 초과되었더라도 서버가 최종 응답을 완전히 정리한 후 반환할 수 있음
이 과정에서 클라이언트는 socketTimeout 내에서 계속 기다리게 됨
🔹 search.timeout 초과 시 즉시 응답을 받으려면?
타임아웃을 단호하게 적용하려면 terminate_after 옵션 추가</p>
<pre><code class="language-java">SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
    .query(QueryBuilders.matchAllQuery())
    .timeout(TimeValue.timeValueSeconds(5))  // 5초 검색 타임아웃
    .terminateAfter(100); // 최대 100개 문서까지만 처리 후 종료</code></pre>
<p>terminate_after는 설정된 문서 개수만큼 처리한 후 강제로 쿼리를 중단하도록 강제하는 옵션
search.timeout과 함께 사용하면 훨씬 강력한 타임아웃 제어 가능
socketTimeout을 search.timeout보다 짧게 설정하지 않도록 주의</p>
<p>예를 들어, search.timeout = 10초인데 socketTimeout = 5초면 클라이언트가 먼저 타임아웃 남
일반적으로 socketTimeout은 search.timeout보다 길게 설정하는 것이 좋음
🔹 결론
search.timeout이 초과되었더라도 Elasticsearch가 즉시 응답을 반환하는 것은 아님
socketTimeout이 초과되지 않으면 클라이언트는 응답을 계속 기다릴 수 있음
search.timeout 초과 시 즉시 응답을 받으려면 terminate_after 같은 강제 중단 옵션을 추가하는 것이 좋음 🚀</p>
<p>나의 말:
복잡한 시스템에서 socketTimeout을 몇초정도로 하려나?
ChatGPT의 말:
복잡한 시스템에서 socketTimeout을 설정할 때 고려할 요소는 많지만, 일반적인 기준을 정리하면 다음과 같습니다.</p>
<p>🔹 1. 일반적인 socketTimeout 설정 기준
시스템 유형    socketTimeout 추천값    설명
일반적인 검색 요청    30<del>60초    기본적인 쿼리 수행에 적절
대량 데이터 조회 (scroll, aggregation)    60</del>120초    데이터가 많아 응답이 오래 걸릴 경우
ML 기반 분석, 복잡한 통계 쿼리    120<del>300초    복잡한 연산 수행 시 안정적
백오피스 배치 시스템 (야간 배치 작업)    300</del>600초    배치성 작업으로 여유롭게 설정
실시간 API (모바일, 웹 등)    1~5초    사용자 경험을 위해 빠른 응답 필요
🔹 2. socketTimeout 설정 시 고려해야 할 요소
✅ (1) 평균 쿼리 응답 시간 측정
Elasticsearch의 slow log를 활용해서 쿼리 응답 시간이 얼마나 걸리는지 분석
예제 (log4j2.properties에서 설정):</p>
<pre><code class="language-pgsql">index.search.slowlog.threshold.query.warn: 5s
index.search.slowlog.threshold.query.info: 3s
index.search.slowlog.threshold.query.debug: 1s</code></pre>
<p>실제 평균 응답 시간이 2초 정도라면 socketTimeout을 5초 정도로 설정하는 것이 합리적
✅ (2) 네트워크 상태 및 부하 고려
시스템이 다중 노드 환경이라면, 네트워크 레이턴시를 포함하여 설정
네트워크 상태가 좋지 않다면 여유 있게 설정하는 게 안전
✅ (3) 쿼리 특성 분석
단순 검색 (match, term) → socketTimeout 5<del>30초
복잡한 Aggregation → socketTimeout 60</del>120초
Deep Pagination (scroll 사용) → socketTimeout 120~300초
✅ (4) search.timeout과 조합하여 설정
search.timeout(쿼리 실행 제한 시간)과 socketTimeout(클라이언트가 기다리는 시간)은 맞춰야 함
예를 들어, search.timeout = 10초이면 socketTimeout = 15초 정도로 설정</p>
<pre><code class="language-java">// search.timeout = 10초
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
    .query(QueryBuilders.matchAllQuery())
    .timeout(TimeValue.timeValueSeconds(10));

// socketTimeout = 15초
RestClient restClient = RestClient.builder(new HttpHost(&quot;localhost&quot;, 9200, &quot;http&quot;))
    .setRequestConfigCallback(requestConfigBuilder -&gt; 
        requestConfigBuilder.setSocketTimeout(15000) // 15초 설정
    )
    .build();
    ```
🔹 3. 현실적인 socketTimeout 설정 예시
🔹 A. 실시간 API (사용자 검색)
요청이 1~2초 내에 끝나야 함
search.timeout = 2초, socketTimeout = 3~5초
```java
searchSourceBuilder.timeout(TimeValue.timeValueSeconds(2));
requestConfigBuilder.setSocketTimeout(5000);</code></pre>
<p>🔹 B. 대량 데이터 조회 (대시보드, 분석)
Aggregation이나 scroll을 사용할 때
search.timeout = 60초, socketTimeout = 90~120초</p>
<pre><code class="language-java">searchSourceBuilder.timeout(TimeValue.timeValueSeconds(60));
requestConfigBuilder.setSocketTimeout(120000);</code></pre>
<p>🔹 C. 배치 작업 (야간 분석, 백오피스)
대량 처리, 시간 여유 있음
search.timeout = 300초, socketTimeout = 600초</p>
<pre><code class="language-java">searchSourceBuilder.timeout(TimeValue.timeValueMinutes(5));
requestConfigBuilder.setSocketTimeout(600000);</code></pre>
<p>🔹 4. 결론: 적절한 socketTimeout 설정
실시간 API → 1<del>5초 (빠른 응답 필요)
일반적인 검색 요청 → 30</del>60초 (기본값 유지)
대시보드 및 Aggregation → 60<del>120초
배치성 분석 작업 → 300</del>600초 (충분한 처리 시간 확보)
🚀 즉, 평균 응답 시간을 측정하고, search.timeout과 조합하여 최적의 socketTimeout을 설정하는 것이 중요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch number_of_shard 기본값 변경 대응]]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-numberofshard-%EA%B8%B0%EB%B3%B8%EA%B0%92-%EB%B3%80%EA%B2%BD-%EB%8C%80%EC%9D%91</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-numberofshard-%EA%B8%B0%EB%B3%B8%EA%B0%92-%EB%B3%80%EA%B2%BD-%EB%8C%80%EC%9D%91</guid>
            <pubDate>Tue, 17 Jan 2023 07:17:35 GMT</pubDate>
            <description><![CDATA[<h4 id="기본값-변경-내역">기본값 변경 내역</h4>
<p>기종 6점대 까지는, index 생성 시 num_of_shards를 지정하지 않으면</p>
<p>기본 5개 였는데, 7점대부터 기본 1개로 변경되었다.</p>
<h4 id="template-생성">template 생성</h4>
<p>기존 자동 생성되도록 만들어진 index들이 그럼 앞으로 shard 1개로 만들어지니, 이를 방지하기 위해 기존 사용되던 5개의 값들을 사용하도록 변경해준다.</p>
<pre><code>PUT _template/basic_shard_setting
{

  &quot;index_patterns&quot;: [&quot;name1-*&quot;,&quot;name2-*&quot;,&quot;name3-*&quot;],    
  &quot;settings&quot;: {
    &quot;number_of_shards&quot;: 5,
    &quot;number_of_replicas&quot;: 1
  }
}</code></pre><h3 id="template-order">template order</h3>
<p>앞에서 생성한 basic_shard_setting은 조회해보면 order가 기본 0으로 되어있다.</p>
<p>그런데 이미 name1-<em>, name2-</em>, name3-* 들에 이미 order 0인 template이 있고, 그곳에 number_of_shards가 지정되어 있다면 어떤것이 우선순위로 수행될까?</p>
<h4 id="기존-생성되어있는-template">기존 생성되어있는 template</h4>
<pre><code>PUT _template/name1_template
{
  &quot;index_patterns&quot;: [
    &quot;name1*&quot;
  ],
  &quot;settings&quot;: {
    &quot;index&quot;: {
      &quot;number_of_shards&quot;: 3,
      &quot;number_of_replicas&quot;: 1
    }
  }
}</code></pre><h4 id="인덱스-생성하여-확인">인덱스 생성하여 확인</h4>
<pre><code class="language-PUT">PUT name1-test
GET name1-test
{
  &quot;name2-test&quot; : {
    &quot;aliases&quot; : { },
    &quot;mappings&quot; : { },
    &quot;settings&quot; : {
      &quot;index&quot; : {
        &quot;routing&quot; : {
          &quot;allocation&quot; : {
            &quot;include&quot; : {
              &quot;_tier_preference&quot; : &quot;data_content&quot;
            }
          }
        },
        &quot;number_of_shards&quot; : &quot;5&quot;,
        &quot;provided_name&quot; : &quot;name2-test&quot;,
        &quot;creation_date&quot; : &quot;1673939210866&quot;,
        &quot;number_of_replicas&quot; : &quot;1&quot;,
        &quot;uuid&quot; : &quot;zXT3F8VbTSm5yGzcHU52dg&quot;,
        &quot;version&quot; : {
          &quot;created&quot; : &quot;7170799&quot;
        }
      }
    }
  }
}
</code></pre>
<p>basic_shard_setting이 적용된 것을 알 수 있다.
잠깐... 어떻게 알고 저걸 적용했지?</p>
<h4 id="name1_template을-업데이트-후-재-확인">name1_template을 업데이트 후 재 확인</h4>
<pre><code>PUT _template/name1_template
{
  &quot;index_patterns&quot;: [
    &quot;name1*&quot;
  ],
  &quot;settings&quot;: {
    &quot;index&quot;: {
      &quot;number_of_shards&quot;: 3,
      &quot;number_of_replicas&quot;: 1
    }
  }
}

DELETE name1-test
PUT name1-test
GET name1-test


{
  &quot;name1-test&quot; : {
    &quot;aliases&quot; : { },
    &quot;mappings&quot; : { },
    &quot;settings&quot; : {
      &quot;index&quot; : {
        &quot;routing&quot; : {
          &quot;allocation&quot; : {
            &quot;include&quot; : {
              &quot;_tier_preference&quot; : &quot;data_content&quot;
            }
          }
        },
        &quot;number_of_shards&quot; : &quot;3&quot;,
        &quot;provided_name&quot; : &quot;name1-test&quot;,
        &quot;creation_date&quot; : &quot;1673939428414&quot;,
        &quot;number_of_replicas&quot; : &quot;1&quot;,
        &quot;uuid&quot; : &quot;6ZsbX6MaTvmNb_9RGc0FRw&quot;,
        &quot;version&quot; : {
          &quot;created&quot; : &quot;7170799&quot;
        }
      }
    }
  }
}
</code></pre><p>결과적으로 template order가 같다면, 나중 생성된template이 우선되는 거였다.
우리는 basic_shard_setting에만 number_of_shard를 적용해 놓을 거지만, 양쪽에 있을 때 어떤 이유에서든 template이 업데이트 되면 업데이트 된 것으로 적용되니 유의해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[KAFKA 데이터지우기]]></title>
            <link>https://velog.io/@diane_at_work/KAFKA-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%A7%80%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@diane_at_work/KAFKA-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%A7%80%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Thu, 31 Mar 2022 01:30:00 GMT</pubDate>
            <description><![CDATA[<p>kafka 설정에 log.retention을 줄이고 한대씩 재기동</p>
<p>log.retention.hours=1 
log.retention.ms=1000</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch reindex 여러 인덱스 한번에 할때는 index이름을 변경해야한다.]]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-reindex-%EC%97%AC%EB%9F%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%ED%95%9C%EB%B2%88%EC%97%90-%ED%95%A0%EB%95%8C%EB%8A%94-index%EC%9D%B4%EB%A6%84%EC%9D%84-%EB%B3%80%EA%B2%BD%ED%95%B4%EC%95%BC%ED%95%9C%EB%8B%A4</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-reindex-%EC%97%AC%EB%9F%AC-%EC%9D%B8%EB%8D%B1%EC%8A%A4-%ED%95%9C%EB%B2%88%EC%97%90-%ED%95%A0%EB%95%8C%EB%8A%94-index%EC%9D%B4%EB%A6%84%EC%9D%84-%EB%B3%80%EA%B2%BD%ED%95%B4%EC%95%BC%ED%95%9C%EB%8B%A4</guid>
            <pubDate>Wed, 29 Sep 2021 05:44:12 GMT</pubDate>
            <description><![CDATA[<h3 id="reindex시-index명-바꾸며-그대로-이동">reindex시 index명 바꾸며 그대로 이동</h3>
<p>아래와 같이 쓰면, 모든 index들이 그대로 신규 clsuter로 이동한다. </p>
<p>log-test-1 -&gt; log-test2-1
log-test-2 -&gt; log-test2-2</p>
<pre><code>POST _reindex?wait_for_completion=false
{
  &quot;conflicts&quot;: &quot;proceed&quot;,
  &quot;source&quot;: {
    &quot;remote&quot;: {
      &quot;host&quot;: &quot;http://ip:port&quot;,
      &quot;username&quot;: &quot;elastic&quot;,
      &quot;password&quot;: &quot;elastic&quot;
    },
    &quot;index&quot;: &quot;log-test-*&quot;
  },
  &quot;dest&quot;: {
    &quot;index&quot;: &quot;log-test2&quot;,
    &quot;op_type&quot;: &quot;create&quot;
  },
  &quot;script&quot;: {
    &quot;lang&quot;: &quot;painless&quot;,
    &quot;source&quot;: &quot;ctx._index = &#39;log-test2&#39; + (ctx._index.substring(&#39;log-test-&#39;.length(), ctx._index.length()))&quot;
  }
}</code></pre><p>es공식문서에서 위와같이 가이드하는데, 도데체 ctx._index가 remote의 index인지 dest의 index인지 헷갈린다.</p>
<h3 id="reindex시-index명을-그대로-두고는-이동-못함">reindex시 index명을 그대로 두고는 이동 못함</h3>
<p>그런데 아래와 같이, index명을 유지하고자 하는 경우에는 의도와 다르게 log-test2 하나의 인덱스로 전부 이동한다.</p>
<p>log-test-1 -&gt; log-test2
log-test-2 -&gt; log-test2</p>
<pre><code>POST _reindex?wait_for_completion=false
{
  &quot;conflicts&quot;: &quot;proceed&quot;,
  &quot;source&quot;: {
    &quot;remote&quot;: {
      &quot;host&quot;: &quot;http://ip:port&quot;,
      &quot;username&quot;: &quot;elastic&quot;,
      &quot;password&quot;: &quot;elastic&quot;
    },
    &quot;index&quot;: &quot;log-test-*&quot;
  },
  &quot;dest&quot;: {
    &quot;index&quot;: &quot;log-test&quot;,
    &quot;op_type&quot;: &quot;create&quot;
  },
  &quot;script&quot;: {
    &quot;lang&quot;: &quot;painless&quot;,
    &quot;source&quot;: &quot;ctx._index = &#39;log-test&#39; + (ctx._index.substring(&#39;log-test-&#39;.length(), ctx._index.length()))&quot;
  }
}</code></pre><p><a href="https://discuss.elastic.co/t/reindex-multiple-indices-from-remote-with-the-same-name/171674/4">https://discuss.elastic.co/t/reindex-multiple-indices-from-remote-with-the-same-name/171674/4</a></p>
<p>위와 관련해 ES소스 내부에서 index이름이 달라졌을 경우에만 script를 적용하고 있다. 그래서 같은 이름으로 reindex하려면 index별로 건건히 해줘야함...
아호...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch auto rollover 적용]]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-auto-rollover-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-auto-rollover-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Mon, 22 Feb 2021 12:56:15 GMT</pubDate>
            <description><![CDATA[<p>elasticsearch에서 자동으로 index를 rollover하는 방식에 대해 알아보자.</p>
<p>주의할점은, rollover는 <strong>alias기준으로 수행</strong>되는것이지, index기준으로 수행되는것이 아니다. </p>
<p>index는 rollover시마다 이름이 바뀌기 때문에, rollover시에도 항상 유지할 alias에 대해 rollover rule을 규정하는것이다.</p>
<h4 id="rolling-policy-만들기">rolling policy 만들기</h4>
<p>kibana의 &gt; Stack Management &gt; Index Management에서 policy를 생성한다.</p>
<p>아래와같이 이름은 rolling, 1GB / 2개문서 / 1days 로 생성했을시, 3개 중 하나라도 먼저 도달하는 것이 있으면 index가 rolling된다.</p>
<p>(키바나 ui가 없으면 아래 api를 날려도 됨)</p>
<pre><code>PUT _ilm/policy/rolling
{
  &quot;policy&quot;: {
    &quot;phases&quot;: {
      &quot;hot&quot;: {
        &quot;actions&quot;: {
          &quot;rollover&quot;: {
            &quot;max_age&quot;: &quot;1d&quot;,
            &quot;max_primary_shard_size&quot;: &quot;1gb&quot;,
            &quot;max_docs&quot;: 2
          },
          &quot;set_priority&quot;: {
            &quot;priority&quot;: 100
          }
        },
        &quot;min_age&quot;: &quot;0ms&quot;
      }
    }
  }
}</code></pre><h4 id="index-template생성">index template생성</h4>
<p>아래와같이 template을 설정하였다.</p>
<pre><code class="language-json">PUT _template/rolling
{
  &quot;order&quot;: 1,
  &quot;version&quot;: 1,
  &quot;index_patterns&quot;: [
    &quot;rolling*&quot;
  ],
  &quot;settings&quot;: {
    &quot;index&quot;: {
      &quot;lifecycle&quot;: {
        &quot;name&quot;: &quot;rolling&quot;,
        &quot;rollover_alias&quot;: &quot;rolling-write-index&quot;
      },
      &quot;number_of_shards&quot; : &quot;1&quot;,
      &quot;number_of_replicas&quot; : &quot;0&quot;
    }
  }
}</code></pre>
<p>rolling이라는 이름으로 시작하는 모든 index에, rolling 라이프사이클을 적용하였고, </p>
<p>rollover대상의 alias는 rolling-write-index라는 alias이다.</p>
<h4 id="index와-alias-생성">index와 alias 생성</h4>
<pre><code>PUT rolling-test-000001
{
  &quot;aliases&quot;: {
    &quot;rolling-write-index&quot;: { &quot;is_write_index&quot;: true } 
  }
}</code></pre><p>alias를 처음에는 template에 넣어뒀다가, 아래와같은 error와 함께 rolling이 제대로 안되서 index시점에 따로 alias를 생성해주었다.</p>
<pre><code class="language-&quot;step_info&quot;">        &quot;type&quot; : &quot;illegal_argument_exception&quot;,
        &quot;reason&quot; : &quot;Rollover alias [rolling-write-index] can point to multiple indices, found duplicated alias [[rolling-write-index]] in index template [rolling]&quot;,</code></pre>
<p>마지막에 더 얘기하겠지만 alias는 template에 넣어서는 안되고 따로 지정해야한다.
template에 넣었을시에는 &quot;is_write_index&quot;: true 가 먹지 않는다.</p>
<h4 id="해당-index에-3개의-doc을-생성함">해당 index에 3개의 doc을 생성함</h4>
<pre><code class="language-json">POST rolling-write-index/_doc
{&quot;id&quot;:1}
POST rolling-write-index/_doc
{&quot;id&quot;:2}
POST rolling-write-index/_doc
{&quot;id&quot;:3}
</code></pre>
<h4 id="ilm상태-확인">ilm상태 확인</h4>
<pre><code>GET rolling-write-index/_ilm/explain</code></pre><pre><code class="language-json">{
  &quot;indices&quot; : {
    &quot;rolling-test-000001&quot; : {
      &quot;index&quot; : &quot;rolling-test-000001&quot;,
      &quot;managed&quot; : true,
      &quot;policy&quot; : &quot;rolloing&quot;,
      &quot;lifecycle_date_millis&quot; : 1613977180668,
      &quot;age&quot; : &quot;16.72s&quot;,
      &quot;phase&quot; : &quot;hot&quot;,
      &quot;phase_time_millis&quot; : 1613977180704,
      &quot;action&quot; : &quot;unfollow&quot;,
      &quot;action_time_millis&quot; : 1613977180755,
      &quot;step&quot; : &quot;wait-for-follow-shard-tasks&quot;,
      &quot;step_time_millis&quot; : 1613977180766,
      &quot;phase_execution&quot; : {
        &quot;policy&quot; : &quot;rolloing&quot;,
        &quot;phase_definition&quot; : {
          &quot;min_age&quot; : &quot;0ms&quot;,
          &quot;actions&quot; : {
            &quot;rollover&quot; : {
              &quot;max_size&quot; : &quot;1gb&quot;,
              &quot;max_age&quot; : &quot;1d&quot;,
              &quot;max_docs&quot; : 2
            },
            &quot;set_priority&quot; : {
              &quot;priority&quot; : 100
            }
          }
        },
        &quot;version&quot; : 1,
        &quot;modified_date_in_millis&quot; : 1613976026352
      }
    }
  }
}</code></pre>
<p>당장 수행되지않고 status가 &quot;wait-for-follow-shard-tasks&quot; 이다. ( status가  &quot;check-rollover-ready&quot;의 경우 아직 rollover 기준에 도달하지 못했다는 것이다.)</p>
<p>아래와 같이, 기본 ILM poll 주기가 10분이라고한다.</p>
<blockquote>
<p>By default the poll interval for ILM is 10 minutes, if you want to test your policy (with a 1 minute rollover), you can lower that by changing the dynamic <code>indices.lifecycle.poll_interval</code> setting.</p>
</blockquote>
<p>아래처럼 10초로 바꿀수있다.</p>
<pre><code>PUT /_cluster/settings
{
  &quot;transient&quot; : {
    &quot;indices.lifecycle.poll_interval&quot; : &quot;10s&quot;
  }
}</code></pre><p>10분후 확인해보면 000002로 rolling되었음을 확인할수있다.</p>
<pre><code>GET rolling-write-index/_ilm/explain</code></pre><pre><code>{
  &quot;indices&quot; : {
    &quot;rolling-test-000002&quot; : {
      &quot;index&quot; : &quot;rolling-test-000002&quot;,
      &quot;managed&quot; : true,
      &quot;policy&quot; : &quot;rolloing&quot;,
      &quot;lifecycle_date_millis&quot; : 1613983439108,
      &quot;age&quot; : &quot;1.75m&quot;,
      &quot;phase&quot; : &quot;hot&quot;,
      &quot;phase_time_millis&quot; : 1613983439190,
      &quot;action&quot; : &quot;unfollow&quot;,
      &quot;action_time_millis&quot; : 1613983439236,
      &quot;step&quot; : &quot;wait-for-follow-shard-tasks&quot;,
      &quot;step_time_millis&quot; : 1613983439265,
      &quot;phase_execution&quot; : {
        &quot;policy&quot; : &quot;rolloing&quot;,
        &quot;phase_definition&quot; : {
          &quot;min_age&quot; : &quot;0ms&quot;,
          &quot;actions&quot; : {
            &quot;rollover&quot; : {
              &quot;max_size&quot; : &quot;1gb&quot;,
              &quot;max_age&quot; : &quot;1d&quot;,
              &quot;max_docs&quot; : 2
            },
            &quot;set_priority&quot; : {
              &quot;priority&quot; : 100
            }
          }
        },
        &quot;version&quot; : 1,
        &quot;modified_date_in_millis&quot; : 1613976026352
      }
    }
  }
}</code></pre><h4 id="index가-rolling-굴러가는-모양이다">index가 rolling, 굴러가는 모양이다.</h4>
<p>롤링하는 순간 
<strong>rolling-test-000001</strong>이 </p>
<pre><code>&quot;rolling-write-index&quot;: {
      &quot;is_write_index&quot;: false
    }</code></pre><p>가 되고</p>
<p><strong>rolling-test-000002</strong>는 </p>
<pre><code>&quot;rolling-write-index&quot;: {
      &quot;is_write_index&quot;: true
    }</code></pre><p>가 된다.</p>
<p>즉 rolling-write-index로 검색하면 두 index모두에서 검색가능하지만, indexing하면 000002에만 인덱싱된다. 000001 인덱스의 크기는 더이상 늘지 않는 것이다.</p>
<h4 id="주의할점은-template에-rollover대상인-alias를-넣어선-안된다는-것">주의할점은, template에 rollover대상인 alias를 넣어선 안된다는 것.</h4>
<p>처음에는 template에 index생성시 alias를 생성하도록 아래와 같이 넣어뒀었다.</p>
<pre><code class="language-json">PUT _template/rolling
{
  &quot;order&quot;: 1,
  &quot;version&quot;: 1,
  &quot;index_patterns&quot;: [
    &quot;rolling*&quot;
  ],
#  &quot;aliases&quot;: {
#    &quot;rolling-write-index&quot;: {
#      &quot;is_write_index&quot;: true
#    }
#  },
  &quot;settings&quot;: {
    &quot;index&quot;: {
      &quot;lifecycle&quot;: {
        &quot;name&quot;: &quot;rolloing&quot;,
        &quot;rollover_alias&quot;: &quot;rolling-write-index&quot;
      },
      &quot;number_of_shards&quot; : &quot;1&quot;,
      &quot;number_of_replicas&quot; : &quot;0&quot;
    }
  }
}</code></pre>
<p>그런데, 이런 경우 rollover시 아래와 같은 에러가 나며 rolling이 제대로 수행되지 않는다.</p>
<pre><code>     &quot;step_info&quot; : {
        &quot;type&quot; : &quot;illegal_argument_exception&quot;,
        &quot;reason&quot; : &quot;Rollover alias [rolling-write-index] can point to multiple indices, found duplicated alias [[rolling-write-index]] in index template [rolling]&quot;,</code></pre><p>policy에 적용되어있는 rolling alias template에 넣지 않고 생성시 한번 따로 생성해야한다.</p>
<h4 id="추가-수동-rollover시">추가: 수동 rollover시</h4>
<p>매핑이 계속 수정되면서 rollover로 신규 템플릿을 적용해왔다.</p>
<pre><code>PUT rolling-write-index/_rollover</code></pre><p>근데 이런 경우 실제 이미 수동 rollover된 index가 ILM에 의해서 또 rollover시도하면서 아래와 같은 error가 난다.</p>
<pre><code>index [rolling-test-000002] is not the write index for alias [rolling-write-index]
</code></pre><p>index를 수동으로 rollover하는 경우 아래와같이 수동으로 complete처리를 해줘야한다.</p>
<pre><code>PUT rolling-test-000002/_settings
{ index.lifecycle.indexing_complete : true }</code></pre><p>수동 complete처리 후 해당 index에 ilm이 제대로 동작하는지 아래와같이 재시도하여 확인할수있다.</p>
<pre><code>POST rolling-test-000002/_ilm/retry</code></pre><p>정상의 경우 ack : true로 리턴된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch 기본 score 계산 방법]]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-%EA%B8%B0%EB%B3%B8-score-%EA%B3%84%EC%82%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-%EA%B8%B0%EB%B3%B8-score-%EA%B3%84%EC%82%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 11 Dec 2020 10:58:32 GMT</pubDate>
            <description><![CDATA[<h4 id="elasticsearch-기본-score는-bm25-알고리즘으로-계산된다">Elasticsearch 기본 score는 BM25 알고리즘으로 계산된다.</h4>
<pre><code class="language-json">GET file/_search?explain=true
{
  &quot;query&quot;: {
    &quot;match&quot;: {
      &quot;file.name&quot;: &quot;test&quot;
    }
  },
  &quot;_source&quot;: &quot;file.name&quot;, 
  &quot;sort&quot;: [
    &quot;_score&quot;
  ]
}</code></pre>
<p>검색시 explain=true값을 인자로 넘기면, score가 어떻게 계산되었는지 자세히 설명해준다.</p>
<p>아래 _explanation부분을 보자.</p>
<pre><code class="language-json">{
          &quot;value&quot; : 1.6962869,
          &quot;description&quot; : &quot;weight(file.objtNm:test in 347841) [PerFieldSimilarity], result of:&quot;,
          &quot;details&quot; : [
            {
              &quot;value&quot; : 1.6962869,
              &quot;description&quot; : &quot;score(doc=347841,freq=30.0 = termFreq=30.0\n), product of:&quot;,
              &quot;details&quot; : [
                {
                  &quot;value&quot; : 0.94338167,
                  &quot;description&quot; : &quot;idf, computed as log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5)) from:&quot;,
                  &quot;details&quot; : [
                    {
                      &quot;value&quot; : 18014.0,
                      &quot;description&quot; : &quot;docFreq&quot;,
                      &quot;details&quot; : [ ]
                    },
                    {
                      &quot;value&quot; : 46272.0,
                      &quot;description&quot; : &quot;docCount&quot;,
                      &quot;details&quot; : [ ]
                    }
                  ]
                },
                {
                  &quot;value&quot; : 1.7980919,
                  &quot;description&quot; : &quot;tfNorm, computed as (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength)) from:&quot;,
                  &quot;details&quot; : [
                    {
                      &quot;value&quot; : 30.0,
                      &quot;description&quot; : &quot;termFreq=30.0&quot;,
                      &quot;details&quot; : [ ]
                    },
                    {
                      &quot;value&quot; : 1.2,
                      &quot;description&quot; : &quot;parameter k1&quot;,
                      &quot;details&quot; : [ ]
                    },
                    {
                      &quot;value&quot; : 0.75,
                      &quot;description&quot; : &quot;parameter b&quot;,
                      &quot;details&quot; : [ ]
                    },
                    {
                      &quot;value&quot; : 102.28585,
                      &quot;description&quot; : &quot;avgFieldLength&quot;,
                      &quot;details&quot; : [ ]
                    },
                    {
                      &quot;value&quot; : 728.0,
                      &quot;description&quot; : &quot;fieldLength&quot;,
                      &quot;details&quot; : [ ]
                    }
                  ]
                }
              ]
            }
          ]
        }</code></pre>
<ol>
<li><p>score값인  1.6962869는  details array에 있는 두개의 value의 곱이다.</p>
<pre><code class="language-javascript">details.value(score) = details.details[0].value * details.details[1].value</code></pre>
</li>
<li><p>details.details[0].value 는 IDF이다.</p>
<pre><code>IDF = log(1 + (docCount - docFreq + 0.5) / (docFreq + 0.5))</code></pre><p>IDF( (Inverse Document Frequency)는 위의 수식으로 결정되는데,   </p>
<p>결국 docFreq가 낮을수록 IDF가 크다는걸 알 수 있다.</p>
<p><strong>즉, 문서에 해당 키워드가 등장하는 빈도가 작을수록, IDF가 커지면서 score도 올라간다.</strong></p>
</li>
</ol>
<ol start="3">
<li><p>details.details[1].value 는 tfNorm이다.</p>
<pre><code>tfNorm = (freq * (k1 + 1)) / (freq + k1 * (1 - b + b * fieldLength / avgFieldLength))</code></pre><p>tfNorm(Term Frequency)는 위의 수식으로 결정되는데, 
여기서 k1과 b는 가중치를 매기는 정해진 상수이니 무시하고, </p>
<p>freq(문서에 매칭된 키워드 수)이 높을수록</p>
<p>fieldLength(평균 필드의 길이)대비 avgFieldLength(검색된 문서의 필드 길이)가 클수록 </p>
<p>TF의 크기가 커지는것을 알수있다.</p>
<p><strong>즉, 검색된 문서에 매칭된 키워드수가 자주 반복될수록, 또 평균 필드 길이보다 검색된 문서의 필드가 길수록 score가 올라간다</strong>.</p>
</li>
</ol>
<hr>
<p>종합해보자면 BM25 알고리즘에서 score는 아래와 같이 계산되고</p>
<pre><code>score = TF * tfNorm</code></pre><p>전체 문서에서 해당 키워드가 등장하는 빈도가 작을수록, 즉 해당 문서에만 검색되는 독특한 단어일수록, 검색된 문서에 해당 키워드가 자주 반복될수록 score는 올라간다.</p>
<p> 기존 TF-IDF보다 BM25가 더 정교한 score계산을 하기 때문에 lucene도 BM25를 적용하였고 따라서 elasticsearch도 6.3버전부터 score계산에 BM25알고리즘을 적용하고 있다고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch 'mapper_parsing_exception']]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-mapperparsingexception</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-mapperparsingexception</guid>
            <pubDate>Tue, 01 Dec 2020 08:27:59 GMT</pubDate>
            <description><![CDATA[<p>Join field에 null이 들어가면 fail to parse exception이 남.</p>
<pre><code>{
  &quot;error&quot; : {
    &quot;root_cause&quot; : [
      {
        &quot;type&quot; : &quot;mapper_parsing_exception&quot;,
        &quot;reason&quot; : &quot;failed to parse&quot;
      }
    ],
    &quot;type&quot; : &quot;mapper_parsing_exception&quot;,
    &quot;reason&quot; : &quot;failed to parse&quot;,
    &quot;caused_by&quot; : {
      &quot;type&quot; : &quot;illegal_state_exception&quot;,
      &quot;reason&quot; : &quot;[null] expected START_OBJECT or VALUE_STRING but was: VALUE_NULL&quot;
    }
  },
  &quot;status&quot; : 400
}
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[consumer startOffset 안먹는 현상]]></title>
            <link>https://velog.io/@diane_at_work/consumer-startOffset-%EC%95%88%EB%A8%B9%EB%8A%94-%ED%98%84%EC%83%81</link>
            <guid>https://velog.io/@diane_at_work/consumer-startOffset-%EC%95%88%EB%A8%B9%EB%8A%94-%ED%98%84%EC%83%81</guid>
            <pubDate>Thu, 03 Sep 2020 12:50:22 GMT</pubDate>
            <description><![CDATA[<h3 id="kafka-cloud-stream-설정-중-startoffset-latest-안먹음">Kafka cloud stream 설정 중 startOffset: latest 안먹음</h3>
<pre><code>    combine:
      destination: {action명}
      binder: kafka
      group: {group}
      consumer:
        batch-mode: true
        ackEachRecord: false
        startOffset: latest</code></pre><p>이렇게 해놔도 latest가 아닌 earlist부터 가져옴. 
원인은.. <strong>group명을 지정하면 startOffset이 earlist로 세팅됨.</strong>
그래서 그간 쌓인 몇백만개를 다 읽어오려고함.. @.@</p>
<p><a href="https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html">https://cloud.spring.io/spring-cloud-stream-binder-kafka/spring-cloud-stream-binder-kafka.html</a> </p>
<p><a href="https://github.com/spring-cloud/spring-cloud-stream-binder-kafka/issues/67">https://github.com/spring-cloud/spring-cloud-stream-binder-kafka/issues/67</a> </p>
<blockquote>
<p>Our suggested workaround is to manage offsets using the Kafka consumer group management CLI tooling (<a href="http://kafka.apache.org/0102/documentation.html#basic_ops_consumer_group">http://kafka.apache.org/0102/documentation.html#basic_ops_consumer_group</a>) between application restarts. (Also set the startOffset to earliest or latest so that the application can restart at the proper offset).</p>
</blockquote>
<p>=&gt; anomynous그룹을 쓰거나 컨슈머 그룹 매니지먼트를 써서 offset을 관리해라.</p>
<h3 id="아래와같이-offset을-latest로-바꿈">아래와같이 offset을 latest로 바꿈..</h3>
<pre><code>
./kafka-consumer-groups.sh \

--bootstrap-server {ip}:9092 \

--group {group} 

--topic {topic}\

--reset-offsets --to-latest --execute

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL binlog설정]]></title>
            <link>https://velog.io/@diane_at_work/MySQL-Binlog%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@diane_at_work/MySQL-Binlog%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 27 Aug 2020 10:17:36 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>mysql binlog설정위치</p>
<pre><code class="language-properties">server-id={서버아이디} 마스터와 슬레이브의 서버는 달라야함
log-bin={빈로그 쌓이는 곳}
binlog_format = ROW {유형} //STATEMENT, ROW, MIXED
max_binlog_size = 1G {롤링할 크기}
expire_logs_days = 2 {보관 기간}
relay_log={슬레이브의 로그가 쌓이는 곳}</code></pre>
<ul>
<li><p>binlog_format</p>
<pre><code>이 옵션을 사용해서 디폴트 포맷을 지정하면, 모든 리플리케이션 슬레이브는 이벤트를 지정 포맷으로 읽게 된다.
ROW — 열 기반 리플리케이션을 디폴트로 설정한다.
STATEMENT — 명령문 기반 리플리케이션을 디폴트로 설정한다.
MIXED — 혼합 기반 리플리케이션을 디폴트로 설정한다.</code></pre></li>
</ul>
</li>
</ul>
<ul>
<li><p>binlog  읽기 위한 권한</p>
<pre><code class="language-sql">GRANT REPLICATION SLAVE, REPLICATION CLIENT ON . TO  {ID}@&#39;{IP}&#39;  IDENTIFIED BY &#39;{PWD}&#39;;</code></pre>
<p><strong>GRANT REPLICATION SLAVE</strong> : Enable the user to ask where master or slave servers are
<strong>REPLICATION CLIENT</strong> : Enable replication slaves to read binary log events from the master</p>
</li>
</ul>
<ul>
<li><p>binlog 파일 읽기</p>
<ul>
<li>파일 원문으로 떨구기</li>
</ul>
<pre><code> mysqlbinlog /{경로}/mysql-bin.000004 &gt; binlog.sql</code></pre></li>
</ul>
<ul>
<li><p>binlog 특정 날짜 원문으로 보기</p>
<pre><code class="language-curl">mysqlbinlog --verbose --database=DB이름 --start-datetime=&quot;{시작시간}&quot; --stop-datetime=&quot;{종료시간}&quot; &quot;{binlog경로}&quot; &gt; binlog.sql

ex)./mysqlbinlog --verbose --database=iofficemetadb --start-datetime=&quot;2020-08-20 18:00:00&quot; --stop-datetime=&quot;2020-08-20 20:00:00&quot; &quot;/mysqldata/3406/log/blog/mysql-bin.002185&quot; &gt;/home/ioffice/temp/binlog.sql

/home/ioffice/temp
</code></pre>
</li>
<li><p>특정 포지션을 원문으로 보기</p>
<pre><code class="language-curl">mysqlbinlog --verbose --database=testdb --start-position=1 --stop-position=100000 {binlog경로} &gt; binlog.sql</code></pre>
</li>
<li><p>로컬에 mysql설치 후 빈로그 파일 </p>
<pre><code class="language-cmd">#window local test
mysqlbinlog.exe -v --base64-output=DECODE-ROWS --stop-never --read-from-remote-server --user=root --password=root binlog.000003

mysqlbinlog: Error writing file &#39;&#39; (Errcode: 2 - No such file or directory)

#보니깐 해당 로그를 파일로 써야되나보다 그냥 나는 cmd창에서 계속 보고싶던건데...
mysqlbinlog.exe -v --base64-output=DECODE-ROWS --stop-never --read-from-remote-server --user=root --password=root binlog.000005 &gt; ./binlog000005.txt</code></pre>
<pre><code class="language-verilog">/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= &#39;273990e1-e37e-11ea-9449-988389a1841d:13&#39;/*!*/;
# at 1718
#200826 17:52:36 server id 1  end_log_pos 1803 CRC32 0x55fae6e5     Query    thread_id=3    exec_time=0    error_code=0
SET TIMESTAMP=1598431956/*!*/;
BEGIN
/*!*/;
# at 5308
#200827 13:02:44 server id 1  end_log_pos 5545 CRC32 0x8ab098af     Table_map: `{db명}`.`{테이블명}` mapped to number 113
# at 5545
#200827 13:02:44 server id 1  end_log_pos 6614 CRC32 0x1b7d9757     Update_rows: table id 113 flags: STMT_END_F
### UPDATE `iofficeefss0001`.`tb_file_f`
### WHERE
###   @1=133900000002300000
###   @2=&#39;181128_1339-배치테스트용_변경금지 - 01-(물리없음)-133900000002300000&#39;
###   @3=&#39;TXT&#39;
###   @4=&#39;대용량 테스트용 파일&#39;
###   @5=&#39;111&#39;
###   @6=&#39;001&#39;
###   @7=1540660923
###   @8=147999346801600097
###   @9=NULL
###   @10=NULL
###   @11=1545941252
###   @12=NULL
###   @13=1111
###   @14=10
###   @15=147999346801600097
###   @16=1535390523
###   @17=1
###   @18=133900000002300110
###   @19=NULL
###   @20=NULL
###   @21=&#39;181128_1339-배치테스트용_변경금지 - 01-(물리없음)-133900000002300000&#39;
###   @22=133900000000100000
###   @23=154337031460200427
###   @24=&#39;015&#39;
###   @25=147927667597500027
###   @26=&#39;1132000000&#39;
###   @27=&#39;Y&#39;
###   @28=&#39;050&#39;
###   @29=1556385723
###   @30=&#39;N&#39;
###   @31=&#39;N&#39;
###   @32=&#39;TAG,INFO,VAL&#39;
###   @33=NULL
###   @34=&#39;EDTDEL&#39;
###   @35=NULL
###   @36=NULL
###   @37=154337033663200430
###   @38=&#39;015&#39;
###   @39=133900000000100000
###   @40=154597309410300287
###   @41=123456789012345670
###   @42=147927667597500027
###   @43=NULL
###   @44=NULL
###   @45=NULL
###   @46=NULL
###   @47=&#39;1132000000&#39;
###   @48=&#39;050&#39;
###   @49=1556385723
###   @50=NULL
###   @51=NULL
###   @52=&#39;W0&#39;
###   @53=&#39;9&#39;
###   @54=&#39;Y&#39;
###   @55=&#39;0123456789&#39;
###   @56=&#39;N&#39;
###   @57=&#39;DOCT_NO&#39;
###   @58=&#39;N&#39;
###   @59=&#39;REG_DSTCT&#39;
###   @60=&#39;TAG,INFO,VAL&#39;
###   @61=NULL
###   @62=NULL
###   @63=NULL
###   @64=&#39;EDTDEL&#39;
###   @65=&#39;PRSRV_RSN_CONTENT&#39;
###   @66=&#39;IF_TGT_SYS&#39;
###   @67=NULL
###   @68=NULL
###   @69=&#39;EFDSC10&#39;
###   @70=&#39;Y&#39;
###   @71=1543339323
###   @72=147999346801600097
###   @73=1545941252
###   @74=147999356147300023
### SET
###   @1=133900000002300000
###   @2=&#39;ioffice&#39;
###   @3=&#39;TXT&#39;
###   @4=&#39;대용량 테스트용 파일&#39;
###   @5=&#39;111&#39;
###   @6=&#39;001&#39;
###   @7=1540660923
###   @8=147999346801600097
###   @9=NULL
###   @10=NULL
###   @11=1598500964
###   @12=NULL
###   @13=1111
###   @14=10
###   @15=147999346801600097
###   @16=1535390523
###   @17=1
###   @18=133900000002300110
###   @19=NULL
###   @20=NULL
###   @21=&#39;181128_1339-배치테스트용_변경금지 - 01-(물리없음)-133900000002300000&#39;
###   @22=133900000000100000
###   @23=154337031460200427
###   @24=&#39;015&#39;
###   @25=147927667597500027
###   @26=&#39;1132000000&#39;
###   @27=&#39;Y&#39;
###   @28=&#39;050&#39;
###   @29=1556385723
###   @30=&#39;N&#39;
###   @31=&#39;N&#39;
###   @32=&#39;TAG,INFO,VAL&#39;
###   @33=NULL
###   @34=&#39;EDTDEL&#39;
###   @35=NULL
###   @36=NULL
###   @37=154337033663200430
###   @38=&#39;015&#39;
###   @39=133900000000100000
###   @40=154597309410300287
###   @41=123456789012345670
###   @42=147927667597500027
###   @43=NULL
###   @44=NULL
###   @45=NULL
###   @46=NULL
###   @47=&#39;1132000000&#39;
###   @48=&#39;050&#39;
###   @49=1556385723
###   @50=NULL
###   @51=NULL
###   @52=&#39;W0&#39;
###   @53=&#39;9&#39;
###   @54=&#39;Y&#39;
###   @55=&#39;0123456789&#39;
###   @56=&#39;N&#39;
###   @57=&#39;DOCT_NO&#39;
###   @58=&#39;N&#39;
###   @59=&#39;REG_DSTCT&#39;
###   @60=&#39;TAG,INFO,VAL&#39;
###   @61=NULL
###   @62=NULL
###   @63=NULL
###   @64=&#39;EDTDEL&#39;
###   @65=&#39;PRSRV_RSN_CONTENT&#39;
###   @66=&#39;IF_TGT_SYS&#39;
###   @67=NULL
###   @68=NULL
###   @69=&#39;EFDSC10&#39;
###   @70=&#39;Y&#39;
###   @71=1543339323
###   @72=147999346801600097
###   @73=1598500964
###   @74=147999356147300023
# at 6614
#200827 13:02:44 server id 1  end_log_pos 6645 CRC32 0x4b5d9a7a     Xid = 833
COMMIT/*!*/;</code></pre>
<pre><code class="language-verilog">SET @@SESSION.GTID_NEXT= &#39;273990e1-e37e-11ea-9449-988389a1841d:13&#39;/*!*/;</code></pre>
<p>gtid_mode의 설정이 &#39;ON&#39;일때만 위 GTID가 찍힘</p>
</li>
</ul>
<ul>
<li><p>아래 여러 옵션들이 있다.</p>
<ul>
<li><a href="https://m.blog.naver.com/PostView.nhn?blogId=sory1008&amp;logNo=220618121633&amp;proxyReferer=https:%2F%2Fwww.google.co.kr%2F">빈로그 옵션들</a></li>
</ul>
</li>
</ul>
<ul>
<li><p>MASTER의 빈로그 파일과 최종 Position</p>
<pre><code class="language-sql">SHOW MASTER STATUS;</code></pre>
<pre><code class="language-json">  {
          &quot;File&quot;: &quot;binlog.000009&quot;,
          &quot;Position&quot;: 5800,
          &quot;Binlog_Do_DB&quot;: &quot;&quot;,
          &quot;Binlog_Ignore_DB&quot;: &quot;&quot;,
          &quot;Executed_Gtid_Set&quot;: &quot;273990e1-e37e-11ea-9449-988389a1841d:1-43&quot;
      }</code></pre>
</li>
</ul>
<ul>
<li><p>이벤트 쿼리로 보기</p>
<pre><code class="language-sql">SHOW BINLOG EVENTS 
IN &#39;binlog.000009&#39; 
FROM 4873 LIMIT 10;</code></pre>
<p>FROM값이 정확히 BINLOG EVENTS의 Pos와 맞지 않으면 조회되지 않는다.</p>
<p>아래와같이 SHOW MASTER STATUS에서 조회한 Position의 값이 최종 End_log_pos인 것을 알 수 있다.</p>
</li>
</ul>
<pre><code class="language-json">  {
              &quot;Log_name&quot;: &quot;binlog.000009&quot;, #빈로그 파일명
              &quot;Pos&quot;: 4873, # 시작 위치
              &quot;Event_type&quot;: &quot;Anonymous_Gtid&quot;,#이벤트 타입
              &quot;Server_id&quot;: 1, 
              &quot;End_log_pos&quot;: 4938, # 끝나는 위치
              &quot;Info&quot;: &quot;SET @@SESSION.GTID_NEXT= &#39;ANONYMOUS&#39;&quot; # 빈로그에 at {Pos} 다음에 오는 end위치 다음 정보 한단어

          },</code></pre>
<pre><code>  # at 4873
#200831 18:30:30 server id 1  end_log_pos 4938 CRC32 0xc0d6daa3     Anonymous_GTID    last_committed=5    sequence_number=6    rbr_only=yes</code></pre><pre><code class="language-json">  {
      &quot;table&quot;: &quot;LIMIT&quot;,
      &quot;rows&quot;:
      [
          {
              &quot;Log_name&quot;: &quot;binlog.000009&quot;,
              &quot;Pos&quot;: 4873,
              &quot;Event_type&quot;: &quot;Anonymous_Gtid&quot;,
              &quot;Server_id&quot;: 1,
              &quot;End_log_pos&quot;: 4938,
              &quot;Info&quot;: &quot;SET @@SESSION.GTID_NEXT= &#39;ANONYMOUS&#39;&quot;
          },
          {
              &quot;Log_name&quot;: &quot;binlog.000009&quot;,
              &quot;Pos&quot;: 4938,
              &quot;Event_type&quot;: &quot;Query&quot;,
              &quot;Server_id&quot;: 1,
              &quot;End_log_pos&quot;: 5023,
              &quot;Info&quot;: &quot;BEGIN&quot;
          },
          {
              &quot;Log_name&quot;: &quot;binlog.000009&quot;,
              &quot;Pos&quot;: 5023,
              &quot;Event_type&quot;: &quot;Table_map&quot;,
              &quot;Server_id&quot;: 1,
              &quot;End_log_pos&quot;: 5260,
              &quot;Info&quot;: &quot;table_id: 108 (iofficeefss0002.tb_file_f)&quot;
          },
          {
              &quot;Log_name&quot;: &quot;binlog.000009&quot;,
              &quot;Pos&quot;: 5260,
              &quot;Event_type&quot;: &quot;Update_rows&quot;,
              &quot;Server_id&quot;: 1,
              &quot;End_log_pos&quot;: 5769,
              &quot;Info&quot;: &quot;table_id: 108 flags: STMT_END_F&quot;
          },
          {
              &quot;Log_name&quot;: &quot;binlog.000009&quot;,
              &quot;Pos&quot;: 5769,
              &quot;Event_type&quot;: &quot;Xid&quot;,
              &quot;Server_id&quot;: 1,
              &quot;End_log_pos&quot;: 5800,
              &quot;Info&quot;: &quot;COMMIT /* xid=302 */&quot;
          }
      ]
  }</code></pre>
<ul>
<li></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[LEVEL-DB]]></title>
            <link>https://velog.io/@diane_at_work/LEVEL-DB</link>
            <guid>https://velog.io/@diane_at_work/LEVEL-DB</guid>
            <pubDate>Tue, 21 Jul 2020 09:26:23 GMT</pubDate>
            <description><![CDATA[<h4 id="level-db란">LEVEL-DB란?</h4>
<blockquote>
<p>A fast and lightweight key-value  DB library made by Google</p>
</blockquote>
<p>key-value : NO-SQL</p>
<p>lightweight library : Embeded DB </p>
<h4 id="level-db특징">LEVEL-DB특징</h4>
<ul>
<li><p>웹 애플리케이션용 DB를 위해 만들었다.</p>
<p>데이터를 로컬에 저장하기 때문에 네트워크 유실등의 상황에 유리함.</p>
<p>크롬의 indexedDB를 구현하기 위해 만듬</p>
<blockquote>
<p>IndexedDB는 사용자의 브라우저에 데이터를 영구적으로 저장할 수 있는 방법 중 하나입니다. IndexedDB를 사용하여 네트워크 상태에 상관없이 풍부한 쿼리 기능을 이용할 수 있는 웹 어플리케이션을 만들 수 있기 때문에, 여러분의 웹 어플리케이션은 온라인과 오프라인 환경에서 모두 동작할 수 있습니다. <a href="https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API/Using_IndexedDB">https://developer.mozilla.org/ko/docs/Web/API/IndexedDB_API/Using_IndexedDB</a></p>
</blockquote>
<blockquote>
<p><strong>Indexed Database API</strong> 또는 <strong>IndexedDB</strong>(과거 이름: WebSimpleDB)는 색인이 포함된 JSON 객체가 모여있는 트랜잭셔널 로컬 데이터베이스를 위해 <a href="https://ko.wikipedia.org/wiki/W3C">W3C</a>가 권고한<a href="https://ko.wikipedia.org/wiki/Indexed_Database_API#cite_note-1">[1]</a> 웹 브라우저 표준 인터페이스의 하나이다. 웹사이트는 데이터베이스에서 영속적인 데이터를 모아서 저장할 수 있다. W3C는 2015년 1월 8일 IndexedDB 인터페이스를 위한 최종 권고안을 발행하였다.<a href="https://ko.wikipedia.org/wiki/Indexed_Database_API#cite_note-2">[2]</a></p>
</blockquote>
</li>
<li><p>data is stored by key  </p>
<p>ordered map이기 때문에 iteration 비용이 저렴, </p>
<p>forward / backward iteration is supported</p>
</li>
<li><p>basic operations are  put / get / key </p>
</li>
<li><p>Users can create snapshot </p>
</li>
<li><p>Data is automatically compressed using the Snappy compression library</p>
<p>snappy는 압축률은 높지 않지만 속도가 매우 빠름</p>
<p>초당 250M압축, 초당 500M압축 해제</p>
</li>
<li><p>Key와 Value는 Byte Arrays 타입 </p>
</li>
<li><p>전체적으로 뛰어난 read/write</p>
</li>
<li><p>이지만, value사이즈가 크면 write시 성능이 하락함</p>
<p>구현상 key와 value를 적어도 두번 복사하기 때문. (?)</p>
<p>역시나 update비용은 비쌈. delete and write이기때문</p>
</li>
<li><p>B-tree의 변형인 LSM이다.</p>
</li>
</ul>
<h4 id="설치">설치</h4>
<p>C++, node, python3로 지원하는데 node에 설치해보자. (<a href="https://www.npmjs.com/package/level">https://www.npmjs.com/package/level</a>)</p>
<pre><code class="language-npm">npm install level
//level = levelup(node) + leveldown(C++)</code></pre>
<h4 id="간단히-파일에-put해보기">간단히 파일에 put해보기</h4>
<pre><code class="language-javascript">var level = require(&#39;level&#39;)
const db = level(&quot;./mydb&quot;);

const put = (key, value) =&gt; {
    db.put(key, value, function (err) {
        if(err) {
            console.log(err);
        }
        db.get(key, function (err, value) {
            if (err) {
                console.log(err);
            }
            console.log(value);
        });
    });
};

put(&quot;111&quot;, &quot;ttt&quot;);
</code></pre>
<p>memory에 쓰려면 memdown 이라는 np를 install 하면 됨. </p>
<p>여러 확장 모듈들은 <a href="https://github.com/Level/awesome">여기서</a>참고</p>
<h4 id="batch로-넣어보기">BATCH로 넣어보기</h4>
<p>level-db의 장점은 빠른 batch수행임.</p>
<blockquote>
<h3 id="dbbatcharray-options-callback-array-form"><code>db.batch(array[, options][, callback])</code> <em>(array form)</em></h3>
<p><code>batch()</code> can be used for very fast bulk-write operations (both <em>put</em> and <em>delete</em>). The <code>array</code> argument should contain a list of operations to be executed sequentially, although as a whole they are performed as an atomic operation inside the underlying store.</p>
</blockquote>
<p>batch예시에 한국인이름이 있네? 싱기방기</p>
<pre><code class="language-javascript">const batch = () =&gt;{
    var ops = [
        { type: &#39;del&#39;, key: &#39;father&#39; },
        { type: &#39;put&#39;, key: &#39;name&#39;, value: &#39;Yuri Irsenovich Kim&#39; },
        { type: &#39;put&#39;, key: &#39;dob&#39;, value: &#39;16 February 1941&#39; },
        { type: &#39;put&#39;, key: &#39;spouse&#39;, value: &#39;Kim Young-sook&#39; },
        { type: &#39;put&#39;, key: &#39;occupation&#39;, value: &#39;Clown&#39; }
    ]

    db.batch(ops, function (err) {
        if (err) return console.log(&#39;Ooops!&#39;, err)
        console.log(&#39;Great success dear leader!&#39;)
    })

    db.get(&#39;spouse&#39;, (err, value) =&gt;{
        console.log(&quot;wow~&quot;, value);
    })
}

batch();</code></pre>
<pre><code class="language-javascript">Great success dear leader!
wow~ Kim Young-sook</code></pre>
<p>.... Yuri Irsenovich Kim =&gt; 김정일 이란다ㅋㅋㅋㅋ</p>
<h4 id="batch를-chaining하기">BATCH를 chaining하기</h4>
<pre><code class="language-javascript">const batchChain = () =&gt; {
    db.batch()
        .del(&#39;father&#39;)
        .put(&#39;binna&#39;, &#39;what?&#39;)
        .put(&#39;태황&#39;, &#39;what??&#39;)
        .put(&#39;지원&#39;, {&quot;type&quot;:&quot;mannerism&quot;, &quot;age&quot;:32})
        .put(&#39;다연&#39;, &#39;퇴근하구싶어요&#39;)
        .write(function () {
            console.log(&#39;Done!&#39;)
        })
}</code></pre>
<p>batch는 위와 같이 chaining 할 수 있음.</p>
<h4 id="그래서-어디에-쓰임">그래서 어디에 쓰임?</h4>
<blockquote>
<p>LevelDB is used as the backend database for <a href="https://en.wikipedia.org/wiki/Google_Chrome">Google Chrome</a>&#39;s <a href="https://en.wikipedia.org/wiki/IndexedDB">IndexedDB</a> and is one of the supported backends for <a href="https://en.wikipedia.org/wiki/Riak">Riak</a>.<a href="https://en.wikipedia.org/wiki/LevelDB#cite_note-8">[8]</a> Additionally, <a href="https://en.wikipedia.org/wiki/Bitcoin_Core">Bitcoin Core</a> and go-ethereum stores the <a href="https://en.wikipedia.org/wiki/Blockchain_(database)">blockchain</a> metadata using a LevelDB database.<a href="https://en.wikipedia.org/wiki/LevelDB#cite_note-9">[9]</a> <em><a href="https://en.wikipedia.org/wiki/Minecraft">Minecraft</a> Bedrock Edition</em> uses a modified version for chunk and entity data storage.<a href="https://en.wikipedia.org/wiki/LevelDB#cite_note-10">[10]</a> Autodesk AutoCAD 2016 also uses LevelDB.</p>
</blockquote>
<p>이더리움의 metadata를 저장. 마인크레프트의 entitiydata저장. AutoCad에서 사용함.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2. java filter plugin 개발]]></title>
            <link>https://velog.io/@diane_at_work/2.-java-filter-plugin-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@diane_at_work/2.-java-filter-plugin-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 12 Jun 2020 08:58:51 GMT</pubDate>
            <description><![CDATA[<h4 id="로그스태시-소스-빌드하기">로그스태시 소스 빌드하기</h4>
<p>로컬에 logstash 소스가 있어야만 filter plugin 빌드가 가능하다. </p>
<ol>
<li><p>logstash download</p>
<pre><code>https://github.com/elastic/logstash.git</code></pre></li>
<li><p>빌드하기</p>
<pre><code>gradlew.bat assemble</code></pre></li>
<li><p>해당 위치에 jar가 생성되었는지 확인</p>
<pre><code>{플젝위치}/build/libs에 logstash-core-*.*.*.jar생성되었는지 확인</code></pre></li>
<li><p>환경변수 설정</p>
<pre><code>set LS_SRC_HOME = {LOGSTASHCORE플젝위치}</code></pre></li>
</ol>
<h4 id="filter-plugin받아-작성하기">filter plugin받아 작성하기</h4>
<ol>
<li><p>filter - example download</p>
<pre><code> https://github.com/logstash-plugins/logstash-filter-java_filter_example</code></pre></li>
<li><p>C:\Users\SDS.gradle\gradle.properties에 아래 추가</p>
</li>
</ol>
<pre><code>```
LOGSTASH_CORE_PATH={LOGSTASHCORE플젝위치}/logstash-core/

#LOGSTASH_CORE_PATH=$LS_SRC_HOME/logstash-core/
#-&gt;아래꺼로 해도 된다고 하는데 뭔가 잘 안맞아서 그냥 절대경로로 세팅함..
#프록시가 있다면 그것도 여기에 넣어줘야함
systemProp.proxySet=true

systemProp.http.proxyHost={IP}
systemProp.http.proxyPort={PORT}
systemProp.http.nonProxyHosts={exclude할주소}|localhost

systemProp.https.proxyHost={IP}
systemProp.https.proxyPort={PORT}
systemProp.https.nonProxyHosts={exclude할주소}|localhost
```</code></pre><ol start="3">
<li><p>build.gradle수정 (dependency가져오기, 이름바꾸기)</p>
<p>나는 기존 maven repository에 배포된 것들을 가져와야해서 다음과 같이 gradle 설정을 바꿨다.</p>
<p>아래와같이 maven url 모두 추가 (settings.xml에 있는그대로)</p>
<pre><code class="language-groovy">repositories{
    maven {
        credentials {
            username &#39;{id}&#39;
            password &#39;{password}&#39;
        }
        url &quot;{url}&quot;
    }
}</code></pre>
<p>아래와 같이 dependencies 추가 (pom.xml에 있는 그대로)</p>
<pre><code class="language-groovy">compile &#39;{groupID}:{artifactId}:{version(RELEASE일 경우 +)}&#39;</code></pre>
<p>이름 바꿈</p>
<pre><code>pluginInfo.pluginClass     = &quot;{yourNewFilterName}&quot;
pluginInfo.pluginName =  &quot;{your_new_filter_name}&quot;</code></pre></li>
</ol>
<ol start="4">
<li><p>java plugin작성..</p>
<p>dependency로 가져온 것들 사용해가며 filter 작성..</p>
<p>위에서 지정한 {pluginInfo.pluginClass}로 class생성하여 만듬</p>
</li>
</ol>
<ol start="5">
<li>gem build</li>
</ol>
<pre><code>gradlew.bat gem</code></pre><ol start="6">
<li>아래와같이 파일 생김</li>
</ol>
<pre><code>logstash-filter-your_new_name-1.00.gem</code></pre><ol start="7">
<li><p>plugin install</p>
<p>여기서 삽질을 많이했는데, window에서는 경로의 backslash를 forwardslash로 바꿔야한다.</p>
<blockquote>
<p>For Windows platforms: Substitute backslashes for forward slashes as appropriate in the command.</p>
</blockquote>
<p>또 local plugin을 설치하는데 자꾸  <a href="https://rubygems.org/">https://rubygems.org </a>에 연결할수 없다고 나오는지 모르겠지만.. 환경변수에 아래와 같이 프록시를 설정했다.</p>
<pre><code>HTTPS_PROXY=https://{IP}:{PORT}
HTTP_PROXY=http://{IP}:{PORT}</code></pre><pre><code>bin\logstash-plugin.bat install --no-verify --local {경로}\logstash-filter-your_new_name-1.00.gem</code></pre><pre><code>Installation successful</code></pre></li>
<li><p>test</p>
<pre><code>input {
  kafka {
    bootstrap_servers =&gt; &quot;{IP:PORT}&quot;
    topics =&gt; &quot;{ACTION_NAME}&quot;
    group_id =&gt; &quot;log-to-es-group&quot;
    consumer_threads =&gt; 1
  }
}

filter {
    your_new_filter_name {}
}

output { 
  elasticsearch {
    hosts =&gt; [&quot;{ES_HOST}&quot;]
    index =&gt; &quot;log-insert&quot;
  }
  stdout { codec =&gt; rubydebug }
}</code></pre></li>
</ol>
<p>문제는 kafka input에서 byte[]의 kafkaMessage를 filter로 줄 때 String으로 변환하여 주고있다. 직렬화된 byte[]를 특정 Class로 역직렬화 하고 싶은데, String으로 변환해서 들어오니 Class -&gt; byte[] -&gt; String -&gt; byte[] -&gt; Class의 비효율이 생긴다.</p>
<p>또 String -&gt; byte[] -&gt; Class로 변환하려고 할때 아래와 같은 exception이 발생함..
byte변환 시 getBytes(&quot;8859_1&quot;)로 charsetName을 주면 누락이 없다고 해서 kafka input 설정의 encoding도 아래와 같이 놓고 시도해보았지만 변환은 계속 안된다.</p>
<pre><code>    codec =&gt; plain {
                   charset =&gt; &quot;ISO-8859-1&quot;
            }</code></pre><pre><code>invalid stream header: EFBFBDEF</code></pre><p>몇일째 삽질중이다. 로그스태시 input에서 어떤 일이 발생하는지 디버깅하기가 쉽지 않다. 다만 그냥 어떤 stream을 그대로 es에 손쉽게 때려박기는 용이한 서비스같다. 일단 flume을 작성해보고 생각하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ 1. logstash설치 및 실행]]></title>
            <link>https://velog.io/@diane_at_work/1.-LOGSTASH-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89</link>
            <guid>https://velog.io/@diane_at_work/1.-LOGSTASH-%EC%84%A4%EC%B9%98-%EB%B0%8F-%EC%8B%A4%ED%96%89</guid>
            <pubDate>Fri, 12 Jun 2020 08:46:32 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>kafka message -&gt; logstash -&gt; ES에 넣어만 보기</p>
<pre><code class="language-javscript">input {
  kafka {
    bootstrap_servers =&gt; &quot;182.193.17.35:9092&quot;
    topics =&gt; &quot;EfssAction&quot;
    group_id =&gt; &quot;log-to-es-group&quot;
    consumer_threads =&gt; 1
  }
}

filter {

}

output { 
  elasticsearch {
    hosts =&gt; [&quot;182.193.17.33:9220&quot;]
    index =&gt; &quot;log-insert&quot;
  }
  stdout { codec =&gt; rubydebug }
}</code></pre>
</li>
<li><p>kafka-client 버전확인.</p>
<p>kafka-input-plugin과 output-plugin이 integration으로 합쳐졌다고한다.</p>
<p>아래 들어가면 kafka-client버전을 확인 할 수 있음.. 호환되나 확인해봄</p>
<pre><code class="language-sh">D:\ELK\logstash-7.7.0\vendor\bundle\jruby\2.5.0\gems\logstash-integration-kafka-10.0.1-java\vendor\jar-dependencies\org\apache\kafka\kafka-clients
</code></pre>
</li>
<li><p>logstash 실행 </p>
<p>{logstash설치경로}로 이동 후 실행 </p>
<pre><code class="language-shell">bin\logstash.bat -f ./config/logstash.conf</code></pre>
</li>
</ul>
<ul>
<li><p>검색</p>
<pre><code class="language-json">  &quot;hits&quot; : {
    &quot;total&quot; : 1,
    &quot;max_score&quot; : 1.0,
    &quot;hits&quot; : [
      {
        &quot;_index&quot; : &quot;log-insert&quot;,
        &quot;_type&quot; : &quot;doc&quot;,
        &quot;_id&quot; : &quot;EKtJmHIB5PmJ0m_JzGP1&quot;,
        &quot;_score&quot; : 1.0,
        &quot;_source&quot; : {
          &quot;message&quot; : &quot;com.***.***.kafka.model.KafkaMessage...</code></pre>
<p>메세지가 잘 indexing되지만 보내는 쪽에서 특정 java class로 wrapping해서 보내고 있음.</p>
<p>이거를 적절히 filter하고 가공하려면 java filter plugin을 개발해야 함</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[0. LOG TO ES 전환 ]]></title>
            <link>https://velog.io/@diane_at_work/0.-LOG-TO-ES-%EC%A0%84%ED%99%98</link>
            <guid>https://velog.io/@diane_at_work/0.-LOG-TO-ES-%EC%A0%84%ED%99%98</guid>
            <pubDate>Fri, 12 Jun 2020 08:45:33 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>기존 audit_log를 kafka message로 쏘고 cassandra로 넣고있는데, cassandra가 조회가 이런저런 요건에 맡게 조회 하기가 힘들고, 활용도가 낮아(카산드라도 물론 설계를 잘 하면 되겠지만 현재는 그렇지 않다.) ES로 전환하기로 했다. </p>
</li>
<li><p>기존 레거시를 유지하며, 추가적으로 es로 넣는 방법은?</p>
<ol>
<li><p>db변경사항을 추적하여 logstash로 넣음</p>
<p>-&gt; 문제는 사용자 각 action을 알수있을까? 데이터변경으로 각 action을 특정할 수 있어야함.</p>
<p>-&gt; PAD편집, 히스토리 검색 등은.... 알수가없다. 단말구분, 생성시 copy인지 new인지 등 구분이 안됨.</p>
</li>
</ol>
</li>
</ul>
<ol start="2">
<li><p>logstash 사용. </p>
</li>
<li><p>flume sink 추가하여 es에 넣기</p>
<p>일단 2번은 POC해보기로.</p>
</li>
</ol>
<ul>
<li>logstash vs flume?</li>
</ul>
<p>logstash -&gt;</p>
<ol>
<li>es에 손쉽게 넣을수있다. 
but 다만 우리처럼 db에서 추가정보를 가져와서 가공하고 업무요건이 복잡하게 많은 서비스에는 활용이 어렵다.</li>
<li>뭐가좋은건지 정말 모르겠네</li>
</ol>
<ul>
<li><p>데이터 활용은? </p>
<p>데이터 활용 방안을 생각해보자.</p>
<ul>
<li>이상action감지</li>
<li>사용량 예측</li>
<li></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[maven import PKIX error]]></title>
            <link>https://velog.io/@diane_at_work/intellij-maven-import-PKIX-error</link>
            <guid>https://velog.io/@diane_at_work/intellij-maven-import-PKIX-error</guid>
            <pubDate>Fri, 05 Jun 2020 00:55:06 GMT</pubDate>
            <description><![CDATA[<h4 id="1-cmd열기">1. cmd열기</h4>
<p>C:\Program Files\Java\jdk1.8.0_251\bin 으로 이동
shift + 우클릭 &gt; 여기서 cmd창 열기 (관리자모드로 실행)</p>
<h4 id="2-인증서-import">2. 인증서 import</h4>
<pre><code>keytool -import -file &quot;D:\{인증서}&quot; -keystore &quot;C:\Program Files\Java\jdk1.8.0_251\jre\lib\security\cacerts&quot; -storepass &quot;changeit&quot;</code></pre><pre><code>keytool -import -file &quot;D:\{인증서}&quot; -keystore cacerts -storepass &quot;changeit&quot;</code></pre><h4 id="3-확인">3. 확인</h4>
<p>확인하기</p>
<pre><code>keytool -list - v</code></pre><h4 id="인증서가-없다면">인증서가 없다면?</h4>
<p>브라우저 설정 &gt; 인증서 &gt; 내보내기 해서 받자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch nested query : nested에 should와 must를 함께 쓰는 경우 inner_hit 안맞는 이슈]]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-nested-query-nested%EC%97%90-should%EC%99%80-must%EB%A5%BC-%ED%95%A8%EA%BB%98-%EC%93%B0%EB%8A%94-%EA%B2%BD%EC%9A%B0-innerhit-%EC%95%88%EB%A7%9E%EB%8A%94-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-nested-query-nested%EC%97%90-should%EC%99%80-must%EB%A5%BC-%ED%95%A8%EA%BB%98-%EC%93%B0%EB%8A%94-%EA%B2%BD%EC%9A%B0-innerhit-%EC%95%88%EB%A7%9E%EB%8A%94-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Tue, 19 May 2020 05:31:19 GMT</pubDate>
            <description><![CDATA[<p>이번에 개발하다가 이상한 점을 발견했다.</p>
<p>기존 should로만 구성된 nested query에 필수 조건이 붙어 must를 추가로 넣었는데, 쿼리는 잘 되는 듯 보였으나, inner_hit이 제대로 나오지 않는 현상이다. 쿼리의 결과는 제대로 나오고 있어서 제대로 되고있다고 착각했다.</p>
<p>아래 예시 데이터로 설명하겠다.</p>
<ul>
<li><p>mapping</p>
<p>blogs라는 인덱스에 post doc이 있고, 그 안에는 nested로 해당 blog가 누구에게 공유되었는지(sharedTo)와 언제까지 공유가 유효한지(expiration)을 가지고 있다.  authority는 해당 post에대한 권한을 의미한다.</p>
</li>
</ul>
<pre><code class="language-json">PUT blogs
{
  &quot;mappings&quot;: {
    &quot;_doc&quot;: {
      &quot;properties&quot;: {
        &quot;post&quot;: {
          &quot;properties&quot;: {
            &quot;postId&quot;: {
              &quot;type&quot;: &quot;keyword&quot;
            },
            &quot;contents&quot;: {
              &quot;type&quot;: &quot;text&quot;
            }
          }
        },
        &quot;sharedPeople&quot;: {
            &quot;type&quot; : &quot;nested&quot;,
          &quot;properties&quot;: {
            &quot;expiration&quot;: {
              &quot;type&quot;: &quot;date&quot;
            },
            &quot;authority&quot;: {
              &quot;type&quot;: &quot;keyword&quot;
            },
            &quot;sharedTo&quot;:{
              &quot;type&quot;:&quot;keyword&quot;
            }
          }
        }
      }
    }
  }
}</code></pre>
<ul>
<li><p>doc 2개 추가</p>
<p>1번 doc은 A,B,C에게 공유되었고</p>
<p>2번 doc은 B,C에게 공유되었다.</p>
<pre><code class="language-json">PUT blogs/_doc/1
{
  &quot;post&quot;: {
    &quot;postId&quot;: 1,
    &quot;contents&quot;: &quot;hello&quot;
  },
  &quot;sharedPeople&quot;: [
    {
      &quot;exiration&quot;: &quot;2021-05-20&quot;,
      &quot;authority&quot;: &quot;1&quot;,
      &quot;sharedTo&quot;: &quot;A&quot;
    },
        {
      &quot;exiration&quot;: &quot;2021-05-20&quot;,
      &quot;authority&quot;: &quot;2&quot;,
      &quot;sharedTo&quot;: &quot;B&quot;
    }
    ,
    {
      &quot;exiration&quot;: &quot;2021-05-20&quot;,
      &quot;authority&quot;: &quot;3&quot;,
      &quot;sharedTo&quot;: &quot;C&quot;
    }
  ]
}

PUT blogs/_doc/2
{
  &quot;post&quot;: {
    &quot;postId&quot;: 2,
    &quot;contents&quot;: &quot;world&quot;
  },
  &quot;sharedPeople&quot;: [
        {
      &quot;exiration&quot;: &quot;2021-05-20&quot;,
      &quot;authority&quot;: &quot;2&quot;,
      &quot;sharedTo&quot;: &quot;B&quot;
    }
    ,
    {
      &quot;exiration&quot;: &quot;2021-05-20&quot;,
      &quot;authority&quot;: &quot;3&quot;,
      &quot;sharedTo&quot;: &quot;C&quot;
    }
  ]
}
</code></pre>
</li>
</ul>
<ul>
<li><p>A가 공유받은 문서를 가져오는 쿼리</p>
<p>여러번 공유되었을 수도 있기 때문에, 쿼리에서는 공유 권한(authority)순으로 정렬해서 가장 큰 값을 inner_hit으로 받아온다.</p>
<pre><code class="language-json">GET blogs/_search
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;filter&quot;: {
        &quot;nested&quot;: {
          &quot;path&quot;: &quot;sharedPeople&quot;,
          &quot;query&quot;: {
            &quot;bool&quot;: {
              &quot;should&quot;: [
                {
                  &quot;bool&quot;: {
                    &quot;must&quot;: [
                      {
                        &quot;terms&quot;: {
                          &quot;sharedPeople.sharedTo&quot;: [
                            &quot;A&quot;
                          ]
                        }
                      }
                    ]
                  }
                }
              ]
            }
          },
          &quot;inner_hits&quot;: {
            &quot;name&quot;: &quot;sharedInfo&quot;,
            &quot;from&quot;: 0,
            &quot;size&quot;: 1,
            &quot;sort&quot;: [
              {
                &quot;sharedPeople.authority&quot;: {
                  &quot;order&quot;: &quot;desc&quot;
                }
              }
            ]
          }
        }
      }
    }
  }
}</code></pre>
<pre><code class="language-json">  &quot;hits&quot; : {
    &quot;total&quot; : 1,
    &quot;max_score&quot; : 0.0,
    &quot;hits&quot; : [
      {
        &quot;_index&quot; : &quot;blogs&quot;,
        &quot;_type&quot; : &quot;_doc&quot;,
        &quot;_id&quot; : &quot;1&quot;,
        &quot;_score&quot; : 0.0,
        &quot;_source&quot; : {
          &quot;post&quot; : {
            &quot;postId&quot; : 1,
            &quot;contents&quot; : &quot;hello&quot;
          },
          &quot;sharedPeople&quot; : [
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;1&quot;,
              &quot;sharedTo&quot; : &quot;A&quot;
            },
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;2&quot;,
              &quot;sharedTo&quot; : &quot;B&quot;
            },
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;3&quot;,
              &quot;sharedTo&quot; : &quot;C&quot;
            }
          ]
        },
        &quot;inner_hits&quot; : {
          &quot;sharedInfo&quot; : {
            &quot;hits&quot; : {
              &quot;total&quot; : 1,
              &quot;max_score&quot; : null,
              &quot;hits&quot; : [
                {
                  &quot;_index&quot; : &quot;blogs&quot;,
                  &quot;_type&quot; : &quot;_doc&quot;,
                  &quot;_id&quot; : &quot;1&quot;,
                  &quot;_nested&quot; : {
                    &quot;field&quot; : &quot;sharedPeople&quot;,
                    &quot;offset&quot; : 0
                  },
                  &quot;_score&quot; : null,
                  &quot;_source&quot; : {
                    &quot;exiration&quot; : &quot;2021-05-20&quot;,
                    &quot;authority&quot; : &quot;1&quot;,
                    &quot;sharedTo&quot; : &quot;A&quot;
                  },
                  &quot;sort&quot; : [
                    &quot;1&quot;
                  ]
                }
              ]
            }
          }
        }
      }
    ]
  }</code></pre>
</li>
</ul>
<ul>
<li><p>여기에 공유기간을 추가조건으로 must로 넣었을때</p>
<pre><code class="language-json">GET blogs/_search
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;filter&quot;: {
        &quot;nested&quot;: {
          &quot;path&quot;: &quot;sharedPeople&quot;,
          &quot;query&quot;: {
            &quot;bool&quot;: {
              &quot;must&quot;: [
                {
                  &quot;range&quot;: {
                    &quot;sharedPeople.exiration&quot;: {
                      &quot;from&quot;: &quot;2020-05-20&quot;,
                      &quot;to&quot;: null,
                      &quot;include_lower&quot;: false,
                      &quot;include_upper&quot;: true
                    }
                  }
                }
              ],
              &quot;should&quot;: [
                {
                  &quot;bool&quot;: {
                    &quot;must&quot;: [
                      {
                        &quot;terms&quot;: {
                          &quot;sharedPeople.sharedTo&quot;: [
                            &quot;A&quot;
                          ]
                        }
                      }
                    ]
                  }
                }
              ]
            }
          },
          &quot;inner_hits&quot;: {
            &quot;name&quot;: &quot;sharedInfo&quot;,
            &quot;from&quot;: 0,
            &quot;size&quot;: 1,
            &quot;sort&quot;: [
              {
                &quot;sharedPeople.authority&quot;: {
                  &quot;order&quot;: &quot;desc&quot;
                }
              }
            ]
          }
        }
      }
    }
  }
}</code></pre>
<pre><code class="language-json">  &quot;hits&quot; : {
    &quot;total&quot; : 1,
    &quot;max_score&quot; : 0.0,
    &quot;hits&quot; : [
      {
        &quot;_index&quot; : &quot;blogs&quot;,
        &quot;_type&quot; : &quot;_doc&quot;,
        &quot;_id&quot; : &quot;1&quot;,
        &quot;_score&quot; : 0.0,
        &quot;_source&quot; : {
          &quot;post&quot; : {
            &quot;postId&quot; : 1,
            &quot;contents&quot; : &quot;hello&quot;
          },
          &quot;sharedPeople&quot; : [
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;1&quot;,
              &quot;sharedTo&quot; : &quot;A&quot;
            },
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;2&quot;,
              &quot;sharedTo&quot; : &quot;B&quot;
            },
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;3&quot;,
              &quot;sharedTo&quot; : &quot;C&quot;
            }
          ]
        },
        &quot;inner_hits&quot; : {
          &quot;sharedInfo&quot; : {
            &quot;hits&quot; : {
              &quot;total&quot; : 3,
              &quot;max_score&quot; : null,
              &quot;hits&quot; : [
                {
                  &quot;_index&quot; : &quot;blogs&quot;,
                  &quot;_type&quot; : &quot;_doc&quot;,
                  &quot;_id&quot; : &quot;1&quot;,
                  &quot;_nested&quot; : {
                    &quot;field&quot; : &quot;sharedPeople&quot;,
                    &quot;offset&quot; : 2
                  },
                  &quot;_score&quot; : null,
                  &quot;_source&quot; : {
                    &quot;exiration&quot; : &quot;2021-05-20&quot;,
                    &quot;authority&quot; : &quot;3&quot;,
                    &quot;sharedTo&quot; : &quot;C&quot;
                  },
                  &quot;sort&quot; : [
                    &quot;3&quot;
                  ]
                }
              ]
            }
          }
        }
      }
    ]
  }</code></pre>
<p>위와같이 결과중에 inner_hit에 C의 권한이 나온다. </p>
<p>inner_hit에서 쿼리 중 아래의 should권한이 무시된 것이다.</p>
<pre><code>              &quot;should&quot;: [
                {
                  &quot;bool&quot;: {
                    &quot;must&quot;: [
                      {
                        &quot;terms&quot;: {
                          &quot;sharedPeople.sharedTo&quot;: [
                            &quot;A&quot;
                          ]
                        }
                      }
                    ]
                  }
                }
              ]</code></pre></li>
</ul>
<ul>
<li>문제는 무시되었다면 쿼리의 결과에 2번문서, 즉 A에게 공유되지 않은 문서도 나왔어야하는데, 실제 쿼리의 결과에서는 1번만 나오지만 inner_hit에서 A의 권한이 아닌 C의권한이 나오고있다.</li>
</ul>
<ul>
<li>ES의 filter context에서 should안의 minimum_should_match는 기본으로 1인데, (version 7.0이하에서) 이것이 inner_hit에서는 적용되지 않고 있었다. 실제로 6.8에서 위 쿼리를 날리면, 앞으로 없어진다는 아래와같은 경고가 나온다</li>
</ul>
<blockquote>
<p>#! Deprecation: Should clauses in the filter context will no longer automatically set the minimum should match to 1 in the next major version. You should group them in a [filter] clause or explicitly set [minimum_should_match] to 1 to restore this behavior in the next major version.</p>
</blockquote>
<ul>
<li><p>아래와같이 minimun_should_match를 명시해보자</p>
<pre><code class="language-json">GET blogs/_search
{
  &quot;query&quot;: {
    &quot;bool&quot;: {
      &quot;filter&quot;: {
        &quot;nested&quot;: {
          &quot;path&quot;: &quot;sharedPeople&quot;,
          &quot;query&quot;: {
            &quot;bool&quot;: {
              &quot;must&quot;: [
                {
                  &quot;range&quot;: {
                    &quot;sharedPeople.exiration&quot;: {
                      &quot;from&quot;: &quot;2020-05-20&quot;,
                      &quot;to&quot;: null,
                      &quot;include_lower&quot;: false,
                      &quot;include_upper&quot;: true
                    }
                  }
                }
              ],
              &quot;should&quot;: [
                {
                  &quot;bool&quot;: {
                    &quot;must&quot;: [
                      {
                        &quot;terms&quot;: {
                          &quot;sharedPeople.sharedTo&quot;: [
                            &quot;A&quot;
                          ]
                        }
                      }
                    ]
                  }
                }

              ],
              &quot;minimum_should_match&quot; : 1
            }
          },
          &quot;inner_hits&quot;: {
            &quot;name&quot;: &quot;sharedInfo&quot;,
            &quot;from&quot;: 0,
            &quot;size&quot;: 1,
            &quot;sort&quot;: [
              {
                &quot;sharedPeople.authority&quot;: {
                  &quot;order&quot;: &quot;desc&quot;
                }
              }
            ]
          }
        }
      }
    }
  }
}</code></pre>
<pre><code class="language-json">  &quot;hits&quot; : {
    &quot;total&quot; : 1,
    &quot;max_score&quot; : 0.0,
    &quot;hits&quot; : [
      {
        &quot;_index&quot; : &quot;blogs&quot;,
        &quot;_type&quot; : &quot;_doc&quot;,
        &quot;_id&quot; : &quot;1&quot;,
        &quot;_score&quot; : 0.0,
        &quot;_source&quot; : {
          &quot;post&quot; : {
            &quot;postId&quot; : 1,
            &quot;contents&quot; : &quot;hello&quot;
          },
          &quot;sharedPeople&quot; : [
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;1&quot;,
              &quot;sharedTo&quot; : &quot;A&quot;
            },
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;2&quot;,
              &quot;sharedTo&quot; : &quot;B&quot;
            },
            {
              &quot;exiration&quot; : &quot;2021-05-20&quot;,
              &quot;authority&quot; : &quot;3&quot;,
              &quot;sharedTo&quot; : &quot;C&quot;
            }
          ]
        },
        &quot;inner_hits&quot; : {
          &quot;sharedInfo&quot; : {
            &quot;hits&quot; : {
              &quot;total&quot; : 1,
              &quot;max_score&quot; : null,
              &quot;hits&quot; : [
                {
                  &quot;_index&quot; : &quot;blogs&quot;,
                  &quot;_type&quot; : &quot;_doc&quot;,
                  &quot;_id&quot; : &quot;1&quot;,
                  &quot;_nested&quot; : {
                    &quot;field&quot; : &quot;sharedPeople&quot;,
                    &quot;offset&quot; : 0
                  },
                  &quot;_score&quot; : null,
                  &quot;_source&quot; : {
                    &quot;exiration&quot; : &quot;2021-05-20&quot;,
                    &quot;authority&quot; : &quot;1&quot;,
                    &quot;sharedTo&quot; : &quot;A&quot;
                  },
                  &quot;sort&quot; : [
                    &quot;1&quot;
                  ]
                }
              ]
            }
          }
        }
      }
    ]
  }</code></pre>
<p>제대로 A의 권한이 나오고 있다.</p>
</li>
</ul>
<ul>
<li>우리는 6.8을 쓰고있는데, 여기서도 inner_hit에서의 minimum_should_match를 없애버리는 실수를 한걸까? 앞으로 7.0대부터 minimum_should_match가 없어지니 항상 명시하는것을 기본으로 해야겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cassandra 스키마 보기 ]]></title>
            <link>https://velog.io/@diane_at_work/Cassandra-%EC%8A%A4%ED%82%A4%EB%A7%88-%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@diane_at_work/Cassandra-%EC%8A%A4%ED%82%A4%EB%A7%88-%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 18 May 2020 09:40:19 GMT</pubDate>
            <description><![CDATA[<p>cassandra 최신버전은 현재 3.11이다.</p>
<pre><code>the latest Apache Cassandra 3.11 release: 3.11.6 (pgp, sha256 and sha512), released on 2020-02-14.</code></pre><p>지금 개발할때 쓰고있는 버전은 2.2.8이다.</p>
<ul>
<li>버전 보기 </li>
</ul>
<pre><code class="language-cassandra">select release_version 
from system.local;</code></pre>
<ul>
<li>모든 KEYSPACE보기 </li>
</ul>
<pre><code class="language-cassandra">select * 
from system.schema_keyspaces;</code></pre>
<ul>
<li>특정 keyspace의 table보기</li>
</ul>
<pre><code class="language-cassandra">select columnfamily_name 
from system.schema_columnfamilies 
where keyspace_name = &#39;audit&#39;;
</code></pre>
<ul>
<li>특정 table의 column보기</li>
</ul>
<pre><code class="language-cassandra">select column_name, type, validator 
from system.schema_columns 
where keyspace_name = &#39;audit&#39; 
and columnfamily_name = &#39;user_action&#39;;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[moment().utcOffset()]]></title>
            <link>https://velog.io/@diane_at_work/moment.utcOffset</link>
            <guid>https://velog.io/@diane_at_work/moment.utcOffset</guid>
            <pubDate>Thu, 07 May 2020 09:31:26 GMT</pubDate>
            <description><![CDATA[<p>이런 코드를 쓴 게 있었다..</p>
<pre><code>moment().utcOffset(UserTimeOffset).getTime()</code></pre><p>알고보니 아래 코드는 모두 같음</p>
<pre><code>moment().utcOffset(360).getTime()
moment().utcOffset(240).getTime()
new Date().getTime()</code></pre><p>utcOffset은 시간대를 어디로 표시할지에 대한 것이지 시간 값 자체를 바꿔주는 것이 아니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[KAFKA 맨날까먹는것]]></title>
            <link>https://velog.io/@diane_at_work/KAFKA-%EB%A7%A8%EB%82%A0%EA%B9%8C%EB%A8%B9%EB%8A%94%EA%B2%83</link>
            <guid>https://velog.io/@diane_at_work/KAFKA-%EB%A7%A8%EB%82%A0%EA%B9%8C%EB%A8%B9%EB%8A%94%EA%B2%83</guid>
            <pubDate>Thu, 19 Mar 2020 08:27:39 GMT</pubDate>
            <description><![CDATA[<h3 id="0-카프카-실행">0. 카프카 실행</h3>
<pre><code class="language-sh">./kafka-server-start.sh ../../config/server.properties</code></pre>
<h3 id="1-카프카-토픽-생성">1. 카프카 토픽 생성</h3>
<pre><code class="language-sh">./kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 2 --topic {topicName}</code></pre>
<h3 id="2-토픽-확인">2. 토픽 확인</h3>
<pre><code class="language-sh">./kafka-topcis.sh --list --zookeeper localhost:2181</code></pre>
<h3 id="3-그룹확인">3. 그룹확인</h3>
<pre><code class="language-sh">./kafka-consumer-groups.sh --list --bootstrap-server {IP}:9092</code></pre>
<h3 id="4--컨슈머-콘솔에서-토픽-읽기">4.  컨슈머 콘솔에서 토픽 읽기</h3>
<pre><code class="language-sh">./kafka-console-consumer.sh --bootstrap-server {IP}:9092 --topic {topicName} --from-beginning</code></pre>
<h3 id="5-특정-그룹-offset확인">5. 특정 그룹 offset확인</h3>
<pre><code>./kafka-consumer-groups.sh --bootstrap-server {IP}:9092 --describe --group abc</code></pre><h3 id="6-특정-그룹의-특정-토픽-offset-설정">6. 특정 그룹의 특정 토픽 offset 설정</h3>
<pre><code>./kafka-consumer-groups.sh \
--bootstrap-server {IP}:9092 \
--group {group} 
--topic {topic} \
--reset-offsets --to-latest --execute</code></pre><p>결과 
<img src="https://images.velog.io/images/diane_at_work/post/58c3fb86-4d0b-45cf-9c37-1b099dfdfbf1/image.png" alt=""></p>
<pre><code>--to-offset n
--to-earlist
--shift-by n</code></pre><p>등으로 offset 설정 가능</p>
<h3 id="7-특정-그룹의-특정-토픽-특정-시간대로-offset-설정">7. 특정 그룹의 특정 토픽 특정 시간대로 offset 설정</h3>
<pre><code>./kafka-consumer-groups.sh \
--bootstrap-server 182.193.17.35:9092 \
--group {group} \
--reset-offsets \
--topic {topic} \
--to-datetime 2020-12-02T00:00:00.000+09:00</code></pre><ul>
<li>kafka client가 낮으면 kafka에 offset을 저장하는게 아니라 zookeeper에 저장함. 
그럴때는 zookeeper에서 해당 작업을 해줘야함</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[flume source NPE ]]></title>
            <link>https://velog.io/@diane_at_work/flume-source-NPE</link>
            <guid>https://velog.io/@diane_at_work/flume-source-NPE</guid>
            <pubDate>Wed, 18 Mar 2020 09:32:43 GMT</pubDate>
            <description><![CDATA[<h3 id="1-로그-바꾸니-에러-발생">1. 로그 바꾸니 에러 발생</h3>
<pre><code class="language-properties">flume.root.logger=DEBUG</code></pre>
<p>root.logger를 debug로 바꾸니 아래와 같은 에러가 발생했다.</p>
<pre><code class="language-verilog">18 Mar 2020 08:58:16,193 DEBUG KafkaSource  - Event #: 0
18 Mar 2020 08:58:16,193 ERROR KafkaSource  - KafkaSource EXCEPTION, {}
java.lang.NullPointerException
        at org.apache.flume.instrumentation.MonitoredCounterGroup.increment(MonitoredCounterGroup.java:261)
        at org.apache.flume.instrumentation.kafka.KafkaSourceCounter.incrementKafkaEmptyCount(KafkaSourceCounter.java:49)
        at org.apache.flume.source.kafka.KafkaSource.process(KafkaSource.java:146)
        at org.apache.flume.source.PollableSourceRunner$PollingRunner.run(PollableSourceRunner.java:139)
        at java.lang.Thread.run(Thread.java:748)
18 Mar 2020 08:58:16,193 ERROR KafkaSource  - KafkaSource EXCEPTION, {}
java.lang.NullPointerException
        at org.apache.flume.instrumentation.MonitoredCounterGroup.increment(MonitoredCounterGroup.java:261)
        at org.apache.flume.instrumentation.kafka.KafkaSourceCounter.incrementKafkaEmptyCount(KafkaSourceCounter.java:49)
        at org.apache.flume.source.kafka.KafkaSource.process(KafkaSource.java:146)
        at org.apache.flume.source.PollableSourceRunner$PollingRunner.run(PollableSourceRunner.java:139)
        at java.lang.Thread.run(Thread.java:748)
18 Mar 2020 08:58:16,196 DEBUG KafkaSource  - Waited: 1002
18 Mar 2020 08:58:16,196 DEBUG KafkaSource  - Event #: 0
18 Mar 2020 08:58:16,196 ERROR KafkaSource  - KafkaSource EXCEPTION, {}
java.lang.NullPointerException
        at org.apache.flume.instrumentation.MonitoredCounterGroup.increment(MonitoredCounterGroup.java:261)
        at org.apache.flume.instrumentation.kafka.KafkaSourceCounter.incrementKafkaEmptyCount(KafkaSourceCounter.java:49)
        at org.apache.flume.source.kafka.KafkaSource.process(KafkaSource.java:146)
        at org.apache.flume.source.PollableSourceRunner$PollingRunner.run(PollableSourceRunner.java:139)
        at java.lang.Thread.run(Thread.java:748)
18 Mar 2020 08:58:16,196 DEBUG KafkaSource  - Waited: 1002
18 Mar 2020 08:58:16,196 DEBUG KafkaSource  - Event #: 0
18 Mar 2020 08:58:16,196 ERROR KafkaSource  - KafkaSource EXCEPTION, {}
java.lang.NullPointerException
        at org.apache.flume.instrumentation.MonitoredCounterGroup.increment(MonitoredCounterGroup.java:261)
        at org.apache.flume.instrumentation.kafka.KafkaSourceCounter.incrementKafkaEmptyCount(KafkaSourceCounter.java:49)
        at org.apache.flume.source.kafka.KafkaSource.process(KafkaSource.java:146)
        at org.apache.flume.source.PollableSourceRunner$PollingRunner.run(PollableSourceRunner.java:139)
        at java.lang.Thread.run(Thread.java:748)
</code></pre>
<h3 id="2-kafksorceclass">2. kafkSorce.class</h3>
<p>에러를 따라가다보니 kafkaSource.class에서 log level이 debug일때만 실행되는 소스가 있는데</p>
<pre><code class="language-java"> if (!iterStatus) {
                if (log.isDebugEnabled()) {
                    this.counter.incrementKafkaEmptyCount();
                    log.debug(&quot;Returning with backoff. No more data to read&quot;);
                }

                return Status.BACKOFF;</code></pre>
<p>incrementKafkaEmptyCount를 할때 </p>
<pre><code class="language-java"> return increment(COUNTER_KAFKA_EMPTY)</code></pre>
<p>를 하는데, increment시에</p>
<pre><code class="language-java">  protected long increment(String counter) {
    return counterMap.get(counter).incrementAndGet();
  }</code></pre>
<p>counterMap.get(counter) 이 부분이 null이어서 nullPointerException 발생.</p>
<p>수정된 버그(<a href="https://issues.apache.org/jira/browse/FLUME-2578)%EB%9D%BC%EA%B3%A0">https://issues.apache.org/jira/browse/FLUME-2578)라고</a> 하는데 우리는 kafka-source를 낮은버전을 써서 발생하는 듯.</p>
<pre><code class="language-xml">        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.flume.flume-ng-sources&lt;/groupId&gt;
            &lt;artifactId&gt;flume-kafka-source&lt;/artifactId&gt;
            &lt;version&gt;1.6.0&lt;/version&gt;
        &lt;/dependency&gt;</code></pre>
<p>...</p>
<pre><code class="language-properties">log4j.logger.org.apache.flume.source = INFO</code></pre>
<p>일단 해당 부분을 info로 해두었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Elasticsearch join 관계에서 조건에 맞는 children count를 세는  composite aggregation]]></title>
            <link>https://velog.io/@diane_at_work/Elasticsearch-join-%EA%B4%80%EA%B3%84%EC%97%90%EC%84%9C-%EC%A1%B0%EA%B1%B4%EC%97%90-%EB%A7%9E%EB%8A%94-children-count%EB%A5%BC-%EC%84%B8%EB%8A%94-composite-aggregation</link>
            <guid>https://velog.io/@diane_at_work/Elasticsearch-join-%EA%B4%80%EA%B3%84%EC%97%90%EC%84%9C-%EC%A1%B0%EA%B1%B4%EC%97%90-%EB%A7%9E%EB%8A%94-children-count%EB%A5%BC-%EC%84%B8%EB%8A%94-composite-aggregation</guid>
            <pubDate>Wed, 18 Mar 2020 03:58:21 GMT</pubDate>
            <description><![CDATA[<h2 id="join-관계의-doc이-있다">join 관계의 doc이 있다</h2>
<p>매핑은 아래와 같다
post의 자식으로 comment가있고, post에는 언급된 사람들이 배열로 있다.
(7버전 아래에서는 mappings 하위에 _doc으로 감싸줘야함.)</p>
<pre><code class="language-json">PUT blog
{
  &quot;mappings&quot;: {
    &quot;properties&quot;: {
      &quot;post&quot;: {
        &quot;properties&quot;: {
          &quot;postId&quot;: {
            &quot;type&quot;: &quot;keyword&quot;
          },
          &quot;mentionedPeople&quot;: {
            &quot;type&quot;: &quot;keyword&quot;
          }
        }
      },
      &quot;comment&quot;: {
        &quot;properties&quot;: {
          &quot;commentId&quot;: {
            &quot;type&quot;: &quot;keyword&quot;
          },
          &quot;content&quot;: {
            &quot;type&quot;: &quot;text&quot;
          }
        }
      },
      &quot;join&quot;: {
        &quot;type&quot;: &quot;join&quot;,
        &quot;relations&quot;: {
          &quot;post&quot;: &quot;comment&quot;
        }
      }
    }
  }
}</code></pre>
<h2 id="데이터는-아래와-같이-들어가있다">데이터는 아래와 같이 들어가있다.</h2>
<p><img src="https://images.velog.io/images/diane_at_work/post/ac4dfeb8-75b4-4d60-a255-5be9e0b2c145/image.png" alt=""></p>
<p>데이터 구분을 위해 모든 댓글의 내용(comment.content)는 &quot;hello&quot;이고, 26번만 &quot; goodbye&quot;로 지정했다.
아래와같이 데이터를 넣었다.</p>
<pre><code class="language-json">### 부모 포스트 2개 넣음

PUT blog/_doc/1
{
  &quot;post&quot;:{
    &quot;postId&quot; :1,
    &quot;mentionedPeople&quot;:[&quot;a&quot;,&quot;b&quot;,&quot;c&quot;]
  },
  &quot;join&quot;:&quot;post&quot;
}
PUT blog/_doc/2
{
  &quot;post&quot;:{
    &quot;postId&quot; :2,
    &quot;mentionedPeople&quot;:[&quot;b&quot;,&quot;c&quot;,&quot;d&quot;,&quot;e&quot;]
  },
  &quot;join&quot;:&quot;post&quot;
}

### 아래와 같이 11개의 댓글을 넣었다.
PUT blog/_doc/11?routing=1
{
  &quot;comment&quot;: {
    &quot;commentId&quot;: 11,
    &quot;content&quot;: &quot;hello&quot;
  },
  &quot;join&quot;: {
    &quot;name&quot;: &quot;comment&quot;,
    &quot;parent&quot;: 1
  }
}
## 2의 6번째 댓글만 content를 bye로 넣었다.
PUT blog/_doc/26?routing=2
{
  &quot;comment&quot;: {
    &quot;commentId&quot;: 26,
    &quot;content&quot;: &quot;goodbye&quot;
  },
  &quot;join&quot;: {
    &quot;name&quot;: &quot;comment&quot;,
    &quot;parent&quot;: 2
  }
}
</code></pre>
<h2 id="comment가-hello인-것의-개수를-언급된-사람별로-count해보자">comment가 hello인 것의 개수를 &#39;언급된 사람별&#39;로 count해보자.</h2>
<p>하고싶은것은, post에는 여러 언급된 사람들이있고, 또 post에는 여러 comment가 있는데,
언급된 사람 &gt; post &gt; 특정 조건의 comment를, 사람별로 집계하고 싶은것이다.</p>
<p><img src="https://images.velog.io/images/diane_at_work/post/ac4dfeb8-75b4-4d60-a255-5be9e0b2c145/image.png" alt=""></p>
<p>즉 b는 1번과 2번 포스트에 언급되어 11개의 모든 댓글을 가지지만, 그중 내용이 hello인 10개를 가진다는 것을 알고싶고, a는 1번 포스트에 언급되어 내용이 hello인 5개의 포스트를 가진다는 것을 집계하고 싶은것이다.</p>
<h3 id="1-mentionedpeople로-aggregation하고---코멘트로-aggregation">1. mentionedPeople로 aggregation하고 -&gt; 코멘트로 aggregation</h3>
<p>comment는 자식이기때문에 aggregation시 children aggr를 써줘야한다.</p>
<pre><code class="language-json">GET blog/_search
{
  &quot;query&quot;: {
    &quot;has_child&quot;: {
      &quot;type&quot;: &quot;comment&quot;,
      &quot;inner_hits&quot;: {
        &quot;_source&quot;: false,
        &quot;size&quot;: 0
      },
      &quot;query&quot;: {
        &quot;bool&quot;: {
          &quot;should&quot;: [
            {
              &quot;match&quot;: {
                &quot;comment.content&quot;: &quot;hello&quot;
              }
            }
          ]
        }
      }
    }
  },
  &quot;_source&quot;: false,
  &quot;aggs&quot;: {
    &quot;byworkspaceId&quot;: {
      &quot;terms&quot;: {
        &quot;field&quot;: &quot;post.mentionedPeople&quot;
      },
      &quot;aggs&quot;: {
        &quot;commentCount&quot;: {
          &quot;children&quot;: {
            &quot;type&quot;: &quot;comment&quot;
          },
          &quot;aggs&quot;: {
            &quot;commentHowMany&quot;: {
              &quot;value_count&quot;: {
                &quot;field&quot;: &quot;comment.commentId&quot;
              }
            }
          }
        }
      }
    }
  }
}</code></pre>
<p>위 결과는 아래와 같다.</p>
<p>inner_hit에 보면, hello인 것의 개수가 a포스트 5개, b포스트 5개이지만
bucket에보면 a사람에 11개, b사람에 11개로 <strong>goodbye인 것이 제외되지 않고 나온다.</strong></p>
<pre><code class="language-json">{
  &quot;took&quot;: 1,
  &quot;timed_out&quot;: false,
  &quot;_shards&quot;: {
    &quot;total&quot;: 4,
    &quot;successful&quot;: 4,
    &quot;skipped&quot;: 0,
    &quot;failed&quot;: 0
  },
  &quot;hits&quot;: {
    &quot;total&quot;: 2,
    &quot;max_score&quot;: 1,
    &quot;hits&quot;: [
      {
        &quot;_index&quot;: &quot;blog&quot;,
        &quot;_type&quot;: &quot;_doc&quot;,
        &quot;_id&quot;: &quot;2&quot;,
        &quot;_score&quot;: 1,
        &quot;inner_hits&quot;: {
          &quot;comment&quot;: {
            &quot;hits&quot;: {
              &quot;total&quot;: 5,
              &quot;max_score&quot;: 0,
              &quot;hits&quot;: []
            }
          }
        }
      },
      {
        &quot;_index&quot;: &quot;blog&quot;,
        &quot;_type&quot;: &quot;_doc&quot;,
        &quot;_id&quot;: &quot;1&quot;,
        &quot;_score&quot;: 1,
        &quot;inner_hits&quot;: {
          &quot;comment&quot;: {
            &quot;hits&quot;: {
              &quot;total&quot;: 5,
              &quot;max_score&quot;: 0,
              &quot;hits&quot;: []
            }
          }
        }
      }
    ]
  },
  &quot;aggregations&quot;: {
    &quot;byworkspaceId&quot;: {
      &quot;doc_count_error_upper_bound&quot;: 0,
      &quot;sum_other_doc_count&quot;: 0,
      &quot;buckets&quot;: [
        {
          &quot;key&quot;: &quot;b&quot;,
          &quot;doc_count&quot;: 2,
          &quot;commentCount&quot;: {
            &quot;doc_count&quot;: 11,
            &quot;commentHowMany&quot;: {
              &quot;value&quot;: 11
            }
          }
        },
        {
          &quot;key&quot;: &quot;c&quot;,
          &quot;doc_count&quot;: 2,
          &quot;commentCount&quot;: {
            &quot;doc_count&quot;: 11,
            &quot;commentHowMany&quot;: {
              &quot;value&quot;: 11
            }
          }
        },
        {
          &quot;key&quot;: &quot;a&quot;,
          &quot;doc_count&quot;: 1,
          &quot;commentCount&quot;: {
            &quot;doc_count&quot;: 5,
            &quot;commentHowMany&quot;: {
              &quot;value&quot;: 5
            }
          }
        },
        {
          &quot;key&quot;: &quot;d&quot;,
          &quot;doc_count&quot;: 1,
          &quot;commentCount&quot;: {
            &quot;doc_count&quot;: 6,
            &quot;commentHowMany&quot;: {
              &quot;value&quot;: 6
            }
          }
        },
        {
          &quot;key&quot;: &quot;e&quot;,
          &quot;doc_count&quot;: 1,
          &quot;commentCount&quot;: {
            &quot;doc_count&quot;: 6,
            &quot;commentHowMany&quot;: {
              &quot;value&quot;: 6
            }
          }
        }
      ]
    }
  }
}</code></pre>
<h3 id="2-aggregation에도-필터를-걸어준다">2. aggregation에도 필터를 걸어준다.</h3>
<pre><code class="language-json">GET blog/_search
{
  &quot;query&quot;: {
    &quot;has_child&quot;: {
      &quot;type&quot;: &quot;comment&quot;,
      &quot;inner_hits&quot;: {
        &quot;_source&quot;: false,
        &quot;size&quot;: 0
      },
      &quot;query&quot;: {
        &quot;bool&quot;: {
          &quot;should&quot;: [
            {
              &quot;match&quot;: {
                &quot;comment.content&quot;: &quot;hello&quot;
              }
            }
          ]
        }
      }
    }
  },
  &quot;_source&quot;: false,
  &quot;aggs&quot;: {
    &quot;byworkspaceId&quot;: {
      &quot;terms&quot;: {
        &quot;field&quot;: &quot;post.mentionedPeople&quot;,
        &quot;size&quot; : 5
      },
      &quot;aggs&quot;: {
        &quot;childCount&quot;: {
          &quot;children&quot;: {
            &quot;type&quot;: &quot;comment&quot;
          },
          &quot;aggs&quot;: {
            &quot;inner_filter&quot;: {
              &quot;filter&quot;: {
                &quot;bool&quot;: {
                  &quot;should&quot;: [
                    {
                      &quot;match&quot;: {
                        &quot;comment.content&quot;: &quot;hello&quot;
                      }
                    }
                  ]
                }
              },
              &quot;aggs&quot;: {
                &quot;commentHowMany&quot;: {
                  &quot;value_count&quot;: {
                    &quot;field&quot;: &quot;comment.commentId&quot;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}</code></pre>
<p>내부 aggr에 filter aggr를 걸었더니, goodbye인것이 제외되고 잘 집계된다. 결과는 아래와같다.</p>
<pre><code class="language-json">{
  &quot;took&quot;: 1,
  &quot;timed_out&quot;: false,
  &quot;_shards&quot;: {
    &quot;total&quot;: 4,
    &quot;successful&quot;: 4,
    &quot;skipped&quot;: 0,
    &quot;failed&quot;: 0
  },
  &quot;hits&quot;: {
    &quot;total&quot;: 2,
    &quot;max_score&quot;: 1,
    &quot;hits&quot;: [
      {
        &quot;_index&quot;: &quot;blog&quot;,
        &quot;_type&quot;: &quot;_doc&quot;,
        &quot;_id&quot;: &quot;2&quot;,
        &quot;_score&quot;: 1,
        &quot;inner_hits&quot;: {
          &quot;comment&quot;: {
            &quot;hits&quot;: {
              &quot;total&quot;: 5,
              &quot;max_score&quot;: 0,
              &quot;hits&quot;: []
            }
          }
        }
      },
      {
        &quot;_index&quot;: &quot;blog&quot;,
        &quot;_type&quot;: &quot;_doc&quot;,
        &quot;_id&quot;: &quot;1&quot;,
        &quot;_score&quot;: 1,
        &quot;inner_hits&quot;: {
          &quot;comment&quot;: {
            &quot;hits&quot;: {
              &quot;total&quot;: 5,
              &quot;max_score&quot;: 0,
              &quot;hits&quot;: []
            }
          }
        }
      }
    ]
  },
  &quot;aggregations&quot;: {
    &quot;byworkspaceId&quot;: {
      &quot;doc_count_error_upper_bound&quot;: 0,
      &quot;sum_other_doc_count&quot;: 0,
      &quot;buckets&quot;: [
        {
          &quot;key&quot;: &quot;b&quot;,
          &quot;doc_count&quot;: 2,
          &quot;childCount&quot;: {
            &quot;doc_count&quot;: 11,
            &quot;inner_filter&quot;: {
              &quot;doc_count&quot;: 10,
              &quot;commentHowMany&quot;: {
                &quot;value&quot;: 10
              }
            }
          }
        },
        {
          &quot;key&quot;: &quot;c&quot;,
          &quot;doc_count&quot;: 2,
          &quot;childCount&quot;: {
            &quot;doc_count&quot;: 11,
            &quot;inner_filter&quot;: {
              &quot;doc_count&quot;: 10,
              &quot;commentHowMany&quot;: {
                &quot;value&quot;: 10
              }
            }
          }
        },
        {
          &quot;key&quot;: &quot;a&quot;,
          &quot;doc_count&quot;: 1,
          &quot;childCount&quot;: {
            &quot;doc_count&quot;: 5,
            &quot;inner_filter&quot;: {
              &quot;doc_count&quot;: 5,
              &quot;commentHowMany&quot;: {
                &quot;value&quot;: 5
              }
            }
          }
        },
        {
          &quot;key&quot;: &quot;d&quot;,
          &quot;doc_count&quot;: 1,
          &quot;childCount&quot;: {
            &quot;doc_count&quot;: 6,
            &quot;inner_filter&quot;: {
              &quot;doc_count&quot;: 5,
              &quot;commentHowMany&quot;: {
                &quot;value&quot;: 5
              }
            }
          }
        },
        {
          &quot;key&quot;: &quot;e&quot;,
          &quot;doc_count&quot;: 1,
          &quot;childCount&quot;: {
            &quot;doc_count&quot;: 6,
            &quot;inner_filter&quot;: {
              &quot;doc_count&quot;: 5,
              &quot;commentHowMany&quot;: {
                &quot;value&quot;: 5
              }
            }
          }
        }
      ]
    }
  }
}</code></pre>
<h3 id="3-한번에-다가져오나-페이징처리는">3. 한번에 다가져오나? 페이징처리는?</h3>
<p>문제는, 위 쿼리에 보면  아래와같이 size를 5개까지만 명시했는데, 명시하지 않으면 10개가 default이다.</p>
<pre><code class="language-json">  &quot;aggs&quot;: {
    &quot;byworkspaceId&quot;: {
      &quot;terms&quot;: {
        &quot;field&quot;: &quot;post.mentionedPeople&quot;,
        &quot;size&quot; : 5
      },</code></pre>
<p>문제는 mentionedPeople이 만명, 십만명 이라면?</p>
<p>bucket이 만개 십만개 생길것이고, 이를 한번의 response로 내리면 메모리 문제가 생길 수 있다.</p>
<h3 id="4-composite-aggr-추가">4. composite aggr 추가</h3>
<p>composite aggr은 조합 집계를 할때쓰이지만, 페이징처리를 통해 모든 집계를 가져오기 위해서도 쓰인다.
아래와같이 composite 을 추가하면 paging처리되어 aggr를 가져오는 것이 가능하다.</p>
<pre><code class="language-json">GET blog/_search
{
  &quot;query&quot;: {
    &quot;has_child&quot;: {
      &quot;type&quot;: &quot;comment&quot;,
      &quot;inner_hits&quot;: {
        &quot;_source&quot;: false,
        &quot;size&quot;: 0
      },
      &quot;query&quot;: {
        &quot;bool&quot;: {
          &quot;should&quot;: [
            {
              &quot;match&quot;: {
                &quot;comment.content&quot;: &quot;hello&quot;
              }
            }
          ]
        }
      }
    }
  },
  &quot;size&quot;: 0,
  &quot;aggs&quot;: {
    &quot;my_compostie&quot;: {
      &quot;composite&quot;: {
        &quot;sources&quot;: [
          {
            &quot;byMentionedPeople&quot;: {
              &quot;terms&quot;: {
                &quot;field&quot;: &quot;post.mentionedPeople&quot;
              }
            }
          }
        ],
        &quot;size&quot;: 2
      },
      &quot;aggs&quot;: {
        &quot;childCount&quot;: {
          &quot;children&quot;: {
            &quot;type&quot;: &quot;comment&quot;
          },
          &quot;aggs&quot;: {
            &quot;inner_filter&quot;: {
              &quot;filter&quot;: {
                &quot;bool&quot;: {
                  &quot;should&quot;: [
                    {
                      &quot;match&quot;: {
                        &quot;comment.content&quot;: &quot;hello&quot;
                      }
                    }
                  ]
                }
              },
              &quot;aggs&quot;: {
                &quot;commentHowMAny&quot;: {
                  &quot;value_count&quot;: {
                    &quot;field&quot;: &quot;comment.commentId&quot;
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}</code></pre>
<p>위 쿼리의 결과에서는 a,b가 나왔다.</p>
<p>결과에서 아래 &quot;after_key&quot;를 주목하자.</p>
<pre><code class="language-json">
 &quot;aggregations&quot;: {
    &quot;my_compostie&quot;: {
      &quot;after_key&quot;: {
        &quot;byMentionedPeople&quot;: &quot;b&quot;
      },
</code></pre>
<p>다음 쿼리시, composite의 after에 위 맵을 그대로 넘겨주면, 그 이후로 조회 가능하다.</p>
<pre><code class="language-json">&quot;aggs&quot;: {
    &quot;my_compostie&quot;: {
      &quot;composite&quot;: {
        &quot;sources&quot;: [
          {
            &quot;byMentionedPeople&quot;: {
              &quot;terms&quot;: {
                &quot;field&quot;: &quot;post.mentionedPeople&quot;
              }
            }
          }
        ],
        &quot;size&quot;: 2,
        &quot;after&quot;: {
          &quot;byMentionedPeople&quot;: &quot;b&quot;
        }
      },</code></pre>
<p>위와같이 b 이후 부터 조회하겠다라고 하면 c와 d를 보여준다.</p>
<h3 id="5-java-ligh-level-client로-작성하면">5. java ligh-level client로 작성하면</h3>
<pre><code class="language-java">    RestHighLevelClient metaEs = ESClient.getClient(&quot;localhost&quot;, 9200);

    BoolQueryBuilder shouldMatchHello = QueryBuilders.boolQuery().should(QueryBuilders.matchQuery(&quot;comment.content&quot;,&quot;hello&quot;));
        HasChildQueryBuilder hasChildQueryBuilder = JoinQueryBuilders.hasChildQuery(&quot;comment&quot;, shouldMatchHello, ScoreMode.None);
        hasChildQueryBuilder.innerHit();

        List&lt;CompositeValuesSourceBuilder&lt;?&gt;&gt; sources = new ArrayList&lt;&gt;();
        sources.add(new TermsValuesSourceBuilder(&quot;byMentionedPoeple&quot;).field(&quot;post.mentionedPeople&quot;));
        CompositeAggregationBuilder compositeAggregation = new CompositeAggregationBuilder(&quot;my_composite&quot;, sources).size(2);

        compositeAggregation.subAggregation(
                new ChildrenAggregationBuilder(&quot;childCount&quot;, &quot;comment&quot;)
                        .subAggregation(AggregationBuilders.filter(&quot;inner_filter&quot;, shouldMatchHello))
        );


        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(hasChildQueryBuilder);
        sourceBuilder.aggregation(compositeAggregation);
        sourceBuilder.size(0);


        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(&quot;blog&quot;);
        searchRequest.source(sourceBuilder);


        try {
            SearchResponse response = metaEs.search(searchRequest, RequestOptions.DEFAULT);
            System.out.println(searchRequest);
            System.out.println(response);

        } catch (IOException e) {
            System.out.println(e);
        }</code></pre>
<h3 id="6사실">6.사실</h3>
<p>사실 inner_filter를 추가할때부터 그 내부의 value_count aggs는 의미가 없어졌다.</p>
<p>이미 filter의 결과에 doc_count가 있기 때문이다. 어쨋든.. 복잡한 요건이 해결되었다.</p>
]]></description>
        </item>
    </channel>
</rss>