トップ «前の日記(2003-02-04) 最新 次の日記(2003-02-08)» 編集

2002|01|02|03|04|05|06|07|08|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|02|03|04|07|

2003-02-07 [長年日記]

_ PostgreSQLのデータ復旧 (20:11)

PostgreSQL(バージョン6.x)をしばらく運用しているとlogファイルが巨大になってくる。logファイルなんだから消してもいいだろうと、適当にmvしたりする人がまれにいる。が、PostgreSQLのlogファイルは人間が目で見るためのテキストlogファイルではなく、PostgreSQLの動作に必要なバイナリファイルであり、それを消すとPostgreSQLがまともに動かなくなる。

という状態になってしまったデータを復旧させてくれないかと頼まれていろいろ調べてみた。

PostgreSQLはupdateなどでデータを書き換える際に、実際にデータを書き換えるのではなく、既存のデータに無効フラグを立てて見えなくしつつ、新規に新しい内容のデータ領域を作成してそちらを有効にする。updateをかければかけるほどデータ領域はひたすら増え続けていく。そのため、定期的なvacuumを行って不必要なデータ領域を削除したりする必要がある。

PostgreSQLではすべてのSQLコマンドはトランザクションで囲まれているものと見なされる。明示的にbegin;commit;で囲まれていないSQLコマンドも、内部的にはその前後がbegin;commit;で囲まれているものとして処理する。そして、すべての処理は32ビット連番のトランザクションIDが割り当てられている。ある処理で変更があったデータには、その処理のトランザクションIDが記録される。

先ほどの「書き換えられたデータに無効なフラグを立てる」という処理には、このトランザクションIDが関わっているようだ。ある行の内容を書き換えた場合、その行には古いデータと新しいデータの二つが存在することになる。それぞれには、そのデータが生成された処理のトランザクションIDが記録されている。現在のトランザクションIDと比較して、より現在のIDに近いトランザクションIDをもつデータが、その行の最新のデータとなる。

logファイルはどうやらトランザクションを管理するデータらしい。logファイルを削除するとトランザクションIDがリセットされてしまう。実データは別ファイル(baseディレクトリ以下)に記録されており、logファイルがおかしくなってもそちらは影響を受けない。しかし、トランザクションIDがリセットされると、実データファイル側に記録されているトランザクションIDとの間に矛盾が生じるため、データが見えなくなってしまう。たとえばトランザクションIDが1000の状態で記録されたデータは、トランザクションIDが100の状態では見えない。

そのようになったデータを再び見えるようにするためには、現在のトランザクションIDを、実データファイルに記録されている最新のトランザクションIDよりも大きくしてやればいい。トランザクションIDを直接操作する方法は見つけられなかったが、SQLコマンドを1回発行すれば現在のトランザクションIDが1増えるので、特に意味のないSQLコマンドを必要な回数だけ発行してやればトランザクションIDを増やすことができる。

データに記録されている最新のトランザクションIDを知る方法も見つけられなかった。もしもDBの利用頻度が分かっているのならば、たとえば「1日の平均トランザクション数×運用日数」などでトランザクション数を予想して、その回数分SQLコマンドを発行してやればいい。利用頻度が分からない場合は、oidを使ってトランザクションIDが最新に追いついたかどうかを類推する方法がある。

害がなさそうな適当なテーブルに対して、1行新しい行を作成し、その行のoidを見る。トランザクションIDはリセットされても、oidはリセットされないようなので、oidはリセットされる前の最新のoidよりも新しいものが割り当てられる。その値を基準として使うことになる。

トランザクションIDを進めていくと、少しずつデータが復旧していく(古いトランザクションIDをもつデータから見えるようになってくる)。そこで既存テーブルの中で新規行の作成が頻繁であったと思われるテーブルのもっとも大きなoidをチェックする。データが復旧していくとそのoidの値が大きくなっていく。その値が先ほど得た基準となるoidの値に限りなく近くなる=データが元の状態に復旧した、ということになる。

トランザクションIDがリセットされると、システム管理テーブルの内容も見えなくなるので、テーブルのスキーマ情報なども得られなくなる。が、トランザクションIDを進めていくことで、システム管理テーブルの内容も回復し、スキーマ情報も得られるようになる。ただし、sequenceやindexなどのデータは完全に回復できないこともあるようだ。それらはスキーマと実データを使って、再生成・設定すればいいだろう。

私の場合は「select 1;」を100万回実行するたびに、oidをチェックし、対象テーブルの最新oidが増えなくなってからさらに念のため200万回ぶんトランザクションIDを進めた辺りで、だいたい元のデータが復旧したと判断した。そのあたりの判断はDBの設計によって異なるだろう。

_ 書評リンクの話 (20:11)

あちこちでやっていてポイントを抑えてリンク張るのが難しいんで、興味がある方は上記からあちこちたどってみてください。

で、俺の場合は、「さまざまな個人Webサイトにおける各自ばらばらな行動の中から、ある一定のベクトルをもつデータを抽出して整理して見せること」一般に対して興味があって、そういうベクトルの一つとしての「書評リンク」に興味があるという立場なんで、この手の話をしようとすると話が発散する方向に向かう可能性が高い。風呂敷がでかくなりすぎるというか。という言い訳をしてから話をはじめてみる。

まずは、個人Webサイトにおけるさまざまな情報を、Webサイト同士でやりとりするための、RSSみたいなXMLベースの汎用データフォーマットを決める。んでもって、主だったWeb日記システムコンテンツ管理システムが、そのフォーマットのデータを自動的に生成できるようにする。

それから、Webサイト間でそのデータをやりとりするためのインターフェース仕様を決める。できればシンプルな(実装しやすい)ものから、汎用性・機能性の高い(けどちょっと実装が面倒な)ものまで数パターンあるといい。あと、インターフェースの起動方法も複数用意しておいた方がいい。個人Webサイト側から送信するパターンとか、集約サーバー側からデータを引っ張ってくるパターンとか。起動に人間が必要なパターンと、必要ないパターンのどちらも用意しておいた方がいい。

そのあたりはアンテナ(サイト更新情報リンク)文化のやり方とかblog系システムのやり方を参考にすると良さそう。アンテナ文化ではLIRSとかDIとかみたいな、個人Webサイトで生成したデータを他のサイトで二次利用するという仕組みを実践的に使ってきている。あと、MovableTypeにおけるTrackBackを使ったサイト間をまたがったやりとりの仕組みとか、RSSを使ったデータ配信の仕組みとかが参考になる。

あんまり深く考えずに適当な仕様を試しに書いてみると、たとえば各個人Webサイトでは、書評を書いたら、各Web日記システムなどが以下のようなフォーマットのデータもはき出せるようにしておく。

<?xml version="1.0" encoding="utf-8" ?>
<bookreview>
 <site>
  <title>ishinao.net</title>
  <url>http://ishinao.net/</url>
 </site>
 <item>
  <updt>2003-01-20 23:54</updt>
  <isbn>4150306931</isbn>
  <title>ムジカ・マキーナ</title>
  <author>高野史緒</author>
  <comment></comment>
  <url>http://ishinao.net/WikiLike/?sid=147</url>
 </item>
</bookreview>

このあたりの項目に関しては、必須項目とオプショナル項目をいろいろ設定しておいて、あったらそれをちゃんと使うし、なくてもそれなりに扱えるようにしておくといい。上記は書評関連しか考えてないけど、本当はベースとなる汎用フォーマットを決めて、その上にジャンルごとの拡張フォーマットが乗ってくるようにするときれいだろう。

んでもって、それをどこかの書評リンクサイトに送信するインターフェースを用意する。実際にやりはじめると認証とかいろいろ絡んでくるけれども、ひとまず情報を受け渡すだけなら簡単だ。個人Webサイト側からPOSTしてもいいし、個人Webサイト側からはこういう情報を得るためのURLだけを渡してやって、実際のデータは書評リンクサイト側から引っ張っていってもいい。

あ、Web日記システムを使っていない人向けに、集約サイト側の方で普通のフォームから必要な情報をその場で入力してPOSTするようなインターフェースも用意しておいた方がいいだろうね。

書評リンクサイト側の内部では、受け取ったデータをどういう形式で保存管理しても構わない。けれども、書評リンクサイト側でも必ず同様の形式でデータを出力できるようにしておく。んでもって、各個人サイト側からの要求に応じて、必要なデータを個人サイト側にも返してやると、個人Webサイト側で他のサイトのデータを再利用することも可能になる。ってのはあくまでもデータの二次利用性を高めるための仕組みであって、もっともメインとなるのは書評リンクサイト上でそこに集約されている情報を見せるリンク集ページ部分だろう。

んでもって、書評リンクサイト上に集約された情報に含まれるキーワード(=ISBNもしくは著者名もしくは作品名)をベースにしたコミュニケーションツール(それがWikiであってもBBSであって構わない)とかを用意すると「はてなダイアリー的なものをグローバルに(http://d.hatena.ne.jp/ishinao/?date=20030203#p1)」な感じになってくる。書評に限らずノンジャンルで情報を集約するようにすればそのままはてなダイアリーっぽいし、各ジャンルごとにさまざまな集約サーバーを立てて独自の文化圏を作っていっても面白そうだ。

ってな感じのものを(ひとまず動くように)作るのはそんなに難しくないけれども、将来性のあるまともな仕様を決めたりとか、各Web日記システム用のプラグインを作ったりとか、参加者を集めたりとか、本気でやろうとすると結構大変そうだね。いろんな言語用のサンプルコードを書いたりとかも必要だし、まじめに集約サーバーを運用するとなるとお金の心配とかもしないといけないし。