🕰️

Cloud Spanner Schedulerを擬似的に実現する

に公開

こんにちは。カウシェでバックエンドチームのエンジニアリングマネージャーを担当しているs.ichijima (@shinichiji) です。

背景

カウシェではDBにCloud Spannerを使っており、Auto Scaling機能の設定をオンにして使っています。
しかし、SpannerのAuto Scalingはトラフィックの急激な増加に即座に対応できない場合があり、スパイク発生時にレイテンシーが悪化する懸念があります。

特に、事前にトラフィック増加が予測できるケースでは、Auto Scalingを待つよりも事前にnode数を増強しておく方が確実です。

課題

カウシェでは、事前にトラフィック増加が予測できるケースとして、主に2つのシナリオがあります。

1つは、テレビ番組に取り上げられる際です。ありがたいことに、カウシェがテレビ番組に取り上げられることがあるのですが、休日の朝に放送されたり、必ず複数人のエンジニアがいる状況ではないこともあり、事前の増強に対応できなかった場合の機会損失は大きいです。

2つ目は、カウシェのサービスの特性上、特定の時間帯にアクセスがスパイク的に集中するケースです。スパイクの頻度や発生タイミングは判明しているので、この問題は「スパイクより前に稼働する最低node数を増やしておく」という対応でなんとかなるというのが分かっています。

しかし、常にスパイクに対応できる状態のnode数にしておくのはコスト効率が良くないですし、かといって毎回、開発者がnode数の設定を変更するのは現実的ではありません。

解決策

これらの問題を解決するため、Cloud Schedulerを使って疑似的にCloud Spanner Schedulerを実現できるのではないかと考え、実装に取り組みました。
カウシェではIaCにTerraformを使っており、以下のように実現しています。

variable "default_spanner_autoscale_min_nodes" {
  description = "Default autoscaling minimum nodes for Spanner"
  type        = number
  default     = 1
}

variable "default_spanner_autoscale_max_nodes" {
  description = "Default autoscaling maximum nodes for Spanner"
  type        = number
  default     = 10
}

variable "peak_spanner_autoscale_min_nodes" {
  description = "Peak autoscaling minimum nodes for Spanner"
  type        = number
  default     = 5
}

variable "peak_spanner_autoscale_max_nodes" {
  description = "Peak autoscaling maximum nodes for Spanner"
  type        = number
  default     = 20
}

resource "google_cloud_scheduler_job" "spanner-autoscale-up" {
  project          = local.project_id
  name             = "spanner-autoscale-up"
  region           = "asia-northeast1"
  schedule         = "0 12 * * *"
  time_zone        = "Asia/Tokyo"

  http_target {
    http_method = "PATCH"
    uri         = "https://spanner.googleapis.com/v1/projects/${local.project_id}/instances/${var.spanner_instance_id}"
    body = base64encode(jsonencode({
      instance = {
        name = "projects/${local.project_id}/instances/${var.spanner_instance_id}"
        autoscalingConfig = {
          autoscalingLimits = {
            minNodes = var.peak_spanner_autoscale_min_nodes
            maxNodes = var.peak_spanner_autoscale_max_nodes
          }
        }
      }
      fieldMask = "autoscaling_config.autoscaling_limits.min_nodes,autoscaling_config.autoscaling_limits.max_nodes"
    }))

    headers = {
      "Content-Type" = "application/json"
    }
    oauth_token {
      service_account_email = google_service_account.spanner-autoscaler.email
      scope                 = "https://www.googleapis.com/auth/cloud-platform"
    }
  }
}

resource "google_cloud_scheduler_job" "spanner-autoscale-down" {
  project          = local.project_id
  name             = "spanner-autoscale-down"
  region           = "asia-northeast1"
  schedule         = "0 13 * * *"
  time_zone        = "Asia/Tokyo"

  http_target {
    http_method = "PATCH"
    uri         = "https://spanner.googleapis.com/v1/projects/${local.project_id}/instances/${var.spanner_instance_id}"
    body = base64encode(jsonencode({
      instance = {
        name = "projects/${local.project_id}/instances/${var.spanner_instance_id}"
        autoscalingConfig = {
          autoscalingLimits = {
            minNodes = var.default_spanner_autoscale_min_nodes
            maxNodes = var.default_spanner_autoscale_max_nodes
          }
        }
      }
      fieldMask = "autoscaling_config.autoscaling_limits.min_nodes,autoscaling_config.autoscaling_limits.max_nodes"
    }))
    headers = {
      "Content-Type" = "application/json"
    }
    oauth_token {
      service_account_email = google_service_account.spanner-autoscaler.email
      scope                 = "https://www.googleapis.com/auth/cloud-platform"
    }
  }
}

おわりに

カウシェではこの仕組みにより、コストの最適化を図りつつ、運用のミスを無くすことができています。特に、テレビ番組の放送時間や日常のスパイクタイミングを事前に把握している場合、この方法は非常に有効です。

もしSpannerをお使いで、事前に分かっている急なトラフィック増に備える必要がある場合は、ぜひこの方法を試してみてください。

カウシェ Tech Blog

Discussion