Gitリポジトリ移行時にgit remote rm -> addとすべきでない理由
先日、とあるリポジトリをBitbucketからGitHubに移行させた。念のためプッシュしていない変更がない状態にしておき、リモートリポジトリを無事に移した。あとはローカルリポジトリで指定しているremoteを更新するだけだったのだが、その時以下のようにやってしまった。
$ git remote rm origin $ git remote add origin <移行先のリモートリポジトリのURL>
その場は何事もなかったが、その後しばらく開発作業を進めていると問題に直面した。 git pull
でリモートの変更を取り込もうとすると、以下のエラーが出て失敗するようになってしまった。
$ git pull There is no tracking information for the current branch. Please specify which branch you want to merge with. See git-pull(1) for details git pull <remote> <branch> If you wish to set tracking information for this branch you can do so with: git branch --set-upstream-to=origin/<branch> master
この原因は、remoteを消してまた作り直したことにある。 git pull
は引数なしで実行すると、カレントブランチが追跡しているリモートブランチの変更を取ってくる。このとき、remoteの名前が origin
であれば、ローカルの .git/refs/remotes/origin
以下に、各ブランチの最新のコミットを示すコミットオブジェクトが保存される。これが「リモート追跡ブランチ」と言われるものの実体である。
しかし今回、 git remote rm origin
をしたことで、 .git/refs/remotes/origin
以下を消してしまった。これにより、リモート追跡ブランチも全て消えてしまい、ローカルブランチがリモートのどのブランチを追跡しているかわからなくなってしまったのだ。現に git branch -vv
で確認すると、移行前まではローカルブランチは同名のリモートブランチを追跡していたのだが、移行後はその紐付きが全てなくなっていた。
もちろん git branch -u origin/<リモートブランチ名>
とすれば再び紐付けることができるが、各ブランチでそれをやるのは面倒くさい。最も楽な方法は、移行するときに以下のようにすることだ。
git remote set-url origin <移行先のリモートリポジトリのURL>
こうすればURLが変わるだけであり .git/refs/remotes/origin
は消えないので、移行後も問題なくリモートと通信することができる。
Data Pipeline Casual Talk Vol.3 に参加しました
会社の人に紹介されてイベントの存在を知りました。
普段は自社サービス(一種の分析ツール)のデータ処理・加工部分の機能開発を担当しており、大量のデータをアプリケーションから分析・表示できるようにすることがミッションです。最近では大規模データ処理のつらみが身にしみることも増えてきたため、他社ではデータ処理としてどんなことをしているのか、そもそも「データエンジニア」(データサイエンティストではない)とはどんな立ち位置の人なのかに興味があり参加しました。
データエンジニアとは?
このイベントにおける「データエンジニア」の定義は明確に定められているわけではありませんが、今回のトークは自分たちの事業が以下のような状況であることを前提とするものが多かったです。
- 日々大量のデータを扱っており、DWHには様々な形式の生データが雑多に蓄積されている
- 機械学習・ビッグデータ解析など、大量のデータに対して多様で複雑な分析をする必要のある業務が社内で行われている
- 上記のデータ分析を精度高く行い、その結果をもとにすばやく事業をカイゼンしていくことが重要視されている
- そのため、大量のデータを自由度高く活用できるように、データを自動で集計・指標化するワークフローを構築する必要がある
どう考えてもつらみが溜まりそうですね...うちの会社はここまでの状況ではないのですが、データを扱う仕事をしている以上、共感できるところが大きかったです。
この前提をふまえて、それぞれのトークを振り返ってみます。
Cloud Composerを半年運用してみて困ったこと
EurekaではCloud Composerを使って、Data WareHouseにあるデータを用途に応じて集計・指標化するワークフローを管理しています。
最初は個々の分析の処理内容ごとにDAGタスクをまとめた(例えば「つぶやきの分析」)のですが、各グループ内に複数の処理が含まれるため、
- 既存のグループを組み合わせて新しいワークフローを作るのがとても難しい
- 順番によってはデータに破壊的な影響を及ぼしてしまいうる
- DAGタスクの内容が理解しづらくなり、みんなコピペするように
といったことが起こったそうです。Cloud Composerは使ったことないけどこのつらみはわかる...
そのため、処理内容ではなく各タスクの関心事ごとにDAGを分離させる(例えばつぶやき分析なら、「既存テーブルを消す」「新しいテーブルを作る」「ツイートをロードする」など)ことで、それぞれのDAGを組み合わせやすくなりました。また、個々のファイルの内容は完全に使い回せるので、レビュー対象がワークフロー自体のみになりコードを見る必要がなくなったそうです。 以前Javaのバッチ処理で関数型インターフェースを扱ったことがあるのですが、正しく責務の分離が行われていれば個々のファイルのテストだけで全体が担保できるので理想的ですね。
Argo Workflowによる機械学習ワークフロー管理
リブセンスではMLシステムの中で、因子計算などのデータ解析にバッチ処理を使っています。さらにそのバッチ処理の中でも複数のアルゴリズムが組み合わされており、同じアルゴリズムを複数のバッチ処理で再利用していることも多いそうです。
しかし、構成が複雑化していったことで開発・運用に限界が生じ、単機能コンポーネントに分割することにしました。各コンポーネントはdocker run やkuberctl run だけで実行でき、各バッチ処理ごとの差分は設定ファイルで吸収するという形を取ったそうです。
そして並列化やリトライなどがある高度なワークフローの場合、Argo Workflowを利用しています。ワークフローはk8sから起動でき、またトリガーなどはなく実行に専念するシンプルな構成です。ドキュメントが少ないようですが、パラメータを渡せたり、ワークフローを分岐させられたりなどなかなか高機能そうでした。
自由と統制のバランス 共通分析基盤のアプローチ
今回最も面白かった発表です。「ユーザにとっての自由度という観点で、データ分析基盤をどう設計するか?」という観点が新鮮でした。
DeNAにはデータで意思決定をするという文化があるため、ユーザが分析システムにbashでログインしてスクリプトを書くことすら許容してきたのですが、会社の規模が大きくなるにつれ、文脈・用途不明の野良スクリプトや書きかけで放置された野良ファイルなどが溢れて混乱が生じてきたそうです。その一方で、AIやMLに取り組む事業の場合、専用環境を組むコストが高いため共通分析基盤をもっと自由に使いたいという声もありました。
そこで、自由と統制を両立するようなデータ分析基盤に作り直す大型プロジェクトが進行しています。まず統制としては、自分でバッチジョブやスクリプトを作りたい場合、社内GitHubと連携させてそこからDigdagタスクとして登録しない限り実行できない構成に変更。そしてスマートなのが自由を与える方法。digdagのタスクとしてk8s apiを叩いて別のコンテナを起動し、そのコンテナを開発環境にするというものでした。これは想像していなかったです。さらにGKEを使うことでリソースの要求に応じてnodeを増減させてくれるため、重い処理を回しても適切なリソース管理がなされて安心です。
コンテナがヘヴィユーザと分析基盤運用者の責任分界点になる という言葉がとても刺激的でした。
感想
データエンジニアの仕事の重要性がよくわかりました(語彙力)。皆さん「IT土方」という自虐的な言葉を使われていましたが、 いやこれは普通に経営課題レベルじゃないか...? と思うような規模の大きい取り組みの話が多く、とても興味が湧きました。
データ活用を推進する企業が増えるに従ってデータエンジニアの重要性は高まるので、この分野のコミュニティや情報発信がもっと盛り上がってほしいですね。
「プログラマが知るべき97のこと」を読み直した
「プログラマが知るべき97のこと」をGWから少しずつ読み直していた。実は昔一回一通り読んだのだが、それから公私でいろいろな経験を積んだためか、今回読み直してみたらまさに何度もハンマーで殴られる感覚を味わうことになった。
GWに「プログラマが知るべき97のこと」を読んでるみんな、どうか流し読みはせずに全部熟読してくれ。
— どもも (@Udomomo) 2019年5月2日
2年ぶりにこの本を読み直して、なんで2年前にもっと真剣に読まなかったんだと50回くらい後悔している私との約束だ。
↑読み直し始めた直後の時点でこんな感じ。その後もたくさん殴られた。
97個(+日本人エンジニアによる10個)もトピックがあり、人によって刺さる箇所はそれぞれ違うと思うが、自分に特に刺さったところをまとめてみる。
07 共有は慎重に
DRYの原則はとても重要だが、だからといって共通の部分は必ずまとめればいいとも限らない。大事なのは、まとめようとしている行が出てくる処理のコンテキストである。意味も文脈も全く異なるロジックの一部を共通化してしまうと、修正時の影響範囲がムダに広がるだけで終わってしまう。
本を読んでコーディングのコツをわかった気になると、こういうミスをしがち。
19 誰にとっての「利便性」か
コードを書くときに、簡潔さと読みやすさのバランスをどう取るか悩んでしまうことがある。そんなときに意識すべき考え方が「そのコードを使う側の立場に立つ」ということだ。自分にとって利便性が高いかではなく、そのコードについて全く知らず初めて使おうとする人が、使い方をすぐ理解し、誤った呼び出し方をしないようにするにはどうすればいいか、常に頭に置いて書くべき。
これは自分にとって良い判断基準になりそう。この項目を読んでから、このコーディングで良いのか?と見返すときの観点が変わった。
21 技術的例外とビジネス的例外を明確に区別する
今まで例外処理のベストプラクティスがよくわからず、なんとなく済ませてしまったこともあるのだが、この項目を読んで腹落ちした。
技術的例外は、例えばDBとの接続が切れたり、メソッドに渡した引数が正しくなかったりなど、そのままではアプリケーションの実行を続けられないような種類の例外である。このような場合、自分で例外を定義したり安易にcatchしたりせず、アプリケーションのトップレベルにある例外処理メカニズムに任せるべき。これに対し、ユーザが正しくない値を入力した場合など、ユーザの側に責任がある例外は、自分で例外をcatchして再び正常な動作に戻す方が良い。この例外処理は、できればクライアント側のコードで済ませておき、サーバには影響を与えない方が良い。
46 すべきことは常に明確に
最初から大目標に向かって走り出すのではなく、まずはそのために必要な小目標に分解してから作業を始める。 そして、途中で内容がずれていたり違う方向が良いと思ったら、それまで書いたコードを一旦破棄して考え直す。
この話はタスク積もりの話にも通じると思う。これができなかったために、目標を期日までに達成できなかったことも何度かあったため、今は特に気をつけている。
61 ビルドをおろそかにしない
仕事ではあまりインフラの業務は扱っておらず、また個人プロジェクトでもコードを書くことに目が行きがちで本番ビルドは適当にやってしまうことが多かった。しかし、最近自分の会社では開発・リリースサイクルを早めるために、ビルドプロセスの自動化・簡潔化が重要な課題になりつつあり、チーム全員がビルドについての知識を求められるようになっている。ビルドプロセスまで含めて自分たちで作る責任があるということを意識したい。
75 面倒でも自動化できることは自動化する
この本の中で、一番読んでほしい項目を1つ挙げろと言われたら、ここを推したい。
自動化が大事ということはみんなわかっていると思うけど、自動化するための仕組み作りには多少の時間と労力がつきものなので、「それだったら手作業で繰り返しやった方が速くない?」と思ってしまう人もいるだろう。また、そもそも単純作業を当たり前のものと思ってしまい、自動化するという発想が浮かばないこともあるかもしれない。
けれど、大事なのは「これってもっと楽なやり方ないのかな?」と思えるかどうか であり、それこそが「怠惰」の能力である。
もうすぐエンジニア3年目というタイミングで、ようやく「怠惰」の重要性に気づいてきた。「もっと楽なやり方はないか」という気持ちを持つようにしないと、いろいろな技術やツールの存在自体を知らないまま終わってしまいかねない。
— どもも (@Udomomo) 2019年3月22日
復習のつもりで軽い気持ちで読み始めたが、ここまでインパクトが大きいとは思わなかった。この本で語られている内容が頭ではわかっていても、まだまだ心に定着していないということなので、これからも読み返して精進していきたい。
アプリケーション作成の原則「The Twelve-Factor App」を読んだ
Dockerに関するウェブ記事の中にたまたま名前が出ており、気になって見てみたら、示唆に富み奥の深い資料だった。
この資料は、HerokuのエンジニアであったAdam Wiggins氏が、大小様々なSaaSアプリケーションのデプロイに接した経験をもとに、言語や技術によらないアプリケーション作成時の12の原則をまとめたものである。抽象的で少し読みにくいところもあるが、自分の経験を振り返っても共感できるところが多かったので、ここにまとめてみる。
I. コードベース
ここでいうコードベースは、リポジトリと考えて差し支えない。コードベースはアプリケーションと1対1であるべき。コードベースが複数あるとデプロイ手順が複雑になり、逆に1つのコードベースから複数アプリケーションをデプロイする場合、片方の保守がもう片方に与える影響を予測しにくい。大規模アプリケーションの場合、コンポーネントごとにリポジトリを分けることも多いだろう。
また、ビルド・リリース・実行の各環境へのデプロイは、同じ内容のコードベースがもとになっているべき。本番だけコードが少し変わっているというようなことはあってはいけない。
II. 依存関係
The Twelve-Factor Appでは、依存関係用のツールを2つに分類している。
- 依存関係宣言マニフェスト: 依存するパッケージの名前とバージョンを指定してインストールするためのもの。GenfileやPipなど。
- 依存関係分離ツール: そのプロジェクトの依存パッケージを、グローバル環境や他プロジェクトから独立した環境に置くことで、宣言マニフェストにない暗黙の依存関係の混入を防ぐためのもの。bundle execやvirtualenvなど。
この両方を使うことで、環境を問わず誰でも開発に参加しやすくなる。逆に、グローバル環境にあるシステムツールやコマンドへの依存はなるべく避ける。
個人的には、依存関係分離ツールは環境汚染による異常な挙動を防ぐためかと思っていたが、依存関係を漏らさないという観点はためになった。
III. 設定
DBの認証情報など、アプリケーション外のサービスと接続するための設定は、ベタ書きや設定ファイルではなく、環境変数に入れる。こうすることで、リポジトリにプッシュしてしまう心配もないうえ、各デプロイ環境ごとに値を簡単に切り替えられる。
IV. バックエンドサービス
DBやキューイングサービスなど、アプリケーション外にあるサービスは、ローカルのものでもネットワーク越しのものであっても、環境変数を変えるだけで異なるサービスに接続できるようにするべき。このためだけにコード修正が発生してはいけない。
疎結合の徹底と捉えるとわかりやすい。また、これを徹底すればひとつ前の項目3も自然と守られるだろう。
V. ビルド、リリース、実行
これらの3つの環境を明確に区別する。また、1でもあったように、全て同じコードベースをもとにしていなければならない。
リリースを簡単にロールバックできるツールがあるとなお良い。
VI. プロセス
アプリケーションの中では極力状態を持たない。状態はDBなど外部サービスに保管するよあにし、アプリケーション内部ではステートレスな処理を保つ。
これは身を持ってわかるが、状態があるとテストが大変になるだけでなく、整合性を保ったままリリースすることが難しい。開発者の苦労が倍どころではなくなる。
VII. ポートバインディング
アプリケーションの中にサーバを組み込むことで、環境にサーバを立てることなく、開放した特定のポートへのアクセスを待つだけで良くなる。こうすると、別のアプリケーションにとってのバックエンドサービスにもなれる。
最近は自分でサーバ設定をする機会はどんどん減っているような気がする。
VIII. 並行性
並行処理を行うアプリケーションの場合、状態を共有せず水平に分割されたプロセスが理想である。これなら簡単にプロセスを増やせるうえ、プロセスの管理も容易になる。
IX. 廃棄容易性
短時間で起動し、gracefulにシャットダウンできるようにするべき。ここでのgracefulとは、SIGTERMを受け取ったら、停止する前に各プロセスでのリクエスト受け付けを止めたり、レコードのロックを解放したりする処理である。
また、万が一突然落ちてしまっても、データが失われたり重複したりしない設計を考えなければいけない。
X. 開発/本番一致
開発環境と本番環境の差が小さいほど、デプロイが容易になる。このため、小さな変更を自動で本番環境に反映できる仕組みが望ましい。
また、開発環境と本番環境で異なるバックエンドサービスを使わない。環境の差異は想定外の挙動を生みやすく、原因の特定も難しい。最近ではパッケージ管理ツールやDockerがあるので、本番と同じ環境を開発環境でも十分に作れる。
XI. ログ
ログはファイルではなく標準出力にする。そしてそのログはlogstashなどのログルーターによって集積される。このとき、ログルーターもバックエンドサービスの1つなので、アプリケーションは設定を感知しない。
ログファイルの方が手軽なようにも見えるが、ログルーターを使うとログの監視・集約を効率的に行える。また、標準出力ならどんなOSでもできるので、実行環境を移す際も手間がない。
XII. 管理プロセス
DBのマイグレーションなど、1回限りの管理タスクであっても、アプリケーションと同じコードベース・設定で管理する。コードベースを共通にすることで、オペレーションミスを減らすことができる。
また、REPLを提供している言語であれば、開発時や本番環境の調査時に管理スクリプトを簡単に実行できる。とはいえ、本番環境での通常の管理スクリプトは可能な限り自動化する方が良い。
今までなんとなくこれが良いんだろうなと思っていたことがしっかり言語化されており、とても参考になった。このような指針があれば、自分で作る小さなアプリケーションでも取り入れやすいかもしれない。
【Rails】RSpecで特定のテストケースだけbeforeをスキップする
RSpecでは、before do ... end
を使うことで、各テストケースの前に共通して行う処理を記述できる。これを使ってテストデータを投入する処理などを書いておくと、テストケースが書きやすくなる。
しかし、例えばデータが1件もない状態での挙動をテストしたいときなど、一部のケースではbeforeをスキップしたいという場合もある。そのための直接の機能はRSpecにはないが、以下のようにすると実現させることができる。
describe 'test_case' do before do |example| unless example.metadata[:skip_before] ... end end context 'condition A' do it 'needs before procedure' do # beforeが実行される ... end it 'not need before procedure', :skip_before do # beforeがスキップされる .... end end
example.metadata
は、任意のexample(=it)やexample group(=context)に対してつけられるメタデータ。これはRSpecの機能の一つであり、今回のケースだけでなく様々な用途に使える。
上の例では、2つ目のitに :skip_before
という名前でメタデータを付与した(名前は何でもよい)。本来はハッシュに対応する値も付与するのだが、True/Falseでメタデータの存在確認をするだけなら値は必要ない。このようなメタデータの指定方法は、以下のドキュメントに記載されている。
relishapp.com
そしてbeforeを実行する際に、:skip_before
というメタデータがあれば処理を抜けるようにすることで、特定のテストケースだけスキップさせることができる。もちろん、あまりにも多くのテストケースでスキップさせるようであればむしろbeforeを使うのをやめるべきだが、一部の特殊ケースをテストさせたい時には手軽な方法だろう。
【Rails】Strong Parametersにおけるrequireとpermitの違い
RailsでStrong Parametersを使ったパラメータの受け渡しをするとき、よく params.require(:task).permit(:title, :content)
のように書く。このとき、require
と permit
はどう異なり、なぜ両方使うのかがよくわからなかった。
公式にあたってみると、APIドキュメントに記載があった。
requireはひとつのキーを指定し、そのキーに値があるか、あるいは値がfalseだった場合に、その値をそのまま返す。
一方permitは、特定のキーを指定し、そのkey-valueの組のパラメータだけを通す。返り値は ActionController:Parameter
オブジェクトであり、同時に permitted
フラグが true
になる。
また、指定したキーの値がハッシュである場合は、そのハッシュの中のキーを個別に指定しなければpermitを通れない。
上のリンクの最後の例からもわかるように、やろうと思えばpermitのみで任意の組合せのパラメータを通すことができる。しかし、公式ドキュメントを含め、ほとんどのコード例では require
で絞った後に permit
でパラメータを通している。
RailsでのPOSTリクエストは、あるModelのインスタンスオブジェクトを追加・更新・削除するという処理がほとんどである。そのため、require
でそのインスタンスオブジェクトに対応するキーのみに絞り、その中の各パラメータから必要なものを permit
で選ぶというのがセオリーになっているようだ。
逆に、 require
だけではパラメータを拾いきれない!という場合、Controller・Modelの設計がセオリーから外れている可能性がある。POSTリクエストで使うパラメータは、操作しようとしているインスタンスオブジェクトと何かしらの関係があるはずであり、それならばハッシュ形式にしてそれを一つのキーの値にすることができるはずだからだ。
RailsのActiveSupport:Concernの役割
Railsで共通部分をmoduleに切り出し、Concernを使う機会があったが、そもそもRubyであまりmoduleを使ったことがなく、Concermが通常のmodule機能と比べてどう特殊なのかわからなかった。この機会におさらいしてみる。
Rubyのmoduleの使い方
まずはRails以前の、Rubyでのmoduleについて。moduleに定義した変数は、moduleの名前とともに指定すれば使うことができる。
mod Constant NUMBER = 5 end class Hoge a = Constant::NUMBER # 5 end
しかし、メソッドはこのやり方では使えない。moduleで定義したメソッドを使うためには、includeをする必要がある。includeとは、そのmodule内で定義されたメソッドや変数を引き継ぐことであり、mix-inとして使うことができる。
mod Constant def plus_one(num) num + 1 end end class Hoge include Constant a = plus_one(4) # 5 end
includeされたメソッドは、そのクラスのインスタンスメソッドとして使うことができる。
Concernでクラスメソッドを楽に使う
ただし、上記のincludeができるのはインスタンスメソッドのみであり、クラスメソッドをmoduleで定義して外から使うためには複雑な記法を使わなければならない。とはいえRailsの場合、データ取得の際などにクラスメソッドを使う機会が多い。
そこでConcernを使うことで、クラスメソッドをmodule化しやすくなる。
module M extend ActiveSupport::Concern included do scope :disabled, -> { where(disabled: true) } end class_methods do ... end end
上記のscopeは当然クラスメソッドだが、 included do ... end
の中に書くだけになっている。あとはこのmoduleを対象クラスにincludeすれば、そのクラス内で定義したscopeと同じように、 :disabled
のスコープを使えるようになる。
Railsは様々な独自記法があると聞いていたが、こんな形であるとは知らなかった。Railsしか知らないとどれが独自の書き方がわからなさそうなので、Rubyの文法と比べる習慣をつけていきたい。