7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ZOZOテクノロジーズ #1Advent Calendar 2020

Day 8

google-cloud-rubyがなぜかPythonを呼び出している件について

Last updated at Posted at 2020-12-07

BigQueryの運用を行っていると定期的に無駄なテーブルを削除することは良くあると思います。
テーブル数が少ない場合はwebからぽちぽちと削除するので対応可能ですが、テーブル数が多い場合はスクリプトを作って削除したほうが楽です。

最初は以下のようなスクリプトを書いていたのですが、削除対象のテーブル数が膨大なため、なかなか終わりませんでした。
※ロギング部分は省略

require 'google/cloud/bigquery'

table_names = [削除したいテーブル名の配列]
project_name = プロジェクト名
dataset_name = データセット名

table_names.each do |table_name|
  bq_client = ::Google::Cloud::Bigquery.new(project: project_name)
  dataset = bq_client.dataset(dataset_name)
  table = dataset.table(table_name)
  table.delete
end

テーブルの削除処理はクライアントサイドのCPU・メモリーをほぼ使わず、ほとんどの待ち時間がBigQuery側での処理待をポーリングしているだけであるため、parallelというgemを使って並列化しました。
https://github.com/grosser/parallel

Rubyのマルチスレッド処理の実装はGVLによって同時に実行されるスレッドが1つだけになってしまっていますが、IO系のブロッキング処理に対してはGVLの解放をするので、今回のケースでは高速化が見込まれます。

require 'google/cloud/bigquery'
require 'parallel'

table_names = [削除したいテーブル名の配列]
project_name = プロジェクト名
dataset_name = データセット名

::Parallel.each(table_names, in_threads: 30).each do |table_name|
  bq_client = ::Google::Cloud::Bigquery.new(project: project_name)
  dataset = bq_client.dataset(dataset_name)
  table = dataset.table(table_name)
  table.delete
end

これで、爆速になると思いプログラムを実行したところ、一瞬でCPU使用率が100%に張り付きました。
あまりにもこのプログラムがCPUを使うせいで、discordで通話中の相手から「やたらエコーかかっているんですが、風呂場で仕事してます?」と言われてしまいました。
discordはクライアントサイドで音声のノイズ除去などの信号処理をしているらしいというムダ知識が1つ増えました。

さて、あまりにもCPUを使いすぎるので、ちょっと気になってdtraceでシステムコール呼び出しの様子を調べてみたら、大量のforkが呼ばれていました。
どんなサブプロセスを生成しているのかを確認するために、top コマンドを実行したところ、なぜかRubyが大量のPythonを呼び出していることが分かりました。
CPUが100%になる原因はこれです。

では、google-cloud-rubyのどこコードがPythonを呼び出しているのでしょうか?
ソースコードを確認していたら見つかりました。

gcloud_json = IO.popen("#{gcloud} #{GCLOUD_CONFIG_COMMAND}", &:read)

このpopenメソッドの第一引数は部分は最終的に gcloud config config-helper --format json --verbosity noneになります。
gcloudコマンドはPythonで実装されているため、このpopenによってPythonが呼び出されていました。

この処理はGCPクライアントインスタンスの初期化をする際に呼ばれる処理で、システムデフォルトの認証情報を取得するものです。

::Google::Cloud::Bigquery.new をループの中で毎回呼び出す必要はなかったので、ループの外に出して1回だけ呼ばれるようにしました。

require 'google/cloud/bigquery'
require 'parallel'

table_names = [削除したいテーブル名の配列]
project_name = プロジェクト名
dataset_name = データセット名

bq_client = ::Google::Cloud::Bigquery.new(project: project_name)

::Parallel.each(table_names, in_threads: 30).each do |table_name|
  dataset = bq_client.dataset(dataset_name)
  table = dataset.table(table_name)
  table.delete
end

これにより、CPUが100%に張り付くことはなくなり、またテーブルの削除速度も大幅に向上しました。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?