Skip to content

BigQueryテーブルのFeature Toggle #63

@takegue

Description

@takegue

はじめに

大規模なデータ処理システムにおいてはテーブルが大規模に利用されているほどリリースのリスクは高まります。Upstreamの予期しないデータの変更やETLの変更により、BigQuery上のテーブルについてデプロイ戦略を考えることが重要です。
BigQueryにおいて バージョナイズは一つの手でありますが、これは利用者によるマイグレーション操作が必要です。
可能であれば十分な監視があると仮定のもと、ユーザが利用しているテーブルにおいて安全に開発&デプロイをできると望ましいでしょう。

本記事では、BigQueryのテーブルリリース戦略として、Feature Toggleを活用しリスクを最小限に抑える方法を紹介します。Viewを使うことでFeature Toggleを実現し、次のような出し分けできます。

  • 小規模なデータの出し分け (特定のレコードのみ)
  • ユーザによる出し分け

Feature Toggleとは?

Feature Toggle(フィーチャートグル)とは、ソフトウェアの機能を切り替えする仕組みのことです。 この仕組み自体を指しFeature Flagと呼ばれます。
アプリケーションの開発にこれを組み込むことによって、開発者は特定の条件にあてはまるユーザのみに新しく開発した機能を提供することができます。
これにより機能開発のリスクを小さく保ちつことができます。

BigQueryでFeature Toggleを実現する方法

BigQueryでは、Viewを使ってFeature Toggleを実現することができます。
次に手順を示します。

セットアップ

-- Set up
create schema if not exists `demo`;
create or replace table `demo.bikeshare_stations_blue`
copy `bigquery-public-data.austin_bikeshare.bikeshare_stations`
;

create or replace table `demo.bikeshare_stations_green`
as
  select * from `bigquery-public-data.austin_bikeshare.bikeshare_stations`
;

基礎: Feature Toggle付きViewの用意

上記のセットアップの元、Feature toggle機能つきのViewは次のSQLにより構築することができます。
この例ではユーザによって 出し分けを行い、10%のユーザには 'green'環境が提供されます。

-- Feature toggle by user
create or replace view `demo.bikeshare_stations`
as
with switched_by_user as (
  with unioned as (
    select *, 'blue' as _metadata_version from `takegue.canary.bikeshare_stations_blue`
    union all
    select *, 'green' as _metadata_version from `takegue.canary.bikeshare_stations_green`
  )
  select *
  from unioned
  where 
    case
      -- ユーザによってテーブルを出しわける 
      when mod(farm_fingerprint(session_user()), 10) = 1 then _metadata_version = 'blue'
      else _metadata_version = 'green'
    end
)

select * from switched_by_user

ユーザによってではなく、レコードのIDよる出し分けを実現することも容易です。

-- Feature toggle by user
create or replace view `demo.bikeshare_stations`
as
with switched_by_user as (
  with unioned as (
    select *, 'blue' as _metadata_version from `takegue.canary.bikeshare_stations_blue`
    union all
    select *, 'green' as _metadata_version from `takegue.canary.bikeshare_stations_green`
  )
  select *
  from unioned
  where 
    case
      -- ユーザによってテーブルを出しわける 
      when mod(farm_fingerprint(format('%t', station_id)), 10) = 1 then _metadata_version = 'blue'
	  -- レコードのIDによって出しわける
      else _metadata_version = 'green'
    end
)

select * from switched_by_user

上記を実施する際の注意点として、動的な出し分けを行う際には利用者側にはどのソースからデータを取得したかの情報を知ることができるように _metadata_version カラムがあるとデバッグが容易になります。

応用: 高度な切り替え

切り替え条件に対し、操作可能なテーブルやメタデータを組み込むことで、高度な切り替え条件を実現することが可能になります。
ここではいくつかの高度な切り替え例を示します。

テーブルのラベル情報による切り替え

テーブルのラベル情報を元にテーブルの実装を動的に切り替えることができます。
次の例では、ラベル=featureの値によってテーブルを切り替える際のコード例です。

with unioned as (
  select *, 'blue' as _source from `demo.bikeshare_stations_blue`
  union all
  select *, 'green' as _source from `demo.bikeshare_stations_green`
)
, metadata__feature as (
  select as value
    label
  from `demo.INFORMATION_SCHEMA.TABLE_OPTIONS`
  left join unnest([struct(
    -- BigQuery の option_valueのパース
    -- https://github.com/takegue/bqmake/blob/a43db847795a5e6be6aa4a3751ba0c43a6b04ff1/bigquery/%40default/v0/%40routines/zget_bqlabel_from_option/ddl.sql
    array(
      select as struct
        string(label[0]) as name, string(label[1]) as value
      from unnest(json_extract_array(safe.parse_json(replace(replace(replace(option_value, "STRUCT", ""), '(', '['), ')', ']')))) as label
    ) as labels
  )])
  left join unnest(labels) as label
  where 
    table_schema = 'canary' and table_name = 'bikeshare_stations'
    and option_name = 'labels'
    and label.name = 'feature'
)
, core as (
  select *
  from 
    unioned
  left join metadata__feature as _feat on true
  where
    case 
      when _feat.value = 'blue' then _source = 'blue'
      else _source = 'green'
    end
)

select * from core

異常系におけるフォールバック

新しく作成したテーブルは時に問題のあるデータを含むかもしれません。
そのようなケースに備え、検査用のCTEを設けて 異常がある場合には
既に安定したテーブルに切り替えることもできます!

with unioned as (
  select *, 'blue' as _source from `demo.bikeshare_stations_blue`
  union all
  select *, 'green' as _source from `demo.bikeshare_stations_green`
)
, metadata__stats as (
  select as value count(*) from `demo.bikeshare_stations_green`
)
, core as (
  select *
  from 
    unioned
  left join metadata__stats as _stats on true
  where
    case 
      when _stats >= 50 then _source = 'blue'
      else _source = 'green'
    end
)

select * from core

上記のコードに対し異常を引き起こす場合には次の操作で demo.bikeshare_stations_blue
に異常を引き起こしてみてください。

truncate from `demo.bikeshare_stations_blue`

この後に上記のコードを実行すると、 _soruce = "green" のテーブルに切り替わっていることが確認できます。

CTE内で集計することで計算コストがあがることを心配するかもしれません。
実はこれは心配はなく、計算コストが問題になる場合には、このコードはマテリアライズドビューによる最適化が可能です! 次のコードにより作成してみてください。

create materialized view `demo.bikeshare_stations`
options(
  allow_non_incremental_definition = true
  , max_staleness = interval 30 minute
)
as
-- 省略

まとめ

今回紹介した切り替え条件の他にも、条件式を工夫することで様々な切り替えが実現できます。
みなさんでぜひ工夫してみてください。
1点利用の際の注意点として今回提示した方法はテーブル自身の一貫性の一部犠牲に開発可能性や再利用性を高める方法といえます。利用の際にはこの点には注意する必要があるでしょう。

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions