RDS(MySQL) + S3 Storage를 “읽기 전용”으로 쓰던 서비스를 Supabase로 옮긴 실제 사례 정리
이 문서는 기존 스키마를 모를 때(까먹었을 때) RDS에서 스키마/데이터를 꺼내 Supabase로 옮기는 전체 과정을 정리한 가이드입니다.
특히 SELECT ... INTO OUTFILE 권한이 막힌 상황에서 MySQL CLI + (선택적으로) S3를 이용해 TSV로 내보내는 패턴을 포함합니다.
예시 프로젝트에서는 DB와 S3 Storage가 단순 Read 용도(조회 전용) 로만 쓰이고 있었기 때문에,
RDS(MySQL): 책/위치 정보 등 컨텐츠 메타데이터 읽기 전용
S3: 이미지 등의 정적 파일 읽기 전용
이라는 전제 하에서, 다운타임 없이 한 번에 덤프 → Import 하는 전략을 썼습니다.
비슷한 상황이라면 예를 들어:
RDS + S3 조합을 하나의 관리형 서비스(Supabase)로 단순화하고 싶거나
저렴한 개발·운영 비용, 쉬운 콘솔/권한 관리가 필요하거나
별도 백엔드 서버 없이도 DB/Storage를 바로 붙여 쓰고 싶은 경우
에 이 글의 흐름을 거의 그대로 가져다가 쓸 수 있습니다.
0. 전체 흐름 한눈에 보기
먼저 “어떤 순서로 뭘 할 건지”를 큰 그림으로 정리해봅시다.
mermaid
다이어그램 정의(mermaid):
flowchart LR
A["RDS MySQL\n스키마/데이터 모름"] --> B["MySQL CLI로\n스키마 파악"]
B --> C["SELECT 결과를\nTSV로 Export"]
C --> D["TSV → CSV 변환\n(따옴표/구분자 정리)"]
D --> E["Supabase(Postgres)에\n테이블 스키마 생성"]
E --> F["Supabase Table Editor로\nCSV Import"]
F --> G["애플리케이션 DB 레이어를\nSupabase로 교체"]
S3 Storage는 별도지만, 패턴은 비슷합니다.
mermaid
다이어그램 정의(mermaid):
flowchart LR
S3["AWS S3\n버킷(정적 파일)"] --> DL["로컬/스크립트로\n일괄 다운로드"]
DL --> UP["Supabase Storage 버킷에\n폴더 구조 유지한 채 업로드"]
UP --> APP["애플리케이션에서\n파일 URL/경로만 Supabase 기준으로 수정"]
이제 각 단계를 조금 더 구체적으로 들어가 보겠습니다.
1. RDS(MySQL) 스키마를 모를 때: CLI로 “실제” 구조 파악하기
마이그레이션을 제대로 하려면 먼저 현재 스키마를 정확히 알아야 합니다.
AWS 콘솔에 나오는 정보만 보고는 세부 타입이나 제약 조건을 알 수 없기 때문에,
MySQL CLI로 직접 붙어 SHOW CREATE TABLE 기준으로 보는 것이 가장 확실합니다.
이제 books_fixed.csv 를 Supabase Table Editor에서 Import 할 수 있습니다.
소규모 데이터라면?
행 수가 아주 적다면(수십 개 수준) Excel / Numbers로 열고 CSV로 다시 저장해도 됩니다.
다만 이 경우 자동 형식 추론(날짜, 숫자 등) 때문에 의도치 않은 타입 변환이 들어갈 수 있어,
정확도가 중요한 마이그레이션에는 위 Python 원라이너처럼 “기계적인 변환”을 추천합니다.
4. Supabase(Postgres) 테이블 생성 및 Import
이제 Supabase에서 Postgres 스키마를 만들고, 방금 만든 CSV를 넣을 차례입니다.
4-1. Postgres 스키마 생성
아래 예시는 이 글에서 사용한 books 테이블의 스키마입니다. 실제 서비스에서는 SHOW CREATE TABLE ... 결과를 기반으로, 각자 테이블 구조에 맞게 컬럼/타입/제약 조건을 조정해 주세요.
중요한 것은 “MySQL에서 쓰이던 의미를 최대한 그대로 보존한 채 Postgres 타입으로 옮긴다”는 원칙입니다.
Supabase SQL Editor에서:
sql
createtablepublic.books ( id integer generated bydefaultasidentityprimarykey, title varchar(100)notnull, season varchar(30), total_num smallintnotnull, composition varchar(100)notnull, author varchar(30), publisher varchar(30),commentvarchar(100), location integernotnull);
generated by default as identity 를 사용해야, CSV Import 시 기존 id 값을 직접 넣을 수 있습니다.
generated always 로 되어 있으면 Import 시 id 컬럼에 값을 넣을 수 없어서 에러가 납니다.
이미 GENERATED ALWAYS 로 만든 경우에는:
sql
altertablepublic.books
altercolumn id
set generated bydefault;
로 한 번만 수정해 줍니다.
타입 매핑 팁
MySQL INT → Postgres integer
MySQL TINYINT(1) → Postgres boolean (또는 smallint)
MySQL VARCHAR(n) → Postgres varchar(n)
대략 이 정도만 지켜도 대부분의 단순 조회용 테이블은 무리 없이 옮길 수 있습니다.
selectcount(*)frompublic.books;select*frompublic.books
orderby id
limit5;
대표적인 몇 행이 RDS에서 보던 데이터와 동일하면 Import 성공입니다.
(행 개수와 대표 레코드 몇 개만 맞춰봐도 1차 검증은 충분합니다.)
5. S3 → Supabase Storage 마이그레이션 (요약)
이 프로젝트에서는 DB뿐만 아니라 이미지 같은 정적 파일도 S3에만 Read-Only로 존재했습니다.
Supabase Storage로 옮길 때는 다음과 같은 패턴을 썼습니다.
mermaid
다이어그램 정의(mermaid):
flowchart LR
S3[AWS S3 버킷] --> DL[로컬/스크립트로<br/>파일 일괄 다운로드]
DL --> UP[Supabase Storage 버킷<br/>폴더 구조 유지한 채 업로드]
UP --> URL[DB/코드에서<br/>파일 경로/URL만 Supabase 기준으로 수정]
S3에서 파일 목록/경로 조회
aws s3 ls s3://my-bucket/path/ --recursive 로 전체 파일 나열
로컬 혹은 임시 서버로 일괄 다운로드
aws s3 sync s3://my-bucket/path ./downloads 패턴
Supabase Storage CLI/콘솔/SDK로 업로드
버킷을 하나 만들고, 될 수 있으면 기존 경로 구조를 그대로 유지해서 업로드
DB 혹은 코드에서 경로만 치환
예: 기존 https://{s3-domain}/path/xxx.jpg → https://{supabase-project}.supabase.co/storage/v1/object/public/{bucket}/path/xxx.jpg
이 글에서는 코드 구현보다는 마이그레이션 흐름 위주로 다루기 때문에,
구체적인 Upload 스크립트는 생략하지만, 큰 틀은 위와 같습니다.
6. 애플리케이션과 Supabase 연동 (이 프로젝트 구조 기준 요약)
이제 데이터와 파일을 다 옮겼으니, 애플리케이션 코드에서 데이터 소스만 RDS → Supabase로 바꾸면 됩니다.
현재 프로젝트 기준으로는 대략 다음과 같이 구성되어 있습니다.