Hatena::ブログ(Diary)

山本大の日記 RSSフィード Twitter

株式会社レベルエンター(http://levelenter.com/)で、プログラミングを若手に教える仕事をメインにやっています。

2011-08-08

信じられないDB文化「固定長DB」でもあうんです。大規模コンシューマ向けサービスのRDB設計

ずいぶん時間があいてしまったけど、大規模コンシューマ向けサービスRDB設計の続き。


僕はこのプロジェクト自分のRDBの知識を使って革新してやろうと思って臨んだ。

しかし結果として逆に、コンシューマ向けサービスに最適化されたRDBの使い方について教わることになった。


※ あと、KVSでいいじゃんって言ってる人もいるけど、それはKVS導入の苦労を知らない人だと思う。KVSの苦労は後で書く。

僕らが最近手がけているのは、とても大規模なコンシューマ向けサービスだ。
100万人の契約ユーザが使い、1テーブルに1億レコード以上のデータを貯め、24時間止めることが許されず、
要求から応答までのターンアラウンドタイムが1秒以内という厳しいSLAのサービスである。
中でも僕はDBやフレームワークの設計とアーキテクトっぽいことを担当している。

僕がこの現場に来て、驚愕した文化が2つある
それは「Join禁止」と「固定長DB」だ。
ありえない。
とはいえ、正直に言えば「またか、、、」という感想でもある。
RDBを知らないレガシーな人たちが設計したDBではよくありがちな設計だからだ。
と僕は早々にこの文化と戦って、絶対に覆してやろうと考えてた。
過去の経験上それはたやすいハズだった。

信じられないDB文化「Join禁止」に「固定長DB」、、でも、合うんです。大規模コンシューマ向けサービスのRDB設計 - 山本大の日記


はじまり

僕らの関わっていたシステムのDBは、カラムの型として固定長(CHARとDATE)しか許されない。
これについても、僕は参画当初、RDBの常識から考えて大いにバカバカしく感じていて、憤慨して絶対に覆してやろうと考えていた。


結論としては冒頭にも述べたとおり、Join禁止の時と似た結末となってしまった。
つまり固定長カラムしか使ってはいけないというポリシーを覆す事は出来なかったのだ。


これについて語るには前提知識として「Oracleのブロック」と「行移行・行連鎖」を理解してもらう必要がある。

Oralceのブロックとは

ブロックとは、Oracleがディスクアクセスするときの最小単位だ。
ブロックサイズは、Oracleの初期設定時に変更できるが、その後変更できない。
データベースのレコード(行)は、ブロック単位に格納される。

行移行/行連鎖とは

行移行も行連鎖も、パフォーマンスを下げる要因として知られた、RDBの設計注意点だ。


どちらの事象も1レコードが複数の「ブロック」に分かれて格納されるのだが、発生メカニズムが異なる。


とにかくDBのパフォーマンスチューニングは、いかにディスクアクセスを減らすかがポイントだから
1レコードを読み込むのに2ブロックや3ブロック読むのでは効率が悪い。
大量データを扱っている場合には、この読取ブロック数をいかに少なくするかに神経を尖らせるのだ。

行連鎖とは

行連鎖とは1レコードのサイズが、そもそもOracleの読み取りの単位(1ブロック)のサイズを超えている場合に発生する。
たとえば1ブロックのサイズを8000バイトにしている場合には、9000バイト分挿入しようとすると1000バイトが余るので、Oracleは個のレコードの格納にもう1ブロック使う。そのため1レコードが、ディスク上は2ブロックに分かれて格納される。


こうなると1レコードを読み書きする時に2回のディスクアクセスが発生するから遅くなる。


この行連鎖の予防は比較的簡単で、ブロックサイズを超えるようなテーブル定義にしなければよい。
つまり上記の場合は、1テーブルのカラムサイズの合計が8000バイトを下回るようにすればよい。

行移行とは

次に、行移行。

行移行こそが、可変長の型を使っている時に発生する問題である

行移行の事象も行連鎖と同じで、1レコードが複数ブロックに格納されてしまうことだ。
しかし、メカニズムはちょっと異なる。


レコードを登録する時に、カラムを初め1バイトの文字だけで登録していたとする。
そして、Updateでそのカラムのデータを4000バイトまで増やしたとしよう。


そのカラムが、可変長型(VARCHARやNUMBERなど)であれば増えたデータは別ブロックに格納されるのだ。


こういった話、下記の本を読むと凄くよくわかる。行移行・行連鎖に対処する一般的な設計なんかも書いてる。*1

この本の著者さん、1日だけ僕らのプロジェクトにヘルプで来てくれた。印象深い人だった。

絵で見てわかるOracleの仕組み (DB Magazine SELECTION)

絵で見てわかるOracleの仕組み (DB Magazine SELECTION)



さて、


この行移行を阻止するために、CHARとDATEという固定長を使うというのが、我がプロジェクトのルールだった。
たしかに、VARCHARやNUMBERといった可変長型を使わなければデータの伸長はおきないが、それ以外に多大なる問題(開発効率を含め)を引きおこすように思える。


固定長DBで想定しうるデメリット

プロジェクト参画当初、このルールには僕は絶対反対を言い張った。
固定長にすることで、以下のようなデメリットをもたらすことは容易に考えられたからだ。
・Trimが必要になる。
・数値型はソートに対応するために0埋めをしなければならない。(0のTrimも必要)
・集合計算(SUMなど)には、CONVERTが必要でパフォーマンス悪化をもたらす。
バッファキャッシュヒット率が低くなる。
・ディスクが高くつく。


これらを検証しながら、いろんな人たちとミーティングを重ね、どうにかこのルールを覆そうと奔走した。


しかし、僕の常識を越えた検討の末のルールであることがだんだんわかってきた。

コンシューマ向けサービスに最適化したDBとは

僕はそもそも一般的な企業システム向けのデータベースの使い方を基本として考えていたところがあった。


しかしコンシューマ向けサービスのRDBの使い方として重要なポイントは
コンシューマ向けサービスでは、データの検索・操作・集計の範囲が広範囲になることはない。または設計上で回避出来る
というところだ。
ほとんどのDBアクセスは、1ユーザの情報を読み書きするだけだ。


僕はコンシューマ向けサービスに最適化したデータベースを考えていなかった。


企業システムなら、帳票や集計こそがRDBの威力の見せ所だが、コンシューマ向けサービスは、他のユーザと集計して嬉しい部分はさほど多くはない。
SUMなどのGROUP関数を使うことはほとんどないのである。


だからそもそもこのプロジェクトではRDBの集計関数を原則禁止している。
プロジェクトの別のルールによって、DBへのCPU負荷をかけることを極力禁止しており、
集計などが発生するにあたっても「Java側でループして集計する」か「設計上、集計が発生しないよう考慮する」のが鉄則なのだ。

RDBに考えさせないポリシー

議論に入る前から、僕も「RDBに考えさせずJavaで考える」ポリシーは理にかなっていると思っていた。


なぜなら、DBサーバーは基本的にスケールアウト(サーバー増設する)が出来ないからだ。

DBがスケールする仕組みも考えてはいたのだけれど、巨大なサービスになることがわかっていたから、DBサーバに負荷をかけないにこしたことはない。


JavaEEサーバーであれば、負荷分散のためにスケールアウトすることが比較的容易であるが、DBサーバーは基本的にスケールアウトできない。*2

苦労するなら初期構築、運用に苦労を回すべきではない

Trimや0埋めなどはDBアクセスフレームワークで吸収できる範囲であるといわれた。
たしかに、パフォーマンス劣化に伴う運用/保守コストに比べれば、初期構築のコストは問題にならない。
というより、問題にされなかった。
なぜなら初期構築費用は一括でドカンと支払われるため、その辺の微細な実装コストはお客さんは誰も気にしないのだ。
そういうことで、僕があてにしていた固定長にすることでのCPUコストでは、このプロジェクトのポリシーを覆す程の問題提起にできなかったのだ。

敗北を決心した理由(苦労を運用に回さないとは)

僕が折れた決め手は運用に関することだ。
運用時には、パフォーマンスがらみの様々なトラブルが発生する。
そこに問題になるかもしれない箇所が1箇所増えるだけで
問題の切り分けや、対処に要する時間は数倍に膨れあがる。

「もしかしたら行移行が発生しているのでは?」

この疑問が、可変長を使っている限り、ことあるごとに誰かの頭の中に登場するだろう。
そしてその検証をしなければならなくなる。パフォーマンス問題のたびにだ。


無駄なスペースがDBに保存されるってことを割り切るだけで、運用時のトラブルが幾つかでも回避出来る。
僕は負けることにした。それは運用後正解だったと確信した。

そして運用へ


そんなこんなで、語り尽くせぬ程の苦労を中略するが、システムは無事にサービスインを迎えた。
今では運用開始してから1年数ヶ月が過ぎすっかり安定したが、サービスインしてから1年ほどは
本当にRDBチューニングの限界に挑戦している感じだった。


読取ブロック数をできるだけ下げる設計にしておいたことが、運用に入ってからとても救われた。


100万人のユーザが毎日毎日いろんな動きをしてくれる。
需要予測に従って設計したつもりが、需要予測無視で傾向が変わる。


上記の固定長DBポリシーに従わなかったら、いろんなところでもっと沢山の障害を踏んでいたかもしれない。
障害切り分け〜復旧にはより時間がかかっていただろう。

結論

コンシューマ向けサービスでRDBを使うのであれば、RDBの負荷をどれだけ下げられるかがとても重要なポイントだ。

これには以下のようなポイントで設計から考慮する必要がある。
・サービス設計上、ユーザをまたがるような処理をしないこと。
・DBに頭を使わせるのではなく、Webアプリケーションに頭を使わせること。
・ユーザアクセスやユーザ動向は常に変化すると考えて設計すること
・できるだけ運用に苦労を回さないこと
・パフォーマンス問題は必ず発生するから、要因をできるだけ減らしておくこと。


こんな話も今は昔


最近は、分散KVS(NoSQLという呼び名はあまり好きじゃない)などもしっかり導入実績のあるものが出てきて、この記事に書いたプロジェクトでも、それらを導入しているのだが、2年前の時点では、分散KVSの導入を検討する段階になかった。


しかし、そもそもKVSでは厳密なトランザクション管理する部分では、RDBに及ばないと感じる。
だからRDBが必要な領域はまだまだ存在する。


そういうことで分散KVSの導入においても、導入検討にあたってはめちゃくちゃ苦労するんだけど、それは別の話。

あわせて読んでほしい

興味があれば、以前に書いた「Join禁止」のポリシーについても一読ください。

*1:行移行・行連鎖を発生させない仕組みとして普通はPCTFREEやPCTUSEDを使ってデータブロックに「遊び」の部分を設計するのが常識だ。しかしながら、サービスの初期構築でこの設計が適切に(明確な根拠を持って)行える者がどこにいるだろうか。サービスの使われ方、ユーザ動向といったものはどれだけ綿密に需要予測をやっていても崩れるものだ。現にそういう事例を運用してから嫌というほど見てきた。もしくは、アプリケーション設計で回避という考慮も可能だろう、しかしこれもまた難しい問題でカラムの最適な桁数を決めるのは本当に難しいことなのだ。結果として、利用しているAPIや外部システムやUIの桁数のうちで一番桁の大きなデータが入るように設計するようになる。こういうことだから、「PCTFREEやPCTUSEでデータがブロックからあふれない設計をします」と方針づけて設計したとしても根拠の薄い設計になってしまうんだ。僕らのお客さんにはそういう説明での設計は許されなかった。

*2:OracleRACを使えばいいという選択肢はあるのだが、お客さんの苦い経験上RACの選択肢ははなからないし、僕もRACに苦しめられるのは好きじゃない。

Eimelle555Eimelle555 2011/08/09 10:48 はじめまして
自分も元はDBエンジニアで企業内システムしか知らなかった者なんですが、
なるほどこんな考え方があったとは…と勉強になりました。
分散KVSについても楽しみにお待ちしております!

cero-tcero-t 2011/08/10 01:51 待ってました!
でも、、、

Joinの話と、今回の話の「結論」の項目は、とてもよく分かるのですが
固定長カラムは、今時のCPUとストレージを使ったベンチマークでも見てみないと
なかなかちょっと、受け入れにくいですね・・・。


まま、
ディスクアクセスのことに気を払って開発効率を下げるよりも、そこは開発コストを浮かせる方に倒して、
その分、キャッシュ(KVS)の使い方に頭とコストを使いましょうよ、
と持っていくのがあるべき姿だと、私も思いますねー。

小田小田 2011/08/10 12:15 ご紹介いただいた本の著者です。ご紹介、ありがとうございます。私、印象深かったですか(笑)。きっと、今年の2月か3月のころ、一晩だけ手伝いにいったプロジェクトだと思うのですが。がんばってください。

iad_otomamayiad_otomamay 2011/08/10 14:21 >cero-tさん
どうもどうも、文字コードの件も続きまってますよー。(^^
いやー、この話は内部的にも賛否両論があるので、書くのに苦労します。
やっぱり、いろんな人から大いに反論もありますね。
Oracleを知ってる人ほど徹底的に受け入れられない内容なので、現場でも新規参画の有力なエンジニアが来るたびに、このポリシーについて皆戦おうとするんですよ。
僕もはじめは挑戦者側の一員だったのですが、徹底的に議論して何とかこのエントリのような理解で自分を納得させた。つまり「速度より安定」というのがこのPJの基本だというところを念頭に置いて、僕は歩み寄ったんですが、新規参画者から見ると、僕もDBをよく知っているということと、アーキテクトという立場から、僕がこの文化の提唱者のように見えるようで、良く論戦を挑まれました。
4半期に1回はそういう議論に巻き込まれるので、とにかく最後の方はもう「どっちでもいいから、あなた決めてみてよ。」と、放り出すこともしばしば(他が忙しくてやってられないし。)
しかし、いろんなシガラミと合わさって結局だれもこのポリシーを崩せませんでした。
開発のプロセスや開発体制、他システムのポリシーにまで及ぶというところもあったのですが、それがなくても超理論派のお客さんに理解をいただくロジックは組み立てられなかったですね。

たは、新しいプロジェクトでやるんだったら、コンシューマサービスだろうが、
ぼくは固定長DBやJoin禁止にはしない。ということは付け加えたい。。。。あ、本文に書くべきか。

iad_otomamayiad_otomamay 2011/08/10 14:31 >小田さん
コメントありがとうございます。そうです2月か3月でしたね。
僕は日中帯受け持ちだったし、深く噛んでなかったですが、
小田さんの登場した、あの事象も中々おもしろかった(と言ったら不謹慎ですが)ので、
コンプラの範囲でエントリにまとめたいのですが、
変な被害者や、変な理解を振りまいてもいけないのであたため中です。

小田さんは印象深くてPJも「あの人はもしや有名な人では」とネット検索して、
ほんとに有名人だったので話題になりました。貴著は非常に丁寧に書かれた良書だと感じています。
またどこかでお会いできればうれしいです。どこかのぎりぎりな現場などでw

小田小田 2011/08/11 14:07 > 貴著は非常に丁寧に書かれた良書だと感じています。

ありがとうございます。

> またどこかでお会いできればうれしいです。どこかのぎりぎりな現場などでw

はい。そういう現場に出没することが多いです(笑)。よろしくです。

rzprzp 2011/08/15 22:50 見当違いのこと書いてたらすみません。
個人的には「固定長にすることでのCPUコスト」よりも「固定長にすることでのI/Oコスト」、つまり記事にもあります「バッファキャッシュヒット率が低くなる。」ことによる性能劣化がとても気になるのですが。。。
その辺は、一括でドカンと支払われた初期投資費用で十分なサイズのメモリを積んで回避されたのでしょうか。

iad_otomamayiad_otomamay 2011/08/16 02:49 >rzpさん
コメントありがとうございます。
1つこの件についてはエントリにしました。
http://d.hatena.ne.jp/iad_otomamay/20110815/1313430485

ご確認いただければ幸甚です。

投稿したコメントは管理者が承認するまで公開されません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証