회사에서 개발하는 레거시 프로젝트의 Version Migration 작업을 준비하기 위해 Spring (boot) Version별 변경 내역을 조사하고 있는 내용입니다. 누락되거나 잘못 기술된 내용이 있을 수 있는 점 양해 부탁드립니다.

 

 

Spring Boot 2.1.x

  • Java 11 지원 (Spring Framework 5.1)
  • Spring Boot 2.1 Release Notes · spring-projects/spring-boot Wiki
  • 포함 의존성 변경, 삭제 등 (Junit 4.12 → Junit 5.2, Tomcat 8.5.39 → 9.x, Hibernate 5.2 → 5.3)
  • Spring Framework 5.1
    • Java 8 ~ 11
    • CGLIB 3.2 fork(Java 9+ API로 위임)
    • @Profile에 and, or expression 지원
    • Hibername와 read-only transaction 사용 시 Memory에 Entity Snapshot이 남지 않습니다.
    • 로거 설정 개선, 컴포넌트 스캐닝 성능 개선 용도의 컴포넌트 인덱스 기능 제공, 함수형 프로그래밍 스타일 지원, 코틀린 지원, 리액티브 프로그래밍 모델 지원, DataSize Type 등
  • 포함 프로퍼티 변경 (servlet path, HibernateProperties 별도 분리)
  • Spring JCL - JCL Interface가 잘 사용되지 않음으로써 (Runtime Logger Lookup이 복잡도 등의 문제) JCL을 사용하던 기존 코드를 쉽게 SLF4J로 전환하기 위해 추가한 JCL 용 브리지 모듈이다. (JCL-over-SLF4J 대체)
  • Auto-Configuration Exclusion @EnableAutoConfiguration, @SpringBootApplication, @ImportAutoConfiguration spring.autoconfigure.exclude에서 적용된 제외 설정이 이제 Local 환경에서만 적용되는 것이 아니라 모든 환경에서 일관되게 적용됩니다.
  • Log Group application.property, yml에서 logging.group.{group-name}={Loggers...} 동일한 Logging 구성 정보를 적용할 Logger 들의 그룹을 정의하는 속성이 지원됩니다. loging.level.{group-name}=DEBUG
    • 기본 제공 그룹으로는 org.springframework.web, org.jooq.tools.LoggerListener 등이 있습니다.
  • @EnableAsync, @EnableScheduling, spring-boot-starter-data-jdbc 등 추가 지원
  • Bean Override 비 허용 - 동일한 이름의 Bean을 중복으로 정의하고 ApplicationContext에 등록할 때 기존에 등록된 Bean을 덮어 쓸지 말지 결정하는 옵션입니다. spring.main.allow-bean-definition-overriding = false (default, BeanDefinitionOverrideException 발생)
    • Spring Boot의 Auto Configuration과 사용자의 Configuration, Component 등이 중복됨으로써 발생하는 상황으로 @ConditionalOnMissingBean와 같은 필터링 용 제약을 거는 것을 권장합니다.
  • Spring Data JPA 저장소 초기화에 대해서 Lazy mode 지원 (bootstrap mode)
  • Spring Actuator /info, /health end-point 사용자의 Spring Security 설정으로 인가 처리가 적용되지 않는 한 /info, /health end-point는 인증, 인가 없이 접근 가능한 공개 API가 됩니다.
  • @WebMvcTest and @WebFluxTest Security Configuration @WebMvcTest, @WebFluxTest Slice Test에서 사용자가 정의한 보안 설정인 WebSecurityConfigurer, ServerHttpSecurity bean을 자동으로 구성 정보에 포함합니다.
  • Context ApplicationConversionService Support ApplicationConversionService가 기본적으로 등록되며, @Value Annotation에 사용할 수 있습니다.
  • h2 Compatibility mode 사용 방법 변경 - 이전 버전까지는 spring.jpa.properties.hibernate.dialect 설정에 따라 Compatibility mode가 설정되었으나 현재는 spring.datasource.url=jdbc:h2...;MODE=MYSQL 직접 설정해주어야 mode type이 적용되도록 변경되었습니다.
  • Property Migrator 달라진 property 정보를 반영하지 않아도 정상적으로 Application을 수행할 수 있도록 도와주는 Module입니다. (Runtime에서 동작)

 

 

 

Spring Boot 2.2.x

  • Java 13 지원 (Spring Framework 5.2)
  • Spring Boot 2.2 Release Notes · spring-projects/spring-boot Wiki
  • 포함 의존성 변경, 삭제 등
    • Jakarta EE dependencies 추가 및 기존 JavaEE dependencies 이전
    • ReactiveWebServerApplicationContext 사용 중단
  • Spring Framework 5.2
    • Spring WebFlux Support for Kotlin Coroutines - Spring WebFlux에서 이제 Kotlin의 Coroutines을 지원합니다.
    • RSocket, R2DBC 지원
    • Spring MVC Router Support for Kotlin DSL Web Flux에서의 DSL을 통한 Router 등록 방식과 유사한 모델을 MVC에서도 지원합니다.
    • @Configuration에 대한 Bean Lite Mode 지원 (proxyBeanMethods = false) - Configuration 생성 시 Proxy를 통해 Configuration Instance를 Wrapping 하는 작업을 수행하지 않도록 설정하는 속성으로 기본 제공되는 AutoConfiguration에 적용하기 위해 추가되었습니다.
      • 성능 향상 (구동 시간 감소)가 있으나, Configuration Method를 여러 번 호출할 경우 매번 Instance를 생성해 반환하기 때문에 조심하여야 합니다.
  • Spring Security 5.2
  • Spring Data Moore
    • 선언적 리액티브 트랜잭션 지원
      • PlatformTransactionManager에서 ReactiveTransactionManager로 전환되었습니다.
      • TransactionalOperator@Transactional 구성이 통합되었습니다.
    • 리액티브 QueryDSL 지원
    • 기타 성능 향상
    • What's new in Spring Data Moore?
  • 프로퍼티 변경
  • Spring Actuator HTTP Trace disable by default - 많은 리소스를 소모하고, in-memory에 저장하는(기본 구현) 방식이 Clustering 구조에 맞지 않아 기본적으로 비활성화되었습니다.
  • Prometheus Push Gateway를 위한 https end-point 추가
  • Lazy initialization by Global spring.main.lazy-initialization property를 통해 Application 전역에 Lazy initialization를 지원할 수 있으며, @Lazy(false)LazyInitializationExcludeFilter 를 통해 지연 초기화 대상에서 제외할 수 있습니다.
  • JMX disabled by default spring.jmx.enabled=false
  • spring-boot-starter-test default by JUnit 5 기본 사용하는 Junit Version이 4에서 5로 변경되었습니다.
  • Hibernate Dialect - Hibernate가 감지한 Database dialect를 사용하던 설정에서 사용자 정의를 통해 선택 가능하도록 변경되었습니다.
  • jOOQ RecordListenerProvider 다수의 RecordListenerProvider Bean을 등록하던 구조에서 RecordListenerProvider[] 방식으로 반환하는 Bean을 정의하는 방식으로 변경되었습니다.
  • Shutdown configuration of task execution and scheduling 자동 구성된 TaskExecutor, TaskScheduler의 종료 시 필요한 설정을 적용할 수 있도록 spring.task.execution.shutdown and spring.task.scheduling.shutdown이 추가되었습니다. (gracefully-shotdown)
  • Kubernetes detection @ConditionalOnCloudPlatform을 통해 현재 Application이 Kubernetes 위에서 동작하고 있는 상태인지 감지할 수 있습니다.
  • Test Application Arguments in integration tests 통합 테스트에서 사용할 Application Argument를 정의할 수 있는 ApplicationArguments Bean이 추가되었습니다.
  • @ConfigurationProperties scanning @ConfigurationProperties이 적용된 Class를 Scan 하여 Bean으로 등록할 수 있는 @ConfigurationPropertiesScan, @EnableConfigurationProperties이 추가되었습니다.
  • Immutable @ConfigurationProperties binding @ConfigurationProperties이 붙은 Class를 불변 객체로 선언할 수 있는 @ConstructorBinding을 지원합니다. (이는 Records에도 적용됩니다.)
    • 생성자를 통한 객체 생성을 지원하며 여러 생성자가 존재할 경우 특정 생성자 위에 정의하면 됩니다.
  • Callback for Redis cache configuration ****RedisCacheManager의 불변 설정을 위한 RedisCacheManagerBuilderCustomizer가 추가되었습니다.
  • Qualifier for Spring Batch datasource Spring Batch에서 사용될 DataSource Bean을 설정하기 위한 @BatchDataSource 가 추가되었습니다. ****
  • Idle JDBC connections metrics 현재 대기 상태에 있는 DataSource Connection Pool의 크기를 확인할 수 있는 Metric 정보가 추가되었습니다.

 

 

 

Spring Boot 2.3.x

  • 자바 14 지원
  • Gradle 5.6.x (Legacy), 6.3+
  • Spring Boot 2.3 Release Notes · spring-projects/spring-boot Wiki
  • 포함 의존성 변경, 삭제 등
    • Spring Cloud Connectors starter has been removed
    • Junit Jupiter 5.6
    • Mockito 3.3
    • Jackson 2.11
      • Date, Calendar의 Timezone 표현 방식이 표준에 맞게 변경되었습니다. +00:00
      • 필드 명 없이 JSON Array를 만들 수 없도록 변경되었습니다.
      • JSON 생성 시 Serialization 순서를 결정할 수 있습니다. @JsonProperty(index = )
      • 표준 통화에 대한 객체를 제공하는 Joda Money를 지원하기 위해 새로운 모듈이 추가되었습니다. jackson-datatype-joda-money
  • Spring Integration 5.3
    • RSocket support for Spring Integration spring-integration-rsocket 의존성 추가 및 해당 의존성 사용 시 IntegrationRSocketEndpoint, RSocketOutboundGateway을 통한 end-point 설정이 가능합니다.
  • Spring Security 5.3
    • Oauth 2.0 Client, Resource Server 개선
  • Spring Kafka 2.5, Spring HATEOAS 1.1
  • Spring Data Neumann
    • Cassandra v4 이전 버전과 호환되지 않는 요소들이 다수 추가되었습니다.
    • Couchbase SDK v3 이전 버전과 호환되지 않는 요소들이 다수 추가되었습니다.
    • Elasticsearch 7.5
    • MongoDB 4 - MongoClientSettingsBuilderCustomizer bean이 필수 드라이버(Mongo Operations) 설정에 적용됩니다. spring-boot-starter-data-mongodb-reactive 의존성의 경우 Mongo Operations이 구성되지 않기 때문에 spring-boot-starter-data-mongodb 의존성을 추가하는 것을 권장합니다.
    • Neo4j Neo4j의 Open Session이 기본적으로 Default 됩니다. spring.data.neo4j.open-in-view property를 통해 설정할 수 있습니다.
    • Data JDBC 2.0
      • 낙관적 락 지원, PagingAndSortingRepository 지원, H2 지원 (모든 기능), 식별자 따옴표, Query Derivation 지원 (query method)
    • R2DBC support
      • auto-configuration, health indicator for Connection Factory, @DataR2dbcTest
  • Validation Starter no longer included in web starters - validation 관련 의존성(javax.validation.*)은 spring-boot-strater-web에서 spring-boot-starter-validation라는 별도의 의존성으로 분리되었습니다.
  • Build OCI images with Cloud Native Buildpacks Cloud Native Buildpack을 사용한 Docker Image 구축 방식 지원 및 spring-boot:build-image, bootBuildImage 가 추가되었습니다. (Maven, Gradle)
  • Build layered jars for inclusion in a Docker image Jar에서 자주 변경되는 부분을 식별하여 Layer를 분할해 재 사용할 수 있도록 개선되었습니다. (효율적인 Docker Image build를 위함)
    • JDK → Library → Application Code 순으로 Layer를 생성합니다.
  • Support of wildcard locations for configuration files Configuration File load시 *를 지원합니다.
  • Graceful shutdown embedded Jetty, Reactor Netty, Tomcat, Undertow 및 Servlet, Reactive 기반 Application에서 Gracefully shutdown이 지원됩니다. server.shutdown=graceful, spring.lifecycle.timeout-per-shutdown-phase=10s
  • WebServerInitializedEvent and ContextRefreshedEvent - Graceful shutdown 기능 지원의 일환으로 Web Server의 초기화 시점이 변경되었습니다. (새로 고침 이후 → Application Context 새로 고침 이후)
  • Spring Actuator Liveness and Readiness probes Application의 활성, 처리 가능 상태를 추적할 수 있는 end-point가 추가되었습니다. (kubernetes 위에서 실행될 경우 자동 설정) /actuator/health/readiness, management.health.probes.enabled=true
  • Date-Time conversion in web applications Application.properties, yml을 통해 시간 및 날짜 변환 시 형식을 지정할 수 있습니다. ex) spring.mvc.format.date=iso , spring.mvc.format.date-time, spring.mvc.format.time, spring.webflux.format.date, spring.webflux.format.date-time, spring.webflux.format.time
  • Unique DataSource Name By Default 자동 구성된 DataSource가 고유한 이름으로 생성되며, H2 DB 사용 시 Database URL이 testdb를 참조하지 않아 기존(H2를 자동 구성에 의존하여 사용 중이던) Project에서 해당 Version으로 Migration 수행 시 영향을 받습니다.
  • Embedded Servlet web server threading configuration Embedded Web Server의 Thread를 구성하는 속성이 server.{tomcat, jetty...}.threads 로 이동되었습니다.
  • Changes to the Default Error Page’s Content Error Message 및 Binding Error는 기본 Error Page에 포함되지 않습니다. server.error.include-message, server.error.include-binding-errors 설정을 통해 포함하거나 상황에 따라 제어할 수 있습니다. (always, on-param, never)
  • ApplicationContextRunner disables bean overriding by default SpringApplication을 통한 실행 방식과 일관성을 제공하기 위해 ApplicationContextRunner에서도 Bean Override를 비 허용합니다. withAllowBeanDefinitionOverriding을 통해 허용할 수 있습니다.
  • Activating multiple profiles with @ActiveProfiles @ActiveProfiles에서 쉼표를 통한 다중 Profile을 지원합니다. @ActiveProfiles({"p1","p2"})

 

 

 

Spring Boot 2.4.x

  • 자바 15 지원
  • Spring Boot 2.4 Release Notes · spring-projects/spring-boot Wiki
  • 포함 의존성 변경, 삭제 등
    • Flyway 7 Overall ordering of Java and SQL Callbacks · Issue #2785 · flyway/flyway
    • Logback Configuration Properties Logback properties들이 logging.pattern.rolling-file-namelogging.logback.rollingpolicy.file-name-pattern과 같은 형대로 변경되었습니다.
      • logging.logback.{기존 속성 path}
    • Hazelcast 4 내부 의존성이 Hazelcast 3.2.x → 4.x로 변경되었습니다. 하위 버전이 필요한 경우 hazelcast.version property를 통해 사용할 버전을 설정할 수 있습니다.
    • Neo4 j Spring Data Neo4j
  • 버전 관리 체계 변경 Updates to Spring Versions
  • Spring Framework 5.3
    • Spring Framework 5.3.x가 LTS로 계속 유지되며, 5.4.x를 만들지 않습니다.
    • GraalVM 관련 개선
    • Spring-R2DBC Module 제공
  • Spring Data 2020.0 버전 명명을 Calander 기반으로 변경하였으며, 코드명은 수학자의 이름을 사용하는 것을 유지합니다.
    • Neo4j
    • R2DBC
    • Supported RxJava 3
  • Spring Batch 4.3
  • Spring Security 5.4
  • Spring Integration 5.4
  • Spring Kafka 2.6, Spring HATEOAS 1.2, Spring Session 2020.0, Spring Security 5.4…
  • Config File Processing (application properties and YAML files) application.properties 및 application.yml 파일의 처리 방법이 변경되었습니다. (properties 파일 안에서 여러 profile 정의 가능) 또한 spring.profiles 대신 spring.config.activate.on-profile을 사용하도록 변경되었습니다.
    • 옛날 파일 처리 방식을 사용하려면 spring.config.use-legacy-processing=true를 사용합니다.
  • Supported Configuration Tree spring.config.location, spring.config.import 속성 설정 시 configtree, optional 등의 prefix를 지원합니다.
  • Custom property name support configtree, optional 이외에도 Custem prefix를 정의하여 기능을 확장할 수 있는 방법을 제공합니다. ConfigDataLoader, ConfigDataLocationResolver
  • Layered jar enabled by default Spring Boot 2.3.x 에서 추가되었던 Build layered jars 기능이 기본적으로 활성화되었습니다.
  • Importing Additional Application Config application.properties 또는. yml 파일에서 spring.config.import속성을 통해 Spring 환경을 구성하는 상황에서 같이 참조할 하나 이상의 구성 파일을 지정할 수 있습니다. (spring.config.use-legacy-processing=true를 사용하지 않는 경우)
  • Volume Mounted Config Directory Trees spring.config.import속성을 통해 ConfigMaps와 같은 Key:Value 형식의 Directory Tree를 가져올 수 있습니다.
  • Importing Config Files That Have no File Extension 파일 확장자를 작성할 수 없는 Cloud Platform에서 [.extension] 형식의 Hint를 지정하여 파일의 확장자를 특정할 수 있습니다.
  • Origin Chains
  • Startup Endpoint Actuator에서 Application의 시작 정보를 제공하는 startup end-point를 지원합니다. 제공되는 timeline object의 events array를 통해 생성되는 Bean의 이름이나 시작 및 초기화 지연 시간 등을 식별할 수 있습니다.
  • Docker/Buildpack Support Maven의 spring-boot:build-image, gradle의 bootBuildImage task를 통해 생성된 Docker Image를 Docker Registry에 등록하는 기능이 추가되었습니다.
  • Maven Buildpack Support Maven에서 spring-boot:build-image을 통해 Docker Image를 생성할 때 모든 Project Module Dependency를 Application Layer에 포함합니다. (여러 Project Module이 존재하는 경우 하나의 Layer에 집약됩니다.)
  • Gradle Buildpack Support Gradle에서 bootBuildImage task를 통해 Docker Image를 생성할 때 모든 Project Module Dependency를 Application Layer에 포함합니다. (여러 Project Module이 존재하는 경우 하나의 Layer에 집약됩니다.)
  • Redis Cache Metric Redis Cache를 사용하는 경우 Micrometer가 응답하는 Metric 정보에서 Cache hit, miss, Cache put, Cache evict 등의 모든 통계 정보를 확인할 수 있습니다. spring.cache.redis.enable-statistics=true
  • Web Configuration Properties Spring MVC, Spring WebFlux에서 지정하는 locale 및 Resource path 등의 Properties 속성들이 해당 Module 여부와 상관없이 일관적으로 구성될 수 있도록 변경되었습니다. spring.mvc.locale, spring.mvc.locale-resolver, spring.resources.*spring.web.locale, spring.web.locale-resolver, spring.web.resources.*
  • Register @WebListeners in a way that allows them to register servlets and filters Servlet Container가 자체적으로 @WebListener 이 적용된 클래스를 인스턴스화 합니다. 이를 통해 @WebListener가 붙은 객체에게 제공하던 Spring Container를 통한 의존성 주입이 지원되지 않습니다.
  • Slice Test for Cassandra @DataCassandraTest를 통한 Cassandra Slice Test를 사용할 수 있습니다.
  • Configuration property for H2 Console’s web admin password H2 Console Admin의 Password를 지정 가능한 spring.h2.console.settings.web-admin-password 속성이 추가되었습니다.
  • CqlSession-Based Health Indicators for Apache Cassandra Spring Data Cassandra에서 제공하던 Health Indicator가 지원되지 않으며, CqlSession 기반의 CassandraDriverHealthIndicator, CassandraDriverReactiveHealthIndicator가 추가되었습니다.
  • Filtered Scraping with Prometheus /actuator/prometheus end-point에서 응답하는 Sample을 Filter 하는 용도로 새로운 query parameter가 지원됩니다.? includedNames=jvm_memory_used_bytes
  • Spring Security SAML Configuration Properties SAML2 관련 acs와 decryption 속성이 추가되었습니다.
  • Failure Analyzers 이제 FailureAnalizers가 ApplicationContext 등이 구성되지 않는 경우도 포함합니다. BeanFactoryAware 또는 EnvironmentAware를 구현하여 추가하는 분석기는 ApplicationContext가 만들어지지 않는 경우 사용되지 않습니다.
  • Jar Optimizations Spring Boot jar를 생성할 때 모든 비어있는 (마킹 용도의) starter dependencies들이 제거됩니다. spring-boot-autoconfigure-processor 또한 Build 시에만 유효하며 이후 제거됩니다.
  • Spring Boot Gradle Plugin Gradle bootJar task의 DSL이 설정 속성이 변경되었습니다. mainClassName → mainClass
  • JUnit 5’s Vintage Engine Removed from spring-boot-starter-test Junit4 vintage engine이 해당 의존성에서 제거되었습니다. (org.junit.Test 등 사용 시 Compile Error 발생) 해당 버전에서 Junit4를 사용하려면 별도의 vintage 의존성을 추가하여야 합니다.
  • Support K8s ConfigMap @ConfigurationProperties나 environment 객체를 통해 K8s ConfigMap에 저장된 설정에 접근할 수 있습니다.
  • Embedded database detection in-memory로 띄워진 Database가 아닌 경우 Database 초기화 및 사용자 이름 설정 (default = sa)가 사용되지 않습니다. (Server, Hybrid H2, File based Derby, HSQL 등)
    • spring.datasource.initialization-mode를 통해 데이터베이스 초기화를 하도록 구성할 수 있습니다.
  • User-defined MongoClientSettings no longer customized MongoClientSettings를 Bean으로 정의하여 사용하는 경우 Auto-configuration을 통한 properties가 적용되지 않습니다. 그렇기에 MongoPropertiesClientSettingsBuilderCustomizer를 활용해 properties, environment를 인자로 넘겨 설정한 다음 반환하여야 합니다.
  • Metrics export in integration tests
  • Elasticsearch RestClient - RestClient bean이 더 이상 Auto-configuration 되지 않습니다. RestHighLevelClient bean은 계속 지원됩니다.
  • Default Servlet Registration 해당 버전부터 Servlet Container에서 제공하는 DefaultServlet을 등록하지 않습니다.
    • 사용을 위해서는 server.servlet.register-default-servlet=true property를 정의하여야 합니다.
  • HTTP traces no longer include cookie headers by default
  • Supported Profile Group 특정 Profile에 여러 Profile들을 묶어 Grouping 하여 같이 사용할 수 있습니다. spring.profiles.group.{profile}={sub profiles...}

 

 

 

Spring Boot 2.5.x (WIP)

 

 

 

 

 

참고 자료


이 글은 특정 구현에 종속되는 내용을 제외한 이론 위주의 정리 글입니다.



Sharding이 필요한 이유?

서비스의 특성이나 운영되어온 기간에 따라 사용자의 Data, State는 지속적으로 축적되게 됩니다. 이러한 것들이 몇 천만에서 수십 억 또는 그 이상으로 늘어난다면, Index와 같은 요소를 잘 구성되어 있더라도 결국 조회 성능의 하락이나 Data, State를 관리하는 Node에게 큰 부하를 주게 됩니다.

이는 결국 서비스의 지연 발생 빈도 수나 기간이 늘어나는 주요한 원인 중 하나가 됩니다.



이를 대비하기 위해 우리는 Performance와 Scalability 속성을 만족하는 서비스 설계를 진행하여야만 합니다. 그리고 이 속성을 지원하는 설계 방법 중 하나로는 Sharding 구조가 있습니다.




Sharding?

Sharding이란 기본적으로 특정 Attribute(Shard key)를 기준으로 하나의 DataSet을 여러 Shard Node에 분산하여 작은 단위의 DataSet으로 나누고 관리하는 것을 의미합니다.

Sharding은 일반적으로 Horizontal Partitioning과 혼용되는 용어입니다. 명확하게 따지자면, horizontal partitioning은 하나의 Node 안에서 DataSet을 여러 DataSet으로 나누는 것이고, Sharding은 DataSet을 여러 Node에 나누는 것입니다.

저는 이 글에서 Sharding, Horizontal Partitioning을 모두 “Sharding”이라고 서술하겠습니다.



이를 통해 우리는 서비스가 관리하는 Data, State에 접근하는 트래픽을 분산하여 관리하고 있는 Shard Node들의 처리 성능과 서비스의 동시 처리량을 향상시킬 수 있습니다. 또한 각 Shard Node의 장애는 격리되어 다른 Shard Node에 전파되지 않기 때문에 서비스의 High Availability를 향상시킵니다.


이러한 특징으로 인해서 Sharding은 “High Availability : 기본적인 복제 개념과 구현 및 동기화 방식” 에서 언급한 Query Off Loading 구조의 한계를 보완할 수 있습니다.

Replication을 활용한 Query Off Loading 구조를 사용하는 경우 Origin의 Read Query 부하는 분산 가능하나, Write Query에 대한 부하는 분산할 수 없습니다. 즉 Write Query 부하가 가중될수록 Replica Node의 수와 상관없이 Read, Write Query 처리 성능이 하락한다는 것입니다. - Write Heavy Situation

이러한 문제가 발생하는 이유는 Replica Node들 또한 Origin Node가 처리한 Write Query의 Log를 별도의 스레드로 처리하여 Node가 관리하는 상태에 반영하는 구조이기 때문입니다.

이때 Sharding을 도입한다면 하나의 Origin Node가 받던 요청을 여러 Origin Node로 분산시키기 때문에 해당 구조의 한계를 보완할 수 있습니다.



하지만 Sharding이 장점만 가지고 있는 전략은 아닙니다.

  • 시스템 구조의 복잡성이 늘어나는 점 (특히나 직접 구현해야 하는 경우)
  • Sharding 되고 있는 Shard Node 들에 대한 불균형 문제를 고려해야 하는 점
  • Shard Node 추가 및 Rebalancing 작업 등 운영 관리 비용의 증가
  • 다른 Node에 분산된 DataSet 간의 Join, Compose가 어려운 점

이러한 문제점도 많기 때문에 사용할 Sharding 전략과 운영 전략을 잘 세우고 구현해야 합니다.


Sharding은 Redis, MongoDB Cluster, MySQL NDB Cluster, ElasticsearchSolr와 같은 여러 Solution 및 Opensource에서 활용되고 있으며 많은 서비스에서 상황에 따라 Code Level의 Sharding 기법을 구현하고 있습니다.



Sharding 구현 방식?

Sharding 구현 방식으로는 크게 Range, Moduler, Directory, Index Sharding 등이 있습니다.

Range Sharding은 Shard Key로 선정된 Attribute의 일정 범위를 관리할 DataSet을 결정하는 것으로 Rebalancing 없이도 쉽게 새로운 Shard Node를 추가할 수 있습니다. 하지만 Shard Node 간에 DataSet Size가 균등하지 않는 문제가 발생할 수 있습니다. - Hotspot problem

결국 Hotspot problem 문제로 인해 기존 Shard Node를 분할하여 Rebalancing 하는 작업이 필요하고, 반대로 들어오는 요청이 적거나 DataSet이 작은 Shard Node는 Integration 또는 Migration 작업을 통해 운영 비용을 최적화해야 하는 어려움이 존재합니다.

  • 이를 보완하는 방법으로는 MongoDB에서 사용하는 Chunk 단위 분할 방식이 있으며, 해당 방식은 Shard key에 대해 특정 범위를 결정하고 그 범위를 넘어설 때마다 새로운 Chunk를 생성하여 Data를 Migration 하는 방식입니다.

 

Moduler Sharding(Hash Sharding)은 Key로 선정된 값을 현재 존재하는 DB Node 개수로 나누어서 결과 값을 기준으로 저장될 Node를 결정하는 방식입니다. 이 방식은 Range Sharding에 비해서 DataSet을 균등하게 분배할 수 있으나 다른 Shard Node를 추가하였을 때 기존 DataSet를 Rebalancing 하는 것이 어렵습니다.

  • User ID, Client IP, Post number 같은 값을 Hash Function이나 Moduler 연산을 통해 Shard Key로 변환하여 Shard Node를 결정합니다.

Data를 일관적으로 분산하기 위해서는 당연히 Hash Function에 넘겨지는 값을 동일한 Attribute열에서 제공해야 하며, 이 값은 변경되지 않는 정적 값이어야 합니다.

해당 방식의 Rebalancing 비용을 줄이기 위해서 Scale-out 시 2개의 배수로 Shard Node를 추가하는 전략(이를 통해 데이터가 전달될 영역을 특정할 수 있습니다.)과 최대로 늘어날 수 있는 한계 값을 미리 정해 놓고 해당 값을 초과할 시점부터는 Scale-up을 진행하는 전략을 고려하기도 합니다.

 

Consistent HashingModuler(Hash) Sharding와 같이 Hash Function을 사용하지만 상대적으로 Rebalancing 시에 다른 Shard Node로 분산되는 Data를 최소화할 수 있는 방식입니다.

 

기본적으로 환형 큐와 같은 자료 구조(논리적인 구조)에 배포되어 있는 Shard Node의 IP를 Hash하여 자료구조의 Index로 변환하고 Query 요청을 하는 사용자 또는 Client의 IP를 Hash 하여 나온 값과 제일 인접한 Shard Node에 저장하는 식으로 Sharding 규칙을 지정하여 Data를 분산시킬 수 있습니다.

 

새로운 노드가 추가되었을 때에는 앞선 상황과 마찬가지로 해당 Shard Node의 IP를 Hash하여 Index를 지정하고, 나온 Index 값보다 낮은 값을 가진 인접 Shard Node와 자신 사이에 속하는 Data만 Rebalancing을 진행합니다.

  • 해당 사진에서는 761 ~ 1332 안에 속하는 Shard Key를 가진 Node 4의 Data가 새로 추가된 Shard Node5로 Migration 됩니다.

 

하지만 해당 방식을 사용하면 Hash 된 값에 따라 특정 Shard Node(Node 2)에게 넓은 범위가 할당되어 많은 양의 Data가 전달되는 불균등 문제가 발생할 수 있으며, 인접한 Shard Node가 장애 발생이나 부하에 의한 처리 지연으로 정상적인 상태가 아닌 것으로 판단되었을 때 인접한 다음 Shard Node에게 부하를 가중시킬 수 있습니다. 

  • 해당 사진에서는 Shard Node 4의 장애 발생으로 1333 ~ 6661 안에 속하는 모든 Data가 Shard Node 2로 분산됩니다.

 

해당 문제를 방지하기 위해서, (그리고 Rebalancing 단위를 좀 더 최소화 하기 위해서) 기존 Shard Node의 ip에 특정 값을 추가하여 Hash 한 후 이를 Virtual Shard Node로 추가하여 Shard Node마다 관리하는 Shard Key, Data의 수를 조절할 수도 있습니다.

  • 해당 방식을 정리하자면 Consistant Sharding 방식은 Hash Function 을 사용하면서도 Rebalancing 작업 시 이동해야 할 Data를 최소화할 수 있으며, 성능 및 상황에 따라 Shard Node의 부하를 조절할 수 있습니다.
  • 하지만 Shard Node 간의 Data 분산이 균등하지 않은 문제나 인접 Shard Node의 장애에 따른 부하 가중 문제가 발생할 수 있으며, 이를 최소화하기 위해 Virtual Shard Node를 추가할 수 있습니다.

 

Directory Sharding은 Shard Key를 기준으로 어떤 Shard Node가 DataSet를 보유하고 있는지 확인할 수 있는 Static Lookup Table을 만들고 유지하는 방식입니다. 특정 Shard Key로 저장된 DataSet 위치를 지속적으로 기록합니다.

Range Sharding이나 Moduler Sharding과 비교하여 유연한 방식이지만, 해당 방식을 사용하면 모든 Read 또는 Write Query 처리 이전에 먼저 Static Lookup Table에 접근해야 하기 때문에 전체적인 처리 성능을 저하시킬 수 있습니다.

  • Range Sharding은 Shard Key에 대한 범위를 지정해야 한다는 점, Moduler Sharding의 경우 Hash Function 또는 Moduler 로직을 구현해서 사용해야 합니다.

 

Index Sharding(또는 Dynamic Sharding)은 Directory Sharding 방식과 유사하나 Shard Key와 Node를 관리하는 주체를 Table이 아니라 외부 Index 서비스(또는 Locator 서비스)로 제공하는 방법으로 저장할 위치를 알고 있는 서비스를 이용하여 정보를 요청한 Client에게 Lookup Key 또는 Shard node 위치를 알려주는 방식입니다.

이러한 Index 서비스에는 Admin 기능을 제공하여 불균형을 감지한 경우에 DataSet의 특정 값들을 다른 Shard Node로 안전하게 Migration 할 수 있도록 Index 서비스에 요청을 하면 Index 서비스는 Write 요청을 거부하고 Read Query만 허용하는 식으로 안전장치를 마련할 수 있습니다.

  • 하지만 해당 방식을 사용하면 Index 서비스를 따로 구축해야 하는 점, 운영 비용, SPoF가 될 수 있다는 점을 고려하여 상대적으로 복잡한 설계 및 개발이 필요합니다.
  • Index 서비스의 장애 상황 발생 시 전체 서비스의 요청 실패 문제를 완화하기 위해 Index 서비스에 대한 요청이 성공한 경우 Shard Node와 관련된 값을 Local Cache에 저장하고 이후 요청에는 이를 활용하도록 개선할 수도 있습니다. (Index 서비스에 대한 트래픽 완화)



참고 자료

+ Recent posts