2020/06/19

Azure Data Factory を使って、GCS から Azure Blob Storage へデータ転送

AzureのData Factoryを使って、Google Cloud StorageからAzure Blob Storageへデータを転送する方法です。

が、ほとんどの場合はMSの公式ドキュメント「Azure Data Factory を使用して Google Cloud Storage からデータをコピーする」で間に合っています。

ところが今回、GCSの特定のバケットにのみ閲覧権限を付与してデータを引っ張りたいと思ったら意外に苦労したので、その備忘録です。なお、当方はAzure初心者ですので、もしかしたら当たり前のことなのかもしれませんが、その辺は割り引いてください。

その前に、GCS。

GCSにて、とあるプロジェクトの中にたくさんバケットを持っている場合に、その中から選んで閲覧権限を付与したい場合があります。例えば、releaseバケットと、workバケットがあって、workは内部用として使っており、外部の人にはreleaseのみ閲覧権限を付与したいなど。

この場合、例えば次のどれかで可能です:
  • Google AccountをバケットのACLに登録する
    • これが一番簡単。閲覧権限を付与したい相手がGoogle Accountならば、それをバケット単位のアクセス制御リストに登録すればOK。Google Cloud Consoleのバケットの画面に「権限」タブがあるので、そこから登録できる。
  • Service Accountを作成して、アクセス条件をつける
    • Google IAMにてサービスアカウントを作成し、ストレージオブジェクト閲覧者のロールを付与。また、同時に「条件」にて、特定のバケット名を指定する。例えばバケット名が release ならば、resource.name == "projects/_/buckets/release" ||
      resource.name.startsWith("projects/_/buckets/release/") を条件に入れる。
  • ロールなしのService Accountを作成して、バケットのACLに登録する
    • IAMにて、Service Account作成の際にロールをなにも付与しないと、何もできないサービスアカウントができます。次にGCSのバケットの権限設定にて、そのサービスアカウントにストレージオブジェクト閲覧者の権限を付与すればよいです。
もっと他にもあるかもしれませんが。

さて、これらのアカウントをAzureから見えるようにします。

GCSのコンソールから「設定」「相互運用性」に行って、HMACキーを作成します。Google Accountならば一番下の「ユーザーアカウントのHMAC」から。サービスアカウントなら「サービスアカウントのHMAC」から。HMACキーを作ると、アクセスキーとシークレットがもらえるので、これを記録しておきます。


次にAzure Data Factory。ここもほぼ公式ドキュメントと一緒です。

Data Factoryを立ち上げて、「Manage」「Linked Service」「+New」でNew Linked Serviceが沢山出てくるので、その中から Google Cloud Storage (S3 API) を選択します。


すると、Access Key ID とSecret Access Key を入れる画面になるので、ここに先ほど作成したHMACのアクセスキーとシークレットを入れます。また、service URLにはHMACキーを作った場所に書いてあったリクエストエンドポイント「 https://storage.googleapis.com 」を書きます。名前は適宜、runtimeはそのままでOK。


記入したらCreateするのですが、ここで注意。

「Test Connection」は失敗しますが、気にしない。


ここがはまりポイントでした。Google Account(ユーザーアカウント)を使っている場合はここは成功するのですが、サービスアカウントを使っていて、かつ限定的な閲覧権限しか付与していないと、failします。ドキュメントには「storage.bucket.getとstorage.bucket.listがいるよ」と書いてあるのですが、読み取れるバケットを制限しているので、閲覧権限にそれらを付与してもうまくいきませんでした。(例えば、上の二つ目の方法、サービスアカウントを閲覧+bucket.get, bucket.listでロールを付与し、条件をつけなければ、ここがsuccessしますが、条件をつけるとNG。)

さて、Data Factoryでデータ転送するには、Azure Blob Storageの方もLinked Serviceに入れておかなきゃとのことで、+NewからAzure Blob Storageを選んで作成。

つぎに、一番左に戻って「Author」からDataSets→New Dataset→ Google Cloud Storage(S3 API) → Binary を選んで、Linked Serviceに先ほど作成したGCSのLinked Serviceを指定。これがデータソース。また、シンクとして同様にAzure Blob Storageも作ります。

DataSetsの下に先ほど作ったDatasetが現れるので、パスなどを指定。ここで、GCSのDatasetは、[Browse]をおしてもLoading Errorになります。先ほど、Test Connectionでエラーが出たのと同じ理由です。もし繋がっていると、ここでバケットの選択画面になるのですが、バケット一覧のアクセス権限がないのでダメなのだろうと思います。よって、Browseを押さずにFile Pathを埋めます。Containerの欄にバケット名を、Directoryのところにファイルパスを、Fileのところにファイル名を入れます。Directoryまで入っていれば、Fileは空欄でもワイルドカード扱いしてもらえるようです。

Blob Storageの方も同様に、Containerにコンテナ名をいれて、後は適当に指定します。

最後に、New Pipeline でパイプラインを作成して、Move And Transformから「Copy Data」をドロップ。選択すると出てくるメニュー画面から、SourceとSinkをそれぞれ先ほど作ったDatasetで指定して、「Publish All」し、Add Trigger → Trigger Nowで実行しますと、データがGCSからBlob Storageへ流し込まれます。

以上です。

ほぼ公式ドキュメントと同じなのですが、ポイントは「Connection Failed」でも気にしないというところでした。

2020/06/01

atcoder ABC169-Eをちゃんと考えてみた

表題の通りです。ややこしかったので、ちゃんと考えてみました。

問題は、次:
https://atcoder.jp/contests/abc169/tasks/abc169_e

 中央値の最小値、最大値がそれぞれ$\{A_i\}$の中央値と$\{B_i\}$の中央値であるのはすぐにわかるのですが、問題は、その間を全て埋め尽くすことができるかどうか。解説pdfでは、$x$を一個ずつ増やしていけば全部網羅できる、と、さらっと書いてあるのですけれど、$x$の可動域が小さい場合を想像すると、本当にそんなにうまく構成できるのかどうか、想像がつきにくいです。

まず、$N$が奇数の場合。

$\{A_i\}$のインデックスを並べ替えた$\{n_1,...,n_N\}$を、$A_{n_1} \le ... \le A_{n_N}$となるように作ります。同様に、$\{B_j\}$のインデックスを並べ替えた$\{m_1,...,m_N\}$も、$B_{m_1} \ge ... \ge B_{m_N}$となるように作成します。$\{n_i\}$と$\{m_j\}$は一般に異なる数列になります。

次に、集合$N_A, N_B$を、$N_A = \{ n_1, ... , n_{(N+1)/2}  \}$, $N_B = \{ m_1, ... , m_{(N+1)/2}  \}$とします。$\#N_A = \#N_B = (N+1)/2$です。

すると、上で示した中央値の最小値、最大値はそれぞれ$A_{n_{(N+1)/2}}$, $B_{m_{(N+1)/2}}$になります。ややこしいのでそれぞれ$A'$, $B'$とし、この区間を$I=[A',B']$とします。また、$A_i \le A'$ for all $i \in N_A$, $B_j \ge B'$ for all $j \in N_B$です。目標は、$I$内の任意の整数$z$を中央値として実現するような$\{x_1,...,x_N\}$を作ることです。

ここで、$N_A$と$N_B$とは一般に異なる集合なのですが、要素数が両方とも$(N+1)/2$であることから、$N_A \cap N_B$は空集合にはなりません。また、$k=\#(N_A \cap N_B)$、$k_A = \#(N_A \setminus N_B)$, $k_B = \#(N_B \setminus N_A)$とすると、$k_A=k_B$かつ$\#(\overline{N_A \cup N_B})=k-1$がわかります。

$N_A \setminus N_B$に入っている$k_A$個のインデックス$i$は区間$I$以下、$N_B \setminus N_A$に入っている$k_B$個のインデックス$j$は区間$I$以上で、かつ相手方に入っておらず、同数存在します。もし、これら以外のインデックスを用いて区間$I$内の任意の値を中央値として構成できれば、区間$I$の左右に同数の$x_i$, $x_j$をそれぞれ$x_i=A_i$, $x_j=B_j$として採用しても中央値は変わりません。つまり、この問題は$k_A = k_B = 0$として一般性を失いません。よって以下では、$k_A=k_B=0$, $N=2k-1$とします。

さて、$k_A=k_B=0$ならば$N_A = N_B$となり、また、この中に入っているインデックス$i$の全てについて、$A_i \le A'$, $B_i \ge B'$です。つまり、$i \in N_A = N_B$について、$[A_i,B_i] \supset I$です。$k$は少なくとも1以上なので、どれか一つの$i$を固定して、その$i$について$x_i = z$とします。この$z$が、実現したい中央値です。$N_A=N_B$以外のインデックスが$k-1$個あるので、それらを任意にとります。すると、$z$の左右には最大$k-1$個の$x_i$が配置されます。最後に、$N_A=N_B$にまだ$k-1$個のインデックス$j$が残っており、そのインデックスの区間は区間$I$を包含するので、$z$の左右のどちらにでも配置することができるため、両方の個数が等しくなるように$x_j$を採用すれば、$z$を中央値として実現することができました。

次に、$N$が偶数の場合はちょっとややこしいです。

$N$が偶数の場合、今回の中央値の定義は、中央を挟む二つの数の平均です。よって中央値の最小値$A'$、最大値$B'$はそれぞれ

$A' = \frac{A_{n_{N/2}} + A_{n_{N/2+1}}}{2}, B' = \frac{B_{m_{N/2}} + A_{m_{N/2+1}}}{2}$

となります。注意として、この値は整数/2の中で値を取ります。問題は、この区間$[A',B']$の間の全ての整数/2について、それを中央値とするような$\{x_1,...,x_N\}$を構成することです。

$N$:evenの場合は、$N_A = \{n_1,...,n_{N/2}\}$, $N_B = \{m_1,...,m_{N/2}\}$とします。インデックスの採用範囲が少し違います。すると、$i \in N_A$ならば$A_i \le A_{n_{N/2}} \le A'$、$j \in N_B$ならば$B_j \ge B_{m_{N/2}} \ge B'$です。

さらに同様に、$N_A \cap N_B$などを考え、$k, k_A, k_B$を作成すると、 $\#(\overline{N_A \cup N_B}) = k$になります。

まず$k = 0$の場合。$N_A \cap N_B = \emptyset$なので、$N_A + N_B =$全体になります。よって、$n_{N/2} \in N_A$, $n_{N/2+1} \in N_B$, $m_{N/2} \in N_B$, $m_{N/2+1} \in N_A$です。さらに、$A_{n_{N/2}}$は$A'$以下で一番大きい$A$ですし、$B_{m_{N/2}}$は$B'$以上で一番小さい$B$なので($n' = n_{N/2+1}$, $m' = m_{N/2+1}$として)

$A_{m'} \le A_{n_{N/2}} \le A' \le A_{n'} \le A_{m_{N/2}},$

$B_{n_{N/2}} \le B_{m'} \le B' \le B_{m_{N/2}} \le B_{n'}$

が成立しています。なお、$m'$と$n_{N/2}$, $n'$と$m_{N/2}$はそれぞれ等しいかもしれません。




 $A'$, $B'$はその定義からそれぞれ$A_{n_{N/2}}$と$A_{n'}$, $B_{m'}$と$B_{m_{N/2}}$の平均だったので、 $x_{m'} \in [A_{n_{N/2}},B_{m'}]$と$x_{n'} \in [A_{n'},B_{m_{N/2}}]$をとって、その平均$z$として$[A',B']$の中の任意の整数/2を作ることができます。(両方を最小にして$A'$, 最大にして$B'$ができ、間が繋がっているので、全ての整数/2を作成可能。)

$n' \in N_B$, $m' \in N_A$だったので、それ以外のインデックスは$N_A$, $N_B$ともに$N/2 -1$個ずつ残っており、かつ、$i \in N_A - {m'}$なら$A_i \le A_{n_{N/2}} \le x_{m'}$, $j \in N_B - {n'}$なら$x_{n'} \le B_{m_{N/2}} \le B_j$なので、$A_i$, $B_j$ともに中央値候補を構成する$x_{m'}$, $x_{n'}$の外側に同数配置できます。よって、$k = 0$の場合に$[A',B']$に含まれる任意の整数/2について、それを中央値とする$\{x_i\}$を構成することができました。

最後に$k > 0$の場合。$k > 0$なので、$I=[A_{n_{N/2}},B_{m_{N/2}}]$として、$[A_i,B_i] \supset I$なる$i$が$k$個、 $[A_j,B_j] \subset I$なる$j$が同数の$k$個あります。これらのインデックスを一旦全て取り除いて、$k=0$の状態にすると、中央値を構成できるので、そこにこれら$k$個ずつの要素を、次のように追加していきます。

 $[A_j,B_j] \subset I$なる$j$を一つ取ります。もし$[A_j,B_j]$が$[x_{m'},x_{n'}]$から左右どちらかにはみ出しているならば、そのはみ出している側に適当に$x_j$を置きます。これに対して、$i$を$[A_i,B_i] \supset I$の側から一つとって、この区間から$x_j$を、$[x_{m'},x_{n'}]$からはみ出して$x_i$とは反対側に置きます。この操作によって中央値を構成する$x_{m'}, x_{n'}$が中心にあることは変わらないので、中央値は変わりません。

また、もし $[A_j,B_j]$が$[x_{m'},x_{n'}]$の左右どちらにもはみ出していない場合、つまり、$x_j \in [x_{m'},x_{n'}]$でしかとれない場合でも、$x_i$は$[x_{m'},x_{n'}]$を含む大きい区間から任意に採用できるので、$x_i$との平均がちょうど$z$と一致するように$x_j$を採用することができます。よってこの場合でも、中央値を変えずに$x_i$, $x_j$を追加することができました。こうして$k > 0$の場合でも、この操作を$k$回繰り返して、中央値$z$をとる数列$\{x_i\}$を構成することができました。

以上。

と、できるだけ厳密に考えてみました。もちろん、もっと簡単に説明できるかもしれませんし、「一個ずつずらす」が明確にイメージできる方には冗長すぎると思いますが、ご参考まで。

2020/05/19

WSLのUbuntuとgccをアップグレードした(備忘録)

WSL(Windows Subsystem for Linux)をアップグレードしたので、その備忘録。

そもそもの発端は、atcoder.jp が主催する競技プログラミングの問題で最大公約数を算出する際の関数「gcd」が、c++17で加わったという話を聞いて、ずっとc++11を使ってきたが、そろそろ17にしようかなと思ったこと。競プロ用には、gcd は実装も容易で、自前ライブラリに入れてあるのでそれをコピペすれば十分なのですが、せっかく標準のがあるのならそれを使わない手はないし、それも含めて新しいライブラリを使えるようにしていこうと思って。

さて、私がatcoderに参加するときの手元の環境は、
  • エディタ = Visual Studio
  • コンパイラ = g++/WSL Ubuntu
  • テスト実行 = WSL Ubuntu Console
VSでコードを書き、そのコードをWSLコンソールにてg++でコンパイルしてテストを実行しています。以前はatcoder内のコードテストを使っていたのですが、コンパイルや実行が遅いことがたまにあって、デバッグで何度もコンパイルと実行を繰り返すならローカルの方が便利。VSの中でコンパイルから実行までできてもいいのですが、標準入力にデータを入れるのがちょっと面倒なので、WSLでコンパイルとテストをしています。

で、gcdが入っているコードを実行しようとしたところ、コンパイルエラーが出てしまい、調べたらgccのバージョンが古い。これをアップグレードしなければならないのだけれど、apt-getでは入ってこないし、ググると、gccだけアップグレードするのはなかなか大変そう。

だったら、Ubuntuごとアップグレードすれば?ということで、Microsoft Storeをのぞいてみたところ、Ubuntu 20が最新版で上がっていました。これをインストールし、gccもインストールしてバージョンを確認したところ、g++9まで上がっていました。もちろん、std=c++17も無事使えました。

ところで、このアップデート中にちょっと気になったところをいくつか備忘録で。

ひとつは、WSLのバージョンについて。

WSLにはWSL1とWSL2があって、WSL1とUbuntu20は相性が悪いよ、という記事(「(今はまだ)WSL1にUbuntu 20.04を入れるな」)を見つけたので、今使っているWSLのバージョンを確認しようとしたところ、いろんな記事で書かれている「PowerShellで、wsl -l -v」が実行できない。似たようなコマンドオプションは、「wsl -l」で、これによってインストールされているUbuntuが表示され、どちらが既定になっているかが表示されるものの、「wsl -l -v」の結果としていろんな記事で示されている、WSLのバージョンナンバーが出てこない。Windows Updateしろとか、PowerShellのアップデートをしろとかいろいろあったけれども、どれも不発。なんだろう?結局わからずじまいで、WSLのバージョンもわからず。作業中、PowerShellのバージョンを調べる方法も記事によってまちまちで、MSはこういうとこだぞ、と内心思いつつ。その点、Linuxは大体どれも同じような書式だったり、ググって出てきた記事がだいたいあてはまったり、オプションもだいたい似たような書式で、--version と書いておけばversionが見られたりするのでいいですね。まあ、普段はエンジン開発のテストくらいでしか使わないし(本チャンはクラウドのインスタンスを使うので)、あとは競プロなので、g++のコンパイルさえできればあまり問題がないかな・・・。

次に、Ubuntuの選択について。

Ubuntu20を入れると、前のUbuntuが消えるわけではなくて、別環境としてインストールされました。ので、旧Ubuntuの環境も引き続き残っています。ホームディレクトリなども別々なので、先のUbuntuでいろいろ整備した環境は、もう一度作り直す必要があります。といっても私の場合は、gcc, vim, screenくらいしか使わないのですが。

で、Shift+右クリックのコンテキストメニューに「Linuxシェルをここにひらく」があるのですが(注:人によってはこのメニューがない方もいらっしゃるそうで、めちゃめちゃ使いやすいのでぜひ入れた方がいいです。ただ、入れ方は知りません。)、これで立ち上がるUbuntuが、上のwslコマンドで現れた「既定」のUbuntuになりました。既定の変更は「wsl -s」で可能。

PS C:\Users\username\Desktop> wsl -l
Windows Subsystem for Linux ディストリビューション:
Ubuntu-20.04 (既定)
Ubuntu
PS C:\Users\username\Desktop>

こんな感じ。用途によって選んで使うと便利そうです。