パターンマッチングマン

この正月休みにルービックキューブを久しぶりにやったら、終盤の手順を忘れていて完成目前で失敗してしまった。 何度かやって、急がずにやればふたたび3分程度で解けるレベルに戻ったようだ。

昔、友人に「ルービックキューブの解放なんてパターンを覚えるだけだよ?」と言われ「やり方を覚えても自分でその解法に辿りつけないなら、そのパターンを覚えてもその人の知力の証明にならない」と意地を張っていたことを思い出した。 結局、自分の力では全面揃えることなんてできなかったのにもかかわらず。

しかし去年何人かの優秀だと感じる知り合いがルービックキューブをスラスラと解くのを見た時に考えを変えることにした。

自分で考えている、考えていない、その解法の意味を理解している、理解していないは別にしてまずはできるようにならないと勝負にならない。 勝負できるような段階になってから、きちんと理解していけばいいのだろう。

序盤の手順については、理屈もわかって自分でアレンジしながら説くことができるようになってきたが、最終盤の手順はまだ「局面Aのときに手順Xを行うと解ける(ブロックがどう動いているかはよくわからない)」という状態のままだ。 それでも、「ルービックキューブぐらい3分もあればどこからでも全面揃えられるよ」とかいうと、その分野について何も知らない人は「へぇー」と反応してくれるので多少自尊心がくすぐられる。

勉強でもプログラミングでも何でも、ある一定のレベルまではパターンマッチングで到達できるという話があった気がするし、達人はそもそも右脳によるパターンマッチングで状況判断をしているという話もある。

実は最後まで自分で考えることって大事だと思っていたけど、そうではないのかもなという(自分にとって)斬新な価値観が頭をもたげる。

「この局面、実際にはどのような構造になっていてベスト解は何か…?」みたいなのは会議でもプログラミングでもルービックキューブでもよくある戸惑い。 自分はそこでいつも「詳細まで理解してないのに、自分自身の頭で考え抜いていないのに結論を出して動いてしまうのは不誠実ではないかな」と立ち止まってしまう。

けれど、自分が立ち止まっている間にも優秀な人は(きっといくつかの失敗もしながらも)パターンマッチを駆使しながら前進していっている。

今年は自分もパターンマッチングマンとして、深く考えずに、世間の定石に乗りながら、生きてもいいのかも。

と、特に理由もなく感じている。

マルチテナントアーキテクチャについて

SaaSシステムを開発しているみなさま、お元気でしょうか。

SaaSシステムというといわゆるWebサービスよりももう少しBtoBの雰囲気が漂ってまいります。

SaaSシステムでは契約者(ここではテナントと呼ぶ)が複数いて、テナント毎に複数のログインユーザーやロールが存在するのが一般的です。そして当然ながらテナント毎のデータは漏洩・混濁が許されない高いセキュリティが求められます。

SaaSシステムの構築はスケーラビリティにおいても100テナント程度から始まりゆくゆくは数千、数万テナントまで少なくとも線形にスケールするアーキテクチャを開発当初から求められ、さらに突発的な大規模テナントも問題なく吸収したいという要求があります。

その要求を満たす設計・開発・保守・運用をやっていくのは当然ながら簡単ではありません。

というわけで今日はマルチテナントアーキテクチャのお話です。

世に出る情報がとっても少ない

マルチテナントアーキテクチャはBtoB領域で必要とされることが多い構成のため、設計に関する概要からその詳細に至るまで各社あまり表に出したりしません。 情報を積極的に出している企業は海外ではSalesForce、国内ではサイボウズといったところでしょうか。

さくっと検索すると以下のような情報がてにはいります。

SalesForceのマルチテナントアーキテクチャ。老舗の実績あるアーキテクチャ

www.publickey1.jp

サイボウズクラウド基盤の解説。サービスセットと呼ばれる概念を導入している。

ascii.jp

手前味噌ですが私の過去記事でも、とあるマルチテナントサービスにおけるアーキテクチャの一例をうかがい知ることができるでしょう。

www.techscore.com

日本語で読めるマルチテナントアーキテクチャの分類やその中で考慮すべき項目を整理した記事として、IBM developerworks のこの記事は外せないでしょう。

Web アプリケーションをマルチテナント型 SaaS ソリューションに変換する

特にマルチテナントアーキテクチャの分離レベルとして以下の3つに分類できるというのは覚えておくとよいです。(以下引用)

  1. クラウド内での単純な仮想化により、ハードウェアのみを共有
  2. 1つのアプリケーションで、テナントごとに異なるデータベースを使用
  3. 1つのアプリケーションでデータベースを共有 (最も効率的な真のマルチテナンシー)

https://www.ibm.com/developerworks/jp/cloud/library/cl-multitenantsaas/figure0.gif

Salesforceは3のタイプを採用しています。

私の経験したマルチテナントアーキテクチャ

私自身も今まで数度のマルチテナントアーキテクチャによるシステムの構築や他企業のアーキテクチャの実装の調査をしてきましたが、追求していくとなかなかおもしろいものです。

私が経験したマルチテナントアーキテクチャはオーソドックスな2です。

顧客データを全て一つのDBに混ぜてしまうのはデータ混濁のリスクが怖いのでDBの機能・ユーザーでデータを分離してしまうパターンです。

また、世のOSSのWebフレームワーク、DBフレームワーク(含むORマッパ)はたいていシングルテナント前提に作られていますから、OSSの恩恵を受けつつマルチテナントを実現しようとするとだいたいこの選択になるかと思います。

Salesforceのマルチテナントアーキテクチャ

Salesforce複数テナントを1DBにストアするアーキテクチャを選択したというのは、アプリケーションレイヤでのテナントのデータ分離について非常に強力なフレームワークを自社で構築しているからこそ選択できたのではないかなぁと思います。 また、開発時期的にOracleで大量のスキーマを作るのは現実的ではなかったという時代背景があるのかも知れません。

アプリケーションレイヤでテナントのデータを分離すると、多数のテナントに少ないリソースでサービス提供できるとよく言われますね。

IBMの記事でも「最も効率的な真のマルチテナンシー」などと言われちゃってます。

開発時の考慮すべき点

さて、私がマルチテナントシステムの構築にあたって、こうすべきだなと思っていることについてつらつらと書いていきます。 ベースにしているマルチテナントアーキテクチャの実現手法は2(テナント毎にデータベースを分離)です。

テナント毎にドメインを分け、ログイン画面も分けよう

URLのドメインはテナント毎にできる限り分けるようにしましょう。

同一ドメインだとCookieに保存するセッションIDのキー名が同じだと他のテナントと混ざってはよくありません。 お客様は複数契約してくださって、テナントAとテナントBに同時にログインしたいと思うかもしれません。

ワイルドカード証明書で *.example.com を取っておいて tennant1.example.com, tennant2.example.com などとするのがよいでしょう。

特定テナントだけ全く別のドメインにしやすいとか、JavaScriptのセキュリティだとか、特定クライアントだけ別基盤に振るとか、IPアドレス制限とかそういうのもやりやすくなります。 一番メリットとして大きいのはDBを引かずともどのテナントからのアクセスか区別できるので障害調査やアプリレイヤのFWが作りやすくなる事でしょう。

歴史のあるSaaSシステムだと時期的にそういうことを考慮して構築できなかった場合もありますが、今から構築するなら分けたほうが苦労が少ないでしょう。

DBの接続先の切り替えはユーザーのリクエストを処理する最初の段階でフレームワークレベルで行う

タイトルが全てですね。

アプリケーションレイヤで開発するときに、自分がどのテナントのデータを処理しているか、どのDBに接続しているかを意識しないといけないとなると開発者のうっかりミスでデータの混濁が発生する可能性がとっても高くなります。

マルチテナントアーキテクチャの方式設計をする際は、慎重なアーキテクトで居るよう心がけましょう。開発者が意識しなくてもデータが混濁しないフレームワークを作りあげることがとても大切です。

また、フレームワーク設計時にはリクエスト完了時にはそのコネクションを忘れず close しておくのが安心です。 なぜかコネクション切り替えがうまく行かず、前回のリクエスト時に利用したコネクションを使いまわしたりすると困ったことになります。

コネクション確立コストの高いRDBMSではコネクションプールを用意しよう

PostgreSQLのことです。MySQLではそういうことを考えずとも十分コネクション確立コストが低いですが、PostgreSQLはコネクション確立コストが高いためコネクションプールを導入しないとアクセス数の多いサービスでは耐えられません。

しかしマルチテナント対応したコネクションプールというのはさほど多くありません。マルチテナント対応したコネクションプールとは、1テナント(スキーマ)あたり最大30コネクション、コネクションプール全体で500コネクションといった制限のかけられるコネクションプールの事です。

Javaではこのような要件に対してマッチするコネクションプール製品は残念ながらありません。 Javaのコネクションプーリング製品とは CommonsDBCP, HikariCP, TomcatCP, C3CP 等の事です。 私の調査では外部コネクションプール製品の pgPool2 などはこの要件を満たせず、最終的に pgbouncer がいいだろうという結論になりました。

複雑なクエリを発行する BtoB システムを作るなら PostgreSQL を使いたいところですが、コネクションプール周りは悩ましくなる事が増えるのでよく考えましょう。

テナントのデータを処理しているログにはテナントを特定できるキーが出るようにしよう

これは、まともに運用しようという意志があれば当然思いつくことですね。

テナント毎のコネクションを確立するのと同じタイミングでリクエストスコープの変数に入れてログ出力時に必ず出力されるようにしましょう。 JavaのLoggerであればMDCなどを用いて実現するのがよいでしょう。

ログインについてはSSOの仕組みをなるべく備えておこう

自社サービスとしてマルチテナントサービスを開発する際、余力があるのであればSSOの仕組みは最初から考慮して構築することにしましょう。

特にあなたの会社がマルチテナントサービスを複数提供する予定があるのであればこれはやっておいたほうが良いことです。

ある会社があなたのサービスAとサービスBを契約したいと考えた時、それぞれのログインID、パスワードを発行し、利用ユーザー情報を登録する手間を考えてみましょう。ゲンナリですね。

世にある様々な ActiveDirectory, LDAP, OpenID Connect, SAML などなどへの対応はサービスが成功してからでも構わないと思いますが、とにかくアカウント情報は一箇所にまとめるのがおすすめです。

このあたり、ヌーラボさんがサービスが複数の製品群を普及後にSSOのローンチに成功させましたが、良き知見・見解などがありそうで、一度そのあたりのお話を詳しくお聞きしてみたいものです。

クォータ・スロットリング

さて、マルチテナントの運用で起きる辛いことは「ある特定のテナントが多くのリソースを利用するために、他のテナントが割を食う」ということです。

で、公平性をなるべく高めようとするとクォータ・スロットリングなどの仕組みをマルチテナントシステムに組み込みたいという解決策に惹かれることになります。

しかし、実際のところ IaaS などのような計算資源を直接提供するならいざ知らず、SaaSでそのような制限を加えるのは実際ツラミが多い&知見少ないので、テナント毎のリソース使用を計測だけできるようにして最初は運用サポートでもいいんじゃないかと思っています。

SalesForce はそこのところはちゃんとガバナ制限という形で用意されており、さすがは老舗だなぁという感はありますね。

疲れてきたので今日はここまで

この記事は本来GWに描き上げる予定だったのですが、書き終わらなかったので放置されていたものです。

先日の勉強会で「マルチテナントアーキテクチャ、最高に面白いのでみんなと話したい」と言ったのものの、面白さが伝わってないとつまらないので取り急ぎでも公開してしまおうということで公開することにしました。

他にも以下のような事を紹介したいと思っていましたが、これは今後「マルチテナントアーキテクチャ大好き」な仲間ができてからということにしようと思います。

  • 過負荷時の時に特定ドメインのリクエストだけ別サーバーにふれるようにしておこう
  • DBのマイグレーションはテナント数だけかかるので作業時間の見積もり困難。無停止でリリースするならなるべく今あるテーブルに影響を及ぼさないよう別テーブルにしよう。
  • テナントのデータはDBインスタンスへの依存を減らし、特定テナントだけ別DBインスタンスに移動することも可能にしておこう(大規模顧客対応)
  • 課金対象項目の計測の仕組みを考えておく
  • 休止、解約、メンテナンス、縮退など正常稼働時以外の状態を設計する

みんなで最高のマルチテナントアーキテクチャの知見を共有し、障害のないSaaS型業務システムが世を席巻する世界を目指そう!

続・Web系の自分が想像と障害で学んだバッチ処理・設計の基本

バッチ処理というのがそれ単体で勉強するのが難しく勉強しようとすると何に手を付けるべきかさっぱりわからないということは、先日のブログで述べたとおり。 自分が経験の中で得てきた知見は正しいのかどうか、世間の人に見てもらいたかったというのが書いた動機。 そして、新たな視点や指摘をゲットしてより不測の事態を考慮できている最高なバッチを作りたいという目的があったわけだ。

で、いろいろな意見をもらったのだけどその中で特に辛いと感じたのはこれ。

読んでいると 「俺達は障害でつらい思いをしてるし当然先人達も障害でつらい思いをしているはずだ。 なのに、なにゆえ未だに知識が体系化されてカジュアルに学べない上にそれを知っているだけで飯が食えるとまで言える状況なのか。 そんなの間違ってる、俺達がそれを体系化していくべきなのではないか…!」 みたいな熱めの感情が当然湧くわけだ。

ちなみにそのコメントを読んだボスの感想はこれ。

ボスは主戦場がJSの人だからね〜。

それはさておき、書くべきだった(事故った経験があるのにまとめ忘れていた)ものや抜けていた視点というのもいくつかあったので、それらについても想像力を巡らせてノウハウとしてまとめておくことにしましょう。

テクニック

実行中の進捗状況を確認できるようにする

データのインポート処理、エクスポート処理、集計処理、なんであれ時間のかかるバッチ処理というものは時に異常に時間がかかることがある。 たとえば出社したらアラートが飛んでおり日時バッチがさっぱり終わっていないことがわかったとしよう。 障害なのはわかったが遅延の状況を知りたいということがある。

軽度の遅延であり、すでに90%ほど終わっているのか。(よかった、待てば終わるのでその間に障害報告書を書こう) あるいはまだ10%しか終わっていないし1%進捗するのにO(n2)で時間がかかるようになっているのか。(残念、終わる見込みはない。ジョブをkillして対策を練るプロジェクトチームを結成しよう。 ソルジャーではないそのあたりでまごついている人に状況を説明し、障害報告書を書いてもらおう)

実行する処理のフェイズやデータ量に対して何%まで処理が進んでいるのかをログにつどつど記録しておけば、障害時にそれを元に判断を下せる。 不幸にもログがなくて判断するための材料がない場合は、モニターと上司と同僚の顔を交互に見つめて誰か声を発するのを待つぐらいしかできない。

ネタ元

Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

実行中の進捗状況を確認できるようにする、を加えて欲しい

2016/02/18 17:18
b.hatena.ne.jp

異常データ、異常動作のアラート

バッチ処理というのはコマンド起動であれ、デーモンであれ、運用時の監視やアラート設定から漏れてしまうことが多い。 そして監視設定から抜けてしまうと、これはユーザーのインタラクションに直接紐付かない場合、誰も気が付かないまま時が過ぎてしまうことがある。 「このマイナー機能の月次処理なんですが、ここ数ヶ月レポートが更新されていないのですが」 みたいな問い合わせが来たりする。当然俺達はマヌケでありこの集計は冪等に実装されていないので直近1ヶ月の集計しかできない。障害報告書だ。

なので、異常データが来たり、異常動作が来た時は速やかにアラートメールなりなんなりで運用管理者に知らせないといけないし、 「実行されているべき時間帯にきちんと正常起動して正常終了した」ということをログから監視できるようにしたうえで、監視を設定してもらう必要がある。

ネタ元

Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

異常動作したときのあらーとも欲しいかなぁ? どれだけ事前に網羅したつもりになっても予期せぬでーたが飛んでくるし,気づくの遅くなるとその分めんどくなるのです(ーωー;【みかん

2016/02/18 12:42
b.hatena.ne.jp

Dry Runオプションの用意

バッチ処理を実装する際は「処理対象範囲を引数で指定可能にする」ようにしたほうがよいと書いた。 では、実際に障害発生時にバッチを再実行しようという時、あなたはオペミス無しに一発で実行できるだろうか? 引数として与える対象日時を計算しているときに同僚が「障害報告書の雛形どこにある?」とか聞いてくるわけなので、当然計算はミスっている。

いざ、本番で再集計を実行しよう。という直前に、Dry Run オプションで対象処理範囲や本番データに影響を及ぼさない範囲で一部の処理を実行してオペレーションの妥当性を確認できるというのはバッチ処理にとってとっても重要なことなので余裕があれば ぜひとも実装しよう。

ネタ元

Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

dry-runあると助かる

2016/02/18 10:15
b.hatena.ne.jp

リカバリー手順を用意する。リカバリー可能な実装にする

「可能であれば冪等に実装する」に似ているが、ちょっと意味が違う。 バッチ処理はマスタなどの重要なデータ変更に直結している部分なので、当然ながら慎重にならなければいけない部分だ。

失敗時に「マスタデータ全部壊れましたワァ。戻りませんワァ」とか言ってると、こういう場合は障害報告書だけでは済まない。 ボスがボスのボスと雁首揃えて顧客に謝罪と迅速な対応と恒久対策の約束をし、ちょっと無理な要件とかもねじ込まれて帰ってくることになる。

と、いうわけでバッチ処理の実行前にマスタ情報などを別の場所にコピーして、バッチ処理が壊れていた時にもリカバリーできるようにしておくと良い。 そして、コピーするときはコピーした時の引数や時刻情報も付与して何世代か保持しておくとより安心だ。 壊れているバッチを二回実行して、コピーに壊れたマスタ情報しか残ってなかったとかよく聞くでしょう?

ネタ元

Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

バッチ処理、マスタ更新とかデータに直結する部分だから、実行怖い印象ある。失敗時のリカバーの方法は考えておかないと。

2016/02/18 17:09
b.hatena.ne.jp
Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

現場の経験がまとまってる / バッチ処理開始時にマテリアライズドビューでデータのスナップショットを用意しておくと良さげ

2016/02/18 11:37
b.hatena.ne.jp

開発・テスト時と本番時の環境差分を最小にしておく

これはもうバッチに限らないですね。 バッチ特有の事情としては本番では連携データの転送用ネットワークが別回線だとかいろいろ揃えようにも揃えられないみたいな話はあるんですが iptables を使うだとかいろいろなツールを駆使し、サーバーの構成からネットワークまで差分を最小にした状態でテストしたいものです。 最近はIaaSの時代になって比較的そういうのやりやすくなっていいですね。

ネタ元

Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

障害起こった時に対応しやすいように融通効かせる設計にするとかログ出力を考えとくしかないのかな。 あとテスト環境と本番環境のディレクトリ構成は常に同じにしておくとかそういうのも大事です。環境差分怖い。

2016/02/18 12:32
b.hatena.ne.jp

ワーキングディレクトリの扱い方

バッチ処理を行うとき、ファイルシステムであれ、RDBMSであれ一時的なデータ領域が必要になる。 オーケー、 /tmp/batchTmp をワーキングディレクトリにしよう。ここを使ってくれ。 みたいなルールにすると、当然 hoge.csv がかち合うことになり、毎時バッチAとBのデータが混ざってつらいことになる。 一時ファイル名なり、一時テーブル名なりにデータが混濁しないようにぶつからない名前空間を作ってやる必要がある。 「処理対象範囲を引数で指定可能にする」で指定したパラメータをパス名に含めてやるといいですね。 あと、一時ファイルや一時テーブルを名前がぶつからないようにしたからといって同一ディレクトリ or 同一スキーマに置くのも避けたほうが良い。 大量のテーブルやファイルがひとつの名前空間に置かれるとだいたい辛いことになるので。

ネタ元(ワーキングディレクトリの件だけ)

ちょっと消化できなかった指摘

並列実行する分散バッチなら終了判定とその集計

分散バッチはProductionでの経験がないのでちょっとまだノウハウがありません。

ネタ元

ロックファイルについて

制御ファイルのロック掴んで残りっぱなし問題は、実際よく聞くんだけどそういうケースを自動でなんとかしちゃうのもなんか怖いので良い答えを持っていない。 RDBMSに制御データも持つというのがひとつの答えなのかなぁ。

ネタ元

Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

よくあるトラブル対策としてまとまってる。あとは制御用ファイル置いとくとエラーで死んだときに残りっぱなしになる問題はあるな。

2016/02/18 11:19
b.hatena.ne.jp
Web系の自分が想像と障害で学んだバッチ処理・設計の基本 - コンポツさん

ロックファイル周りは処理が異常終了して解放されず、毎回手作業が発生するトラブルには遭遇したな。ロック解放処理を終了時のハンドラ登録する、サーバ再起動時に強制クリアする、で対応した

2016/02/18 09:11
b.hatena.ne.jp

いろいろ書いたけれど

バッチ処理は楽しい!

  • 事前条件と事後条件が明らかであり、テストがしやすい。
  • クソみたいに長いSQLを書いて障害を起こして、それを別のクソみたいに長いSQLに書き換えて数百倍のパフォーマンスを得るという最高の体験ができる。
  • 大量のデータを扱うことが多いので、単純にワクワクするし、レポート画面がとか出ると価値っぽいものを感じる。
  • 実装しているとインクレディブル・マシーンを思い出す。

つまり、障害を起こさないバッチ処理がかければ最高なんだ。 バッチ処理の知見を体系的にまとめて最高の人生を送ろう。

Web系の自分が想像と障害で学んだバッチ処理・設計の基本

バッチ処理というのはそれ単体で勉強しようとするとなかなか何を勉強したらいいのかわからないことが多い。 特に経験がWeb系ばっかりだと、いざバッチ処理を実装しようとした時に基本的なノウハウを知らないままに書いてしまうことが多い。

バッチ処理というのは実態を整理すると「何らかのトリガーを期に起動し、データをロード・加工・変換・集計してから、出力する」という事になる。 まぁ、INがあって処理してOUTがあるという点では関数だと考えてもいいだろう。 システムの利用者(人に限らない)のアクションとは直接関係ない処理であったり、利用者のアクションをトリガーとしていても、即時にレスポンスがいらないor返せない場合に バッチ処理を選択する事が多い。 実現方式はシェルスクリプトLL言語、実行可能バイナリだったりするし、デーモンとして立ち上げる場合もある。

利用者の操作に対して対話的・同期的な処理はオンライン処理、そうでない処理はバッチ処理と言われることが多い気がする。

バッチ処理のフェイズ

では「何らかのトリガーを機に起動し、データをロード・加工・変換・集計してから、出力する」について細かく考えていこう。 このあたりを本当は細かく説明したほうがいいのかもしれないけれど、今は気力がない。

起動タイミングについての適当な説明

起動タイミングについては大きく二つ種類がある。 定時起動とトリガー起動だ。

  • 定時起動
    • 毎時N分
    • 毎日N時
    • 毎週N曜日
    • 毎月N日
  • トリガー
    • WebUIの操作
    • WebAPIへのリクエスト
    • scpでファイルが置かれる
    • メッセージキュー

ざっくりこんな感じだ。

データをロード

システム内部・外部のデータを特定期間・範囲でロードする。 ロード元はいろいろあるけどだいたいこのあたりだろう。

  • CSVファイル
  • ログファイル
  • 画像ファイル
  • RDBMS
  • 外部Webサービス
  • SCPからファイル取得or受信
  • メッセージキュー

加工・変換・集計する

ロードしたデータを処理していく。 処理内容はいろいろあるけど大体このあたりの処理をする。

  • クリーニング・ベリファイ
  • 加工
  • 変換
  • 集計

データの出力

さて、データを加工・集計しても、それをどこかに出力しないと意味がない。 ので、出力する。出力先はまぁいろいろある。ここが外部へ影響を与える瞬間なので一番怖い。

  • RDBMSへ保存・更新
  • CSVへ保存
  • メール・メッセージを送る
  • 外部Webサービスに送信
  • 次のバッチを起動する

テクニック・運用のためにやっておきたいこと

さて、ここからがこの記事のメインだ。バッチ処理を実装する際には、結構いろいろなテクニックがある。 これらすべてを対応する必要はないかもしれないけど、実装する際に一度は考えておいたほうがいいことばかりだと思っている。

データの更新はギリギリまで避ける

RDBMSなどのばあい、トランザクション複数のデータを更新する際に早い段階からレコードを一行ずつ更新なんかしたりしていると、 ロック範囲が広がるばっかりでデッドロックの温床になったりして何もいいことがない。 一時テーブルなどに変更結果やインサートデータを投入しておいて、更新クエリ一発で対象データをガッツリ書き換える様な実装をするほうがよいだろう。 ファイルなどの場合も、書き込み途中で次のジョブに参照されるみたいな不幸を避けるために一時テーブルに書き込んでおいて最後にリネームするとかしたほうがよい。

可能であれば冪等に実装する

冪等についてはいろいろなところでかなり触れられているので、特に言うことは無い。 これをきちんとやっておかないと、集計にバグがあったりデータが届いてないとかで再集計を強いられた時に「じゃぁ、この日次バッチを今年の頭から再集計やっていきますか 」というつらいことになる。 ただ、これは設計センスがかなり問われるし、自分も失敗した経験があるので「可能な限りがんばっていきましょう!」みたいな気持ちがある。

並列化可能なポイントを抑えておく

バッチ処理は大体時間がかかるからこそバッチ処理になってるのだけど、一連のバッチ処理をナイーブに直列に実行していたらいつまで立っても処理が終わらないということも ままある。 バッチ処理のフェイズ毎だとかユーザー毎に、影響範囲が重なっていなければ並列で実行することで処理が早く終わることもあるので抑えておきましょう。 ただ、並列処理すると当然負荷も上がる場合が多いので、主目的のサービスが死なないよう最大同時スレッド数など、実行負荷の調整ができる機構を備えておきましょう。

時刻の記録

バッチの対象とするデータの範囲・開始時刻・終了時刻は必ずログやテーブルに記録しよう。 ログにはかかった時間も出力するとよい。パフォーマンスの劣化を監視するための指標にできる。 問題が起きた時に開始時刻・終了時刻から計算するのは面倒なので計算してログに出しておこう。 RRDToolsなどの運用ツールに放り込みやすいようになってるとよりよい。 RDBMSに投入するときは、バッチ処理そのもののトランザクションと実行記録を残すためのトランザクションを同一にしていると、処理失敗時に記録用レコードもロールバックされてしまうので、実行記録はログに出力しておくほうがよいだろう。

処理対象範囲を引数で指定可能にする

不幸なことに障害が数日続くという時があるかもしれない、問題解消後に日次バッチを手動集計します。みたいなときに助かる。 指定しないときは動かないようにするか適切なデフォルト値を自分で判定するかについては、実装者の好みで決めればよい。

ロックファイルによる多重起動禁止

たとえば /var/lock/hogehoge~batch~.pid みたいなファイルがあれば、既にバッチが起動しているので新たに起動したバッチは処理を続行しない。 みたいな制御を入れておくと多重起動してはいけない処理を重複して実行されずに済む。

ロックファイルではなくて、DBの設定レコードを見るとかでもよいと思う。 むしろ、バッチサーバーが複数台にまたがっている場合などは一台のDBでロックをとるほうが安心だ。

停止ファイルによる起動禁止

たとえば /var/lock/maintenance みたいなファイルがあれば、メンテナンスモードに入っているため新たに起動したバッチは処理を続行しない。 みたいな制御を入れておくとリリースや障害対応の際に crontab のコメントアウトし忘れたり、外部からのトリガーが不意に来たりすることがないのでよい。 停止ファイルではなくて、DBの設定レコードを見るとかでもよいと思う。

バッチ処理の実行、停止はコントローラブルにしておこう。

データの保持期限・削除基準の決定

バッチ処理の処理結果や処理の副産物として生成されたファイルとかテーブルの後始末について、カウボーイコーディングをしていたり仕様の検討から漏れてたりで、考慮が抜 けてしまう事が多い。

そのまま残しておくとディスク容量を圧迫するけれど、システムからはほぼ利用される事が無い。 そういうデータは定期的に消すような処理(これもバッチ処理だ)を忘れずに入れておきましょう。

法律的な理由で一定期間残さなければならないだとか、実装後半年たったら本当に消してもいいデータなのかどうかよくわからなくなった。 というケースも多い。最初からデータのライフサイクルは意識しておこう。

デーモンの採用基準

バッチの実行プロセスをデーモンにする理由は、単にカッコイイからだとちょっと弱い。感覚の話でアーキテクチャを決めてはいけない。

  • 起動処理が遅く、期待するレスポンスタイムを満たせない
  • トリガーの頻度が多く、常時起動のほうが負荷を制御しやすい
  • トリガーとなるアクションを常に監視しなければいけない

みたいな理由をでっちあげて君だけのかっこいいデーモンを作り上げよう。 面倒なら delayed~job~ や webアプリサーバーにエンドポイント作る方法でもいい。 webアプリサーバーに組み込んでしまうと複数バッチ処理をひとつのインスタンスで実行するということになるので、再起動のタイミングなどが少しシビアになってしまうかもしれない。 このあたりはトレードオフをきちんと考えておこう。

以上、ざっくり踏みまくった地雷から得た知見をまとめたので、各位には「お前の書いていることはインチキだ」だとか「これを読めば全部わかる」だとか「これは間違っている」などどんどん殴って来て欲しい。俺はもうこれ以上障害を起こしたくないんだ!

追記

はてぶで意見をいっぱいもらったので続きも書きました。 mitomasan.hatenablog.com

Doma勉強会

Doma勉強会に行ってきた。具体的にはこれ。

kanjava.connpass.com

資料はこれ

Doma実践

メモから

  • View層までDomaのエンティティやドメインオブジェクトを持ち込む?
    • うらがみさんとしては結構やってるとのこと。
    • JAX-RSでControllerの引数として受け取るところからドメインオブジェクトにする方法を説明していたので、DB層のエンティティがViewまで来ているというよりビジネスモデルがViewやDBのレイヤでシームレスに扱えてるという事だと思った。
      • Domaがエンティティにドメインオブジェクトを持てることと、黒魔術が無いからこういうことが実現できている気がする。View層やDB層のフレームワークPOJOをそのまま扱えるように発展してきたからからこそできることっぽい感。
  • Publicフィールド使うほうが多い?(Beanにしないことが多い?)
    • Beanでも別にいい。ただし、JPAのようにMethodに@Idや@Columnアノテーションをつけて云々みたいなのはできないみたい。
    • フィールドに詰め込むときは、リフレクションで詰め込んでいるけどもだからといってパフォーマンス的にそこで問題を感じたことは無いとのこと。
  • Domaの設計思想的にあまりサロゲートキーは使わない(使う必要がない?)
    • これは聞き忘れた。複数主キー指定できるのでなくても困らない感じではある。
    • 追記 ↑↑上記は私が発表を聞いた時に感じた感想で、実際にはサロゲートキーを使われているとのこと。
  • Batch用ConfigとAppServer用Config、両方で使えるDaoって作れるの?
    • 使えるよ。DaoにConfig受け取る引数がある
    • 複数DBとかもConfig工夫したらいけそうですね。
  • 複合主キーをつけるときは @Id を複数につける
    • 複合主キーに何か意味があるのかな。@Update時にうまいこと使ってくれる?
    • 使ってくれる
  • Lombokとの対応状況現状
    • 問題はあるけど結構共存できるよとのこと
      • 識者の意見
  • JAX-RSとの併用について
    • jaxbのXmlAdapter 書けば jackson もそれを使ってくれるのでエエカンジとのこと。
    • ただし、ドメインオブジェクト分の XmlAdapter 書くのはだるいので APT プロセッサーを書いているとのこと。
    • github.com
  • ダイアレクトはページング時に使われるのが代表的な使われ方
  • 1フィールド1カラムが原則

所感

Java のようにあまり高機能でない静的型の言語でORMでいい感じにインピーダンスミスマッチを解消するって言うのはなかなかうまく行かない場合が多いなぁと思っている。SELECT文の結果というのは型がある。なのでSELECT句が動的に変わる場合(JOINとかだね)というのは静的型ではどうやったってミスマッチを吸収できない。

HibernateJPAはSELECT句とEntityをマッピングするわけではなくテーブルとEntityをマッピングするので、テーブル定義とSELECT句が一致する場合は問題ないのだけれども、一致しない場合に途端に辛いことになってくる。

Domaの場合、そもそも ORM とも言ってないし結果セットをEntityにしてくれるだけというシンプルさがある。そしてSQLを書かせる代わりにメソッドの情報として利用しているのでコンパイル時に強力に型チェックしてくれる。みたいなのはやっぱりいいなぁと思う。

ひとつだけ気になっているのはDomaのいろいろ割り切った故のほぼ完成しちゃっている感。うらがみさんにも「今後のロードマップとかあるんですかね?」って聞こうと思ってたけど忘れてた。完成しちゃってメンテナンスフェイズとかに入ると活発に見えなくなっちゃってプロダクトの死に近づいちゃうみたいなの、どうしてもあるので。

それはそれとして、今度レイヤードアーキテクチャだとかDDD的な設計とか話したいということもワイワイ話せたので良かった。

今度、梅田で夕飯などを食べながら技術を語る会のようなものをできたらいいなぁと思ってます。

DevLOVE関西行ってきた。

具体的にはこれ。

https://devlove-kansai.doorkeeper.jp/events/36446

以下、自分の勝手な解釈が多々含まれるけど感じたこと、きいたことをまとめる。

 

「エンジニアが幸せな人生を過ごすための学び方、関わり方、あり方」
というエモーショナルなタイトルで、現場よりの話。

仕事のできないメンバーをできるように変えていくにはという話。

仕事ができるとは何か。できないとは何か。
まずそこからして人によって受け止めかたは様々だろうけどまぁそれはそれ。

とにかく人から期待した結果を引き出すためその人に変わってもらおうとするとき、どう変えたらいいのか、本当に変えていいのかという不安が出てきがちである。

そして人によってはそれは簡単なことだと感じられ、人によっては困難な道のりに感じられる。
その差は人によってそれぞれ抱えるメンタルやとるべき責任、思い込みなどの背景が違うからだという話。

んで、それじゃぁ行動を変えるには、習慣化するにはどうすればいいだろう。
こういうとき、たいてい気合いとか気の持ちようだとかの解決策が提示されがちだが、そういう解決策は実行・継続不可能なことが多い。

よって、小さく行動を変えさせ、分かりやすいフィードバックを与えてやり、その射幸性によって習慣付けさせ、その人を期待通りの行動を取ってくれるように計らうのが行動学的に理にかなっているという話。

そしてフィードバックは早ければ早い方がよい。動物的なレベルでいうと60秒以内が望ましい。理性的な大人は2週間ぐらいはギリ待てる。

ちょっと雑だし、ソシャゲーみたいな表現になったけど、根本はそんな感じかなと思った。

もしかしたら「小さく行動を変えさせ」のところは行動を変えさせる必要もなくて、好ましい指標を外部から計測し、公表してあげるだけでもよいかもしれない。

こういうのは行動科学マネジメントというジャンルらしい。

そのまま他人に適用するよりも、まずは自分に適用してみるのがよさそうだと感じた。
「人はこういう風に振る舞うんだよ」と、生半可な知識で知った風なことを言って逆鱗に触れてはいけない。

「こういう心理作用かあるとのことなので、最近心がけています」みたいな所から始めて効果を確かめつついい感触であれば周囲にも広めていくぐらいが今の自分にはよさそう。

 

こうして、勉強会のフィードバックを早めに出すことも、今回の勉強会で学んだことだ。

と、いいオチがついたのでそれではまた。

fitdesk使用感日記

fitdeskを買ってからというもの、PCに向かうとき=fitdeskぐらいの勢いで乗っている。PCがfitdeskの上においてあるのだから当たり前なんだけど。

実際にfitdeskを買うまでは「これはとてもいいものだからオフィスへの導入を考えてもいいものなのでは」ぐらいに思っていたのだが、実際に乗っていると思った以上に汗をかいてしまうので、だいたいパンツ一丁の状態で乗っていることが多い。職場でパンツ一丁で働いているメンバーが居ると辛いものがあるだろうから導入は厳しそうだ。

まぁ、職場への導入は別に真面目に考えてるわけでもないのでそれはそれとして。

よくあるサイクルマシンのように絶対にダイエットするんだ!みたいに強い意志でガツガツ乗る必要がないので気に入っている。PCで作業したければサイクルマシンに乗る事になるので、自然と運動するようになる。とはいえずっと乗っているとさすがに疲れるのでちょっと疲れた時は普通の机で仕事をする。みたいに使っている。

体重は81kg周辺をさまよっているが、2kgぐらいは普段から増減あるので特に効果は出ていないと言える。

78kgぐらいがコンスタントに出るようになってきたら効果が出てきたと言えるんじゃないかなぁと淡い期待を抱いている。