youngseo's TECH blog

[DB] RDS DB 마이그레이션 과정 - RDB와 NoSQL 본문

BackEnd

[DB] RDS DB 마이그레이션 과정 - RDB와 NoSQL

jeonyoungseo 2024. 1. 6. 13:47

SpringBoot에 PostgreSQL과 MongoDB 연결하기

연결이유

끄악

RDS를 사용한지 어엿.. 3주밖에 되지 않았으나 3만원 결제 통보를 받고 말았다.. 💸
RDS는 자동 백업 기능,, 데이터베이스 복원을 위해 스냅샷을 생성 ,, 등의 이슈로 생각보다 비싸다고 한다. 🙄 그래서 갓선배님의 조언으로 마이그레이션을 해보기로 했다 ! (사실 서비스 전이라 엄밀히 따지면 마이그레이션 아님)
암튼 application.yml 과 함께라면 처음 써보는 PostgreSQL DB 연동도 두렵지 않기 때문에 넉넉한 프리티어를 제공해주는 Supabase PostgreSQL을 도입하게 되었다. 


Supabase PostgreSQL 연동

우선 이 글 을 통해 DB를 만들고, 아래 DB Settings 를 참고하여 Datagrip과 연동해주면 된다. ..끝 ! JDBC Template 은 위대해 ✨

https://supabase.com/dashboard/project/프로젝트/settings/database
Datagrip에서 supabase PostgreSQL 연동 확인

postgreSQL 의존성을 추가해준다.

// DB - postgresql 
    implementation 'org.postgresql:postgresql:42.6.0'

application.yml 파일로 DB를 연동한다.

spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://db.tlps***.supabase.co:5432/postgres #?serverTimezone=Asia/Seoul&useSSL=false&characterEncoding=utf8&allowPublicKeyRetrieval=true
    username: ${POSTGRES_DB_USERNAME}
    password: ${POSTGRES_DB_PW}

  jpa:
    database-platform: postgres
    show-sql: true
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
    generate-ddl: false

MongoDB 연동

추가로 NoSQL도 연동해보았다. 
프로젝트 기능 중에 학생별로 푼 문제들을(50개~1000개 가량) 매주 싹 긁어오는 API가 필요하다. 예를 들어 이런 형태다.

백준 - 맞은 문제
{
	'name': '20wjsdudtj',
    'week': '1',
    'solvedBaekjoon': '1000, 1001, 1002, 1004',
}

{
	'name': '20wjsdudtj',
    'week': '2',
    'solvedBaekjoon': '1000, 1001, 1002, 1004, 19283, 24523', 
}

한 학생이 푼 문제들을 리스트 형태로 가져오게 되는데, RDB 형태에서 굳이 문제 Table을 만들어 학생들과 문제들을 N:M 매핑할 필요성은 없다고 판단하였다. 문제가 3만 개 이상일뿐만 아니라 계속해서 갱신되는(문제들이 만들어지는) 형태이고, 문제를 이용한 기능은 존재하지 않기 때문이다.
🤔 문제들을 RDB table에 구분자(,) 형태로 나열해 저장할까 하다가 이 참에 NoSQL을 사용해 연동해보기로 하였다.

MongoDB ( NoSQL ) 란?

mongoDB는 데이터를 도큐먼트 형태로 저장하는 도큐먼트 데이터베이스이다. 아래와 같은 형태로, 도큐먼트는 JSON 형태의 데이터로 구성된다. 

CRUD 또한 그대로 JSON을 넣는 형태이며, insert / find 명령어로 POST/GET 요청을 손쉽게 할 수 있다.

//Post 요청
db.users.insert({
    name: '홍길동',
    age: 20,
});

//Get 요청
db.users.find(); // 출력 - { "_id" : ObjectId("6239sdf39..."), "name":"홍길동", "age": 20}

NoSQL을 RDB와 같이 쓰면 좋지 않은 부분이 바로 JOIN 연산이다.
RDB는 Row/Col의 table 형태이고, NoSQL은 key-value 형태이므로 둘을 JOIN 하는 개념으로 사용할 수 없다.
따라서 나는 기존에 Entity(DB Table)로 할당되어있던 History의 특정 컬럼(List 형태로 들어가던 solved_backjoon_week) 만을 NoSQL로 분리하고, 이 때 JOIN은 아니지만 History id 값을 key값으로 사용하여 둘을 동기화 하기로 하였다. 


mongoDB Repository 구현

우선 mongodb를 사용하기 위한 의존성을 추가해준다.

    //DB - mongodb
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
    implementation 'org.postgresql:postgresql:42.6.0'

SpringApplication 구성 클래스 경로에 @EnableMongoRepositories 어노테이션을 붙여 MongoDB 를 사용하도록 설정하자.
src/main/java/com/example/demo/DemoApplication.java

 
@SpringBootApplication
@EnableMongoRepositories
public class DemoApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
///

@Entity 어노테이션이 아닌 @Document 어노테이션을 이용해 MongoDB의 '문서 클래스'임을 나타내자.
src/main/java/com/example/demo/domain/BaekjoonHistory.java

@Document(collection="history")
@Getter @Setter @AllArgsConstructor
public class BaekjoonHistory {
    @Id
    private Long id;
    private List<Integer> solvedBaekjoon = new ArrayList<>();

}

JPARepository가 아니라 MongoRepository를 사용한다. MongoRepository를 통해 JPARepository와 마찬가지로 기본 CRUD 생성, 커스텀 쿼리 생성 등을 동일하게 사용할 수 있다.

@Repository
public interface BaekjoonHistoryRepository extends MongoRepository<BaekjoonHistory, Long> {
}

아래와 같이 MongoDB 에서 데이터를 조회해보면 잘 저장된 것을 볼 수 있었다.


리뷰

구현 도중 RDB와 NoSQL을 함께 쓰는 구조가 과연 옳을까? 에 대한 고민이 본능적으로 계속 들었다. 결론적으로 말하면 확장 측면에서 봤을 때, "이 NoSQL이 RDB와 JOIN할 일이 있을까?" 라는 질문에 "절대 없을 것 같다"가 아니라면 NoSQL을 덤으로 쓰는 것은 그리 좋은 방식은 아닌 것 같다. 
물론 현재 아키텍처에서는 1. NoSQL을 써보고 싶다.라는 욕구 충족과 2. DB 분산으로 인한 비용/접근 시간 감소 라는 장점이 있지만 '유지 보수' 관점에서 큰 서비스로 발전하고자 한다면 단점이 장점에 상회한다. 하지만 현재 상황에서 문제들을 조인할 일은 없을 것이므로 괜찮다 !😬
이후 서비스 이후 들었던 비용에 대해 이 글에 나중에 정리해봐야겠다.