Spring boot 3, AWS Redshift + JPA + Tsid

Spring boot에서 jpa로 redshift를 쓰고 싶다면..
김주혁's avatar
Jul 04, 2024
Spring boot 3, AWS Redshift + JPA + Tsid
 
저희 회사에서는 로그 데이터를 Amazon Redshift에 적재하고 있습니다. 기존에는 myBatis를 사용했으나 다른 프로젝트와 버전을 맞추고 통일성있게 프로젝트 구성을 가져가기 위해 jpa로 마이그레이션 하게 됐습니다.
 
일반적으로 Redshift는 AWS Glue ETL 작업대용량 데이터 세트를 처리하기 위한 선택지로 많이 활용됩니다. 또한, 데이터를 압축하여 제공하는 기능도 있어, 대규모 데이터를 효율적으로 관리하는 데 매우 유용합니다. 이러한 이유로 Redshift는 대규모 데이터 처리를 필요로 하는 프로젝트에서 충분히 고려할만한 스택입니다.
 
이 글을 읽고 계신다면, 이미 Redshift를 프로젝트에 도입하기로 결정하셨을 가능성이 높습니다. 그렇다면, Redshift의 장단점에 대해 이미 조사해보셨을 것입니다. 프로젝트 설정만 빠르게 얘기하겠습니다.
 
사용중인 라이브러리 버전
plugins { id 'java' id 'org.springframework.boot' version '3.0.6' id 'io.spring.dependency-management' version '1.1.0' id 'org.asciidoctor.convert' version '1.5.8' } java { sourceCompatibility = '17' } dependencies { runtimeOnly 'com.mysql:mysql-connector-j' implementation 'com.zaxxer:HikariCP:5.0.1' implementation 'org.hibernate.orm:hibernate-core:6.0.2.Final' implementation group: 'org.javassist', name: 'javassist', version: '3.15.0-GA' implementation 'org.apache.commons:commons-pool2:2.11.1' implementation 'com.amazon.redshift:redshift-jdbc42:2.1.0.8' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.0' }
 
  1. 필수 라이브러리 설치
    1. implementation 'com.amazon.redshift:redshift-jdbc42:2.1.0.8' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api"
      Redshift를 활용한 JPA 기반의 프로젝트 설정시 필수 라이브러리들이라고 생각해주시면 좋습니다. AWS Redshift JDBC 드라이버JPA, QueryDSL을 포함한 기본 의존성 설정입니다.
       
      추가적으로, 프로젝트에서 ID 관련 작업을 보다 유연하게 처리할 수 있도록 Hypersistence Utilis를 설치하겠습니다.
      implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.0'
       
  1. Spring 환경 파일 설정
    1. datasource: driver-class-name: com.amazon.redshift.Driver url: jdbc:redshift://localhost:5439/dbname username: ${DB_USERNAME} password: ${DB_PASSWORD} hikari: max-lifetime: 170000 connection-timeout: 8000 jpa: properties: hibernate: temp: use_jdbc_metadata_defaults: false format_sql: true implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy physical_naming_strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy hbm2ddl.auto: none jdbc: lob: non_contextual_creation=true: true batch_size: 10 dialect: org.hibernate.dialect.MySQLDialect hibernate: ddl-auto: none open-in-view: false show-sql: false
      Redshift가 PostgreSQL을 기반으로 한다고 했지만, MySQLDialect로도 동작하여 MySQLDialect로 설정하였습니다.
       
      MySQLDialect를 사용하면 한 가지 문제가 발생하는데
      method com.amazon.redshift.jdbc.redshiftconnectionimpl.createclob() is not yet implemented.
      Redshift JDBC 드라이버에서 CLOB 기능을 아직 구현하지 않았기 때문에 발생하는 것으로 추정합니다. 이를 해결하기 위해서는 다음 설정을 추가해야 합니다
       
      temp: use_jdbc_metadata_defaults: false
      이 설정을 통해 JPA가 기본 JDBC 메타데이터를 사용하지 않도록 하여, 위 에러를 방지할 수 있습니다.
 
  1. Id 컬럼 설정
    1. Redshift에서 JPA를 사용할 때 가장 큰 문제 중 하나는 JPA를 사용할 때 일반적으로 Id 컬럼에 사용되는 Auto Generate Identity Column이 지원되지 않는 다는 것 입니다. 처음엔 이 걸 모르고,
      @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
       
      하지만 이 설정을 사용하면 Redshift에서는 여러 가지 에러가 발생하게 됩니다. 이 문제는 AWS Redshift의 특성상 JPA에서 Auto Generate ID 기능을 제대로 지원하지 않기 때문입니다.
      (만약 IDENTITY 컬럼을 사용할 수 있는 방법이 있다면, 따로 메일로 남겨주시면 감사하겠습니다. 정말 몰라서 편법으로 사용 중인 예시이기 때문입니다.)
       
      수 많은 시행착오가 있었지만 결론만 말하자면, Redshift를 계속 사용해야 하고 JPA도 포기할 수 없다면, ID 생성 방식을 포기해야 한다는 것이었습니다.
       
      제일먼저 떠오른 것은 가상면접 사례로 배우는 대규모 시스템 설계 기초책에서 나오는 Id Generator 파트에 나오는 snowflake와 uild였으나 두 방식을 합친 TSID라는 방식이 있어, TSID를 찾아보기로 했습니다. 찾아본 결과 TSID가 적합한 ID 생성방식이라고 결론 지어 사용했습니다.
       
      왜 TSID 를 사용했을까요? 그 이유는,
       
      • TSID는 Snowflake와 유사하게 고유 ID를 생성하는 방식입니다. 분산 시스템에서 중앙 서버 없이 여러 노드에서 동시에 고유한 ID를 생성할 수 있어 확장성이 매우 뛰어납니다.
      • TSID는 시계열로 ID를 생성하기 때문에, 순차적 ID가 생성됩니다. 이렇게 하면 인덱스의 프래그먼테이션(fragmentation)을 줄일 수 있어 데이터베이스 성능에 더 이점이 있습니다.
      • Redshift는 대규모 데이터를 빠르게 처리하는 데 최적화된 시스템이지만, ID 생성과 관련된 몇 가지 제약이 존재합니다. identity로 생성된 ID는 수정이 어렵고, 테이블이 생성된 후 추가 작업도 까다롭습니다. 반면 TSID는 BIGINT 타입으로 간편하게 사용할 수 있어 Redshift와의 호환성도 좋습니다.
       
      Redshift의 ID 타입은 BIGINT로 설정하고, TSID를 사용하여 다음과 같이 적용했습니다:
      @Id @Tsid private Long id;
      이 설정을 통해, repository.save(entity)를 호출할 때 자동으로 TSID가 생성되어 데이터베이스에 저장됩니다. 이렇게 하면 JPA와 Redshift가 문제없이 연결되어 사용할 수 있게 됩니다.
 
id integer default "identity"(292331, 0, '1,1'::text) not null encode az64 distkey
Redshift에서 id integer default "identity"(292331, 0, '1,1'::text) not null encode az64 distkey와 같이 자동 생성되는 ID 컬럼은 여러 문제를 가지고 있습니다. ID 컬럼을 생성한 후 수정이 불가능하며, 수정이 필요한 경우에도 불편함이 따릅니다. GUI에서 데이터 추가 시에도 제약이 많습니다.
 
따라서 Snowflake, TSID, 또는 ULID와 같은 ID 생성 전략을 도입하는 것은 단순히 Auto Generate된 Id를 생성하신 것 보다 더 나은 선택일 수 있습니다. 특히 TSID는 JPA와 Redshift의 호환성을 유지하면서도 분산 환경에서 고유 값을 제공할 수 있는 좋은 선택지가 될 수 있습니다.
Share article

vlogue