digdag 手動実行時の session time、last_session_time

workflow 画面の 「RUN」をクリックした場合の挙動

2021-01-10 に実行したとする。

cron を使った場合

schedule:
  cron>: 10 1 * * *
1時10分より前に実行した場合
session_time = クリックした日時
last_session_time= 2021-01-09 01:10:00
1時10分より後に実行した場合
session_time = クリックした日時
last_session_time= 2021-01-10 01:10:00

daily を使った場合

schedule:
  daily>: 01:10:00

1/10に実行したら、何時に実行しても以下の通り。

session_time = クリックした日時
last_session_time= 2021-01-10 01:10:00

digdag schedule 実行時の session time、last_session_time

  • cron でスケジュールすると session_time = cron の時刻
  • daily でスケジュールすると session_time = 実行日の0時0分

hourly の場合も同様。


例えば、 2021-01-10 のバッチを考える。

cron を使った場合

schedule:
  cron>: 10 1 * * *

session_time      = 2021-01-10 01:10:00
last_session_time = 2021-01-09 01:10:00

daily を使った場合

schedule:
  daily>: 01:10:00

session_time      = 2021-01-10 00:00:00
last_session_time = 2021-01-09 00:00:00

なお、workflow の画面に Next Run TimeNext Schedule Time という項目があるが、 Next Run Time が次回の実行開始時刻で、Next Schedule Time がその際の session time のことのようだ。

ruby スクリプトから BigQuery の external table (google sheet) を SELECT できなくてハマった

Mac で自分の個人アカウントを使って ruby スクリプトの動作確認をしようとしたのだが、BigQuery の external table (google sheet) を SELECT するところで PERMISSION_DENIED が出て一晩ハマった。

「個人アカウントを使って」というのは
gcloud auth application-default login した上で ADC(Application Default Credential)で認証
という意味。

ruby 側で drive の oauth scope は付与しているのに動かない。
service account の鍵を使うと動いた。
具体的には、googleauth の ServiceAccountCredentials を使うやり方だと動いた。

よくよく確認してみると、gcloud auth application-default に --scopes オプションがあったので、

gcloud auth application-default login --scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/drive

を打ったら動くようになった。

よく分からないが、User Account を使う場合(Authorization Code)は、oauth scope は gcloud auth する際に指定しなければならないっぽい。
逆にこれやっておけば、 ruby 側で drive の scope 指定せずとも動いた。
ちなみに、bqコマンドに drive の scope を許可したい場合は、

gcloud auth login --enable-gdrive-access

とする。

gcp の認証方式についてはこちらの記事が詳しい。 medium.com

前にも同じようなことでハマった気がする...
GCP の認証ムズい...

なお、

gcloud auth application-default login --scopes=https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/accounts.reauth,https://www.googleapis.com/auth/drive

で指定している scope についてだが、
cloud-platform というのは、GCPのフルアクセスっぽい。
とりあえずこれつけとけばGCPAPIに弾かれることはなさそう。
drive というのは Google Drive, sheet, docs 等のAPIにアクセスするための scope 。

BQ で最新日付のパーティションを select したい

  • データベースのスナップショットを日次バッチでBQに load している。
  • x日の朝に、その時点のスナップショットをx-1日のパーティションに入れている。

というケースで、その時点でBQに入っている最新のスナップショットを select したい。
そして、最新のスナップショットを select するクエリを view として提供したい。

クエリ案1: 素直にサブクエリ書く
SELECT
  *
FROM
  `some_dataset.partitioned_table`
WHERE
  _PARTITIONDATE = (SELECT MAX(_PARTITIONDATE) FROM `some_dataset.partitioned_table`);

パーティションの絞り込みが行われず、フルスキャンになってしまう...

クエリ案2: scripting 使う

ritchiekotzen.hatenablog.com

パーティションを絞り込みつつ select できる。ただし、view にすることはできない。

クエリ案3: スナップショットが load される時間を見計らって参照先切り替え

朝6時までにはスナップショットの load が済んでいるとする。
→ x日朝6時以降は x-1日のパーティションを参照する。
→ 24 + 6 = 30。現在時刻の30時間前の日付のパーティションを参照する。

SELECT
  *
FROM
  `some_dataset.partitioned_table`
WHERE
  _PARTITIONDATE = ( DATE(TIMESTAMP_SUB(CURRENT_TIMESTAMP() , INTERVAL 30 HOUR), "Asia/Tokyo"));

ちゃんとパーティションの絞り込みしてくれる。ただし、load が6時より遅れた場合、クエリの結果がゼロ件になる。

クエリ案4: 案1を改良し2段階で絞り込み
WITH
  src AS (
  SELECT
    *,
    _PARTITIONDATE AS pdate,
  FROM
    `some_dataset.partitioned_table`
  WHERE
    _PARTITIONDATE >= DATE_SUB( CURRENT_DATE("Asia/Tokyo"), INTERVAL 2 DAY))

SELECT
  * EXCEPT(pdate)
FROM
  src
WHERE
  src.pdate = (
  SELECT
    MAX(pdate)
  FROM
    src)

最大2日分パーティションを読むことになるがフルスキャンにはならない。
load が1日以上遅れるとクエリの結果がゼロ件になる。

BQ: scripting を使ったクエリは view にできなかった...

「日付がもっとも新しいパーティションに対してクエリをかけたい」というケースがあった。

DECLARE pdate DATE;
SET pdate = (
  SELECT MAX(_PARTITIONDATE) FROM `some_dataset.partitioned_table`
);

SELECT
  *
FROM
  `some_dataset.partitioned_table`
WHERE
  _PARTITIONDATE = pdate;

みたいなクエリは動くのだが、view として保存しようとすると Only SELECT statements are allowed in view queries って怒られる...

BQで重複排除したviewを作る

ログをBQに突っ込むんだけど、レコードが重複して出力されてる可能性がある。
但し、レコード毎にユニークキーなキーが付与されてる。

という場合、以下のようなviewを作れば重複排除できる。

CREATE OR REPLACE VIEW
  `dataset.view_name` AS
WITH
  src AS (
  SELECT
    *,
    ROW_NUMBER() OVER (PARTITION BY timestamp, uniq_key ) AS row_num
  FROM
    `dataset.table_name`)
SELECT
  * EXCEPT(row_num)
FROM
  src
WHERE
  row_num = 1

元のテーブルが timestamp というカラムで partitioning されている前提。
window関数の PARTITION BY に timestamp を入れてるのがミソで、こうしておくと、view をクエリする際に timestamp で絞ればスキャン対象の partition を絞ることができる。

pythonの環境管理メンドイ...

しばらく触らないといろいろ忘れるし、ツールが乱立してて混乱しがち。

pyenv で3系をインストールしようとしたらpyexpat でエラー

この記事で紹介して下さってる方法で Command Line Tools for Xcodeのバージョンを上げ、 qiita.com コマンドラインを開き直した上で、この記事で紹介して下さってる方法で pyenv install したらインストールできた。 www.nozograph.com

環境管理ツール

production は「k8s使う」でよいが、手元でサクッと動作確認したり軽くスクリプト書く時などにどうするか?
いちいちコンテナ作って起動するのはメンドイ。
最近は、「python は手動で入れて、環境管理は venv」というのがおすすめらしい。 でも、python のバージョン管理はやっぱ pyenv 使ったほうが楽じゃない?(上の pyexpatのエラーみたいなはまり所はあるのかもしれないが...)

というわけで、pyenv はとりあえず使い続けようと思う。

ただ、virtualenv は venv か pipenv で置き換えてもいいのかもしれない。
venv は python 標準だが、いちいち activate 打たなきゃダメとかイマイチな面もあるらしい。
pipenv は便利らしいが、Pipfile とか独自形式ぽい。人によって使うツール違うとやはり互換性の問題が...
強いこだわりがなければ、おとなしく標準に従って venv 使っとくのがよいか?