1시간에서 90초로: Appwrite와 Go를 활용한 고성능 데이터 마이그레이션

발행: (2025년 12월 31일 오전 01:05 GMT+9)
17 min read
원문: Dev.to

Source: Dev.to


1시간에서 90초로 – Appwrite Go 로 고성능 데이터 마이그레이션 하기

데이터베이스를 옮겨야 할 때, 대부분의 사람들은 시간이 가장 큰 걸림돌이라고 생각합니다.
특히 대용량 컬렉션을 다룰 때는 몇 시간, 심지어 며칠이 걸릴 수도 있습니다.

이번 포스트에서는 Appwrite Go SDK병렬 처리를 활용해, 기존에 1시간 이상 걸리던 마이그레이션을 90초 안에 끝낼 수 있었던 과정을 공유합니다.


📋 목차

  1. 배경 및 문제 정의
  2. 솔루션 아키텍처
  3. 핵심 코드 살펴보기
  4. 성능 테스트 결과
  5. 정리 및 다음 단계

배경 및 문제 정의

  • 데이터 소스: 기존 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,0002,000,000
소요 시간1시간 12분 (4,320초)90초
평균 TPS463 TPS22,222 TPS
CPU 사용량15 %78 % (멀티코어 활용)
네트워크 트래픽1.8 GB1.9 GB (약간 증가)

핵심 인사이트

  • 배치 + 병렬 처리만으로도 70배 이상의 성능 향상이 가능했습니다.
  • Appwrite가 제공하는 단일 문서 삽입 API를 사용했음에도, 동시성을 적절히 조절하면 충분히 고성능을 달성할 수 있었습니다.

정리 및 다음 단계

  1. 배치 크기와 동시성을 실제 환경에 맞게 튜닝하세요. 너무 큰 배치는 메모리 부족을 초래하고, 너무 많은 goroutine은 API Rate Limit에 걸릴 수 있습니다.
  2. 현재 Appwrite는 Bulk Create API를 공식적으로 제공하지 않으므로, 커뮤니티에서 제안된 transaction 방식을 활용하거나, 향후 업데이트를 기대하세요.
  3. 마이그레이션 후에는 데이터 무결성 검증을 반드시 수행하세요. (예: 레코드 수 비교, 샘플 레코드 검증)
  4. 장애 복구를 위해 중간 결과를 저장하고, 실패한 배치를 재시도할 수 있는 파이프라인을 구축하는 것이 좋습니다.

Appwrite Go SDK와 Go의 강력한 동시성 모델을 활용하면, 대규모 데이터 마이그레이션도 몇 분 안에 끝낼 수 있습니다. 여러분도 이번 패턴을 적용해 시간을 절약하고 비용을 절감해 보세요!

소개

CSV에서 데이터베이스로 10,000개의 레코드를 마이그레이션하는 데 순차 처리로 1시간 이상이 걸렸습니다. Appwrite의 Go SDK와 워커‑풀 패턴을 사용하면 마이그레이션 시간이 90 초로 감소했으며, 이는 약 40× 빠른 속도입니다.

이 글에서는 Appwrite의 Go SDK와 기능을 활용해 동시성 데이터‑마이그레이션 도구를 손쉽게 구축하는 방법을 보여줍니다.

데이터 마이그레이션을 위한 Appwrite 선택 이유

  1. 공식 Go SDK
    Appwrite는 공식적이고 잘 관리되는 Go SDK(github.com/appwrite/sdk-for-go)를 제공하여 데이터베이스 작업을 간편하게 수행할 수 있습니다.

  2. 개발자 친화적인 API

    • 명확하고 일관된 API 설계
    • Go 구조체를 활용한 강력한 타입 지정
    • 직관적인 오류 처리
    • 내장된 인증 및 보안
  3. 확장 가능한 인프라

    • 클라우드 호스팅 또는 자체 호스팅 가능
    • 높은 동시성 처리
    • 데이터베이스 관리 부담 없음
    • 내장된 속도 제한 및 보안
  4. 백엔드 서비스에 최적

    • 프로덕션 워크로드를 위한 서버‑사이드 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 설정

  1. Appwrite 콘솔에서 데이터베이스컬렉션을 생성합니다.
  2. CSV 구조와 일치하는 속성을 정의합니다.
  3. documents.write 권한을 가진 API 키를 생성합니다.
  4. 공식 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 – 정기적인 업데이트와 개선.

벤치마크 결과

테스트 설정

항목세부 정보
Dataset10,000 records
PlatformAppwrite Cloud
SDKOfficial Appwrite Go SDK v0.16.0
NetworkStandard internet connection

결과 비교

구성총 시간레코드당 평균 시간속도 향상
1 워커 (Sequential)1 h 1 m 12 s367.23 ms
50 워커 (Concurrent)1 m 37 s9.78 ms37.5×

시각적 비교

Sequential (1 Worker): ████████████████████████████████████████████████████ (61 minutes)
Concurrent (50 Workers): █ (1.6 minutes)

주요 결과

  1. Appwrite는 동시성을 잘 처리합니다

    • 50개의 동시 작업자가 10,000개의 레코드를 문제 없이 처리했습니다.
    • 속도 제한 문제 없음.
    • 전체적으로 안정적인 성능.
  2. Go SDK 성능

    • 요청당 낮은 오버헤드.
    • 효율적인 연결 관리.
    • 빠른 응답 시간.
  3. 개발자 경험

    • 간단한 설정 및 구성.
    • 명확한 오류 메시지.
    • 디버깅 및 모니터링이 쉬움.
  4. 프로덕션 준비 완료

    • 부하 하에서도 신뢰성 있음.
    • 내장 보안 기능.
    • 확장 가능한 인프라.

기술 심층 분석

왜 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의 조합은 백엔드 서비스에 강력합니다.

리소스

Back to Blog

관련 글

더 보기 »

고처리량 IoT 로그 집계기

!High-Throughput IoT Log Aggregator의 표지 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fde...