<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>wedul</title>
    <link>https://wedul.tistory.com/</link>
    <description>wedul.chul@gmail.com / github.com/weduls</description>
    <language>ko</language>
    <pubDate>Sun, 10 May 2026 11:28:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>wedul</managingEditor>
    <image>
      <title>wedul</title>
      <url>https://t1.daumcdn.net/cfile/tistory/996C84445B4969A22B</url>
      <link>https://wedul.tistory.com</link>
    </image>
    <item>
      <title>jooq 사용 시 aop를 사용하는 경우에 application loading이 오래걸리는 이유</title>
      <link>https://wedul.tistory.com/729</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 jpa를 사용하고 있지 않고 기존에 spring-data-jdbc를 사용하고 있었다. 그리고 대부분은 findById, findAll을 제외한 쿼리는 @Query annotaion을 붙여서 사용하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀의 다른 프로젝트들이 mybatis, ibatis를 사용하고 있다보니 그대로 쿼리만 가져와서 spring data jdbc를 사용하도록만 바꾼 것 같다. mybatis가 나쁜건 아니지만 런타임시가 아니면 에러를 확인하기도 어렵고 값 매핑도 어렵다 보니 요새는 많이 선호하지 않아서 바꾼것 같은데 사실 annotaion으로 쿼리를 사용한다면 그것도 크게 다르지 않다고 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 입사 후 jooq나 query dsl 형태의 dsl 구조를 도입해서 컴파일 단위에서 타입 세이프한 쿼리를 사용할 수 있도록 기반을 만들고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 예전에 많이 사용했었던 query-dsl의 경우 엔티티 기반으로 코드를 만들다 보니 Jpa 없이 사용하기가 애매해서 jooq를 사용하기로 했다. jooq는 db schema를 기준으로 코드를 만들다 보니 entity 클래스가 따로 없는 상황에서는 이게 더 컬럼/타입 관리가 편하게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 jooq를 도입해서 코드로 쿼리를 짜는 편안함으로 작업을 다하고 나서 애플리케이션 배포를 했는데 애플리케이션 배포가 너무 느렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현상을 확인해보기 위해 디버깅을 해보니 AOP관련 타겟 클래스를 스캔하는 영역으로 의심되는 부분에서 시간이 오래걸렸다. 그래서 테스트를 위해 임시 프로젝트를 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트&lt;/h3&gt;
&lt;pre id=&quot;code_1769260089831&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE member
(
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,
    email      VARCHAR(100) NOT NULL,
    name       VARCHAR(50)  NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX ux_member_email ON member (email);

CREATE TABLE student
(
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(50)  NOT NULL,
    class     VARCHAR(20)  NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


CREATE table classs
(
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(50)  NOT NULL,
    teacher    VARCHAR(50)  NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jooq 프로젝트를 만들고 h2 db사용하게 하고 해당 스키마 파일을 참조해서 jooq codegenerate하도록 프로젝트를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769260168929&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.jooq;

import com.wedul.jooq.request.MemberRequest;
import lombok.RequiredArgsConstructor;
import org.jooq.DSLContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.example.jooq.Tables.MEMBER;


@Service
@RequiredArgsConstructor
public class MemberService {

    private final DSLContext dslContext;

    @Transactional
    public void joinMember(MemberRequest request) {
        dslContext.insertInto(MEMBER)
                .columns(MEMBER.NAME, MEMBER.EMAIL)
                .values(request.name(), request.email())
                .execute();
    }

    @Transactional
    public String getMemberEmail(String name) {
        return dslContext.select(MEMBER.EMAIL)
                .from(MEMBER)
                .where(MEMBER.NAME.eq(name))
                .fetchOneInto(String.class);
    }

}


package com.wedul.jooq;

import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class JooqConfiguration {

    @Bean
    public DSLContext dslContext(DataSource dataSource) {
        return DSL.using(dataSource, SQLDialect.H2);
    }

}

// application.yml
spring:
  application:
    name: jooq

  datasource:
    url: jdbc:h2:mem:testdb;MODE=MySQL
    driver-class-name: org.h2.Driver
    username: sa
    password:

  sql:
    init:
      mode: always
      schema-locations: classpath:db/schema.sql

  jooq:
    sql-dialect: H2

logging:
  level:
    org.jooq.tools.LoggerListener: DEBUG


// build.gradle
plugins {
    id 'org.springframework.boot' version '3.1.2'
    id 'io.spring.dependency-management' version '1.1.7'
    id 'java'
    id 'nu.studer.jooq' version '8.0'
}

ext {
    jooqVersion = '3.17.6' // 프로젝트에서 사용할 jOOQ 버전으로 변경
}

group = 'com.wedul'
version = '0.0.1-SNAPSHOT'
description = 'jooq'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        mavenBom &quot;org.springframework.boot:spring-boot-dependencies:3.1.2&quot;
    }
}

dependencies {
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'com.mysql:mysql-connector-j'

    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

    implementation 'org.jooq:jooq:3.17.6'

    jooqGenerator 'org.jooq:jooq-meta:3.17.6'
    jooqGenerator 'org.jooq:jooq-codegen:3.17.6'
    jooqGenerator 'com.h2database:h2'

    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-aop'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

}

tasks.named('test') {
    useJUnitPlatform()
}

jooq {
    version = '3.19.7'

    configurations {
        main {
            generateSchemaSourceOnCompilation = false

            generationTool {
                jdbc {
                    driver = 'org.h2.Driver'
                    url = 'jdbc:h2:mem:jooq_codegen;' +
                            'DB_CLOSE_DELAY=-1;' +
                            'MODE=MySQL;' +
                            &quot;INIT=RUNSCRIPT FROM 'src/main/resources/db/schema.sql'&quot;
                    user = 'sa'
                    password = ''
                }

                generator {
                    name = 'org.jooq.codegen.DefaultGenerator'

                    database {
                        name = 'org.jooq.meta.h2.H2Database'
                        inputSchema = 'PUBLIC'

                        // 필요하면 특정 테이블만
                        // includes = 'member|order.*'
                    }

                    generate {
                        records = true
                        pojos = true
                        daos = false
                        fluentSetters = true
                    }

                    target {
                        packageName = 'com.example.jooq'
                        directory = 'build/generated-src/jooq/main'
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 구성을 다 한 뒤 애플리케이션을 구동해봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HoGqP/dJMcaiB5jKt/BvcEs97tJJufpBVDn0vm6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HoGqP/dJMcaiB5jKt/BvcEs97tJJufpBVDn0vm6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HoGqP/dJMcaiB5jKt/BvcEs97tJJufpBVDn0vm6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHoGqP%2FdJMcaiB5jKt%2FBvcEs97tJJufpBVDn0vm6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1356&quot; height=&quot;364&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구동은 관련 내용이 워낙 적다보니 약 1초정도로 빠르게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 의심이 되었던 aop 관련 pointcut을 execution을 사용해서 2개정도 만들어봤다. (회사 프로젝트는 7~10개정도로 많은 pointcut을 사용하고 있다. 동일하게 execution을 사용하고 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769260376878&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.jooq;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;

@Aspect
@Configuration
public class MethodAopConfiguration {

    @Pointcut(&quot;execution(* com.wedul.jooq.MemberService.*(..))&quot;)
    public void memberServiceMethods() {}

    @Around(&quot;memberServiceMethods()&quot;)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println(&quot;Method &quot; + pjp.getSignature().getName() + &quot; is called with arguments: &quot;);
        return pjp.proceed();
    }


}

package com.wedul.jooq.request;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;

@Aspect
@Configuration
public class ControllerAopConfiguration {

    @Pointcut(&quot;execution(* com.wedul.jooq.*Controller.*(..))&quot;)
    public void memberControllerMethods() {}

    @Around(&quot;memberControllerMethods()&quot;)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {

        System.out.println(&quot;Controller &quot; + pjp.getSignature().getName() + &quot; is called with arguments: &quot;);
        return pjp.proceed();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAhrI/dJMcafyC9qZ/C5aTmyr9PuaYldRyR0K7qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAhrI/dJMcafyC9qZ/C5aTmyr9PuaYldRyR0K7qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAhrI/dJMcafyC9qZ/C5aTmyr9PuaYldRyR0K7qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAhrI%2FdJMcafyC9qZ%2FC5aTmyr9PuaYldRyR0K7qK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1323&quot; height=&quot;481&quot; data-origin-width=&quot;1323&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 18초정도 걸렸다. 확실히 원인은 aop가 맞는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Aop 대상객체를 판단하는 AopUtils쪽의 canApply 코드를 확인하는데 aop 대상이 맞는지 판단하는 부분에서 엄청나게 많은 시간이 걸리고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 jooq로 code generate를 하게 되면 굉장히 많은 클래스들이 생성되는데 이 클래스들이 선언된 aop 대상이 되는지를 판단하는 시간으로 사용되어서 클래스 * 메소드 수 * aop pointcut만큼 시간이 걸린것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이를 해결하기 위해서는 aop대상인지를 판단하는걸 method level까지 내려가지 않고 class filter 레벨에서 판단이 되도록 수정해 줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 문제의 그 코드인데 ClassFilter에서 걸러지면 밑에 해당 클래스에 있는 모든 메소드를 뒤져보는 Method Filter 부분을 보지 않ㅇ을 수 있지만 그렇게 하지 못하면 엄청나게 많은 시간이 소요된다.&lt;/p&gt;
&lt;pre id=&quot;code_1769261946662&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static boolean canApply(Pointcut pc, Class&amp;lt;?&amp;gt; targetClass, boolean hasIntroductions) {
    Assert.notNull(pc, &quot;Pointcut must not be null&quot;);
    // ClassFilter 
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }

    MethodMatcher methodMatcher = pc.getMethodMatcher();
    if (methodMatcher == MethodMatcher.TRUE) {
        // No need to iterate the methods if we're matching any method anyway...
        return true;
    }

    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    if (methodMatcher instanceof IntroductionAwareMethodMatcher iamm) {
        introductionAwareMethodMatcher = iamm;
    }

    Set&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; classes = new LinkedHashSet&amp;lt;&amp;gt;();
    if (!Proxy.isProxyClass(targetClass)) {
        classes.add(ClassUtils.getUserClass(targetClass));
    }
    classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));

	// method filter
    for (Class&amp;lt;?&amp;gt; clazz : classes) {
        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
        for (Method method : methods) {
            if (introductionAwareMethodMatcher != null ?
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                    methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }

    return false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 어떻게 해결 해야할까? class filter에서 걸릴 수 있도록 이거를 수정해줘야한다. 그렇게 하기 위해서는 메소드 실행 기준을 가지고 있는 execution 대신에 class 타입 기준으로 체크하는 within을 사용해주면 해결할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두개의 execution pointcut을 아래 within으로 바꾸고 다시실행해봤다.&lt;/p&gt;
&lt;pre id=&quot;code_1769263341807&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Pointcut(&quot;within(com.wedul.jooq.MemberService)&quot;)
@Pointcut(&quot;within(com.wedul.jooq..*Controller)&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1839&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CKnHZ/dJMcaaKReCn/Inr8KvsMsMDQDo7geUg1k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CKnHZ/dJMcaaKReCn/Inr8KvsMsMDQDo7geUg1k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CKnHZ/dJMcaaKReCn/Inr8KvsMsMDQDo7geUg1k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCKnHZ%2FdJMcaaKReCn%2FInr8KvsMsMDQDo7geUg1k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1839&quot; height=&quot;388&quot; data-origin-width=&quot;1839&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1초만에 다시 실행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 issue는 몇가지 git issue에서도 확인이 가능하다. (결론은 aop 범위를 within으로 줄여라..로 이슈는 종결되어있는 것 같다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/TencentBlueKing/bk-job/pull/2663&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/TencentBlueKing/bk-job/pull/2663&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jOOQ/jOOQ/issues/5902&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jOOQ/jOOQ/issues/5902&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769263694573&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;jOOQ,spring-boot and aop. &amp;middot; Issue #5902 &amp;middot; jOOQ/jOOQ&quot; data-og-description=&quot;Hi, After a migration to jOOQ in an application with the following stack : Java 8 Spring-boot 1.5.1 jOOQ version managed by the boot starter. I had perf issues on startup, after investigation, i fo...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jOOQ/jOOQ/issues/5902&quot; data-og-url=&quot;https://github.com/jOOQ/jOOQ/issues/5902&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yP31E/dJMb9cBCqOs/JDufVjVvnzHYtkNQcKaq2K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/fcdf1/dJMb9bvWCww/FQXS0jhWb0ILKKocStzee0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jOOQ/jOOQ/issues/5902&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jOOQ/jOOQ/issues/5902&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yP31E/dJMb9cBCqOs/JDufVjVvnzHYtkNQcKaq2K/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/fcdf1/dJMb9bvWCww/FQXS0jhWb0ILKKocStzee0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;jOOQ,spring-boot and aop. &amp;middot; Issue #5902 &amp;middot; jOOQ/jOOQ&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Hi, After a migration to jOOQ in an application with the following stack : Java 8 Spring-boot 1.5.1 jOOQ version managed by the boot starter. I had perf issues on startup, after investigation, i fo...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 테스트 코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/weduls/jooq&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/weduls/jooq&lt;/a&gt;&lt;/p&gt;</description>
      <category>web/Spring</category>
      <category>AOP</category>
      <category>Application</category>
      <category>classfilter</category>
      <category>deploy</category>
      <category>jooq</category>
      <category>pointcut</category>
      <category>Slow</category>
      <category>느림</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/729</guid>
      <comments>https://wedul.tistory.com/729#entry729comment</comments>
      <pubDate>Sat, 24 Jan 2026 23:08:23 +0900</pubDate>
    </item>
    <item>
      <title>spring metrics sever, client max-uri-tags</title>
      <link>https://wedul.tistory.com/728</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 사용하고 있는 서버가 파일관련 서버이다 보니 조건이 많아 호출에 대한 종류가 생각보다 많았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출되는 api도 mvc server endpoint도 100개가 넘었고 내부에서 webclient로 호출하는 외부 api client도 100개가 넘었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느날 새로운 api가 개발이 되고 선 배포 된 이후 실제 배포일자가 되서 호출이 되었는데 metric정보를 찾을수가 없었다. 처음에는 exporter에서 관련 tags cardinality가 높아서 정상 수집이 안되나 확인해봤지만 이슈가 없었고 각 서버에 들어가서 /actuator/prometheus를 통해 수집되는 메트릭 지표를 확인해봤는데 해당 endpoint가 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 서버에서 이렇게 많은 api를 호출하는것도 호출받는것도 처음이어서 이부분을 모르고 있었다. 기존 팀에서도 해당 지표를 보기보다 모두 커스텀 메트릭으로 저장하고 있어서 해당 부분에 대한 탐지는 내가 그라파나를 추가하면서 드러나게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련해서 현상 확인을 정확하게 하기위해 테스트를 진행해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;end point 100개 이상 생성&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1769174904606&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.study;

import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DefaultController {

    @ResponseBody
    public String handle() {
        return &quot;wedul&quot;;
    }

}


package com.wedul.study;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

@Configuration
public class DynamicMappingConfiguration {

    @Bean
    public ApplicationRunner registerMappings(
            @Qualifier(&quot;requestMappingHandlerMapping&quot;)
            RequestMappingHandlerMapping handlerMapping,
            DefaultController defaultController
    ) {
        return args -&amp;gt; {
            Method method = DefaultController.class.getMethod(&quot;handle&quot;);

            for (int i = 0; i &amp;lt; 110; i++) {
                RequestMappingInfo info = RequestMappingInfo
                        .paths(&quot;/test/&quot; + i)
                        .methods(RequestMethod.GET)
                        .build();
                handlerMapping.registerMapping(info, defaultController, method);
            }
        };
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RequestMapping을 하나씩 생성하는건 안되고 handlerMapping을 만들어서 추가해주는 방식으로 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1769175035668&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  application:
    name: study
management:
  endpoints:
    web:
      exposure:
        include: prometheus
  prometheus:
    metrics:
      export:
        enabled: true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;metric정보를 볼수 있도록 관련 설정도 추가해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;테스트 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1769175315777&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.study;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;

@SpringBootTest
public class UriEndpointTest {

    private final RestTemplate restTemplate = new RestTemplate();

    @Test
    void test() {
        for (int i = 0; i &amp;lt; 110; i++) {
            String url = &quot;http://localhost:8080/test/&quot; + i;
            restTemplate.getForObject(url, String.class);
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100개를 넘겨서 호출할수 있도록 110개의 enpoint를 모두 호출 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;돌리고 확인해보니 실제 운영에서 겪었었던 것 처럼 동일하게 endpoint가 모두 노출되지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRbDRX/dJMcabXihaf/0kjRjTKBgiNvm5roc0XaBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRbDRX/dJMcabXihaf/0kjRjTKBgiNvm5roc0XaBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRbDRX/dJMcabXihaf/0kjRjTKBgiNvm5roc0XaBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRbDRX%2FdJMcabXihaf%2F0kjRjTKBgiNvm5roc0XaBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1035&quot; height=&quot;189&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 확인해보니 다음과 같은 에러를 확인 할수 있었고&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;41&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjRIaE/dJMcahiUPyP/Sgku79RZEbRkDpk8JGSaT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjRIaE/dJMcahiUPyP/Sgku79RZEbRkDpk8JGSaT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjRIaE/dJMcahiUPyP/Sgku79RZEbRkDpk8JGSaT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjRIaE%2FdJMcahiUPyP%2FSgku79RZEbRkDpk8JGSaT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;562&quot; height=&quot;41&quot; data-origin-width=&quot;562&quot; data-origin-height=&quot;41&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2026-01-23T22:35:14.543+09:00&amp;nbsp;&amp;nbsp;WARN&amp;nbsp;17171&amp;nbsp;---&amp;nbsp;[study]&amp;nbsp;[nio-8080-exec-9]&amp;nbsp;.s.b.m.m.MaximumAllowableTagsMeterFilter&amp;nbsp;:&amp;nbsp;Reached&amp;nbsp;the&amp;nbsp;maximum&amp;nbsp;number&amp;nbsp;of&amp;nbsp;'uri'&amp;nbsp;tags&amp;nbsp;for&amp;nbsp;'http.server.requests'.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring 문서나 설정되어있는 기본 config를 보면 100으로 설정되어있는걸 확인할 수 있다. (server, client)&lt;/p&gt;
&lt;pre class=&quot;vim&quot; style=&quot;background-color: #f8f8f8; color: #000000; text-align: start;&quot;&gt;&lt;code&gt;management.metrics.web.client.max-uri-tags=100 # Maximum number of unique URI tag values allowed. After the max number of tag values is reached, metrics with additional tag values are denied by filter.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 110으로 수정하고 돌려보면 잘 수집되고 에러메시지도 없는걸 확인 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/okllq/dJMcacu8aqA/ay7sHM1Jp3Z3CXTIHtDNWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/okllq/dJMcacu8aqA/ay7sHM1Jp3Z3CXTIHtDNWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/okllq/dJMcacu8aqA/ay7sHM1Jp3Z3CXTIHtDNWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fokllq%2FdJMcacu8aqA%2Fay7sHM1Jp3Z3CXTIHtDNWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;157&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표가 무작정 많이 수집되는건 여러 영향을 줄수 있기 때문에 크기를 어느정도 설정할지에 대해서는 잘 판단하는게 좋을 것 같다. 하지만 이설정이 누락되어서 관련 지표나 알림이 누락될 수 있으니 사전에 확인하는게 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/common-application-properties.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/common-application-properties.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769175705817&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Appendix&amp;nbsp;A.&amp;nbsp;Common application properties&quot; data-og-description=&quot;Appendix&amp;nbsp;A.&amp;nbsp;Common application properties Various properties can be specified inside your application.properties file, inside your application.yml file, or as command line switches. This appendix provides a list of common Spring Boot properties and refer&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/common-application-properties.html&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/common-application-properties.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/common-application-properties.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/html/common-application-properties.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Appendix&amp;nbsp;A.&amp;nbsp;Common application properties&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Appendix&amp;nbsp;A.&amp;nbsp;Common application properties Various properties can be specified inside your application.properties file, inside your application.yml file, or as command line switches. This appendix provides a list of common Spring Boot properties and refer&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1.0-M3-Configuration-Changelog&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.1.0-M3-Configuration-Changelog&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/weduls/promethus-max-uri-tags-test&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/weduls/promethus-max-uri-tags-test&lt;/a&gt;&lt;/p&gt;</description>
      <category>web/Spring</category>
      <category>max-uri-tags</category>
      <category>promethes</category>
      <category>Spring</category>
      <category>Tags</category>
      <category>최대</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/728</guid>
      <comments>https://wedul.tistory.com/728#entry728comment</comments>
      <pubDate>Fri, 23 Jan 2026 22:44:25 +0900</pubDate>
    </item>
    <item>
      <title>swagger  OpenApiCustomizer를 사용하여 커스텀 하기</title>
      <link>https://wedul.tistory.com/727</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 사용하고 있는 rpc library가 있는데 해당 rpc는 proto를 사용하는것이 아닌 java class로 만든 IDL을 사용하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/iOoquUhKT5g&quot;&gt;https://youtu.be/iOoquUhKT5g&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1717&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB45w0/btsN2QOSxwq/pIfs9xYPUJVzXePqpaTRG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB45w0/btsN2QOSxwq/pIfs9xYPUJVzXePqpaTRG0/img.png&quot; data-alt=&quot;출처 2023 우아콘&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB45w0/btsN2QOSxwq/pIfs9xYPUJVzXePqpaTRG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB45w0%2FbtsN2QOSxwq%2FpIfs9xYPUJVzXePqpaTRG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1717&quot; height=&quot;741&quot; data-origin-width=&quot;1717&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 2023 우아콘&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;method를 정의하고 method에 사용된 request, response 객체를 사용해서 rpc 호출을 하는 구조이다. 기존에 http로 통신하는 경우에는 spring rest docs를 사용하거나 spring swagger ui를 사용해서 api명세를 편하게 만들어서 외부 사용하는 팀에 전달 할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 java method기반으로 통신 프로토콜을 정의하는 방식에서는 swagger 기존 방식으로는 사용할 수 없기 때문에 이를 custom해줘야했다. 그 과정에서 문서를 만들기 위해서 custom한 과정을 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;RequestBody, ApiResponse에 대한 custom annotation으로의 대체&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 @Schema 애노테이션을 붙여서 RequestBody, ApiResponse로 사용될 객체에 클래스 위, 필드위에 설명을 적용할 수 있었다. 하지만 내부 library에 경우 @Schema를 붙여서 사용하지 않고 특정 custom annotation을 사용하고 있다. 여기서는 그 이름을 CustomSchemeAnnotation이라고 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1747577852909&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.openapi.annotation;

import io.swagger.v3.oas.annotations.media.Schema;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Schema
@Target({ElementType.FIELD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomSchemeAnnotation {

    String description() default &quot;&quot;;

    String example() default &quot;&quot;;

    String format() default &quot;&quot;;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Annotation을 기존 @Scheme와 같이 RequestBody, ApiResponse로 사용될 객체에 명세를 작성해줄 수 있다. 여기서는 @Schema에서 제공하는 필드 중 내부적으로 사용할 필드 세가지만 가지고 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RequestBody (CustomRequest)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1747577953120&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.openapi.annotation;

import java.util.List;
import java.util.Map;

@CustomSchemeAnnotation(description = &quot;custom request class&quot;)
public class CustomRequest {

    @CustomSchemeAnnotation(description = &quot;custom annotation name&quot;, example = &quot;wedul&quot;, format = &quot;String&quot;)
    private String name;

    @CustomSchemeAnnotation(description = &quot;custom annotation age&quot;, example = &quot;10&quot;, format = &quot;int&quot;)
    private int age;

    @CustomSchemeAnnotation(description = &quot;list of string&quot;, example = &quot;[\&quot;classA\&quot;, \&quot;classB\&quot;]&quot;, format = &quot;array of string&quot;)
    private List&amp;lt;String&amp;gt; stringList;

    @CustomSchemeAnnotation(description = &quot;data list example&quot;, example = &quot;{\&quot;fieldName\&quot;:\&quot;exampleField\&quot;}&quot;, format = &quot;array of Data&quot;)
    private Data data;

    @CustomSchemeAnnotation(description = &quot;generic string wrapper&quot;, example = &quot;{\&quot;value\&quot;:\&quot;hello\&quot;}&quot;, format = &quot;GenericWrapper&amp;lt;String&amp;gt;&quot;)
    private GenericWrapper&amp;lt;String&amp;gt; wrappedString;

    @CustomSchemeAnnotation(description = &quot;generic data wrapper&quot;, example = &quot;{\&quot;value\&quot;:{\&quot;fieldName\&quot;:\&quot;nestedField\&quot;}}&quot;, format = &quot;GenericWrapper&amp;lt;Data&amp;gt;&quot;)
    private GenericWrapper&amp;lt;Data&amp;gt; wrappedData;

    @CustomSchemeAnnotation(description = &quot;metadata map&quot;, example = &quot;{\&quot;key1\&quot;: 1, \&quot;key2\&quot;: 2}&quot;, format = &quot;Map&amp;lt;String, Integer&amp;gt;&quot;)
    private Map&amp;lt;String, Integer&amp;gt; metadata;

}



package com.wedul.openapi.annotation;

@CustomSchemeAnnotation(description = &quot;data inner class&quot;)
public class Data {
    @CustomSchemeAnnotation(description = &quot;field name in data&quot;, example = &quot;exampleField&quot;, format = &quot;string&quot;)
    private String fieldName;
}


package com.wedul.openapi.annotation;

@CustomSchemeAnnotation(description = &quot;generic wrapper&quot;)
public class GenericWrapper&amp;lt;T&amp;gt; {

    @CustomSchemeAnnotation(description = &quot;wrapped value&quot;, example = &quot;exampleValue&quot;, format = &quot;generic type&quot;)
    private T value;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ApiResponse&lt;/p&gt;
&lt;pre id=&quot;code_1747577966095&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.openapi.annotation;

@CustomSchemeAnnotation
public class CustomResponse {

    @CustomSchemeAnnotation(description = &quot;result Message&quot;, example = &quot;this is result.&quot;, format = &quot;String&quot;)
    private String resultMessage;

    public CustomResponse(String resultMessage) {
        this.resultMessage = resultMessage;
    }

    public CustomResponse() {

    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;RestController의 명세인 Operation을 대체 할 Custom annotation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;http가 아닌 메소드이름 자체가 path가 되어야한다. OpenApiCustomizer에서 path에 대한 작업을 진행하겠지만 path가 될 method를 명시해야하기 때문에 사용하고자 하는 method에 custom annotation CustomMethodAnnotation를 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1747578116933&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.openapi.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomMethodAnnotation {

    String title();

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1747578127924&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.openapi.annotation;

public class CustomMethodImpl implements CustomMethod {

    @Override
    @CustomMethodAnnotation(title = &quot;test method&quot;)
    public CustomResponse test(CustomRequest request) {
        return new CustomResponse();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;OpenApiCustomizer를 문서 커스텀&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 open api 문서를 생성하는 방식이 아닌 새롭게 정의한 애노테이션을 사용하도록 해야하고 path를 메소드 이름으로 사용할 수 있도록 커스텀을 진행해야한다. 이를 커스텀 하기 위해서 OpenApiCustomizer를 커스텀을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1747578270089&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.wedul.openapi.annotation.config;

import com.wedul.openapi.annotation.CustomMethodAnnotation;
import com.wedul.openapi.annotation.CustomSchemeAnnotation;
import io.swagger.v3.core.util.PrimitiveType;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ConfigurationBuilder;
import org.springdoc.core.customizers.OpenApiCustomizer;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class MethodCustomizer implements OpenApiCustomizer {

    @Override
    public void customise(OpenAPI openApi) {
        Reflections reflections = new Reflections(
                new ConfigurationBuilder()
                        .forPackages(&quot;com.wedul&quot;)
                        .addScanners(Scanners.TypesAnnotated, Scanners.MethodsAnnotated, Scanners.SubTypes)
        );

        Set&amp;lt;Method&amp;gt; annotatedMethods = reflections.getMethodsAnnotatedWith(CustomMethodAnnotation.class);

        for (Method annotatedMethod : annotatedMethods) {
            CustomMethodAnnotation annotation = annotatedMethod.getAnnotation(CustomMethodAnnotation.class);

            Operation operation = new Operation();
            operation.setSummary(annotation.title());
            operation.setOperationId(annotatedMethod.getName());

            Parameter[] parameters = annotatedMethod.getParameters();
            Class&amp;lt;?&amp;gt; requestType = parameters[0].getType();

            Schema&amp;lt;?&amp;gt; schema = buildSchemaFromCustomAnnotation(requestType, openApi);

            RequestBody requestBody = new RequestBody()
                    .content(new Content().addMediaType(&quot;application/json&quot;,
                            new MediaType().schema(schema)));
            operation.setRequestBody(requestBody);


            Class&amp;lt;?&amp;gt; returnType = annotatedMethod.getReturnType();
            Schema&amp;lt;?&amp;gt; responseSchema = buildSchemaFromCustomAnnotation(returnType, openApi);

            ApiResponse apiResResponse = new ApiResponse()
                    .content(new Content().addMediaType(&quot;application/json&quot;,
                            new MediaType().schema(responseSchema)));

            operation.setRequestBody(requestBody);
            operation.setResponses(new ApiResponses().addApiResponse(&quot;200&quot;, apiResResponse));

            PathItem pathItem = new PathItem().get(operation);
            openApi.path(annotatedMethod.getName(), pathItem);
        }
    }

    public Schema&amp;lt;?&amp;gt; buildSchemaFromCustomAnnotation(Class&amp;lt;?&amp;gt; clazz, OpenAPI openApi) {
        Schema&amp;lt;?&amp;gt; schema = new ObjectSchema();

        CustomSchemeAnnotation classAnnotation = clazz.getAnnotation(CustomSchemeAnnotation.class);
        if (classAnnotation != null) {
            applyCustomAnnotationToSchema(schema, classAnnotation);
        }

        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            CustomSchemeAnnotation fieldAnnotation = field.getAnnotation(CustomSchemeAnnotation.class);
            if (fieldAnnotation != null) {
                Schema&amp;lt;?&amp;gt; fieldSchema = resolveFieldSchema(field.getGenericType(), fieldAnnotation, openApi);
                schema.addProperties(field.getName(), fieldSchema);
            }
        }
        openApi.getComponents().addSchemas(clazz.getSimpleName(), schema);
        return schema;
    }

    private Schema&amp;lt;?&amp;gt; resolveFieldSchema(Type type, CustomSchemeAnnotation annotation, OpenAPI openApi) {
        Schema&amp;lt;?&amp;gt; schema;

        if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            Type raw = pt.getRawType();
            Type actual = pt.getActualTypeArguments()[0];

            if (raw == Map.class) {
                Type valueType = pt.getActualTypeArguments()[1];
                Schema&amp;lt;?&amp;gt; valueSchema = resolveFieldSchema(valueType, annotation, openApi);

                schema = new MapSchema().additionalProperties(valueSchema);
            } else if (raw == List.class || raw == Set.class) {
                schema = new ArraySchema().items(resolveFieldSchema(actual, annotation, openApi));
            } else {
                schema = resolveFieldSchema(actual, annotation, openApi);
            }
        } else if (type instanceof Class&amp;lt;?&amp;gt;) {
            Class&amp;lt;?&amp;gt; clazz = (Class&amp;lt;?&amp;gt;) type;
            schema = PrimitiveType.createProperty(clazz);
            if (schema == null) {
                buildSchemaFromCustomAnnotation(clazz, openApi);
                schema = new Schema&amp;lt;&amp;gt;().$ref(&quot;#/components/schemas/&quot; + clazz.getSimpleName());
            }
        } else {
            schema = new StringSchema();
        }

        applyCustomAnnotationToSchema(schema, annotation);
        return schema;
    }

    private void applyCustomAnnotationToSchema(Schema&amp;lt;?&amp;gt; schema, CustomSchemeAnnotation ann) {
        if (!ann.description().isEmpty()) schema.setDescription(ann.description());
        if (!ann.example().isEmpty()) schema.setExample(ann.example());
        if (!ann.format().isEmpty()) schema.setFormat(ann.format());
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;void&amp;nbsp;customise(OpenAPI&amp;nbsp;openApi)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; OpenApiCustomizer 인터페이스에 정의된 문서로 이곳을 통해서 openapi를 커스텀을 진행한다. 우선 com.wedul 로 시작하는 패키지 기준으로 애노테이션이 붙은 클래스들을 스캔한다. Operation의 기준이 될 method 들을 먼저 스캐닝하고 찾은 메소드의 CustomMethodAnnotation의 필드를 꺼내서 Operation객체를 새로 생성한다. 그 다음 해당 메소드의 Parameter와 response를 꺼내서 schema를 등록한다. 과정은 아래 메소드로 나눠서 설명한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Schema&amp;lt;?&amp;gt; buildSchemaFromCustomAnnotation(Class&amp;lt;?&amp;gt; clazz, OpenAPI openApi)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; class에 annotation이 달려있으면 상위 schema에 대해서 설명을 추가한다. 그 다음 필드를 순회하면서 CustomSchemeAnnotation이 달려있는 정보를 꺼내서 filed별 schema를 생성하고 클래스 schema에 properties로 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 생성 된 후 현재 class의 schema를 componets에 추가해준다. (스키마 재사용을 위해)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;private&amp;nbsp;Schema&amp;lt;?&amp;gt;&amp;nbsp;resolveFieldSchema(Type&amp;nbsp;type,&amp;nbsp;CustomSchemeAnnotation&amp;nbsp;annotation,&amp;nbsp;OpenAPI&amp;nbsp;openApi)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; RequestBody, ApiResponse 클래스 내부에 정의된 filed들에 대해서 schema를 만드는 로직이다. 이 로직을 통해서 ParameterizedType의 경우 collection과 제네릭에 대한 처리를 진행 할 수 있다. 그리고 그 이외 Primitive type에 대한 처리와 추가 처리 되지 못한건 string schema 처리를 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;최종 화면&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;1003&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caYxBq/btsN1CKRZvI/CKt5eRnRVGIn5k9VvCtOYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caYxBq/btsN1CKRZvI/CKt5eRnRVGIn5k9VvCtOYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caYxBq/btsN1CKRZvI/CKt5eRnRVGIn5k9VvCtOYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaYxBq%2FbtsN1CKRZvI%2FCKt5eRnRVGIn5k9VvCtOYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1445&quot; height=&quot;1003&quot; data-origin-width=&quot;1445&quot; data-origin-height=&quot;1003&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEsQtx/btsN2bTuMIH/twBROTkTpfUtrKY3POz5l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEsQtx/btsN2bTuMIH/twBROTkTpfUtrKY3POz5l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEsQtx/btsN2bTuMIH/twBROTkTpfUtrKY3POz5l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEsQtx%2FbtsN2bTuMIH%2FtwBROTkTpfUtrKY3POz5l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1444&quot; height=&quot;1004&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 이곳에서 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/weduls/custom-openapi/tree/main&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/weduls/custom-openapi/tree/main&lt;/a&gt;&lt;/p&gt;</description>
      <category>web/Spring</category>
      <category>API</category>
      <category>OpenAPI</category>
      <category>openapicustomizer</category>
      <category>REST Docs</category>
      <category>rpc</category>
      <category>Spring</category>
      <category>swagger</category>
      <category>swagger-ui</category>
      <category>문서</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/727</guid>
      <comments>https://wedul.tistory.com/727#entry727comment</comments>
      <pubDate>Sun, 18 May 2025 23:41:22 +0900</pubDate>
    </item>
    <item>
      <title>Virtual thread pinning issue와 java 24에서 해소 방법</title>
      <link>https://wedul.tistory.com/726</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;virtual thread&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java 21부터 virtual thread가 나온 것은 대부분의 알고 있는 사실이다. 간단하게 virtual thread는 기존 jvm에 사용하는 thread가 os kernal thread에 매핑되어 사용되던 걸 carrier thread (platform thread)에 virtual thread(향후 vt)를 사용하여 내부적 요청에 vt를 carrier thead에 mount, unmont하여 kernal os를 덜 사용하는 전략을 사용하는 thread를 말한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vSwua/btsLg6AB6om/Kraks0jDkMnPbI27FXc3y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vSwua/btsLg6AB6om/Kraks0jDkMnPbI27FXc3y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vSwua/btsLg6AB6om/Kraks0jDkMnPbI27FXc3y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvSwua%2FbtsLg6AB6om%2FKraks0jDkMnPbI27FXc3y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1864&quot; height=&quot;806&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 실행되는 로직을 확인해보자. openjdk에 있는 테스트용 VThreadRunner.java 코드를 이용해서 호출해보면서 내부적으로 VThread에서 사용되는 carrier thread 호출 scheduler는 fork join pool을 사용하는 걸 알 수 있다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1734062823784&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*
 * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javas.main;

import java.lang.reflect.Field;
import java.time.Duration;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Helper class to support tests running tasks a in virtual thread.
 */
public class VThreadRunner {
    private VThreadRunner() { }

    /**
     * Characteristic value signifying that initial values for inheritable
     * thread locals are not inherited from the constructing thread.
     */
    public static final int NO_INHERIT_THREAD_LOCALS = 1 &amp;lt;&amp;lt; 2;

    /**
     * Represents a task that does not return a result but may throw
     * an exception.
     */
    @FunctionalInterface
    public interface ThrowingRunnable {
        /**
         * Runs this operation.
         */
        void run() throws Exception;
    }

    /**
     * Run a task in a virtual thread and wait for it to terminate.
     * If the task completes with an exception then it is thrown by this method.
     * If the task throws an Error then it is wrapped in an RuntimeException.
     *
     * @param name thread name, can be null
     * @param characteristics thread characteristics
     * @param task the task to run
     * @throws Exception the exception thrown by the task
     */
    public static void run(String name,
                           int characteristics,
                           ThrowingRunnable task) throws Exception {
        AtomicReference&amp;lt;Exception&amp;gt; exc = new AtomicReference&amp;lt;&amp;gt;();
        Runnable target =  () -&amp;gt; {
            try {
                task.run();
            } catch (Error e) {
                exc.set(new RuntimeException(e));
            } catch (Exception e) {
                exc.set(e);
            }
        };

        Thread.Builder builder = Thread.ofVirtual();
        if (name != null)
            builder.name(name);
        if ((characteristics &amp;amp; NO_INHERIT_THREAD_LOCALS) != 0)
            builder.inheritInheritableThreadLocals(false);
        Thread thread = builder.start(target);

        // wait for thread to terminate
        while (thread.join(Duration.ofSeconds(10)) == false) {
            System.out.println(&quot;-- &quot; + thread + &quot; --&quot;);
            for (StackTraceElement e : thread.getStackTrace()) {
                System.out.println(&quot;  &quot; + e);
            }
        }

        Exception e = exc.get();
        if (e != null) {
            throw e;
        }
    }

    /**
     * Run a task in a virtual thread and wait for it to terminate.
     * If the task completes with an exception then it is thrown by this method.
     * If the task throws an Error then it is wrapped in an RuntimeException.
     *
     * @param name thread name, can be null
     * @param task the task to run
     * @throws Exception the exception thrown by the task
     */
    public static void run(String name, ThrowingRunnable task) throws Exception {
        run(name, 0, task);
    }

    /**
     * Run a task in a virtual thread and wait for it to terminate.
     * If the task completes with an exception then it is thrown by this method.
     * If the task throws an Error then it is wrapped in an RuntimeException.
     *
     * @param characteristics thread characteristics
     * @param task the task to run
     * @throws Exception the exception thrown by the task
     */
    public static void run(int characteristics, ThrowingRunnable task) throws Exception {
        run(null, characteristics, task);
    }

    /**
     * Run a task in a virtual thread and wait for it to terminate.
     * If the task completes with an exception then it is thrown by this method.
     * If the task throws an Error then it is wrapped in an RuntimeException.
     *
     * @param task the task to run
     * @throws Exception the exception thrown by the task
     */
    public static void run(ThrowingRunnable task) throws Exception {
        run(null, 0, task);
    }

    /**
     * Returns the virtual thread scheduler.
     */
    private static ForkJoinPool defaultScheduler() {
        try {
            var clazz = Class.forName(&quot;java.lang.VirtualThread&quot;);
            var field = clazz.getDeclaredField(&quot;DEFAULT_SCHEDULER&quot;);
            field.setAccessible(true);
            return (ForkJoinPool) field.get(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Sets the virtual thread scheduler's target parallelism.
     * @return the previous parallelism level
     */
    public static int setParallelism(int size) {
        return defaultScheduler().setParallelism(size);
    }

    /**
     * Ensures that the virtual thread scheduler's target parallelism is at least
     * the given size. If the target parallelism is less than the given size then
     * it is changed to the given size.
     * @return the previous parallelism level
     */
    public static int ensureParallelism(int size) {
        ForkJoinPool pool = defaultScheduler();
        int parallelism = pool.getParallelism();
        if (size &amp;gt; parallelism) {
            pool.setParallelism(size);
        }
        return parallelism;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HC0lz/btsLhfYyw2p/2jmEFy7kuE9bTkFlRw7G8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HC0lz/btsLhfYyw2p/2jmEFy7kuE9bTkFlRw7G8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HC0lz/btsLhfYyw2p/2jmEFy7kuE9bTkFlRw7G8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHC0lz%2FbtsLhfYyw2p%2F2jmEFy7kuE9bTkFlRw7G8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1484&quot; height=&quot;834&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 contiton을 생성해서 task를 실행시키는데 이때 사용되는 VThreadContinuation을 생성한다. 이 Continuation은 실제 해당 vt가 실행될 때 호출되는 메소드이고 wrap이외에 onPinned가 구현되어 있는데 onPinned는 내부적으로 vt가 pinning되었을 때 실행되는 메소드이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/poNfD/btsLg5B0XzD/pyVKbzCEDEsPFvZnYHLaw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/poNfD/btsLg5B0XzD/pyVKbzCEDEsPFvZnYHLaw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/poNfD/btsLg5B0XzD/pyVKbzCEDEsPFvZnYHLaw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpoNfD%2FbtsLg5B0XzD%2FpyVKbzCEDEsPFvZnYHLaw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1184&quot; height=&quot;880&quot; data-origin-width=&quot;1184&quot; data-origin-height=&quot;880&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Vt를 실행시키는 runContinuation을 선언하는데 이 runContinuation은 vt가 실행되는 yield상태에서 호출되고 바로 호출 하는 것이 아닌 위에 생성했던 scheduler에 스케줄 처리되면서 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9GUAC/btsLg8r2XtY/9kVxDU7yyWuOtK43qO8PAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9GUAC/btsLg8r2XtY/9kVxDU7yyWuOtK43qO8PAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9GUAC/btsLg8r2XtY/9kVxDU7yyWuOtK43qO8PAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9GUAC%2FbtsLg8r2XtY%2F9kVxDU7yyWuOtK43qO8PAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1338&quot; height=&quot;682&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;condition내부에는 vt를 mount하고 종료되면 unmount 하는 코드가 들어있는 걸 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;1546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c093CV/btsLhM23tuQ/8nsKR9e5En5MsYxo2fE6A1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c093CV/btsLhM23tuQ/8nsKR9e5En5MsYxo2fE6A1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c093CV/btsLhM23tuQ/8nsKR9e5En5MsYxo2fE6A1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc093CV%2FbtsLhM23tuQ%2F8nsKR9e5En5MsYxo2fE6A1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1732&quot; height=&quot;1546&quot; data-origin-width=&quot;1732&quot; data-origin-height=&quot;1546&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;virtual thread의 효과적인 사용처&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;virtual thread를 사용할 때 cpu intensive한 환경이 아닌 io intensive한 환경에서 사용하라고 한다. 그이유는 virtual thread의 경우 i/o bound 작업에 효율성이 있어 높은 처리량을 주기위해서 설계되었기 때문에 cpu bound가 높은 환경에서는 오히려 오버헤드가 늘어날 수 있다. 또한 virtual thread는 가볍게 여러개 생성되면서 여러 작업을 수행할 수 있게 해주는데 cpu intensive한 환경에서는 어울리지 않으며 컨텍스트 스위칭 비용에 대한 효과를 보기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;virtual thread의 문제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;carrier thread에서는 vt를 할당받아서 내부적으로 수행하게 되는데 synchronized 되어 있는 코드를 수행하게 되면 pinning이 발생하는 문제가 생긴다. 그 이유는 synchronized안에는 동시성 제어를 위한 monitor를 들고 있으며 이 걸 사용해서 lock, unlock을 수행한다. 내부적으로 이 monitor에 대한 소유는 carrier thread가 보유하게 되기 때문에 blocking되는 상황에서는 carrior thread자체가 blocking 되는 이슈가 있다. 이는 synchronized가 jvm 레벨에서 구현되어 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해 대한 해결법으로 ReentrantLock을 사용하면 해소가 가능하다고 한다. ReentrantLock는 synchronized와 다르게 FIFO 큐로 관리되어있어 lock에 대한 점유 순서를 보장하고 있기 때문에 VT에서는 효율적인 스케줄링이 가능하기 때문에 필요에 따라서 Carrier Thread에서 mount/unmount가 가능하다. 이는 java level에서 구현되어 있어서 가능한 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 : &lt;a href=&quot;https://www.reddit.com/r/java/comments/13ze03y/question_about_virtual_threads_and_their/?rdt=60476&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.reddit.com/r/java/comments/13ze03y/question_about_virtual_threads_and_their/?rdt=60476&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/78671922/why-reentrantlock-is-better-for-virtual-threads-than-synchronized&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/78671922/why-reentrantlock-is-better-for-virtual-threads-than-synchronized&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734065991572&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;From the java community on Reddit&quot; data-og-description=&quot;Explore this post and more from the java community&quot; data-og-host=&quot;www.reddit.com&quot; data-og-source-url=&quot;https://www.reddit.com/r/java/comments/13ze03y/question_about_virtual_threads_and_their/?rdt=60476&quot; data-og-url=&quot;https://www.reddit.com/r/java/comments/13ze03y/question_about_virtual_threads_and_their/?rdt=60476&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bUGy4E/hyXKtXYNFk/dMXwLUnpE2lPm9EBZa0gs0/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584,https://scrap.kakaocdn.net/dn/zdQpv/hyXKyEWX9N/mtwadZ8UAzEMcIV5Cdphn1/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/java/comments/13ze03y/question_about_virtual_threads_and_their/?rdt=60476&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.reddit.com/r/java/comments/13ze03y/question_about_virtual_threads_and_their/?rdt=60476&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bUGy4E/hyXKtXYNFk/dMXwLUnpE2lPm9EBZa0gs0/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584,https://scrap.kakaocdn.net/dn/zdQpv/hyXKyEWX9N/mtwadZ8UAzEMcIV5Cdphn1/img.jpg?width=1120&amp;amp;height=584&amp;amp;face=0_0_1120_584');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;From the java community on Reddit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Explore this post and more from the java community&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.reddit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넷플릭스에서 찾은 관련된 이슈&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://netflixtechblog.com/java-21-virtual-threads-dude-wheres-my-lock-3052540e231d&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://netflixtechblog.com/java-21-virtual-threads-dude-wheres-my-lock-3052540e231d&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 테스트 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734667851669&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package javas.main.wedul;

import lombok.Synchronized;

public class Wedul {

    @Synchronized
    public synchronized void start() throws InterruptedException {
        System.out.println(&quot;start&quot;);
        Thread.sleep(1000);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;synchronized가 붙어있는 Wedul클래스의 start라는 메소드가 있다고 가정하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 두개의 virtual thread가 Wedul.start()를 실행시킨다고 가정해보면 synchronized가 pinning을 발생시키는지 확이할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 여러개의 carrier thread가 존재할 경우 vt로 인한 pinning이 아닌 여러개의 carrier thread가 실행될 수 있으니 vm option을 주어 개수를 제한한다. 그리고 trace mode가 켜져있어야 하기 때문에 해당 옵션도 켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734668502925&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-Djdk.virtualThreadScheduler.parallelism=1 
-Djdk.virtualThreadScheduler.maxPoolSize=1 
-Djdk.virtualThreadScheduler.minRunnable=1 
-Djdk.tracePinnedThreads=short&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위에 사용했던 VThreadRunner를 사용해서 thread를 실행시켜보자.&lt;/p&gt;
&lt;pre id=&quot;code_1734668847283&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package javas.main.wedul;

import javas.main.VThreadRunner;

public class JavaApplication {

    public static void main(String[] args) throws Exception {
        Wedul w = new Wedul();
        VThreadRunner.run(w::start);
        VThreadRunner.run(w::start);
    }


}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5LB9p/btsLpQkcF8L/KyERQIkcEeYQCQRuMCTFS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5LB9p/btsLpQkcF8L/KyERQIkcEeYQCQRuMCTFS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5LB9p/btsLpQkcF8L/KyERQIkcEeYQCQRuMCTFS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5LB9p%2FbtsLpQkcF8L%2FKyERQIkcEeYQCQRuMCTFS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1096&quot; height=&quot;244&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pinning 이 발생해서 VT 생성시에 추가되었던 VThreadContinuation에 onPinned가 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnJnhF/btsLpKK365V/QXEK0MFyNGrbaDljZ3Jiy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnJnhF/btsLpKK365V/QXEK0MFyNGrbaDljZ3Jiy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnJnhF/btsLpKK365V/QXEK0MFyNGrbaDljZ3Jiy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnJnhF%2FbtsLpKK365V%2FQXEK0MFyNGrbaDljZ3Jiy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1882&quot; height=&quot;452&quot; data-origin-width=&quot;1882&quot; data-origin-height=&quot;452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;java 24에서 virtual thread 해소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://openjdk.org/jeps/491&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://openjdk.org/jeps/491&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734669517681&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JEP 491: Synchronize Virtual Threads without Pinning&quot; data-og-description=&quot;JEP 491: Synchronize Virtual Threads without Pinning AuthorPatricio Chilano Mateo &amp;amp; Alan BatemanOwnerAlan BatemanTypeFeatureScopeImplementationStatusCompletedRelease24Componenthotspot&amp;thinsp;/&amp;thinsp;runtimeDiscussionhotspot dash dev at openjdk dot org, loom dash de&quot; data-og-host=&quot;openjdk.org&quot; data-og-source-url=&quot;https://openjdk.org/jeps/491&quot; data-og-url=&quot;https://openjdk.org/jeps/491&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://openjdk.org/jeps/491&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://openjdk.org/jeps/491&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JEP 491: Synchronize Virtual Threads without Pinning&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JEP 491: Synchronize Virtual Threads without Pinning AuthorPatricio Chilano Mateo &amp;amp; Alan BatemanOwnerAlan BatemanTypeFeatureScopeImplementationStatusCompletedRelease24Componenthotspot&amp;thinsp;/&amp;thinsp;runtimeDiscussionhotspot dash dev at openjdk dot org, loom dash de&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;openjdk.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openjdk에 올라온 내용을 보면 jdk24부터는 synchroize를 사용하더라도 pinning이 되지 않도록 수정했다고 하는데 그 주요 내용은 carrier thread가 synchorinzed를 만났을 때 monitor를 점유하는게 아닌 vt가 점유하게 하고 그 vt를 unmount시키도록 하는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/em1NbB/btsLoKLSPA9/BPkbqah2c3XKVsrY3Kgk6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/em1NbB/btsLoKLSPA9/BPkbqah2c3XKVsrY3Kgk6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/em1NbB/btsLoKLSPA9/BPkbqah2c3XKVsrY3Kgk6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fem1NbB%2FbtsLoKLSPA9%2FBPkbqah2c3XKVsrY3Kgk6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1614&quot; height=&quot;404&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;똑같은 코드를 jdk24로 수행해보니 pinning 경고가 사라진걸 확인 할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOsMpd/btsLravpt1c/ZK1gekyyNvhgOGzj67xAzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOsMpd/btsLravpt1c/ZK1gekyyNvhgOGzj67xAzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOsMpd/btsLravpt1c/ZK1gekyyNvhgOGzj67xAzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOsMpd%2FbtsLravpt1c%2FZK1gekyyNvhgOGzj67xAzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;308&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;virtual thread를 사용한다면 특히 synchronized가 많은 mysql drive를 사용한다면 jdk 24이상부터 사용하자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위 예시 코드 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://github.com/weduls/virtual-theadtest&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/weduls/virtual-theadtest&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>JAVA/고급 자바</category>
      <category>carrier thread</category>
      <category>jdk21</category>
      <category>jdk24</category>
      <category>pinning</category>
      <category>virtual thread</category>
      <category>VT</category>
      <category>가상스레드</category>
      <category>캐리어스레드</category>
      <category>플랫폼 스레드</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/726</guid>
      <comments>https://wedul.tistory.com/726#entry726comment</comments>
      <pubDate>Fri, 20 Dec 2024 14:07:31 +0900</pubDate>
    </item>
    <item>
      <title>lettuce pipeline코드 사용시 커넥션 풀 사용 필요</title>
      <link>https://wedul.tistory.com/725</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;lettuce를 사용해서 레디스를 사용을 하고 있었는데 어느날 갑자기 아래처럼 레디스 커넥션을 못잡는 이슈가 발생했다.&lt;/p&gt;
&lt;pre id=&quot;code_1717849695291&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: Master is currently unknown: [RedisMasterReplicaNode [redisURI=redis://xxx.xxx.com?timeout=20s, role=REPLICA]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 aws 레디스쪽 이슈일것으로 의심했으나 레디스쪽에는 별다른 지표가 없었고 애플리케이션에서 커넥션을 사용하지 못하는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 배포 하면서 들어간 코드는 비동기 코드로 파이프라인 코드를 사용하는 부분이 들어갔었다. 파이프라인을 사용한 부분은 set + expire를 줘야했고 한번에 100개 이상의 키를 넣었어야했는데 단건으로 요청할시 하나의 요청이 20ms걸린다고 가정했을 때 100개면 2초가 소요되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 로직상 너무 늦어지는 부분이었고 이걸 줄이기 위해서 요청을 한번에 레디스에 보낼수 있도록 파이프라인을 사용했고 그결과 20~100ms사이로 요청을 확 줄일 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이부분이 커넥션을 못찾게 되는 부분을 만들게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유가 모였을까? 기본적으로 lettuce는 하나의 싱글 커넥션을 가지고 처리를 하고 내부적으로는 네티 기반(event-driven i/o)으로 되어있어서 멀티 스레드 환경에서 사용하는데 문제가 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 파이프라인이 들어가기 전에 비동기 환경에서 사용했어도 별도의 커넥션 풀을 사용하지 않아도 큰 문제가 없었다. &lt;b&gt;하지만 pipeline이나 트랜잭션의 요청의 경우 블로킹 요청이기 때문에 네티 환경에서 사용하게 되면 문제가 발생된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 사실을 알고 우리는 배치 애플리케이션이었고 굳이 레디스에 msetex하는 파이프라인 부분에 비동기로 할필요가 없어서 동기로 바꿨고 문제는 해결되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 만약 계속 비동기 사용이 필요한경우에는 하나의 커넥션으로 하게되면 block되는 과정에서 다른 스레드에서 커넥션을 사용하지 못하는 문제가 발생하기 때문에 커넥션 풀을 꼭 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커넥션 풀 사용하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/redis/lettuce/wiki/Connection-Pooling&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/redis/lettuce/wiki/Connection-Pooling&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&amp;nbsp; :&lt;a href=&quot;https://bytepitch.com/blog/article/redis-integration-spring-boot&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://bytepitch.com/blog/article/redis-integration-spring-boot&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717850150902&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Software Engineering Company Blog | BytePitch&quot; data-og-description=&quot;BytePitch shares knowledge on software development topics to support the community and help software engineering projects succeed.&quot; data-og-host=&quot;bytepitch.com&quot; data-og-source-url=&quot;https://bytepitch.com/blog/article/redis-integration-spring-boot&quot; data-og-url=&quot;https://bytepitch.com/blog/redis-integration-spring-boot&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://bytepitch.com/blog/article/redis-integration-spring-boot&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://bytepitch.com/blog/article/redis-integration-spring-boot&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Software Engineering Company Blog | BytePitch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;BytePitch shares knowledge on software development topics to support the community and help software engineering projects succeed.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;bytepitch.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web/Spring</category>
      <category>Connection</category>
      <category>lettuce</category>
      <category>Pipeline</category>
      <category>redis</category>
      <category>Spring</category>
      <category>transaction</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/725</guid>
      <comments>https://wedul.tistory.com/725#entry725comment</comments>
      <pubDate>Sat, 8 Jun 2024 21:40:40 +0900</pubDate>
    </item>
    <item>
      <title>spring batch에서 파라미터 시 - 사용 주의</title>
      <link>https://wedul.tistory.com/724</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;spring batch에서 job param을 전달할 때 관용적으로 -를 붙여서 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 된 시점은 프로젝트에서 중복으로 돌면 안되는 배치에 preventrestart를 붙히고 돌리고 있는데 주기적으로 already job param으로 동작한 배치가 있다면서 배치가 자꾸 죽는 이슈가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 파악하기 위해 디버깅을 하던 중 spring batch에서 job의 unique를 판단하는 부분에서 job param으로 전달하고 있는 값들을 job instance에 유니크로 확인하는데 사용을 못하고 있는 부분을 발견했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2750&quot; data-origin-height=&quot;1998&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deRY9s/btsHcKBnLBe/kMt3XSjfZz1JQeplWcbIk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deRY9s/btsHcKBnLBe/kMt3XSjfZz1JQeplWcbIk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deRY9s/btsHcKBnLBe/kMt3XSjfZz1JQeplWcbIk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeRY9s%2FbtsHcKBnLBe%2FkMt3XSjfZz1JQeplWcbIk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2750&quot; height=&quot;1998&quot; data-origin-width=&quot;2750&quot; data-origin-height=&quot;1998&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보면 알겠지만 실제 parameter는 push라는 형태로 전달 되지만 identifying이 false로 되어있는걸 확인할 수 있다. 이러다보니 job instance가 이미 돈적이 있다는 오류가 계속 발생했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 identifying을 체크하는 로직을 따라가보니 param으로 설정할 때 -를 붙히면 identifying을 false로 지정하고 있는걸 확인했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2352&quot; data-origin-height=&quot;1146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vQ8WD/btsHfBjflkT/3ZYHwRyF383OtIyY0utHk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vQ8WD/btsHfBjflkT/3ZYHwRyF383OtIyY0utHk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vQ8WD/btsHfBjflkT/3ZYHwRyF383OtIyY0utHk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvQ8WD%2FbtsHfBjflkT%2F3ZYHwRyF383OtIyY0utHk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2352&quot; height=&quot;1146&quot; data-origin-width=&quot;2352&quot; data-origin-height=&quot;1146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관용적으로 쓰던 -가 실제 의미를 가지고 동작하는지는 처음 확인하게된 부분이었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2696&quot; data-origin-height=&quot;1664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JTlor/btsHbm9Dywh/BkKtrnUbcReQyLZ2VkkhRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JTlor/btsHbm9Dywh/BkKtrnUbcReQyLZ2VkkhRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JTlor/btsHbm9Dywh/BkKtrnUbcReQyLZ2VkkhRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJTlor%2FbtsHbm9Dywh%2FBkKtrnUbcReQyLZ2VkkhRk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2696&quot; height=&quot;1664&quot; data-origin-width=&quot;2696&quot; data-origin-height=&quot;1664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;param에서 -를 빼고 요청하니 정상적으로 identifying이 true로 동작하는걸 확인했다. 아마 기존에는 spring batch실행 시 prevent restart를 붙히지 않았거나 run.id를 붙여서 사용했어서 큰 이슈가 없었던 것 같다. 조심하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>web/Spring</category>
      <category>-</category>
      <category>identifying</category>
      <category>JOB</category>
      <category>job parameter</category>
      <category>Parameter</category>
      <category>Spring</category>
      <category>Spring Batch</category>
      <category>스프링</category>
      <category>스프링배치</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/724</guid>
      <comments>https://wedul.tistory.com/724#entry724comment</comments>
      <pubDate>Tue, 7 May 2024 11:01:11 +0900</pubDate>
    </item>
    <item>
      <title>gradle build시 파라미터 전달받아 사용하기</title>
      <link>https://wedul.tistory.com/723</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;beta에는 restdocs를 만들어서 static/index.html에 위치시키고 싶었고 운영에 경우에는 이로직을 빼고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는 build시점에 profile을 전달받아야했고 그것에 따라서 로직 분리가 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는 ./gradlew build 시 param으로 값을 전달하고 그 전달한 값을 사용하여 빌드 로직을 분리해야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 build를 할 build.gradle에 argument를 받는코드와 분기로직을 작성했다.&lt;/p&gt;
&lt;pre id=&quot;code_1715046448932&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bootJar {
    enabled = true
    String activeProfile = project.findProperty('profiles') ?: ''
    println &quot;zone: $activeProfile&quot;
    if (activeProfile == 'beta') {
		// beta 로직 
    } else if (activeProfile == 'prod') {
    	// 운영 로직
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ./gradlew에 param을 추가한다.&lt;/p&gt;
&lt;pre id=&quot;code_1715046566617&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; -Pprofiles=beta&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZvtaL/btsHbCqSwOt/Oazdq38LYHeOQKrgd9eiEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZvtaL/btsHbCqSwOt/Oazdq38LYHeOQKrgd9eiEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZvtaL/btsHbCqSwOt/Oazdq38LYHeOQKrgd9eiEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZvtaL%2FbtsHbCqSwOt%2FOazdq38LYHeOQKrgd9eiEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;572&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;572&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 된다.&lt;/p&gt;</description>
      <category>web</category>
      <category>Gradle</category>
      <category>param</category>
      <category>Profile</category>
      <category>Spring</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/723</guid>
      <comments>https://wedul.tistory.com/723#entry723comment</comments>
      <pubDate>Tue, 7 May 2024 10:51:28 +0900</pubDate>
    </item>
    <item>
      <title>elasticsearch routing 사용하기</title>
      <link>https://wedul.tistory.com/722</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[기본구조]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elasticserch에서 데이터는 index에 저장되고 index는 shard로 구성되어 있다. 기본적으로 데이터가 shard에 들어가는 기준은 document에 _id를 기준으로 들어가게 된다. 그렇기 때문에 index에 데이터를 조회할 때 어떤 샤드에 값이 저장되어 있는지 알수 없기 때문에 모든 shard에 값을 질의하고 그 값을 조합해서 값을 내려준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번 일반적인 index를 만들고 조회해보자. 아래처럼 Index를 생성하고 &quot;name&quot;에 wedul을 넣고 조회해보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1690293810714&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT localhost:19200/before-route
{
  &quot;settings&quot;: {
    &quot;number_of_shards&quot;: 4,
    &quot;number_of_replicas&quot;: 0
  },
  &quot;mappings&quot;: {
    &quot;properties&quot;: {
      &quot;name&quot;: { &quot;type&quot;: &quot;text&quot; }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 아래에 보는것처럼 4개의 shard 모두 조회에 사용된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;1816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkzDzz/btso0O8UWhJ/8TWl9kdb8vXM2zXnolZAKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkzDzz/btso0O8UWhJ/8TWl9kdb8vXM2zXnolZAKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkzDzz/btso0O8UWhJ/8TWl9kdb8vXM2zXnolZAKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkzDzz%2Fbtso0O8UWhJ%2F8TWl9kdb8vXM2zXnolZAKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;818&quot; height=&quot;1816&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;1816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[routing]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 routing을 추가해서 값을 조회해보자. 우선 routing을 index에 무조건 넣어서 데이터를 indexing하도록 만들어보자. 기존 before-route index를 놔두고 새로운 after-route index를 생성해보자.&amp;nbsp; mappings에 _routing필드를 추가하고 required 속성을 true로 지정하면 무조건 indexing할때 routing 정보가 있어야 라우팅 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1690294090740&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT localhost:19200/after-route
{
    &quot;settings&quot;: {
        &quot;number_of_shards&quot;: 4,
        &quot;number_of_replicas&quot;: 0
    },
    &quot;mappings&quot;: {
        &quot;_routing&quot;: {
            &quot;required&quot;: true
        },
        &quot;properties&quot;: {
            &quot;name&quot;: {
                &quot;type&quot;: &quot;text&quot;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 강제해서 무조건 필요한 데이터 조회 시 특정한 샤드를 조회할 수 있도록 해야한다. 물론 아무때나 routing field를 추가할 수 있는건 아니다. 전체 문서 기준으로 조회가 매번 필요하고 별도 샤드를 나눌 기준이 없는경우에는 routing을 기준으로 삼을만한게 없다. 이럴때는 routing을 넣을 때 신중하게 기준을 삼는게 좋고 그게 어렵다면 routing filed를 사용하지 않는것도 방법이다. routing field에 적합한 예로는 특정 가게에 있는 상품들을 조회한다고 가정하였을 때, 단순 쿼리로 shopNo를 필터한다면 모든 샤드를 다조회해야하고 그 양이 일반적인 커머스라면 수억건이 될거라서 부담을 많이 줄 수 있기에 가게번호 기준으로 routing을 해놓으면 특정 샤드만 조회하기에 이점을 가져갈 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;단 주의해야할 점이 routing은 특정 shard를 매칭하기 위함이기에 shopNo에 대한 필터 쿼리는 유지해야한다. routing에 가게번호를 넣어서 조회한다고해서 해당가게 데이터만 나오는게 아니다. 단순히 routing은 shard에 대한 routing이기에 해당 shard에 있는 다른 데이터들도 함께 조회되기 때문에 filter 쿼리를 빼먹으면 안된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 값을 넣어보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baK9nV/btsoZMDySWv/5j9IjBA8KdUXW2zmV1Hnm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baK9nV/btsoZMDySWv/5j9IjBA8KdUXW2zmV1Hnm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baK9nV/btsoZMDySWv/5j9IjBA8KdUXW2zmV1Hnm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaK9nV%2FbtsoZMDySWv%2F5j9IjBA8KdUXW2zmV1Hnm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1042&quot; height=&quot;1330&quot; data-origin-width=&quot;1042&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;routing을 사용해서 값을 넣을 때는 request param으로 routing 필드를 넣어줘야한다. 그럼 이렇게 들어간 데이터의 routing 필드 데이터를 _search api로 확인해보면 아까 넣은값이 들어있는걸 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PLhOm/btsoZbQ9zsK/nrueIiyMNwHtwU7yzTHC81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PLhOm/btsoZbQ9zsK/nrueIiyMNwHtwU7yzTHC81/img.png&quot; data-alt=&quot;_search 조회 시 _routing field값 보임&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PLhOm/btsoZbQ9zsK/nrueIiyMNwHtwU7yzTHC81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPLhOm%2FbtsoZbQ9zsK%2FnrueIiyMNwHtwU7yzTHC81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;1062&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;_search 조회 시 _routing field값 보임&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, _routing field를 강제한 상황에서 doc을 조회할 시 기존처럼 id로 조회할 때 무조건 routing을 함께 적어야한다. 그렇지 않으면 routing_missing_exception이 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ditb5l/btso07NZz1h/gJBXeHcx1H944kvjusEzuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ditb5l/btso07NZz1h/gJBXeHcx1H944kvjusEzuK/img.png&quot; data-alt=&quot;_routing정보를 빼고 조회한경우&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ditb5l/btso07NZz1h/gJBXeHcx1H944kvjusEzuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fditb5l%2Fbtso07NZz1h%2FgJBXeHcx1H944kvjusEzuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1204&quot; height=&quot;616&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;_routing정보를 빼고 조회한경우&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690295081752&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;GET localhost:19200/after-route/_doc/1?routing=1000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 routing을 넣어줘야 값이 제대로 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음으로 실제로 routing을 넣어서 조회하면 하나의 shard에서만 조회가 발생하는지 확인해보면 아래 같은 쿼리에서 routing만 붙여서 조회해도 shard가 1개에서 조회가 이뤄진거를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;1936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/00dhR/btsoTm0FyBr/c1NMkDX6FXxNvQvxH881Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/00dhR/btsoTm0FyBr/c1NMkDX6FXxNvQvxH881Bk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/00dhR/btsoTm0FyBr/c1NMkDX6FXxNvQvxH881Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F00dhR%2FbtsoTm0FyBr%2Fc1NMkDX6FXxNvQvxH881Bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;1936&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;1936&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 routing을 사용하면 성능상 이점을 가져올 수 있고 리소스 사용량도 확 줄일 수 있다. 하지만 잘못된 routing을 넣으면 값이 없기 때문에 내가 넣은 데이터의 routing정보를 잘 확인하고 조회에 사용해야 서비스 버그를 예방할 수 있다. 실제 60000 routing field에 저장된 값을 1로 임의로 바꿔서 조회하면 아래처럼 값이 나오지 않는걸 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;1606&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mAM0E/btsoTnrG0Un/J4EY8FC8LelkuSFkLkzaNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mAM0E/btsoTnrG0Un/J4EY8FC8LelkuSFkLkzaNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mAM0E/btsoTnrG0Un/J4EY8FC8LelkuSFkLkzaNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmAM0E%2FbtsoTnrG0Un%2FJ4EY8FC8LelkuSFkLkzaNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1070&quot; height=&quot;1606&quot; data-origin-width=&quot;1070&quot; data-origin-height=&quot;1606&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 routing을 사용했다고 해서 전체 document를 대상으로 조회가 되지 않는건 아니다. 만약 routing을 제외하고 전체 document를 대상으로 조회하고 싶은경우에는 _search api에서 routing parameter만 빼고 조회하면 된다. (단, 위에서 봤듯이 특정 document에 경우에는 조회하고자 할경우에 무조건 routing정보를 기입해줘야한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스/Elasticsearch</category>
      <category>elasticsearch</category>
      <category>route</category>
      <category>routing</category>
      <category>Shard</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/722</guid>
      <comments>https://wedul.tistory.com/722#entry722comment</comments>
      <pubDate>Tue, 25 Jul 2023 23:33:20 +0900</pubDate>
    </item>
    <item>
      <title>shard reroute api 테스트</title>
      <link>https://wedul.tistory.com/721</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;elasticsearch에서는 들어온 요청에 대해서 primary shard, replica shard를 병렬로 요청을하기 때문에 replica가 있는게 좋긴하다. 하지만 replica shard나 primary shard가 제대로 노드에 분배되어 있지 않으면 조회가 특정노드에 몰리거나 인덱싱 시 노드에 부하가 심해질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분배를 위해서는 기본적으로 es cluster에 아래 옵션들이 제공된다. (&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cluster.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;링크&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot;&gt;cluster.routing.allocation.balance.shard&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;- 노드에 샤드를 균등하게 분배 (기본값 0.45f, 값이 높아질수록 노드들에 샤드들이 골고루 분배됨)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cluster.routing.allocation.balance.index&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인덱스당 샤드 분배를 균등하게 (기본값은 0.55f, 값이 높아질후록 클러스터 모든 노드에 shard가 골구루 분배됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cluster.routing.allocation.balance.threshold&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 클러스터에서 노드 간 데이터 분배를 조정하는데 사용한다. (기본값은 1.0f, 이값이 높아지면 높아질 수록 클러스터는 밸런스가 깨진 상태가 됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위 설정을 한다고 해도 일부 샤드만 재조정을&amp;nbsp; 하고자하는 경우가 있을것이다. 관련해서 우리는 &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-reroute.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;reroute api&lt;/a&gt;를 사용할것이고 몇가지 사례를 가정해서 테스트 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;primary shard를 노드를 이동하고 싶을 때&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;primary shard가 3개있고 노드가 3개인 상황에서 shard0을 elasticsearch3 node로 move시켜보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ubvWd/btsoZc9GRnq/UMo4bc3pw9UiyHZ5UOb8V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ubvWd/btsoZc9GRnq/UMo4bc3pw9UiyHZ5UOb8V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ubvWd/btsoZc9GRnq/UMo4bc3pw9UiyHZ5UOb8V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FubvWd%2FbtsoZc9GRnq%2FUMo4bc3pw9UiyHZ5UOb8V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1030&quot; height=&quot;848&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690267771947&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST _cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;move&quot;: {
                &quot;index&quot;: &quot;wedul&quot;,
                &quot;shard&quot;: 0,
                &quot;from_node&quot;: &quot;elasticsearch1&quot;,
                &quot;to_node&quot;: &quot;elasticsearch3&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csoU8p/btsoU6PXuij/RYKnNIfvAp271mvjS8kNO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csoU8p/btsoU6PXuij/RYKnNIfvAp271mvjS8kNO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csoU8p/btsoU6PXuij/RYKnNIfvAp271mvjS8kNO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsoU8p%2FbtsoU6PXuij%2FRYKnNIfvAp271mvjS8kNO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;814&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shard 0이 elasticsearch 3로 이동되고 1이 elasticsearch0으로 swap 된거를 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 primary shard가 수가 노드마다 다를때는?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hguut/btsoZZWtcXH/3Yue1NJA20UCJtfVON91RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hguut/btsoZZWtcXH/3Yue1NJA20UCJtfVON91RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hguut/btsoZZWtcXH/3Yue1NJA20UCJtfVON91RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHguut%2FbtsoZZWtcXH%2F3Yue1NJA20UCJtfVON91RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1048&quot; height=&quot;842&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0을 elasticsearch2노드로 옮겨보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;870&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZCEiT/btsoRzFAnKA/AJEgbhxS6QkJr59aa7mDOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZCEiT/btsoRzFAnKA/AJEgbhxS6QkJr59aa7mDOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZCEiT/btsoRzFAnKA/AJEgbhxS6QkJr59aa7mDOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZCEiT%2FbtsoRzFAnKA%2FAJEgbhxS6QkJr59aa7mDOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1044&quot; height=&quot;870&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;870&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690268167111&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST _cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;move&quot;: {
                &quot;index&quot;: &quot;wedul2&quot;,
                &quot;shard&quot;: 0,
                &quot;from_node&quot;: &quot;elasticsearch1&quot;,
                &quot;to_node&quot;: &quot;elasticsearch2&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;샤드가 한쪽이 더 많을 때는 스왑되지 않고 그대로 옮겨진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 샤드가 할당되지 않은 노드에 할당한다면?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7xJXW/btso0sLntsy/8gAYdICP28JBnMOKJOpSpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7xJXW/btso0sLntsy/8gAYdICP28JBnMOKJOpSpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7xJXW/btso0sLntsy/8gAYdICP28JBnMOKJOpSpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7xJXW%2Fbtso0sLntsy%2F8gAYdICP28JBnMOKJOpSpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1014&quot; height=&quot;590&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690278112930&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST localhost:19200/_cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;move&quot;: {
                &quot;index&quot;: &quot;.ds-.logs-deprecation.elasticsearch-default-2023.07.25-000001&quot;,
                &quot;shard&quot;: 0,
                &quot;from_node&quot;: &quot;elasticsearch7&quot;,
                &quot;to_node&quot;: &quot;elasticsearch4&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 옮겨진다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz1xvS/btsoYh5axUN/kAeaB5HoX5gkQh9nRXpxv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz1xvS/btsoYh5axUN/kAeaB5HoX5gkQh9nRXpxv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz1xvS/btsoYh5axUN/kAeaB5HoX5gkQh9nRXpxv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz1xvS%2FbtsoYh5axUN%2FkAeaB5HoX5gkQh9nRXpxv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;562&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;replica가 있는 상태에서 primary shard를 노드를 이동하고 싶을 때&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;replica shard가 1개로 primary shard를 이동시켜보자. elasitcsearch3에 있는 1번 shard를 elasticsearch2 샤드로 옮겨보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkmPs4/btso0t4iBBY/nyp46j38OakviGDg9Re5l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkmPs4/btso0t4iBBY/nyp46j38OakviGDg9Re5l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkmPs4/btso0t4iBBY/nyp46j38OakviGDg9Re5l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkmPs4%2Fbtso0t4iBBY%2Fnyp46j38OakviGDg9Re5l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1038&quot; height=&quot;810&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690270401080&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST _cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;move&quot;: {
                &quot;index&quot;: &quot;wedul2&quot;,
                &quot;shard&quot;: 1,
                &quot;from_node&quot;: &quot;elasticsearch3&quot;,
                &quot;to_node&quot;: &quot;elasticsearch2&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHhQtX/btsoRCI4n05/4vhYfCTKVpTJKg6wIKwJL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHhQtX/btsoRCI4n05/4vhYfCTKVpTJKg6wIKwJL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHhQtX/btsoRCI4n05/4vhYfCTKVpTJKg6wIKwJL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHhQtX%2FbtsoRCI4n05%2F4vhYfCTKVpTJKg6wIKwJL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;988&quot; height=&quot;768&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 샤드가 옮겨지면서 elasticsearch2에 있던 3 replica shard가 옮겨지는 rebalance가 진행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이번에는 primary shard가 없는 노드로 primary shard를 보내보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;elasticseearch2에 있는 primary shard2를 elasticsearch3로 이동시키면 아래와 같이 조정이되었다. 그럼 아래상태에서 다시 shard 1을 elasticsearch2로 옮겨보는 command를 날려보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdRqKP/btsoYiJu2yL/z7SUf2rZAvWH741x6KKkK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdRqKP/btsoYiJu2yL/z7SUf2rZAvWH741x6KKkK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdRqKP/btsoYiJu2yL/z7SUf2rZAvWH741x6KKkK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdRqKP%2FbtsoYiJu2yL%2Fz7SUf2rZAvWH741x6KKkK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1066&quot; height=&quot;848&quot; data-origin-width=&quot;1066&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690271170267&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /_cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;move&quot;: {
                &quot;index&quot;: &quot;wedul2&quot;,
                &quot;shard&quot;: 1,
                &quot;from_node&quot;: &quot;elasticsearch3&quot;,
                &quot;to_node&quot;: &quot;elasticsearch2&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와는 다르게 실패하게 되는데 원인을 로그에서 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690271151271&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;error&quot;: {
        &quot;root_cause&quot;: [
            {
                &quot;type&quot;: &quot;illegal_argument_exception&quot;,
                &quot;reason&quot;: &quot;[move_allocation] can't move 1, from {elasticsearch3}{T-R4unaNTrS3cG0TBLZI1w}{VnPxLF9MSSyY2i5v28y34w}{172.21.0.4}{172.21.0.4:9300}{cdfhilmrstw}{ml.machine_memory=8232894464, xpack.installed=true, transform.node=true, ml.max_open_jobs=512, ml.max_jvm_size=536870912}, to {elasticsearch2}{fZmPapXcTmOXUZW5qgY37g}{5BmI1q8hS8SBr6Pn7itftw}{172.21.0.3}{172.21.0.3:9300}{cdfhilmrstw}{ml.machine_memory=8232894464, ml.max_open_jobs=512, xpack.installed=true, ml.max_jvm_size=536870912, transform.node=true}, since its not allowed, reason: [YES(shard has no previous failures)][YES(shard is primary and can be allocated)][YES(explicitly ignoring any disabling of allocation due to manual allocation commands via the reroute API)][YES(can relocate primary shard from a node with version [7.17.5] to a node with equal-or-newer version [7.17.5])][YES(no snapshots are currently running)][YES(ignored as shard is not being recovered from a snapshot)][YES(this node is not currently shutting down)][YES(neither the source nor target node are part of an ongoing node replacement (no replacements))][YES(node passes include/exclude/require filters)][NO(a copy of this shard is already allocated to this node [[wedul2][1], node[fZmPapXcTmOXUZW5qgY37g], [R], s[STARTED], a[id=YXQNS0DQTbqQ0yAh6AtE7Q]])][YES(enough disk for shard on node, free: [49.3gb], shard size: [3.3kb], free after allocating shard: [49.3gb])][YES(below shard recovery limit of outgoing: [0 &amp;lt; 2] incoming: [0 &amp;lt; 2])][YES(total shard limits are disabled: [index: -1, cluster: -1] &amp;lt;= 0)][YES(allocation awareness is not enabled, set cluster setting [cluster.routing.allocation.awareness.attributes] to enable it)][YES(index has a preference for tiers [data_content] and node has tier [data_content])][YES(shard is not a follower and is not under the purview of this decider)][YES(decider only applicable for indices backed by searchable snapshots)][YES(this decider only applies to indices backed by searchable snapshots)][YES(decider only applicable for indices backed by searchable snapshots)][YES(this node's data roles are not exactly [data_frozen] so it is not a dedicated frozen node)]&quot;
            }
        ],
        &quot;type&quot;: &quot;illegal_argument_exception&quot;,
        &quot;reason&quot;: &quot;[move_allocation] can't move 1, from {elasticsearch3}{T-R4unaNTrS3cG0TBLZI1w}{VnPxLF9MSSyY2i5v28y34w}{172.21.0.4}{172.21.0.4:9300}{cdfhilmrstw}{ml.machine_memory=8232894464, xpack.installed=true, transform.node=true, ml.max_open_jobs=512, ml.max_jvm_size=536870912}, to {elasticsearch2}{fZmPapXcTmOXUZW5qgY37g}{5BmI1q8hS8SBr6Pn7itftw}{172.21.0.3}{172.21.0.3:9300}{cdfhilmrstw}{ml.machine_memory=8232894464, ml.max_open_jobs=512, xpack.installed=true, ml.max_jvm_size=536870912, transform.node=true}, since its not allowed, reason: [YES(shard has no previous failures)][YES(shard is primary and can be allocated)][YES(explicitly ignoring any disabling of allocation due to manual allocation commands via the reroute API)][YES(can relocate primary shard from a node with version [7.17.5] to a node with equal-or-newer version [7.17.5])][YES(no snapshots are currently running)][YES(ignored as shard is not being recovered from a snapshot)][YES(this node is not currently shutting down)][YES(neither the source nor target node are part of an ongoing node replacement (no replacements))][YES(node passes include/exclude/require filters)][NO(a copy of this shard is already allocated to this node [[wedul2][1], node[fZmPapXcTmOXUZW5qgY37g], [R], s[STARTED], a[id=YXQNS0DQTbqQ0yAh6AtE7Q]])][YES(enough disk for shard on node, free: [49.3gb], shard size: [3.3kb], free after allocating shard: [49.3gb])][YES(below shard recovery limit of outgoing: [0 &amp;lt; 2] incoming: [0 &amp;lt; 2])][YES(total shard limits are disabled: [index: -1, cluster: -1] &amp;lt;= 0)][YES(allocation awareness is not enabled, set cluster setting [cluster.routing.allocation.awareness.attributes] to enable it)][YES(index has a preference for tiers [data_content] and node has tier [data_content])][YES(shard is not a follower and is not under the purview of this decider)][YES(decider only applicable for indices backed by searchable snapshots)][YES(this decider only applies to indices backed by searchable snapshots)][YES(decider only applicable for indices backed by searchable snapshots)][YES(this node's data roles are not exactly [data_frozen] so it is not a dedicated frozen node)]&quot;
    },
    &quot;status&quot;: 400
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;a copy of this shard is already allocated to this node (주요원인)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 옮기려고하는 node에 이미 replica shard가 존재하고 있어서 발생하는 에러로 위에 그림에서 보면 shard1에 replica shard가 이미 존재하는것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;total&amp;nbsp;shard&amp;nbsp;limits&amp;nbsp;are&amp;nbsp;disabled&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- total shard가 제한이 있을 경우 문제인데 disabled되어있어서 이유가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;enough&amp;nbsp;disk&amp;nbsp;for&amp;nbsp;shard&amp;nbsp;on&amp;nbsp;node&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 대상 노드에 shard를 할당하기에 충분한 디스크 공간이 있는 것으로 보여서 이유가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;shard&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;follower&amp;nbsp;and&amp;nbsp;is&amp;nbsp;not&amp;nbsp;under&amp;nbsp;the&amp;nbsp;purview&amp;nbsp;of&amp;nbsp;this&amp;nbsp;decider&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;allocation&amp;nbsp;awareness&amp;nbsp;is&amp;nbsp;not&amp;nbsp;enabled&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;this&amp;nbsp;node's&amp;nbsp;data&amp;nbsp;roles&amp;nbsp;are&amp;nbsp;not&amp;nbsp;exactly&amp;nbsp;[data_frozen]&amp;nbsp;so&amp;nbsp;it&amp;nbsp;is&amp;nbsp;not&amp;nbsp;a&amp;nbsp;dedicated&amp;nbsp;frozen&amp;nbsp;node&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 힌트들을 확인해 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위에서 주요이슈가 되었던 동일한 shard가 없는 elasticsearch1 노드로 옮겨보자.&lt;/p&gt;
&lt;pre id=&quot;code_1690274867160&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /_cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;move&quot;: {
                &quot;index&quot;: &quot;wedul2&quot;,
                &quot;shard&quot;: 1,
                &quot;from_node&quot;: &quot;elasticsearch3&quot;,
                &quot;to_node&quot;: &quot;elasticsearch1&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RcTaH/btso0tDpY4F/Usl1wqKGbUpkU4SVBf5mdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RcTaH/btso0tDpY4F/Usl1wqKGbUpkU4SVBf5mdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RcTaH/btso0tDpY4F/Usl1wqKGbUpkU4SVBf5mdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRcTaH%2Fbtso0tDpY4F%2FUsl1wqKGbUpkU4SVBf5mdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1054&quot; height=&quot;816&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 옮겨진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;모든 노드의 replica shard가 있는 상태에서 primary shard를 노드를 이동하고 싶을 때&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 자주 교체되고 몇개의 노드가 shutdown되면서 데이터가 유실될 수 있는 환경에서는 모든 노드에 replica를 넣기도한다. 보통이럴때는 &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;auto_expand_replicas&lt;/a&gt;: &quot;0-all&quot;로 설정하여 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;650&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4shH2/btsoZLEnSLS/TSkLhyr4z0hKH7VKLFtaEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4shH2/btsoZLEnSLS/TSkLhyr4z0hKH7VKLFtaEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4shH2/btsoZLEnSLS/TSkLhyr4z0hKH7VKLFtaEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4shH2%2FbtsoZLEnSLS%2FTSkLhyr4z0hKH7VKLFtaEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1078&quot; height=&quot;650&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;650&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 primary shard가 elasticsearch4노드에 모여있으서 이것을 elasitcsearch5, elasitcsearch6 node에 옮겨보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 모든 샤드가 노드에 다있어서 위에서 발생했던 동일 replica shard, primary shard가 이동하려는 곳에 같이있어서 발생했던 이슈가 무조건 발생한다. (&lt;b&gt;a copy of this shard is already allocated to this node)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 cancel을 사용해서 primary shard 할당을 cancel해주면 replica shard에 primary shard가 할당되면서 primary shard를 분산할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1690277764793&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST localhost:19200/_cluster/reroute
{
    &quot;commands&quot;: [
        {
            &quot;cancel&quot;: {
                &quot;index&quot;: &quot;wedul&quot;,
                &quot;shard&quot;: 3,
                &quot;node&quot;: &quot;elasticsearch6&quot;,
                &quot;allow_primary&quot;: &quot;true&quot;
            }
        }
    ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ewZKSm/btso0sxPbnC/ZaseZIU0BbO9AQsFgQKnd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ewZKSm/btso0sxPbnC/ZaseZIU0BbO9AQsFgQKnd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ewZKSm/btso0sxPbnC/ZaseZIU0BbO9AQsFgQKnd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FewZKSm%2Fbtso0sxPbnC%2FZaseZIU0BbO9AQsFgQKnd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1508&quot; height=&quot;688&quot; data-origin-width=&quot;1508&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이렇게 하면 특정 노드에 할당할수 없고 마지막에 올라온 노드로 자동으로 primary shard가 할당된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 찾은 방법은 우선 auto_expand_replica 옵션을 false로 끄고 number_of_replicas의 값을 0으로 바꾸면 replica shard는 모두 사라지고 primary shard는 각 노드에 분산되어 저장된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4JLOL/btsoZLEvVf8/F0S7IJblcBF1GWQxAFyfF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4JLOL/btsoZLEvVf8/F0S7IJblcBF1GWQxAFyfF1/img.png&quot; data-alt=&quot;한쪽에 몰려있는 primary shard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4JLOL/btsoZLEvVf8/F0S7IJblcBF1GWQxAFyfF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4JLOL%2FbtsoZLEvVf8%2FF0S7IJblcBF1GWQxAFyfF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1068&quot; height=&quot;444&quot; data-origin-width=&quot;1068&quot; data-origin-height=&quot;444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;한쪽에 몰려있는 primary shard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1690282175114&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT localhost:19200/wedul/_settings
{
  &quot;settings&quot;: {
    &quot;number_of_replicas&quot;: 0,
    &quot;auto_expand_replicas&quot;: false
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HTgBm/btso1uvoBlS/9RDcRtQ5IBFKoLFkmBLelK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HTgBm/btso1uvoBlS/9RDcRtQ5IBFKoLFkmBLelK/img.png&quot; data-alt=&quot;replica가 사라지면서 분산된 primary shard&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HTgBm/btso1uvoBlS/9RDcRtQ5IBFKoLFkmBLelK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHTgBm%2Fbtso1uvoBlS%2F9RDcRtQ5IBFKoLFkmBLelK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1034&quot; height=&quot;732&quot; data-origin-width=&quot;1034&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;replica가 사라지면서 분산된 primary shard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;primary shard가 분산된걸 확인할 수 있다. 그다음 다시 auto_expand_replicas옵션을 &quot;0-all&quot;로 켜주면 이상태에서 replica shard만 붙는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp8v7L/btsoSmTVDon/eQlFJxEEMR6ePfnjMEoik0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp8v7L/btsoSmTVDon/eQlFJxEEMR6ePfnjMEoik0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp8v7L/btsoSmTVDon/eQlFJxEEMR6ePfnjMEoik0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp8v7L%2FbtsoSmTVDon%2FeQlFJxEEMR6ePfnjMEoik0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1004&quot; height=&quot;476&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;replica shard는 조회 성능에 많은 영향을 끼치고 이과정에서 만약 노드하나가 죽어서 primary shard가 날아간다면... 조심해야한다. 오히려 reindex를 해주는게 나을수도 있지만 운영중인환경에서 쉽지 않은 선택이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련해서 elasticsearch github에도 &lt;a href=&quot;https://github.com/elastic/elasticsearch/issues/41543&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;issue ticket&lt;/a&gt;이 올라와 있으나 아직 뾰족하게 어떻게 하는게 좋을지 감이 오지는 않는다. index를 하나 더만들고 alias를 같이 붙이고 reindex를 하면서 is_write_true를 새로운 index에 붙여야하나..&amp;nbsp; &lt;a href=&quot;https://discuss.elastic.co/t/when-using-the-index-settings-with-auto-expand-replicas-set-to-0-all-an-issue-arises-where-primary-shards-are-concentrated-on-specific-nodes/339185&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;질문을 올려보긴 했는데 기다려보자.&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주의사항&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shard rerouting은 Elasticsearch 클러스터의 샤드를 다시 할당하는 작업을 의미합니다. 이 작업은 특정 상황에서 유용할 수 있지만, 잘못 사용하거나 신중하지 않게 진행할 경우 몇 가지 위험성이 있다. 아래는 Shard reroute 작업의 위험성에 대한 몇 가지 주요 요인들이다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1. 데이터 손실&amp;nbsp; &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shard rerouting은 샤드를 다른 노드로 이동시키는 작업이므로 실수로 잘못된 노드로 샤드를 이동시키면 해당 샤드에 저장된 데이터를 손실할 수 있다. 신중하게 계획되지 않은 샤드 이동은 데이터 유실을 초래할 수 있으므로 조심해야 한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 클러스터 불안정성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shard reroute 작업은 클러스터의 상태를 변경하는 작업이기 때문에 클러스터 불안정성을 유발할 수 있다. 잘못된 샤드 이동이나 충돌하거나 네트워크 문제가 발생하면 클러스터의 안정성과 가용성이 저하될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다행히 primary shard 이동 시 기존 primary shard는 유지한채 새로운 shard를 복사하면서 switch하기 때문에 이것만으로는 cluster가 red가 되지 않는다. 단, 진행하는 도중에 여러이유로 발생할수 있으니 신중히 테스트가 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8Uefv/btsoZcoTK3Z/7yfZKDLip8PwxuwqIXCYBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8Uefv/btsoZcoTK3Z/7yfZKDLip8PwxuwqIXCYBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8Uefv/btsoZcoTK3Z/7yfZKDLip8PwxuwqIXCYBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8Uefv%2FbtsoZcoTK3Z%2F7yfZKDLip8PwxuwqIXCYBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1044&quot; height=&quot;586&quot; data-origin-width=&quot;1044&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. 네트워크 부하 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shard reroute 작업은 노드 간에 샤드 데이터를 이동시키므로 클러스터의 네트워크 부하가 증가할 수 있다. 대규모 클러스터에서 많은 샤드를 이동시키는 경우 네트워크 병목 현상이 발생할 수 있으므로 성능을 감안하여 계획해야 한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4. 클러스터 성능 저하&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shard reroute 작업은 클러스터의 상태를 변경하므로 이 작업이 실행되는 동안에는 클러스터 성능이 저하될 수 있다. 특히, 대규모 클러스터에서 수천 개의 샤드를 이동시키는 경우 클러스터의 응답 속도가 느려질 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;5. 충돌 위험&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬로 실행되는 shard reroute 작업이 충돌하거나 동시에 여러 작업이 발생할 경우 예상치 못한 결과가 발생할 수 있다. 이러한 충돌은 클러스터의 안정성을 악화시킬 수 있으므로 주의가 필요하다.&lt;br /&gt;&lt;br /&gt;위험성을 최소화하려면 Shard reroute 작업을 신중하게 계획하고 테스트를 거쳐야 한다. 큰 변화를 가하는 작업이므로 라이브 환경에서 수행하기 전에 백업을 만들고 롤백 계획을 갖추는 것이 좋다. 또한 Elasticsearch 클러스터의 상태와 성능을 모니터링하고 작업 진행 중에 잠재적인 문제를 신속하게 식별하고 대응해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;#부록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용한 클러스터 docker-compose.yml 정보&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;version:&amp;nbsp;&quot;2&quot;&lt;br /&gt;&lt;br /&gt;services:&lt;br /&gt;&amp;nbsp;&amp;nbsp;elasticsearch1:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image:&amp;nbsp;docker.elastic.co/elasticsearch/elasticsearch:7.17.5&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_name:&amp;nbsp;elasticsearch1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;environment:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.name=docker-cluster&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.name=elasticsearch1&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;ES_JAVA_OPTS=-Xms512m&amp;nbsp;-Xmx512m&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;indices.lifecycle.history_index_enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.monitoring.collection.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.security.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.roles=master&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;19200:9200&quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;elasticsearch2:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image:&amp;nbsp;docker.elastic.co/elasticsearch/elasticsearch:7.17.5&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_name:&amp;nbsp;elasticsearch2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;environment:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.name=docker-cluster&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.name=elasticsearch2&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;ES_JAVA_OPTS=-Xms512m&amp;nbsp;-Xmx512m&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.monitoring.collection.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;indices.lifecycle.history_index_enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.security.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.roles=master&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;29200:9200&quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;elasticsearch3:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image:&amp;nbsp;docker.elastic.co/elasticsearch/elasticsearch:7.17.5&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_name:&amp;nbsp;elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;environment:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.name=docker-cluster&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.name=elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;ES_JAVA_OPTS=-Xms512m&amp;nbsp;-Xmx512m&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.monitoring.collection.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;indices.lifecycle.history_index_enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.security.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.roles=master&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;39200:9200&quot;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;elasticsearch4:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;image:&amp;nbsp;docker.elastic.co/elasticsearch/elasticsearch:7.17.5&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;container_name:&amp;nbsp;elasticsearch4&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;environment:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.name=docker-cluster&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.name=elasticsearch4&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;ES_JAVA_OPTS=-Xms512m&amp;nbsp;-Xmx512m&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.monitoring.collection.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;indices.lifecycle.history_index_enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3,elasticsearch4&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;xpack.security.enabled=false&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;node.roles=data&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ports:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;nbsp;&quot;49200:9200&quot;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>데이터베이스/Elasticsearch</category>
      <category>elasticsearch</category>
      <category>reroute</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/721</guid>
      <comments>https://wedul.tistory.com/721#entry721comment</comments>
      <pubDate>Tue, 25 Jul 2023 21:44:41 +0900</pubDate>
    </item>
    <item>
      <title>elasticsearch cluster 구성 시 기본으로 생성되는 index확인</title>
      <link>https://wedul.tistory.com/720</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;elasticsearch cluster를 사용하기 위해서 cluster를 구성하기전에 꼭 추가해야하는 부분이 있는데 기본으로 생성되는 index에 대한 disable처리가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;geoip_databases 인덱스&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 cluster 구성 시 geoip_databases가 인덱스가 추가가 되어있는데 이게 hidden index여서 모르고 지나칠 수 있다. 만약 모르고 클러스터를 구성하고 노드를 조작할 경우 해당 인덱스의 primary shard가 unassigned되면서 cluster 상태가 red가 될 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2608&quot; data-origin-height=&quot;1394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/osMj6/btsoU7gvPWi/sU1rRkuWCPUGIrq0vepEG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/osMj6/btsoU7gvPWi/sU1rRkuWCPUGIrq0vepEG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/osMj6/btsoU7gvPWi/sU1rRkuWCPUGIrq0vepEG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FosMj6%2FbtsoU7gvPWi%2FsU1rRkuWCPUGIrq0vepEG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2608&quot; height=&quot;1394&quot; data-origin-width=&quot;2608&quot; data-origin-height=&quot;1394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 왜 geoip_databases가 기본적으로 생기는가? 생기지 않도록 할 수 없는가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 인덱스가 생성되는 이유는 &lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/geoip-processor.html#geoip-cluster-settings&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ingest.geoip.downloader.enabled&lt;/a&gt; 속성 때문에 생성되는데 해당 속성의 기본값이 true이다. 그리고 이게 true인 상태에서 cluster red 상태로 만든 index를 지울 수 없게 되어있어서 서비스 운영시 장애를 유발할 가능성이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결방법은 간단하다. 이미 클러스트를 구성하고 있다면 아래 api를 호출하여 ingest.geoip.downloader.enabled를 비활성화 해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1690259888023&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PUT _cluster/settings
{
    &quot;persistent&quot;: {
       &quot;ingest.geoip.downloader.enabled&quot;: false
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 red상태에 빠졌던 클러스터는 index가 제거되면서 다시 green으로 돌아온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2062&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccEUdI/btsoQuqHEb6/vSYH67A5P3034tlH72gGyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccEUdI/btsoQuqHEb6/vSYH67A5P3034tlH72gGyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccEUdI/btsoQuqHEb6/vSYH67A5P3034tlH72gGyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccEUdI%2FbtsoQuqHEb6%2FvSYH67A5P3034tlH72gGyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2062&quot; height=&quot;1252&quot; data-origin-width=&quot;2062&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 클러스터를 구성하기 전이었다면 elasticsearch.yml에 해당 설정을 추가해주고 cluster를 올리는게 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1690259943563&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ingest.geoip.downloader.enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;.ds-ilm-history-*&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/current/ilm-settings.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ilm&lt;/a&gt;은 index lifecycle management에 약자로 elasticsearch에서 인덱스에 라이프사이클에 따라서 인덱스의 생명주기를 관리하는 것인데 이 policy에 따라서 진행된 내용을 history index에 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이걸 사용하지 않는다고 하면 해당인덱스를 보유하고 있을 필요가 없다. 레플리카가 한개있기 때문에 만약 두개의 데이터 노드가 이탈된다면 이또한 cluster상태를 red로 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cluster 구성 시 indices.lifecycle.history_index_enabled=false를 설정해서 history가 저장되지 않게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;[참고]&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클러스터 생성 docker-compose.yml&lt;/p&gt;
&lt;pre id=&quot;code_1690265970679&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: &quot;2&quot;

services:
  elasticsearch1:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
    container_name: elasticsearch1
    environment:
     - cluster.name=docker-cluster
     - node.name=elasticsearch1
     - &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;
     - indices.lifecycle.history_index_enabled=false
     - xpack.monitoring.collection.enabled=false
     - discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3
     - cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3
     - xpack.security.enabled=false
    ports:
      - &quot;19200:9200&quot;

  elasticsearch2:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
    container_name: elasticsearch2
    environment:
     - cluster.name=docker-cluster
     - node.name=elasticsearch2
     - &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;
     - xpack.monitoring.collection.enabled=false
     - indices.lifecycle.history_index_enabled=false
     - discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3
     - cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3
     - xpack.security.enabled=false
    ports:
      - &quot;29200:9200&quot;

  elasticsearch3:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.17.5
    container_name: elasticsearch3
    environment:
     - cluster.name=docker-cluster
     - node.name=elasticsearch3
     - &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;
     - xpack.monitoring.collection.enabled=false
     - indices.lifecycle.history_index_enabled=false
     - discovery.seed_hosts=elasticsearch1,elasticsearch2,elasticsearch3
     - cluster.initial_master_nodes=elasticsearch1,elasticsearch2,elasticsearch3
     - xpack.security.enabled=false
    ports:
      - &quot;39200:9200&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 index들이외에도 언제든지 생성될 수 있다. 어떤 index가 설정에 따라서 기본으로 special 속성으로 생성될지 모르기 때문에 항상 클러스터 운영시 설정을 지켜보고 Index를 주기적으로 체크하는게 좋다.&amp;nbsp;&lt;/p&gt;</description>
      <category>데이터베이스/Elasticsearch</category>
      <category>Cluster</category>
      <category>elasticsearch</category>
      <category>GeoIP</category>
      <category>MaxMind</category>
      <category>setting</category>
      <category>yml</category>
      <author>wedul</author>
      <guid isPermaLink="true">https://wedul.tistory.com/720</guid>
      <comments>https://wedul.tistory.com/720#entry720comment</comments>
      <pubDate>Tue, 25 Jul 2023 13:40:14 +0900</pubDate>
    </item>
  </channel>
</rss>