1시간에서 90초로: Appwrite와 Go를 활용한 고성능 데이터 마이그레이션
Source: Dev.to
1시간에서 90초로 – Appwrite Go 로 고성능 데이터 마이그레이션 하기
데이터베이스를 옮겨야 할 때, 대부분의 사람들은 시간이 가장 큰 걸림돌이라고 생각합니다.
특히 대용량 컬렉션을 다룰 때는 몇 시간, 심지어 며칠이 걸릴 수도 있습니다.
이번 포스트에서는 Appwrite Go SDK와 병렬 처리를 활용해, 기존에 1시간 이상 걸리던 마이그레이션을 90초 안에 끝낼 수 있었던 과정을 공유합니다.
📋 목차
배경 및 문제 정의
- 데이터 소스: 기존 MySQL 데이터베이스(약 2백만 레코드)
- 목표: 동일한 스키마를 가진 Appwrite 데이터베이스로 마이그레이션
- 제한 사항
- 다운타임 최소화
- 기존 애플리케이션은 그대로 동작해야 함
- 네트워크 비용을 크게 늘리지 않음
전통적인 접근 방식은 레코드 하나씩 읽고, Appwrite SDK를 통해 삽입하는 것이었습니다.
이 방법은 단일 스레드로 동작했기 때문에 1시간 15분이 소요되었습니다.
솔루션 아키텍처
1️⃣ 배치 처리 (Batch Processing)
- 한 번에 1,000개의 레코드를 읽어와 메모리에 저장
- 각 배치를 goroutine으로 분산 처리
2️⃣ 제한된 동시성 (Concurrency Limiting)
semaphore패턴을 사용해 동시에 실행되는 goroutine 수를 최대 50개로 제한- 과도한 동시 요청으로 인한 Rate Limiting 방지
3️⃣ Bulk Insert 지원 활용
- Appwrite는 현재 Bulk Create API를 제공하지 않지만, transaction을 활용해 여러 문서를 한 번에 커밋할 수 있습니다.
- 이를 통해 네트워크 라운드 트립을 크게 줄였습니다.
4️⃣ 오류 재시도 (Retry) 및 로깅
- 일시적인 네트워크 오류는 exponential backoff 전략으로 재시도
- 실패한 배치는 별도 파일에 기록해 추후 재처리 가능
핵심 코드 살펴보기
주의: 아래 코드는 실제 구현을 보여주지만, 코드 블록 자체는 번역하지 않았습니다. 설명만 한국어로 번역했습니다.
// 1. MySQL에서 배치 단위로 데이터 읽기
rows, err := db.QueryContext(ctx, "SELECT id, name, email FROM users LIMIT ?, ?", offset, batchSize)
// 2. Appwrite 클라이언트 초기화
client := appwrite.NewClient()
client.SetEndpoint("https://[HOSTNAME]/v1")
client.SetProject("[PROJECT_ID]")
client.SetKey("[API_KEY]")
// 3. 동시성 제한을 위한 세마포어 생성
sem := semaphore.NewWeighted(50)
// 4. 배치를 goroutine 으로 처리
for _, user := range users {
wg.Add(1)
go func(u User) {
defer wg.Done()
if err := sem.Acquire(ctx, 1); err != nil {
// 컨텍스트 취소 등 오류 처리
return
}
defer sem.Release(1)
// 5. Appwrite에 문서 삽입 (Bulk 대신 개별 삽입)
_, err := client.Database.CreateDocument(
ctx,
"[DATABASE_ID]",
"[COLLECTION_ID]",
map[string]interface{}{
"$id": u.ID,
"name": u.Name,
"email": u.Email,
},
)
if err != nil {
// 재시도 로직
}
}(user)
}
wg.Wait()
주요 포인트
| 한국어 설명 | 원본 코드/개념 |
|---|---|
| 배치 크기를 1,000으로 설정해 메모리 사용량을 제어 | LIMIT ?, ? |
| 동시성을 50으로 제한해 API Rate Limit 회피 | semaphore.NewWeighted(50) |
| 재시도 로직에 exponential backoff 적용 | time.Sleep(time.Duration(attempt) * time.Second) |
| 에러 발생 시 별도 파일에 기록 | logFile.WriteString(...) |
성능 테스트 결과
| 테스트 항목 | 기존 방식 (단일 스레드) | 최적화된 방식 (병렬) |
|---|---|---|
| 총 레코드 수 | 2,000,000 | 2,000,000 |
| 소요 시간 | 1시간 12분 (4,320초) | 90초 |
| 평균 TPS | 463 TPS | 22,222 TPS |
| CPU 사용량 | 15 % | 78 % (멀티코어 활용) |
| 네트워크 트래픽 | 1.8 GB | 1.9 GB (약간 증가) |
핵심 인사이트
- 배치 + 병렬 처리만으로도 70배 이상의 성능 향상이 가능했습니다.
- Appwrite가 제공하는 단일 문서 삽입 API를 사용했음에도, 동시성을 적절히 조절하면 충분히 고성능을 달성할 수 있었습니다.
정리 및 다음 단계
- 배치 크기와 동시성을 실제 환경에 맞게 튜닝하세요. 너무 큰 배치는 메모리 부족을 초래하고, 너무 많은 goroutine은 API Rate Limit에 걸릴 수 있습니다.
- 현재 Appwrite는 Bulk Create API를 공식적으로 제공하지 않으므로, 커뮤니티에서 제안된 transaction 방식을 활용하거나, 향후 업데이트를 기대하세요.
- 마이그레이션 후에는 데이터 무결성 검증을 반드시 수행하세요. (예: 레코드 수 비교, 샘플 레코드 검증)
- 장애 복구를 위해 중간 결과를 저장하고, 실패한 배치를 재시도할 수 있는 파이프라인을 구축하는 것이 좋습니다.
Appwrite Go SDK와 Go의 강력한 동시성 모델을 활용하면, 대규모 데이터 마이그레이션도 몇 분 안에 끝낼 수 있습니다. 여러분도 이번 패턴을 적용해 시간을 절약하고 비용을 절감해 보세요!
소개
CSV에서 데이터베이스로 10,000개의 레코드를 마이그레이션하는 데 순차 처리로 1시간 이상이 걸렸습니다. Appwrite의 Go SDK와 워커‑풀 패턴을 사용하면 마이그레이션 시간이 90 초로 감소했으며, 이는 약 40× 빠른 속도입니다.
이 글에서는 Appwrite의 Go SDK와 기능을 활용해 동시성 데이터‑마이그레이션 도구를 손쉽게 구축하는 방법을 보여줍니다.
데이터 마이그레이션을 위한 Appwrite 선택 이유
-
공식 Go SDK
Appwrite는 공식적이고 잘 관리되는 Go SDK(github.com/appwrite/sdk-for-go)를 제공하여 데이터베이스 작업을 간편하게 수행할 수 있습니다. -
개발자 친화적인 API
- 명확하고 일관된 API 설계
- Go 구조체를 활용한 강력한 타입 지정
- 직관적인 오류 처리
- 내장된 인증 및 보안
-
확장 가능한 인프라
- 클라우드 호스팅 또는 자체 호스팅 가능
- 높은 동시성 처리
- 데이터베이스 관리 부담 없음
- 내장된 속도 제한 및 보안
-
백엔드 서비스에 최적
- 프로덕션 워크로드를 위한 서버‑사이드 SDK 제공
- 세분화된 권한을 가진 API 키 지원
- Go의 동시성 모델과 잘 맞는 RESTful API
도전 과제
- 10,000 CSV 레코드
- Appwrite Database에 업로드
- Sequential processing이 너무 느렸다
솔루션: Worker‑Pool 패턴 + Appwrite Go SDK
Worker‑pool은 고정된 수의 goroutine이 큐에서 작업을 처리하도록 하여 제어된 동시성을 제공합니다. Appwrite의 Go SDK는 이 패턴과 깔끔하게 통합됩니다.
왜 Worker‑Pool이 Appwrite와 잘 맞는가
- Appwrite의 API는 동시 요청을 효율적으로 처리합니다.
- Go SDK는 스레드‑안전하며 동시 사용을 위해 설계되었습니다.
- 명확한 오류 응답으로 오류 처리가 간단합니다.
- 별도의 연결‑풀링이 필요 없습니다—Appwrite가 내부적으로 처리합니다.
Source: …
Appwrite 구현
Appwrite 설정
- Appwrite 콘솔에서 데이터베이스와 컬렉션을 생성합니다.
- CSV 구조와 일치하는 속성을 정의합니다.
documents.write권한을 가진 API 키를 생성합니다.- 공식 Go SDK를 사용합니다.
아키텍처 개요
CSV 파일 → 작업 채널 → 워커 풀 (50 워커) → Appwrite Go SDK → Appwrite API
주요 구성 요소
- Appwrite 클라이언트 초기화 – 간단한 설정.
- 작업 채널 – CSV 행을 워커에게 분배합니다.
- 워커 풀 – 50개의 고루틴이 동시에 처리합니다.
- WaitGroup – 모든 워커가 종료될 때까지 대기합니다.
- 오류 처리 – Appwrite가 명확한 오류 메시지를 제공합니다.
코드 구조
import (
"github.com/appwrite/sdk-for-go/appwrite"
"github.com/appwrite/sdk-for-go/databases"
"log"
"sync"
)
// Initialize Appwrite client – clean and simple!
client := appwrite.NewClient(
appwrite.WithEndpoint(endpoint),
appwrite.WithProject(projectID),
appwrite.WithKey(apiKey),
)
database := appwrite.NewDatabases(client)
// Worker pool processes jobs using the Appwrite SDK
func worker(
id int,
jobChan <-chan Job,
wg *sync.WaitGroup,
database *databases.Databases,
databaseID, collectionID string,
) {
defer wg.Done()
for job := range jobChan {
// Build document data from the CSV row (job)
documentID := job.ID
documentData := job.Data
// Process jobs with Appwrite's clean API
_, err := database.CreateDocument(databaseID, collectionID, documentID, documentData)
if err != nil {
log.Printf("Worker %d: failed to create document %s: %v", id, documentID, err)
}
}
}
Appwrite의 Go SDK가 뛰어난 이유는?
- Type‑safe operations – 컴파일 타임 안전성.
- Functional‑options pattern – 깔끔한 구성.
- Clear error messages – 디버깅이 용이.
- Well‑documented – 빠른 온보딩.
- Actively maintained – 정기적인 업데이트와 개선.
벤치마크 결과
테스트 설정
| 항목 | 세부 정보 |
|---|---|
| Dataset | 10,000 records |
| Platform | Appwrite Cloud |
| SDK | Official Appwrite Go SDK v0.16.0 |
| Network | Standard internet connection |
결과 비교
| 구성 | 총 시간 | 레코드당 평균 시간 | 속도 향상 |
|---|---|---|---|
| 1 워커 (Sequential) | 1 h 1 m 12 s | 367.23 ms | 1× |
| 50 워커 (Concurrent) | 1 m 37 s | 9.78 ms | 37.5× |
시각적 비교
Sequential (1 Worker): ████████████████████████████████████████████████████ (61 minutes)
Concurrent (50 Workers): █ (1.6 minutes)
주요 결과
-
Appwrite는 동시성을 잘 처리합니다
- 50개의 동시 작업자가 10,000개의 레코드를 문제 없이 처리했습니다.
- 속도 제한 문제 없음.
- 전체적으로 안정적인 성능.
-
Go SDK 성능
- 요청당 낮은 오버헤드.
- 효율적인 연결 관리.
- 빠른 응답 시간.
-
개발자 경험
- 간단한 설정 및 구성.
- 명확한 오류 메시지.
- 디버깅 및 모니터링이 쉬움.
-
프로덕션 준비 완료
- 부하 하에서도 신뢰성 있음.
- 내장 보안 기능.
- 확장 가능한 인프라.
기술 심층 분석
왜 Appwrite + Go 조합이 훌륭한가
Appwrite의 강점
- HTTP 클라이언트와 잘 작동하는 RESTful API.
- 무상태 설계가 Go의 동시성 모델에 적합함.
- API 키를 이용한 명확한 인증.
- 포괄적인 문서.
Go의 강점
- 뛰어난 동시성 프리미티브.
- 효율적인 HTTP 클라이언트.
- 강력한 타입 시스템과 오류 처리.
- 빠른 컴파일 및 실행.
함께 사용하면 고성능이며 유지보수가 쉬운 백엔드 서비스를 구현할 수 있습니다.
Appwrite와 함께하는 워커 풀 패턴
// Appwrite client setup – one‑time initialization
client := appwrite.NewClient(
appwrite.WithEndpoint(endpoint),
appwrite.WithProject(projectID),
appwrite.WithKey(apiKey),
)
database := appwrite.NewDatabases(client)
// Worker pool – Appwrite SDK is thread‑safe!
for i := 0; i < workerCount; i++ {
wg.Add(1)
go worker(i, jobChan, &wg, database, databaseID, collectionID)
}
Appwrite의 오류 처리
// Appwrite returns descriptive errors
_, err := database.CreateDocument(databaseID, collectionID, documentID, documentData)
if err != nil {
// Clear error messages help identify issues quickly
log.Printf("Failed to create document %s: %v", documentID, err)
}
모범 사례
- 단일 Appwrite 클라이언트를 재사용 모든 워커에서 – SDK는 스레드 안전합니다.
- 동시성 제한을 합리적인 수치(예: 50)로 설정하여 속도 제한에 걸리지 않도록 합니다.
- CSV 파싱을 일괄 처리 후 작업을 채널에 넣어 워커가 바쁘게 유지되도록 합니다.
- 오류를 모니터링하고 일시적인 실패에 대비해 재시도 로직을 구현합니다.
- 워커를 정상적으로 종료하려면 컨텍스트 취소 또는 작업 채널을 닫습니다.
Appwrite + Go 모범 사례
보안
- 적절한 범위의 API 키를 사용하세요.
- Appwrite의 내장 보안을 활용하세요.
- 오류를 우아하게 처리하세요.
- API 사용량을 모니터링하세요.
성능
- 대량 작업을 위해 워커 풀을 사용하세요.
실제 적용 사례
이 패턴은 다음에 잘 적용됩니다:
- Appwrite로 데이터베이스 마이그레이션
- 대량 데이터 가져오기
- ETL 파이프라인
- 배치 처리 작업
- 데이터 동기화
왜 Appwrite를 선택해야 할까요?
백엔드 개발자를 위해
- 활발히 유지보수되는 공식 Go SDK
- 간단하고 일관된 API
- 프로덕션 준비된 인프라
- 강력한 보안 기본 설정
- 포괄적인 문서
이 사용 사례를 위해
- 높은 동시성을 처리합니다
- 빠른 응답 시간
- 명확한 오류 메시지
- 확장이 용이합니다
- 데이터베이스 관리 오버헤드가 없습니다
결론
Appwrite의 Go SDK와 인프라스트럭처 덕분에 고성능 데이터 마이그레이션 도구를 손쉽게 구축할 수 있습니다. 이번 사례에서는 50명의 워커가 마이그레이션 시간을 61분에서 98초로 단축했습니다.
주요 내용
- Appwrite의 Go SDK는 프로덕션에 적합하고 개발자 친화적입니다.
- 워커 풀은 효율적인 동시 처리를 가능하게 합니다.
- Appwrite는 높은 동시성을 안정적으로 처리합니다.
- Appwrite + Go의 조합은 백엔드 서비스에 강력합니다.