Leon's Blogging

Coding blogging for hackers.

GCP BigQuery With API

| Comments

最近剛好碰到 BigQuery,可以直接透過 API 帶 sql 指令去拉資料!

安裝

1
2
# Gemfile
gem 'google-cloud-bigquery'
1
bundle

安 gcp SDK

1
2
3
4
5
6
7
8
9
10
# 安裝
curl https://sdk.cloud.google.com | bash
# Restart your shell
exec -l $SHELL
# 記得在 google 那邊要先開權限,才可以指定到自己要的 project
gcloud init
# auth,但最好的話是要申請一個 憑證,並設定在環境變數(environmentvalue)
gcloud auth application-default login
# 取得憑證
gcloud auth application-default print-access-token

執行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require "google/cloud/bigquery"

# This uses Application Default Credentials to authenticate.
# @see https://cloud.google.com/bigquery/docs/authentication/getting-started
bigquery = Google::Cloud::Bigquery.new(project: "project_id")

sql     = "SELECT " +
          "CONCAT('https://stackoverflow.com/questions/', " +
          "       CAST(id as STRING)) as url, view_count " +
          "FROM `bigquery-public-data.stackoverflow.posts_questions` " +
          "WHERE tags like '%google-bigquery%' " +
          "ORDER BY view_count DESC LIMIT 10"
results = bigquery.query sql

results.each do |row|
  puts "#{row[:url]}: #{row[:view_count]} views"
end

問題

在執行上有遇到一些問題,ruby 用 query 第一次去打的時候,回來的值卻會是空 Array,第二次就會有值

可以看到下面 job_complete 也是 false,第二次打就會是 true

1
2
3
4
5
6
7
8
9
10
11
12
13
bigquery.query(sql)

# Sending HTTP post https://www.googleapis.com/bigquery/v2/projects/project_id/queries?
# 200
# #<Hurley::Response POST https://www.googleapis.com/bigquery/v2/projects/project_id/queries == 200 (184 bytes) 11197ms>
# Success - #<Google::Apis::BigqueryV2::QueryResponse:0x007fa1021033f8
#  @job_complete=false,
#  @job_reference=
#   #<Google::Apis::BigqueryV2::JobReference:0x007fa102102318
#    @job_id="job_0HlGAo3KB4WrAGz20MypIHluup9B",
#    @project_id="project_id">,
#  @kind="bigquery#queryResponse">
#  => []

原因

Google 回覆

job_complete=false 代表這個 bigquery job 還沒跑完.

在 bigquery 下 query 的時候, 執行 query 的 API call 會有一個 timeout, 當 API call 執行的時間超過這個 timeout 但是 query 還沒跑完的時候, API call 會 return HTTP code 200, 但是 query result 是空的, 並且 job_complete = false.

詳細可以參考文件[1]一開頭 “Runs a BigQuery SQL query and returns results if the query completes within a specified timeout.” 以及同一份文件下方的 timeoutMs 以及 jobComplete 這些參數.

如前所述, 如果收到 job_complete = false 的 HTTP code 200 response, 表示 job 還在執行. 這時候的標準做法是去 poll GetQueryResults 這個 API (參考文件[2]) 直到 job_complete = true 再拿取結果.

解決方式

在 new 的時候,新增 timeout 時間(也可以設定 retries)

1
Google::Cloud::Bigquery.new(project: project_id, timeout: 120, retries: 10)

但實際測試加上 timeout 卻好像沒有用..

在試的時候,有用另外的方式去解決,自己去處理 job 就可以確保 job 跑完,再拿資料回來

1
2
3
4
5
6
# 先設定 job
bigquery.query_job(sql)
# 再讓它去執行 wait_until_done!
job.wait_until_done!
# 最後再將結果回傳,這樣就可以確保第一次可以拉到值
job.query_results

但去看原始碼,實際上也是做一樣的動作..

因該是內建的 query 有設定 10 秒就會 response,因此改直接去執行裡面的動作,就沒有這個限制

1
2
3
4
5
6
7
8
9
def query query, params: nil, external: nil, max: nil, cache: true,
          standard_sql: nil, legacy_sql: nil, &block
  job = query_job query, params: params, external: external,
                  cache: cache, standard_sql: standard_sql,
                  legacy_sql: legacy_sql, &block
  job.wait_until_done!
  ensure_job_succeeded! job
  job.data max: max
end

參考文件

Auth

Comments