1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

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

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%に張り付くことはなくなり、またテーブルの削除速度も大幅に向上しました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?