tsucchi’s diary(元はてなダイアリー)

はてなダイアリー(d.hatena.ne.jp/tsucchi1022)から移行したものです

no strict 'refs'じゃないとできないと思ってた

今日、ちょっとだけビックリしたことメモ。

#!/usr/bin/perl
use strict;
use warnings;

sub aa { print "aa\n"; }
'aa'->();

こんなの書いて、実行すると、ふつうに

tsucchi@vogue[512]$ perl a.pl
aa

こんな感じで実行できる。表題のとおり、no strict 'refs' じゃないとできないと思い込んでた。

けど、冷静に考えたら、

my %class_for = (
  aaa => 'MyProj::aaa',
  bbb => 'MyProj::bbb',
);
my $class = $class_for{$some_key};
$class->create();

みたいな感じのファクトリメソッドは良く書いてた。基本的にはこれと同じだよね。

主キーが決まらないとき

マーチン・ファウラーの著書、アナリシスパターンの最初の方に、こんな記述があります。

概念モデリングではドメインエキスパートの参加が「不可欠」である。筆者は、効果的なモデルを組み立てられるのは問題領域を本当に理解している人、つまりその問題領域を「生業(なりわい)」としている人だけであると考えている。
(中略)
情報技術の経験はモデリングの技能にとって毒にも薬にもならないことがわかった。筆者の知っている最高のモデラーはロンドンのある外科医である。

海外だと違うのか、ユーザが違うと違うのか*1、ユーザがちゃんと要件出せて、分析設計できてるところがもしあるなら、凄いしうらやましいなー、と思います。

で、僕はお仕事で、DB 設計と、そのDBとやりとりするWeb-API を書いているのですが、表題にも書いてるように、「主キーが決まらない」ということが起こりました。

いや、正確には、「○○単位で見たい(or 更新したい)」っていうのが出てはいるのですが、従来の画面と明らかにミスマッチしているし、僕の直感とも「ちょっとちがうなー」って思ったりして、案の定、次の日に、「やっぱりここは△△で」とか言われたりして、まあ主キーに相当する項目が決まらないわけです。

で、その上諸事情あって、締切りは変えられなくて*2、それなので、無理やりでもキーを決めないと DB も API も作れない。

なので、色々悩んで、こんな仕組みを考えた。

僕の業界用語で説明すると伝わらないので、みんな分かりそうな小売業っぽいモデルで説明すると、例えば、ある製品の「発送単位」「受注」「受注明細」「部品」みたいな単位があるとします。(で、この順に細かくなっていくとします)

で、僕の今の状況は、「A画面では受注単位」、「B画面では部品単位」、「C画面では受注明細単位」みたいなことを言われていて、それが日々変わるような、そんな状況です。

そんな状況なので、DB 側では最小単位(この例だと「部品」)で、主キーを設定し、参照のAPI ではそれぞれのキーをどれを使っても参照可能にし、更新の API には「更新単位」というパラメータをつけて、それに応じて同一の受注などを更新できるしくみをつけてみました。

まだリリースはしていないので、この先どうなるか分からないけど、今のところうまく行っているみたい。

これが「常識」なのか「バッドノウハウ」なのか「世紀の大発明」なのか「トンチンカンな設計ミス」なのか良く分からないけど、今まで勉強してきた中では見ることができなかったパターンだったので、書いてみた次第。

みんなこういうときはどうしてるのかなー?

*1:確かにお医者さんとは一度だけ少し仕事したことがあるが、システム詳しく無い人でもちょっとした質問とかに凄みを感じて、「お医者さんってすごいなー」って思った。社会人1年目の話。この話はいつか書くかもしれないし、書かないかもしれない

*2:本当に頭にくるのだが、この業界ではよくあること。内製でこうなのだから、お客さんからお金もらってるところは大変だよねー。。。

最近のお仕事の話とか、ORM のはなしとか

たまに書いてますが、最近は、Teng使ったりしてます。ほかの ORM はあんま知らないです。つーか、ちゃんとした Web アプリとかあんまり書いたことないし、つまり、Web系の前線とかには全然居ない人です。地味に MES(製造実行システム)のバックエンドを作ってる人なのです。なので話半分くらいで見てもらえれば幸いです。

さてさて、そんな私ですが、

  • 内製の WAF を使って、Web-API を書いてます
  • WAF の機能は、ストアドを実行して XML を返す物体でした*1
  • クライアントは簡単には変えれないので、XML のレスポンスは当面そのままでしょうねー*2
  • ストアド書くのは楽しくないし、色々問題がでた(詳細はMySQL のストアドプロシージャの話を参照)ので、普通の SQL を叩けるように改造しました*3
  • SQL 叩けるだけだと、簡単な SQL 書くのがだるいので、自分でテーブルデーゲートウェイを書きました
  • 自分で書いたテーブルデーゲートウェイのメンテがだるいので、ありものを使おう、と思って、Teng を使えるようにしました(イマココ!)

と、言うわけで、最近は難しい機能の場合はめんどくさい SQL を手で書いて実行して、簡単なやつは ORM(Teng)に振るような感じで、過ごしていたわけです。

で、Teng に限らず、大抵の ORM はスキークラスを持っているのですが、僕はアホなので、テーブルの変更があったのにうっかりスキーマクラスを更新するのを忘れて、「ひでぶ」となることが多々あります。で、結構テーブルの変更は頻繁にあるので、「ひでぶ」も結構頻繁に起こります。テーブル数はアホみたいに多いので、スキーマクラスを動的に作成とかしたくありません。

いろいろ悩んだのですが、まあ解は2つかなー。

前にも書きましたが、スキーマの変更をテストしてるので、ORM のスキーマクラスもそのタイミングでテストしちゃえ、ってのが後者。で、基本的には前者でいきたいんだけど、Teng にその方向に手を入れるのは無理くさいし、自作するのはそれはそれで大変だなー、と思ってたりします。*4

つーか、そもそも何でスキーマクラスって必要なんだろ???と思ってちょっと考えてみたのですが、

  • 重量級の ORM だと、そもそも DDL 管理もやってるから必要だよねー
  • 軽量級(Teng とか DDL 管理やってないやつ)なら無くてもやってけるけど、そうすると Row オブジェクトが何にもできなくなっちゃうよねー

という感じかなー、と思います。例えば Teng だと、Row オブジェクトから UPDATE かけたりできるんですが、スキーマクラスが無いと主キーが分からないからできなくなっちゃいますよね。INSERT で Row オブジェクトを返すとかもできなくなっちゃう。あとは inflate/deflate もスキーマクラスがあった方がやりやすいかな。*5

上記のようなトレードオフやその他を検討した結果、

  • スキーマクラスはなくしたい
  • Row オブジェクトの機能はそんなに求めてない*6
  • 生の DBI はちょっとさすがに厳しいので、もう少し簡単にしたい
  • 名前付きプレースホルダーが使いたいなー

あたりが今やりたいことのようです。名前付きプレースホルダー以外は DBIx::Simple で機能的にはよさげなんですけどねー。

色々考えてみたわけですが、新旧 ORM は色々ありますが、歴史あるものは(僕が思いつきで作ったものと違って)それなりの理由とか思想があって良くできてるんだなー、と思った次第であります。

*1:だいぶ改造したけど、僕が書いたものではない

*2:これはこれで問題あるんだけど、今回の話とは関係ないので割愛

*3:SQL::Library というモジュールを使ってます。mod_perl じゃなかったら Data::Section::Simple を使いたかった。

*4:長期スパンで、自作の方向で検討してます。Teng とある程度互換性のあるブリッジが作れればいいかなー、と思ってます。

*5:inflate は Row オブジェクトで代替してもいいんじゃないかな、と思ってますが

*6:前述の Row オブジェクトから UPDATE はあればうれしいけど、INSERT で Row オブジェクトとか使ったことないや。どうやら欲しかったのはちゃんとしたテーブルデーゲートウェイだったようだ。最初にちゃんと作れば良かった...

SQL::Executor ってモジュールを書いてみた

僕は基本的には、SQL生派で、最近少しずつ Teng を試したりしています。

なので、DBI でも機能的には問題ない、と思っているのですが、テストコードやちょっとしたスクリプトだと、prepare して、execute して fetchrow はちょっとダルいなぁ、と思うわけです。で、DBI には selectrow_(arrayref|hashref) とか selectall_arrayref とかあるのですが、アレのパラメータはちょっと意味不明すぎるなぁ、とも思うわけです。

なので、SQL::MakerDBI の select(row|all)* をいい感じにくっつけたら、いい感じになるんじゃないか、と思って書いてみました。*1 *2

p5-SQL-Executor

使い方は SYNOPSYS に書いたとおりで、こんな感じです。(SYNOPSYS 丸写し)

use DBI;
use SQL::Executor;
my $dbh = DBI->connect($dsn, $id, $pass);
my $ex = SQL::Executor->new($dbh);
my @rows = $ex->select('SOME_TABLE', { id => 123 });
$ex->insert('SOME_TABLE', { id => 124, value => 'xxxx'} );
$ex->update('SOME_TABLE', { value => 'yyyy'}, { id => 124 } );
$ex->delete('SOME_TABLE', { id => 124 } );

select 系はいっぱいメソッドありますが、基本的に select_row* は1行だけ、select_all* は全行取るやつです。row も all もつかないやつは、コンテキストに応じて row か all かを決めます。

select(|all|row)_named は名前付きプレースホルダーが使えるやつ。*3

select(|all|row)_by_sql は、普通に SQL を投げるやつ(名前付きプレースホルダーじゃなくて、「?」を使うやつ)です。

select(|all|row)_with_fields は普通の select 系と同様に SQL::Maker を使うのですが、select * ではなく、出力フィールドを指定できます。

連休開けたら実案件に投入してみます。

ってか、みんなこういうのどうしてるんだろう?そもそも困ってないのか、律儀に DBI のインターフェース叩いてるのか、こういうラッパーを t::Util とかに置いてるのか、DBIx::Simple とか使ってるのか、うーむ、どうしてるんでしょうね。。。

*1:いや、嘘。仕事でそれっぽいのを一度書いたことがあるのですが、プロジェクト構造とかそういうのに依存しない形で切り離して一から実装しなおしてみました

*2:DBIx::Simple がコレの類似品に相当するのは一応知っているけど、使ったことないのと、SQL::Maker に慣れてるのでそれを使いたいってのと、SQL の部分だけなんとかするやつが欲しかった。

*3:Teng の search_named() のコードをぱくってます

C# で mv する

積ん読ならぬ、積んブログ。*1

FileInfo クラスの MoveTo は、ディレクトリへの移動*2ができないのと、強制上書き*3ができないので、こんな感じの外部メソッドを使っている。

private void MoveToDir(DirectoryInfo dir, FileInfo file)
{
    FileInfo target = new FileInfo(System.IO.Path.Combine(dir.FullName, file.Name));
    if ( target.Exists ) //既に存在している場合は上書きしたいので削除
        target.Delete();
    file.MoveTo(target.FullName);
}

*1:ネタ帳にかいてあったけど、記事に起こしてなかった

*2:mv file dir/ みたいなやつ

*3:移動先にファイルがあると例外が吐かれてしまう。CopyTo は強制上書きするオプションあるのに。。。

書評: 達人に学ぶDB設計徹底指南書

DB 設計の入門書です。初心者はもちろん、ある程度経験を積んだ人も自分の知識を整理するのに役にたつんじゃないかなー。*1

DB の本の場合、「理論」と「実践」のどちらかしか書いてないものが多かったりするのですが、この本は両方にふれて、かつトレードオフをきっちり説明しています。
また、「バッドノウハウ」や「グレーノウハウ」といったものにキチンとふれていて、さらに何故そういったものが生まれてしまうのかの説明とかもあって大変よかったです。

正誤表もあるのでみておくべし。っていうか、みんなよくこんなの見つけられるなー。(いくつか気づいたのもあったけど)

さて、基本的にはとても良い本で、超おすすめ(まだ読んでない人は、今すぐこの下のアフィ付きリンクからポチって GW 中に読むべし! :-)なのですが、「ちょっと違うよなー」と思ったところもいくつかあったので、それについて書きます。

RAID1 について

RAID1(ミラー)について、「性能は向上しない」とあるのですが、大抵のミラーは(ソフトでもハードでも)、読み込みは両面からやるので、読み出しは少し早くなります。

RAID1+0とかRAID6 とか

良く分かっていない初心者が対象なら、「RAID6+スペア」が一番良い構成だと思うので、RAID1+0 が本編で RAID6 が欄外、というのはちょっと納得ができません。5年前ならそれで良かったと思いますが。*2

参考: RAIDレベルの話: 1+0と6はどっちが安全か?

RAID5とか6とか

「書き込みは遅い」ってのはまあ事実なのですが、ハードウェアRAID なら大抵キャッシュバッテリーついているので、あまり気にならないはず。また、キャッシュが電池切れすると書き込みが超遅くなってビックリとか、WEB 系でも業務系でも「あるある」なハズなので、そこも触れてほしかったかも。

バックアップについて

フルバックアップについて、「サービス停止が必要」とあるのですが、Oracle ならアーカイブログモードなら稼働しててもフルバックアップ取れるし、MySQLInnoDB なら mysqldump に --single-transaction つけるとか、(使ったことないけど) xtrabackup 使うとか、DB のダンプコマンドと OS やディスク装置のスナップショット機能を組み合わせるとか、「サービス停止なし」で一貫性のあるフルバックアップを取る手段はあるので、これは「間違い」だと思います。

統計情報について

「統計情報は夜間にバッチで取られる」ので「重いから注意」的なことが書いてあるのですが、MySQLInnoDB だと更新時に自動で行われていて、しかもかなり軽い処理(らしい。実際気にしたことなかった)ので、あんまり気にしなくていいです。

参考: 大人のためのInnoDBテーブルとの正しい付き合い方。

単一参照テーブル

著者の造語で、コード定義のマスタとかを共通で持つやつです。これが「バッドノウハウ」として書かれているのですが、個人的には「グレーノウハウ」くらいだと思います。中規模以上のシステムだと、(コード定義以外の実績系とかマスタ系で)テーブル数が数百とかふつうなので、ここにコード定義が加わると下手するとテーブルが 1000 越えちゃいます。それが嫌な場合は(僕は嫌です)使っても良いのではないかなー、と思います。

ただ、これを使う場合、当該データにコード定義以上の意味が出現してしまった場合は、新しくマスタテーブルを起こす必要があります。僕のところではそういう変更は受け入れられていますが、こういったデータベースリファクタリング的なテーブル変更が受け入れられていない組織もあると思います。そういうところでは使わない方が良いと思います。

あと、Oracle だとキーに CHAR を使うと思われるので、そういう場合はキーの桁数拡張しにくくてダメってのはあると思いますが、MySQL の CHAR(あるいは VARCHAR 使ってもいいと思う)だとそういう心配は無いので、もう少し気軽に使える、というのもあります。*3

垂直分割

テーブルの列を分割するのが垂直分割なのですが、これがバッドノウハウとして紹介されています。僕はグレーノウハウ or セーフではないかな、と思っています。

たとえばあるキーに紐づくあたらしい画面が一個できたりすると、同一主キーに対する項目が数十個くらいできたりします。こういうときに、同じテーブルに項目追加するか、新しいテーブルを起こすかは難しい問題です。こういったケースで、新しいテーブルを起こしたり、あるいは既存のテーブルに項目追加したけど、「やっぱテーブルでかくてヤダから分けよう」とかなったら垂直分割だと思うのですが、これはセーフだと思います。

また、テーブルがでかすぎて、ALTER TABLE でやろうとすると停止時間中に終わらないから、やむなく新しいテーブルを起こすこともあったりします。これはグレーですね。

オートナンバリング

この本に書いてあるとおり、アプリでやるのは筋悪。シーケンスでもオートインクリメントでも DBMS でサポートしてるのなら好きなの使えばいいと思う。どうせたいがい片方しかサポートされてないので、標準の準拠とか割とどうでもいいんじゃないかなー。

列持ちと行持ち

僕は「タテ持ち」と「ヨコ持ち」とか言ったりしてますが、要は、項目が複数ある場合に、行とする(行持ち、タテ持ち)か、列とする(列持ち、ヨコ持ち)かの話です。

列持ちはグレーノウハウとしているのですが、僕はちょっと違うと思います。昔もDB のスキーマ設計とかの話(データの縦持ちについての項)に書いたのですが、実績系は列持ちすべきで、マスタは(要件に応じて)好きな方を使うべきかな、と思います。理由は前に書いたとおりで、実績系でたとえば 1000万件あるテーブルに、100項目行持ちすると、10億件に達してしまいちょっと運用が大変になるからです。

再度まとめ

「ちょっと違うなー」と思ったことをつらつら書きましたが、目次と比較してもらうと分かると思うのですが、本編と思われる DB の論理設計、物理設計の部分にはまったくケチがついてないことが分かるかと思います。そのくらい良い本だ、ということです。

まだ読んでない人は、今すぐこの下のアフィ付きリンクからポチって GW 中に読むべし! です。(大事なことなので2回言いました)

*1:少なくとも自分にとってはそうでした

*2:昔はソフト・ハードとも RAID6 は不安定な実装が結構あったため

*3:私は普段の業務で MySQL を使ってます

Webエンジニアのための データベース技術[実践]入門

ブクログに書いたのと同じ内容です。

MySQL をベースにしている部分が多いですが、概念自体は他のDBを使っている場合でもそんなに違いはないはずなので、他のDBを使っている人にもおすすめです。

「Webエンジニアのための」とのタイトルだし、一応対象はWebエンジニアなのだと思われますが、業務アプリ作っている僕にとっても有用でした。

「入門」というタイトルの割には、ちょっと高度かな?という気もしなくもないのですが、ただこの内容をマスターしただけでは実際の運用はできないわけで、やっぱり「入門」なのかな、と思います。(たとえば、バックアップのやり方を何通り知っていようとも、実際のプロジェクトで適用してそのプロジェクトなりのベストプラクティスを作らないと意味ないでしょう?そういうことです)。

各トピックは高度な内容にふれつつもバランスよくまとまっているので、ここに書いてあることを出発点にして色々出来るようにしたいですねー。

amazon のレビューとかみてると、なぜか評価があんまり高くない(平均3)のですが、何故なんでしょうね?初心者が「初心者用の入門書」と勘違いしてるのかなー?

MySQL の DBA してる人は必読、それ以外のDBA やアプリケーション開発者も読んでおいた方がよい、くらいの良書だと思います。