GCP

GCP 데이터베이스 선택 완전 가이드: Cloud SQL vs Spanner vs Firestore vs BigQuery

Somaz 2026. 3. 10. 00:00
728x90
반응형

Overview

현대 애플리케이션의 데이터 요구사항은 그 어느 때보다 복잡하고 다양하다. Google Cloud Platform(GCP)은 이러한 다양한 요구사항을 충족하기 위해 관계형, NoSQL, 분석형 데이터베이스 서비스를 포괄적으로 제공한다. 각 데이터베이스는 고유한 강점과 적용 시나리오를 가지고 있어, 올바른 선택이 애플리케이션의 성능과 비용 효율성을 크게 좌우한다.

 

Cloud SQL은 전통적인 관계형 데이터베이스의 완전 관리형 서비스로, MySQL, PostgreSQL, SQL Server를 지원하며 기존 애플리케이션의 클라우드 마이그레이션에 최적화되어 있다. Cloud Spanner는 Google이 개발한 혁신적인 글로벌 분산 관계형 데이터베이스로, ACID 트랜잭션과 수평적 확장성을 동시에 제공하는 독특한 위치를 차지한다.

 

Firestore는 서버리스 NoSQL 문서 데이터베이스로, 실시간 동기화와 자동 스케일링을 통해 모바일 및 웹 애플리케이션에 최적화되어 있다. BigQuery는 페타바이트 규모의 데이터 분석을 위한 완전 관리형 데이터 웨어하우스로, 머신러닝과 비즈니스 인텔리전스를 위한 강력한 분석 기능을 제공한다.

 

최근 데이터베이스 기술의 발전과 함께, 각 서비스는 더욱 정교한 기능들을 추가하고 있다. Cloud SQL의 고가용성과 읽기 복제본 최적화, Spanner의 멀티리전 강력한 일관성, Firestore의 오프라인 지원과 실시간 리스너, BigQuery의 실시간 스트리밍과 ML 통합 등이 그 예다.

 

이 글에서는 각 데이터베이스의 핵심 원리와 특성을 심층 분석하고, 비용과 성능, 일관성 간의 트레이드오프를 구체적으로 검토한다. 또한 실제 운영 환경에서의 적용 시나리오와 Terraform을 활용한 구현 예제, 그리고 OLTP와 분석 워크로드의 최적화 전략을 함께 제시한다.

 

 

 

 


 

 

데이터베이스 기초 개념과 분류

 

 

관계형 데이터베이스의 원리

관계형 데이터베이스는 Edgar F. Codd의 관계형 모델을 기반으로 하며, 데이터를 테이블 형태로 구조화하여 저장한다. ACID(Atomicity, Consistency, Isolation, Durability) 속성을 통해 데이터 무결성을 보장하며, SQL을 통한 복잡한 쿼리와 조인 연산을 지원한다.

 

ACID 속성의 구현 원리

  1. 원자성(Atomicity): 트랜잭션의 모든 연산이 완전히 수행되거나 전혀 수행되지 않음
  2. 일관성(Consistency): 트랜잭션 실행 전후에 데이터베이스가 일관된 상태 유지
  3. 격리성(Isolation): 동시 실행되는 트랜잭션들이 서로 영향을 주지 않음
  4. 지속성(Durability): 커밋된 트랜잭션의 결과가 영구적으로 저장됨

 

 

 

NoSQL 데이터베이스의 특성

NoSQL 데이터베이스는 CAP 정리(Consistency, Availability, Partition tolerance)에 따라 설계되며, 대규모 분산 환경에서의 확장성과 성능을 중시한다. 스키마가 유연하고 수평적 확장이 용이하다는 장점이 있다.

 

CAP 정리의 실제 적용

  • 일관성(Consistency): 모든 노드가 동시에 같은 데이터를 볼 수 있음
  • 가용성(Availability): 시스템이 지속적으로 운영됨
  • 분할 내성(Partition tolerance): 네트워크 분할 상황에서도 시스템이 동작함

 

분석형 데이터베이스의 구조

분석형 데이터베이스는 OLAP(Online Analytical Processing) 워크로드에 최적화되어 있으며, 컬럼 지향 저장과 대용량 데이터 처리를 위한 병렬 처리 엔진을 활용한다.

 

 

 

 

 


 

 

 

 

 

 

Cloud SQL 심층 분석

 

Cloud SQL의 아키텍처와 원리

Cloud SQL은 Google의 인프라 위에서 실행되는 완전 관리형 관계형 데이터베이스 서비스다. 자동 백업, 패치 관리, 고가용성을 제공하며 기존 애플리케이션과의 호환성을 보장한다.

 

 

고가용성 구성 원리

Cloud SQL의 고가용성은 동기식 복제를 통해 구현된다. 기본 인스턴스와 대기 인스턴스가 서로 다른 가용 영역에 배치되며, 자동 페일오버를 통해 RTO(Recovery Time Objective) 60초 이하를 달성한다.

# Cloud SQL MySQL 인스턴스 구성
resource "google_sql_database_instance" "main" {
  name             = "main-instance"
  database_version = "MYSQL_8_0"
  region          = "us-central1"
  
  settings {
    tier = "db-n1-standard-2"
    
    # 고가용성 구성
    availability_type = "REGIONAL"
    
    # 백업 구성
    backup_configuration {
      enabled                        = true
      start_time                    = "02:00"
      point_in_time_recovery_enabled = true
      transaction_log_retention_days = 7
      backup_retention_settings {
        retained_backups = 30
        retention_unit   = "COUNT"
      }
    }
    
    # IP 구성
    ip_configuration {
      ipv4_enabled    = true
      private_network = google_compute_network.private_network.id
      require_ssl     = true
      
      authorized_networks {
        name  = "office-network"
        value = "203.0.113.0/24"
      }
    }
    
    # 데이터베이스 플래그
    database_flags {
      name  = "slow_query_log"
      value = "on"
    }
    
    database_flags {
      name  = "general_log"
      value = "on"
    }
    
    # 디스크 구성
    disk_type    = "PD_SSD"
    disk_size    = 100
    disk_autoresize = true
    disk_autoresize_limit = 500
  }
  
  deletion_protection = true
}

# 읽기 복제본 구성
resource "google_sql_database_instance" "read_replica" {
  name                 = "read-replica"
  master_instance_name = google_sql_database_instance.main.name
  region              = "us-east1"
  database_version     = "MYSQL_8_0"
  
  replica_configuration {
    failover_target = false
  }
  
  settings {
    tier = "db-n1-standard-1"
    
    availability_type = "ZONAL"
    
    ip_configuration {
      ipv4_enabled    = true
      private_network = google_compute_network.private_network.id
      require_ssl     = true
    }
    
    disk_type = "PD_SSD"
    disk_size = 50
  }
}

# 데이터베이스 생성
resource "google_sql_database" "database" {
  name     = "application_db"
  instance = google_sql_database_instance.main.name
  charset  = "utf8mb4"
  collation = "utf8mb4_unicode_ci"
}

# 사용자 생성
resource "google_sql_user" "app_user" {
  name     = "app_user"
  instance = google_sql_database_instance.main.name
  password = var.db_password
  host     = "%"
}

# SSL 인증서 생성
resource "google_sql_ssl_cert" "client_cert" {
  common_name = "client-cert"
  instance    = google_sql_database_instance.main.name
}

 

 

Cloud SQL 성능 최적화

 

연결 풀링과 캐싱 전략

# Cloud SQL Proxy를 위한 서비스 어카운트
resource "google_service_account" "sql_proxy" {
  account_id   = "sql-proxy"
  display_name = "Cloud SQL Proxy Service Account"
}

# Cloud SQL 클라이언트 권한 부여
resource "google_project_iam_member" "sql_proxy_client" {
  project = var.project_id
  role    = "roles/cloudsql.client"
  member  = "serviceAccount:${google_service_account.sql_proxy.email}"
}

# GKE에서 Cloud SQL Proxy 사용
resource "kubernetes_deployment" "app" {
  metadata {
    name = "web-app"
  }
  
  spec {
    replicas = 3
    
    selector {
      match_labels = {
        app = "web-app"
      }
    }
    
    template {
      metadata {
        labels = {
          app = "web-app"
        }
      }
      
      spec {
        service_account_name = google_service_account.sql_proxy.email
        
        container {
          name  = "web-app"
          image = "gcr.io/project/web-app:latest"
          
          env {
            name  = "DB_HOST"
            value = "127.0.0.1"
          }
          
          env {
            name  = "DB_PORT"
            value = "3306"
          }
          
          env {
            name = "DB_PASSWORD"
            value_from {
              secret_key_ref {
                name = "db-credentials"
                key  = "password"
              }
            }
          }
        }
        
        # Cloud SQL Proxy 사이드카
        container {
          name  = "cloudsql-proxy"
          image = "gcr.io/cloudsql-docker/gce-proxy:1.33.2"
          
          command = [
            "/cloud_sql_proxy",
            "-instances=${google_sql_database_instance.main.connection_name}=tcp:3306"
          ]
          
          security_context {
            run_as_non_root = true
          }
        }
      }
    }
  }
}

 

 

 

 

 


 

 

 

 

 

Cloud Spanner 혁신적 아키텍처

 

 

Spanner의 글로벌 분산 원리

Cloud Spanner는 Google의 TrueTime API를 활용하여 글로벌 분산 환경에서 강력한 일관성을 제공하는 혁신적인 데이터베이스다. 전통적인 CAP 정리의 한계를 넘어서는 독특한 아키텍처를 가지고 있다.

 

 

 

TrueTime과 외부 일관성

TrueTime은 GPS와 원자시계를 활용한 글로벌 시간 동기화 시스템으로, 각 트랜잭션에 글로벌하게 유일한 타임스탬프를 할당한다. 이를 통해 외부 일관성(External Consistency)을 보장한다.

# Cloud Spanner 인스턴스 구성
resource "google_spanner_instance" "main" {
  config       = "regional-us-central1"
  display_name = "Main Instance"
  name         = "main-instance"
  num_nodes    = 3
  
  labels = {
    environment = "production"
    team       = "backend"
  }
}

# Spanner 데이터베이스 생성
resource "google_spanner_database" "database" {
  instance = google_spanner_instance.main.name
  name     = "application_db"
  
  ddl = [
    "CREATE TABLE Users (UserId STRING(36) NOT NULL, Email STRING(MAX), Name STRING(MAX), CreatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)) PRIMARY KEY (UserId)",
    "CREATE TABLE Orders (OrderId STRING(36) NOT NULL, UserId STRING(36) NOT NULL, Amount NUMERIC NOT NULL, Status STRING(20) NOT NULL, CreatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)) PRIMARY KEY (OrderId)",
    "CREATE INDEX UserEmailIndex ON Users(Email)",
    "CREATE INDEX UserOrdersIndex ON Orders(UserId, CreatedAt DESC)"
  ]
  
  deletion_protection = true
}

# 멀티리전 구성
resource "google_spanner_instance" "multi_region" {
  config       = "nam-eur-asia1"  # 북미-유럽-아시아 멀티리전
  display_name = "Multi-Region Instance"
  name         = "multi-region-instance"
  num_nodes    = 9  # 각 리전당 3노드
  
  labels = {
    environment = "production"
    scope      = "global"
  }
}

 

 

Spanner의 스키마 설계 최적화

 

핫스팟 방지를 위한 키 설계

Spanner에서는 순차적인 키로 인한 핫스팟을 방지하기 위해 키 설계가 중요하다.

-- 잘못된 설계 (순차적 키로 인한 핫스팟)
CREATE TABLE Orders (
  OrderId INT64 NOT NULL,  -- 자동 증가 키는 핫스팟 유발
  UserId STRING(36) NOT NULL,
  Amount NUMERIC NOT NULL,
  CreatedAt TIMESTAMP NOT NULL
) PRIMARY KEY (OrderId);

-- 올바른 설계 (UUID 또는 해시 기반 키)
CREATE TABLE Orders (
  OrderId STRING(36) NOT NULL,  -- UUID 사용
  UserId STRING(36) NOT NULL,
  Amount NUMERIC NOT NULL,
  CreatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (OrderId);

-- 또는 복합키를 통한 분산
CREATE TABLE UserOrders (
  UserId STRING(36) NOT NULL,
  OrderId STRING(36) NOT NULL,
  Amount NUMERIC NOT NULL,
  CreatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true)
) PRIMARY KEY (UserId, OrderId);

 

 

 

 


 

 

 

 

 

 

Firestore 실시간 NoSQL 데이터베이스

 

Firestore의 문서 기반 데이터 모델

Firestore는 문서-컬렉션 계층 구조를 사용하는 NoSQL 데이터베이스로, 실시간 동기화와 오프라인 지원을 제공한다. 각 문서는 키-값 쌍으로 구성되며, 하위 컬렉션을 가질 수 있다.

# Firestore 데이터베이스 생성
resource "google_firestore_database" "database" {
  project     = var.project_id
  name        = "(default)"
  location_id = "us-central"
  type        = "FIRESTORE_NATIVE"
  
  concurrency_mode = "OPTIMISTIC"
  app_engine_integration_mode = "DISABLED"
}

# 복합 인덱스 생성
resource "google_firestore_index" "user_orders_index" {
  project = var.project_id
  
  collection = "orders"
  
  fields {
    field_path = "userId"
    order      = "ASCENDING"
  }
  
  fields {
    field_path = "createdAt"
    order      = "DESCENDING"
  }
  
  fields {
    field_path = "status"
    order      = "ASCENDING"
  }
}

 

 

Firestore 보안 규칙

// Firestore 보안 규칙 예제
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // 사용자 프로필 접근 제어
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    
    // 주문 정보 접근 제어
    match /orders/{orderId} {
      allow read, write: if request.auth != null 
        && request.auth.uid == resource.data.userId;
      allow create: if request.auth != null 
        && request.auth.uid == request.resource.data.userId;
    }
    
    // 공개 상품 정보
    match /products/{productId} {
      allow read: if true;
      allow write: if request.auth != null 
        && 'admin' in request.auth.token.roles;
    }
  }
}

 

 

 

 


 

 

 

 

 

BigQuery 대규모 분석 플랫폼

 

BigQuery의 컬럼 지향 저장 구조

BigQuery는 Dremel 기술을 기반으로 한 완전 관리형 데이터 웨어하우스로, 컬럼 지향 저장과 대규모 병렬 처리를 통해 페타바이트 규모의 데이터 분석을 지원한다.

# BigQuery 데이터셋 생성
resource "google_bigquery_dataset" "analytics" {
  dataset_id  = "analytics"
  description = "Analytics data warehouse"
  location    = "US"
  
  default_table_expiration_ms = 3600000  # 1시간
  
  access {
    role          = "OWNER"
    user_by_email = google_service_account.bigquery_admin.email
  }
  
  access {
    role   = "READER"
    domain = "example.com"
  }
  
  labels = {
    environment = "production"
    team       = "analytics"
  }
}

# 파티션 테이블 생성
resource "google_bigquery_table" "user_events" {
  dataset_id = google_bigquery_dataset.analytics.dataset_id
  table_id   = "user_events"
  
  description = "User events partitioned by date"
  
  time_partitioning {
    type                     = "DAY"
    field                   = "event_timestamp"
    require_partition_filter = true
    expiration_ms           = 5184000000  # 60일
  }
  
  clustering = ["user_id", "event_type"]
  
  schema = jsonencode([
    {
      name = "event_id"
      type = "STRING"
      mode = "REQUIRED"
    },
    {
      name = "user_id"
      type = "STRING"
      mode = "REQUIRED"
    },
    {
      name = "event_type"
      type = "STRING"
      mode = "REQUIRED"
    },
    {
      name = "event_timestamp"
      type = "TIMESTAMP"
      mode = "REQUIRED"
    },
    {
      name = "properties"
      type = "JSON"
      mode = "NULLABLE"
    }
  ])
}

 

 


 

 

 

 

 

데이터베이스 선택 기준 및 비교 분석

 

워크로드별 데이터베이스 선택 매트릭스

특성 Cloud SQL Cloud Spanner Firestore BigQuery
데이터 모델 관계형 (테이블) 관계형 (스키마) 문서 기반 NoSQL 컬럼 지향 분석형
확장성 수직적 (최대 96vCPU) 수평적 (글로벌) 자동 수평적 페타바이트 급
일관성 ACID 강력한 일관성 ACID 외부 일관성 최종 일관성 배치 일관성
지연시간 < 10ms < 10ms (리전 내) < 100ms 초-분 단위
쿼리 언어 SQL SQL NoSQL 쿼리 SQL (분석 최적화)
실시간 기능 트리거/복제 변경 스트림 실시간 리스너 스트리밍 삽입
글로벌 분산 읽기 복제본 네이티브 지원 멀티리전 복제 글로벌 가용
오프라인 지원 없음 없음 네이티브 지원 없음
트랜잭션 ACID ACID 분산 제한적 배치
월 비용 (중간 규모) $200-800 $900-3000 $100-500 $100-1000
적용 시나리오 기존 앱 마이그레이션 글로벌 OLTP 모바일/웹 앱 데이터 분석

 

 

성능과 비용 트레이드오프 분석

 

처리량과 지연시간 비교

# 성능 테스트를 위한 리소스 구성
locals {
  performance_scenarios = {
    oltp_high_throughput = {
      database_type = "spanner"
      config = {
        instance_config = "regional-us-central1"
        node_count     = 3
        expected_qps   = 10000
      }
    }
    
    oltp_cost_optimized = {
      database_type = "cloud_sql"
      config = {
        tier           = "db-n1-standard-4"
        replica_count  = 2
        expected_qps   = 2000
      }
    }
    
    mobile_app_backend = {
      database_type = "firestore"
      config = {
        location     = "us-central"
        read_units   = 100000
        write_units  = 50000
      }
    }
    
    analytics_workload = {
      database_type = "bigquery"
      config = {
        slot_capacity    = 500
        storage_tb      = 10
        query_frequency = "hourly"
      }
    }
  }
}

# 비용 계산 모듈
module "cost_calculator" {
  source = "./modules/database-cost-calculator"
  
  for_each = local.performance_scenarios
  
  scenario_name = each.key
  database_type = each.value.database_type
  config       = each.value.config
  
  region = var.region
  usage_pattern = {
    hours_per_day = 24
    days_per_month = 30
  }
}

output "cost_analysis" {
  value = {
    for scenario, config in local.performance_scenarios :
    scenario => {
      database_type = config.database_type
      estimated_monthly_cost = module.cost_calculator[scenario].monthly_cost
      performance_tier = module.cost_calculator[scenario].performance_tier
    }
  }
}

 

 

실시간 분석과 OLTP 워크로드 최적화

 

하이브리드 아키텍처 구성

# OLTP + 실시간 분석 하이브리드 아키텍처
resource "google_sql_database_instance" "oltp_primary" {
  name             = "oltp-primary"
  database_version = "POSTGRES_14"
  region          = "us-central1"
  
  settings {
    tier = "db-custom-4-16384"  # 4 vCPU, 16GB RAM
    
    availability_type = "REGIONAL"
    
    backup_configuration {
      enabled = true
      start_time = "02:00"
      point_in_time_recovery_enabled = true
    }
    
    # 실시간 복제를 위한 설정
    database_flags {
      name  = "cloudsql.logical_decoding"
      value = "on"
    }
    
    database_flags {
      name  = "max_replication_slots"
      value = "10"
    }
  }
}

# 실시간 CDC를 위한 Dataflow 작업
resource "google_dataflow_job" "cdc_to_bigquery" {
  name              = "postgres-cdc-to-bigquery"
  template_gcs_path = "gs://dataflow-templates/latest/Cloud_SQL_to_BigQuery_CDC"
  temp_gcs_location = "gs://${google_storage_bucket.temp.name}/temp"
  
  parameters = {
    inputSubscription    = google_pubsub_subscription.cdc_subscription.id
    outputTableSpec     = "${var.project_id}:${google_bigquery_dataset.realtime.dataset_id}.transactions"
    outputDeadletterTable = "${var.project_id}:${google_bigquery_dataset.realtime.dataset_id}.failed_records"
  }
  
  depends_on = [google_sql_database_instance.oltp_primary]
}

# 실시간 대시보드를 위한 구체화된 뷰
resource "google_bigquery_table" "realtime_metrics" {
  dataset_id = google_bigquery_dataset.realtime.dataset_id
  table_id   = "realtime_metrics"
  
  description = "Real-time business metrics"
  
  materialized_view {
    query = <<-SQL
      SELECT
        DATETIME_TRUNC(transaction_timestamp, MINUTE) as minute,
        COUNT(*) as transaction_count,
        SUM(amount) as total_amount,
        AVG(amount) as avg_amount,
        COUNT(DISTINCT user_id) as unique_users
      FROM `${var.project_id}.${google_bigquery_dataset.realtime.dataset_id}.transactions`
      WHERE transaction_timestamp >= DATETIME_SUB(CURRENT_DATETIME(), INTERVAL 1 HOUR)
      GROUP BY minute
      ORDER BY minute DESC
    SQL
    
    enable_refresh = true
    refresh_interval_ms = 60000  # 1분마다 갱신
  }
}

 

 


 

 

 

성능 튜닝과 최적화

 

Cloud SQL 성능 튜닝

-- Cloud SQL PostgreSQL 성능 최적화 예제

-- 1. 인덱스 최적화
CREATE INDEX CONCURRENTLY idx_orders_user_created 
ON orders (user_id, created_at DESC) 
WHERE status = 'active';

-- 2. 파티션 테이블 구성
CREATE TABLE orders_partitioned (
    order_id UUID PRIMARY KEY,
    user_id UUID NOT NULL,
    amount DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    status VARCHAR(20) NOT NULL
) PARTITION BY RANGE (created_at);

-- 월별 파티션 생성
CREATE TABLE orders_2024_01 PARTITION OF orders_partitioned
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

-- 3. 구체화된 뷰 활용
CREATE MATERIALIZED VIEW user_order_summary AS
SELECT 
    user_id,
    COUNT(*) as total_orders,
    SUM(amount) as total_amount,
    AVG(amount) as avg_amount,
    MAX(created_at) as last_order_date
FROM orders 
GROUP BY user_id;

-- 인덱스 추가
CREATE UNIQUE INDEX ON user_order_summary (user_id);

 

 

Spanner 쿼리 최적화

-- Spanner 성능 최적화 예제

-- 1. 인덱스 설계 (핫스팟 방지)
CREATE INDEX UserOrdersByTimestamp ON Orders(UserId, CreatedAt DESC);

-- 2. 인터리빙 테이블 활용
CREATE TABLE OrderItems (
    OrderId STRING(36) NOT NULL,
    ItemId STRING(36) NOT NULL,
    ProductId STRING(36) NOT NULL,
    Quantity INT64 NOT NULL,
    Price NUMERIC NOT NULL
) PRIMARY KEY (OrderId, ItemId),
  INTERLEAVE IN PARENT Orders ON DELETE CASCADE;

-- 3. 배치 쿼리 최적화
-- 올바른 방식 (인덱스 활용)
SELECT * FROM Orders@{FORCE_INDEX=UserOrdersByTimestamp} 
WHERE UserId = @user_id AND CreatedAt > @start_date;

-- 4. 뮤테이션 배칭
-- 단일 뮤테이션 대신 배치 처리
INSERT INTO Orders (OrderId, UserId, Amount, CreatedAt)
VALUES 
    ('order1', 'user1', 100.00, PENDING_COMMIT_TIMESTAMP()),
    ('order2', 'user2', 200.00, PENDING_COMMIT_TIMESTAMP()),
    ('order3', 'user3', 150.00, PENDING_COMMIT_TIMESTAMP());

 

 

BigQuery 성능 최적화

-- BigQuery 성능 최적화 예제

-- 1. 파티션과 클러스터링 활용
SELECT 
    user_id,
    COUNT(*) as event_count,
    SUM(revenue) as total_revenue
FROM `project.dataset.user_events`
WHERE _PARTITIONDATE = '2024-01-15'  -- 파티션 프루닝
  AND event_type = 'purchase'        -- 클러스터링 활용
GROUP BY user_id;

-- 2. 윈도우 함수 최적화
SELECT 
    user_id,
    event_timestamp,
    revenue,
    SUM(revenue) OVER (
        PARTITION BY user_id 
        ORDER BY event_timestamp 
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) as running_total
FROM `project.dataset.user_events`
WHERE _PARTITIONDATE BETWEEN '2024-01-01' AND '2024-01-31';

-- 3. 어근사 집계 함수 활용 (대용량 데이터)
SELECT 
    event_type,
    APPROX_COUNT_DISTINCT(user_id) as unique_users,
    APPROX_QUANTILES(revenue, 100)[OFFSET(50)] as median_revenue
FROM `project.dataset.user_events`
WHERE _PARTITIONDATE >= '2024-01-01'
GROUP BY event_type;

 

 

데이터베이스 마이그레이션 전략

 

Cloud SQL 마이그레이션

 

 

Database Migration Service 활용

# Database Migration Service 구성
resource "google_database_migration_service_connection_profile" "source" {
  location              = "us-central1"
  connection_profile_id = "source-mysql"
  display_name         = "Source MySQL Database"
  
  mysql {
    host     = var.source_db_host
    port     = 3306
    username = var.source_db_user
    password = var.source_db_password
    
    ssl {
      ca_certificate = file("./ssl/ca-cert.pem")
    }
  }
}

resource "google_database_migration_service_connection_profile" "destination" {
  location              = "us-central1"
  connection_profile_id = "destination-cloudsql"
  display_name         = "Destination Cloud SQL"
  
  cloudsql {
    cloud_sql_id = google_sql_database_instance.target.connection_name
  }
}

resource "google_database_migration_service_migration_job" "default" {
  location         = "us-central1"
  migration_job_id = "mysql-migration"
  display_name     = "MySQL to Cloud SQL Migration"
  
  source          = google_database_migration_service_connection_profile.source.name
  destination     = google_database_migration_service_connection_profile.destination.name
  
  type = "CONTINUOUS"
  
  vpc_peering_connectivity {
    vpc = google_compute_network.migration_vpc.id
  }
  
  dump_path = "gs://${google_storage_bucket.migration.name}/dump"
}

 

 

Spanner 스키마 마이그레이션

# Spanner 마이그레이션을 위한 Harbourbridge 구성
resource "google_cloudbuild_trigger" "spanner_migration" {
  name = "spanner-migration"
  
  github {
    owner = var.github_owner
    name  = var.github_repo
    
    push {
      branch = "^main$"
    }
  }
  
  build {
    step {
      name = "gcr.io/cloud-spanner-pg-adapter/harbourbridge"
      args = [
        "-source=mysql",
        "-source-profile=host=${var.source_host},user=${var.source_user},password=${var.source_password}",
        "-target-profile=projects/${var.project_id}/instances/${google_spanner_instance.main.name}/databases/${google_spanner_database.database.name}",
        "-schema-file=gs://${google_storage_bucket.migration.name}/schema.json"
      ]
    }
    
    step {
      name = "gcr.io/google.com/cloudsdktool/cloud-sdk"
      args = [
        "gcloud", "spanner", "databases", "ddl", "update",
        google_spanner_database.database.name,
        "--instance=${google_spanner_instance.main.name}",
        "--ddl-file=schema.sql"
      ]
    }
  }
}

 

 

보안과 규정 준수

 

데이터 암호화와 키 관리

# Cloud KMS 키 관리
resource "google_kms_key_ring" "database_keyring" {
  name     = "database-keyring"
  location = "global"
}

resource "google_kms_crypto_key" "database_key" {
  name     = "database-encryption-key"
  key_ring = google_kms_key_ring.database_keyring.id
  
  rotation_period = "2592000s"  # 30일
  
  version_template {
    algorithm = "GOOGLE_SYMMETRIC_ENCRYPTION"
  }
}

# CMEK를 사용하는 Cloud SQL
resource "google_sql_database_instance" "encrypted" {
  name             = "encrypted-instance"
  database_version = "POSTGRES_14"
  region          = "us-central1"
  
  encryption_key_name = google_kms_crypto_key.database_key.id
  
  settings {
    tier = "db-n1-standard-2"
    
    ip_configuration {
      ipv4_enabled                                  = false
      private_network                              = google_compute_network.private_network.id
      enable_private_path_for_google_cloud_services = true
    }
  }
  
  depends_on = [google_kms_crypto_key_iam_member.sql_kms_binding]
}

# BigQuery 테이블 암호화
resource "google_bigquery_table" "encrypted_table" {
  dataset_id = google_bigquery_dataset.analytics.dataset_id
  table_id   = "encrypted_user_data"
  
  encryption_configuration {
    kms_key_name = google_kms_crypto_key.database_key.id
  }
  
  schema = jsonencode([
    {
      name = "user_id"
      type = "STRING"
      mode = "REQUIRED"
    },
    {
      name = "encrypted_data"
      type = "BYTES"
      mode = "REQUIRED"
    }
  ])
}

 

감사 로깅과 모니터링

# Cloud Audit Logs 구성
resource "google_project_iam_audit_config" "database_audit" {
  project = var.project_id
  
  audit_log_config {
    log_type = "ADMIN_READ"
  }
  
  audit_log_config {
    log_type = "DATA_READ"
  }
  
  audit_log_config {
    log_type = "DATA_WRITE"
  }
  
  service = "cloudsql.googleapis.com"
}

resource "google_project_iam_audit_config" "spanner_audit" {
  project = var.project_id
  
  audit_log_config {
    log_type = "ADMIN_READ"
  }
  
  audit_log_config {
    log_type = "DATA_READ"
  }
  
  audit_log_config {
    log_type = "DATA_WRITE"
  }
  
  service = "spanner.googleapis.com"
}

# 로그 기반 메트릭
resource "google_logging_metric" "database_access_metric" {
  name   = "database_access_count"
  filter = "resource.type=\"cloudsql_database\" AND protoPayload.methodName=\"cloudsql.instances.query\""
  
  metric_descriptor {
    metric_kind = "DELTA"
    value_type  = "INT64"
    display_name = "Database Access Count"
  }
  
  label_extractors = {
    "user" = "EXTRACT(protoPayload.authenticationInfo.principalEmail)"
  }
}

 

 

데이터베이스 모니터링과 운영

 

성능 메트릭과 알림

# 종합적인 데이터베이스 모니터링 대시보드
resource "google_monitoring_dashboard" "database_overview" {
  dashboard_json = jsonencode({
    displayName = "Database Performance Overview"
    
    gridLayout = {
      widgets = [
        {
          title = "Cloud SQL CPU Utilization"
          xyChart = {
            dataSets = [{
              timeSeriesQuery = {
                timeSeriesFilter = {
                  filter = "resource.type=\"cloudsql_database\""
                  aggregation = {
                    alignmentPeriod = "60s"
                    perSeriesAligner = "ALIGN_MEAN"
                  }
                }
              }
            }]
            yAxis = { scale = "LINEAR" }
          }
        },
        {
          title = "Spanner Query Latency"
          xyChart = {
            dataSets = [{
              timeSeriesQuery = {
                timeSeriesFilter = {
                  filter = "resource.type=\"spanner_instance\""
                  aggregation = {
                    alignmentPeriod = "60s"
                    perSeriesAligner = "ALIGN_MEAN"
                  }
                }
              }
            }]
          }
        },
        {
          title = "Firestore Read/Write Operations"
          xyChart = {
            dataSets = [{
              timeSeriesQuery = {
                timeSeriesFilter = {
                  filter = "resource.type=\"firestore_database\""
                  aggregation = {
                    alignmentPeriod = "60s"
                    perSeriesAligner = "ALIGN_RATE"
                  }
                }
              }
            }]
          }
        },
        {
          title = "BigQuery Slot Utilization"
          xyChart = {
            dataSets = [{
              timeSeriesQuery = {
                timeSeriesFilter = {
                  filter = "resource.type=\"bigquery_project\""
                  aggregation = {
                    alignmentPeriod = "300s"
                    perSeriesAligner = "ALIGN_MEAN"
                  }
                }
              }
            }]
          }
        }
      ]
    }
  })
}

# SLO 기반 알림 정책
resource "google_monitoring_slo" "database_availability" {
  service      = google_monitoring_service.database_service.service_id
  display_name = "Database Availability SLO"
  
  request_based_sli {
    good_total_ratio {
      total_service_filter = "resource.type=\"cloudsql_database\""
      good_service_filter  = "resource.type=\"cloudsql_database\" AND metric.type=\"cloudsql.googleapis.com/database/up\""
    }
  }
  
  goal {
    performance_goal = 0.995  # 99.5% 가용성
    rolling_period   = "2592000s"  # 30일
  }
}

 

 

자동화된 백업과 복구

# 크로스 리전 백업 전략
resource "google_sql_backup_run" "automated_backup" {
  instance = google_sql_database_instance.main.name
  
  description = "Automated daily backup"
  type        = "ON_DEMAND"
  location    = "us-east1"  # 다른 리전에 백업
}

# Spanner 백업 자동화
resource "google_spanner_backup" "automated_backup" {
  instance    = google_spanner_instance.main.name
  database    = google_spanner_database.database.name
  backup_id   = "auto-backup-${formatdate("YYYY-MM-DD", timestamp())}"
  expire_time = timeadd(timestamp(), "2160h")  # 90일 후 만료
}

# Firestore 백업 자동화
resource "google_cloud_scheduler_job" "firestore_backup" {
  name      = "firestore-backup"
  schedule  = "0 2 * * *"  # 매일 오전 2시
  time_zone = "UTC"
  
  http_target {
    http_method = "POST"
    uri         = "https://firestore.googleapis.com/v1/projects/${var.project_id}/databases/(default):exportDocuments"
    
    headers = {
      "Content-Type" = "application/json"
    }
    
    body = base64encode(jsonencode({
      outputUriPrefix = "gs://${google_storage_bucket.backups.name}/firestore-backup"
    }))
    
    oauth_token {
      service_account_email = google_service_account.backup_service.email
    }
  }
}

# BigQuery 테이블 백업 스케줄링
resource "google_bigquery_data_transfer_config" "table_backup" {
  display_name   = "BigQuery Table Backup"
  location       = "us"
  data_source_id = "scheduled_query"
  schedule       = "every day 03:00"
  
  destination_dataset_id = google_bigquery_dataset.backup.dataset_id
  
  params = {
    query = "CREATE OR REPLACE TABLE `${var.project_id}.${google_bigquery_dataset.backup.dataset_id}.user_events_backup` AS SELECT * FROM `${var.project_id}.${google_bigquery_dataset.analytics.dataset_id}.user_events`"
  }
}

 

 

 


 

 

마무리

GCP의 데이터베이스 서비스들은 각각 독특한 강점과 적용 영역을 가지고 있어, 애플리케이션의 요구사항에 따른 신중한 선택이 필요하다. Cloud SQL은 기존 관계형 데이터베이스 워크로드의 클라우드 마이그레이션에 최적화되어 있으며, 완전 관리형 서비스의 편리함과 익숙한 SQL 인터페이스를 제공한다.

 

Cloud Spanner는 글로벌 분산 애플리케이션에서 ACID 트랜잭션과 수평적 확장성을 동시에 요구하는 까다로운 요구사항을 해결하는 혁신적인 솔루션이다. TrueTime을 통한 외부 일관성 보장은 전통적인 분산 데이터베이스의 한계를 뛰어넘는 독특한 가치를 제공한다.

 

Firestore는 모바일과 웹 애플리케이션의 실시간 협업과 오프라인 지원 요구사항에 특화되어 있으며, 서버리스 아키텍처와 자동 스케일링을 통해 개발 생산성을 크게 향상시킨다. BigQuery는 페타바이트 규모의 분석 워크로드에서 탁월한 성능을 보여주며, 머신러닝 통합을 통해 고급 분석 기능을 제공한다.

 

 

핵심 선택 기준

  • 워크로드 특성: OLTP에는 Cloud SQL이나 Spanner를, 실시간 협업에는 Firestore를, 분석 워크로드에는 BigQuery를 선택하는 것이 기본 원칙이다.
  • 확장성 요구사항: 수직적 확장으로 충분하다면 Cloud SQL을, 글로벌 수평적 확장이 필요하다면 Spanner를, 자동 스케일링이 중요하다면 Firestore를 고려해야 한다.
  • 일관성 요구사항: 강력한 일관성이 필요한 금융 애플리케이션에는 Spanner를, 최종 일관성으로 충분한 소셜 애플리케이션에는 Firestore가 적합하다.
  • 비용 최적화: 예측 가능한 워크로드에는 Cloud SQL이, 가변적인 트래픽에는 Firestore나 BigQuery의 온디맨드 모델이 경제적이다.
  • 운영 복잡성: 완전 관리형 서비스의 장점을 최대한 활용하되, 각 서비스의 고유한 특성과 제약사항을 충분히 이해하고 적용해야 한다.

 

 

적절한 데이터베이스 선택과 최적화는 애플리케이션의 성능, 확장성, 그리고 운영 효율성을 결정하는 핵심 요소다. 이 가이드에서 제시한 비교 분석과 최적화 전략을 바탕으로, 각 조직의 요구사항에 가장 적합한 데이터베이스 아키텍처를 구축하시기 바란다.

 

 

 

 

 

 

 


Reference

 

 

 

Somaz | DevOps Engineer | Kubernetes & Cloud Infrastructure Specialist

728x90
반응형