2012年11月29日木曜日

PostgreSQLのSQLチューニングについて

青柳です。

PostgreSQLのSQLをチューニングするのに役に立つドキュメントは本家のドキュメントです。
ただし、本家のドキュメントは読みにくいところがあるので、用語の解説とわかりやすい説明のリンクをつけました。
本家を読みながらか、先にこちらを読んでから本家を読むとよいです。
あと、実際のチューニング例のリンクも追加しました。

第 14章性能に関するヒント
http://www.postgresql.jp/document/9.2/html/performance-tips.html

■用語
・シーケンシャルスキャン
テーブルを順に走査していく。テーブルの多数を読む場合、インデックススキャンよりも有効。インデックススキャンはインデックス用のデータ領域にアクセスするので余分にアクセスすることになる。例えば本の全てを読むのに一々目次を見ながら読むのは効率的では無いのと一緒。

・インデックススキャン
インデックス用のデータを検査し、本体のテーブルにアクセスする。検索する件数が少ない場合有効。例えば本で目的の章のみ見たい場合、全部読むよりも目次からページを探す方が有効なのと一緒。

・ビットマップインデックススキャン
テーブルの行に対応する位置を0/1で表現して検査する。フラグなど識別する値が少ないと(カーディナリティが低い)有効。


・ネステッドループ結合
2つのテーブルを行単位で逐次比較。
データが小さい場合向いている

・マージ結合
2つのテーブルをソートしておいてから(もしくはBtreeインデックスを使って)結合。
データ量が多い場合向いている

・ハッシュ結合
ハッシュ表を作成しておいてから2つのテーブルを結合
ソートメモリが多い場合向いている


[PostgreSQLウォッチ]第17回 新しい実行プラン・タイプによるPostgreSQL 8.1の性能向上
http://itpro.nikkeibp.co.jp/members/ITPro/oss/20050514/160833/?ST=oss&P=1
ビットマップスキャンについて詳しく説明している

基礎から理解するデータベースのしくみ(9)
http://itpro.nikkeibp.co.jp/article/COLUMN/20060113/227241/
ビットマップスキャンに対する図がわかりやすい

Oracle SQLチューニング講座(3)
SQLチューニングの必須知識を総ざらい(後編)
http://www.atmarkit.co.jp/fdb/rensai/orasql03/orasql03_1.html
結合の図がわかりやすい

■チューニングの例

Linuxトラブルシューティング
第3回 PostgreSQLを遅くしている犯人はどこだ?
http://www.atmarkit.co.jp/flinux/rensai/troubleshoot03/ts03c.html
わかりやすいチューニングの例

20090107 Postgre Sqlチューニング(Sql編)
http://www.slideshare.net/kwappa/20090107-postgre-sqlsql-presentation#btnNext
いささか古い記述があるがわかりやすい


2012年11月9日金曜日

Yiiフレームワークのテーマとレイアウト機能


高瀬です。

YiiはPHPでWebアプリケーションを開発するためのMVCフレームワーク。以下のWebサイトを参照。

初めに: Yii とは何か | The Definitive Guide to Yii | Yii Framework

Yiiでの開発を始めて半年ほどではあるけれども、使おうと思っても触れることができなかった機能に、「テーマ」と「レイアウト」がある。ここではそれらの使い方を考えてみる。


テーマの作成

「テーマ」はWordPressのテーマのように、Webサイトの見栄えを切り替えるために利用する。以下を参照。

その他のトピック: テーマ | The Definitive Guide to Yii | Yii Framework

実際に使ってみよう。試しに、Yiiが自動生成する初期プロジェクトの見栄えを変えるテーマを作成してみる。

[初期プロジェクトのテーマ]



[変更後のテーマ]




変更後のテーマには「uniquevision」という名前を付けることとする。まず最初にすることは、ページのレイアウトを決めているファイルmain.phpを、新しく作るテーマ用にコピーすることだ。

(basePath)/protected/views/layouts/main.php

(basePath)/themes/uniquevision/views/layouts/main.php

※basePathは、プロジェクトのルートパスを指していることとする。

テーマはthemesというディレクトリ内に作成する。作成したディレクトリの名前がテーマの名前となる。

では、uniquevisionテーマのカスタマイズをしてみよう。コピーしたmain.phpを編集して、ページの左上に表示されている「My Web Application」の表示を、ユニークビジョンのロゴ画像に変更してみる。

ロゴ画像のファイルはテーマごとのディレクトリ内に配置したいので、以下のパスを作成し、保存。その上で、パスをビューに記述する。

(basePath)/themes/uniquevision/images/ti_logo.gif

(変更前)
<div id="header">
    <div id="logo"><?php echo CHtml::encode(Yii::app()->name); ?></div>
</div><!-- header -->

(変更後)
<div id="header">
    <div id="logo"><img src="<?= $this->themeBaseUrl; ?>images/ti_logo.gif" /></div>
</div><!-- header -->

テーマのディレクトリに配置した画像ファイルのパスを指すには、Yii::app()->theme という変数を利用することができる。テーマのパスを参照しやすいように、Controllerクラスで以下のように変数を定義した。

class Controller extends CController
{
:
  public $themeBaseUrl = '';

public function isThemeUsed() { return !is_null(Yii::app()->theme); }

  public function init()
  {
  parent::init();
  if ($this->isThemeUsed()) {
  $this->themeBaseUrl = Yii::app()->theme->baseUrl . DIRECTORY_SEPARATOR;
  }

        Yii::app()->clientScript->registerCoreScript('jquery');
  }

    :


$this->themeBaseUrl という変数を使用して画像ファイルへのパスを作っている($thisはコントローラのインスタンス)。

さて、画像ファイルのパスの切り替え派できたが、CSSはどうするか。これもテーマごとのディレクトリ内に配置したい。とりあえず元のCSSファイルがあるディレクトリをコピーしてしまおう。

(basePath)/css

(basePath)/themes/uniquevision/css

コピー後のCSSファイルを参照するには、やはり$this->themeBaseUrl を使用する。レイアウト用のmain.php内では、元は Yii::app()->request->baseUrl という変数でパスを生成しているので、これを書き換える。

(変更前)
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/main.css" />

(変更後)
<link rel="stylesheet" type="text/css" href="<?= $this->themeBaseUrl; ?>css/main.css" />

main.css を編集しておくと、テーマによりデザインが切り替わることが確認できる。

最後に、新しく作成したテーマ uniquevision を有効にする。有効にするには、config/main.phpにテーマの名前を記述する。

(basePath)/protected/config/main.php

return array(
    'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
    'name'=>'My Web Application',
    'theme'=>'uniquevision',
    :


clientScriptの設定によるテーマごとのJavaScriptとCSS

views/layouts/main.php 内に記述されたCSSのファイルパスは前述のようにテーマごとに切り替えることができた。では、clientScriptで指定するJavaScriptやCSSの場合はどうだろうか。

例えば、すべてのテーマで共通に「jQuery Alert Dialogs」を使いたいとする。その上で、テーマ個別のJavaScriptも使いたいとする。

config/main.php で、以下のように定義してみた。

(basePath)/protected/config/main.php

return array(
    :
    'components'=>[
        :
        'clientScript'=>[
            'packages'=> [
                'common' => [
                    'baseUrl' => '',
                    'js' => [
                        'js/jquery.alerts.js',
                        'js/sample.js'
                    ],
                    'css' => [
                        'css/jquery.alerts.css',
                    ],
                ],
                'uniquevision' => [
                    'baseUrl' => 'themes/uniquevision/',
                    'js' => [
                        'js/sample_uv.js'
                    ],
                    'css' => [
                    ],
                ],
            ],
        ],
        :

packages内、common はどのテーマでも利用するもの、uniquevision は uniquevision テーマが有効になっている時に common に加えて利用するもの、とする。これらを読み込むために、コントローラに以下のメソッドを定義する。

class Controller extends CController
{
    :
    public function registerPackage($js = [], $css = [])
    {
        $depends = ['common'];

        if ($this->isThemeUsed()) {
            $depends []= Yii::app()->theme->name;
        }

        Yii::app()->clientScript->packages[Yii::app()->name] = [
            'baseUrl' => '',
            'js' => $js,
            'css' => $css,
            'depends' => $depends
        ];
        Yii::app()->clientScript->registerPackage(Yii::app()->name);
    }

depends の指定において、common は常に読み込み対象とし、テーマが設定されている場合にだけ、テーマと同じ名前のパッケージも読み込み対象としている。

このメソッドをアクションから呼び出せば、config/main.phpに記述したJavaScriptとCSSがビューの<head>内に取り込まれる。アクションからの呼び出しは以下のとおり。

class SiteController extends Controller
{
    public function actionIndex()
    {
        $this->registerPackage();

        $this->render('index');
    }


デフォルトのテーマ用とuniquevisionテーマ用にそれぞれJavaScriptを用意し、jQuery Alert Dialogsでメッセージボックスを表示させるなどすると、どちらのテーマでも動作することを確認することができるだろう。

アクション個別で利用したいJavaScriptやCSSがある場合はどうすればいいだろうか。

上記のControllerクラスに定義したメソッド registerPackage() は、引数で追加のJavaScriptとCSSを指定できるようにしてある。ここに必要なファイルを指定することにする。

    public function actionIndex()
    {
        $basePath = $this->isThemeUsed() ? 'themes/' . Yii::app()->theme->name . '/' : '';
        $js = [
            $basePath . 'js/index.js'
        ];
        $css = [];
        $this->registerPackage($js, $css);

        $this->render('index');
    }

上記はテーマが指定されていればそのテーマ用のJavaScriptを参照するようになっているが、どのテーマでも共通で使用するJavaScriptを指定したい場合もあるだろう。その場合は、$basePath で指定しているパスを付加しなければよい。

ここまで、ページのヘッダー/フッター部分やJavaScript、CSSの切り替えをしてきたが、この後はビューの内容をテーマで切り替えてみる。


レイアウトによる段組み

Yiiは初期プロジェクトで2種類の段組みを指定できるようになっている。これはControllerクラスの変数 $layout で指定されている。

class Controller extends CController
{
    :
    public $layout='//layouts/column1';
    :
    public $menu=array();
    :

$layout '//layouts/column2' を指定すると、2列の段組みでページが表示されるようになる。2列の場合、左側の列がメインのコンテンツを表示するエリア、右側の列がサイドバーのエリアになっている。

簡単に2列の段組みにできるのだが、通常、ビューで内容を描画できるのは左側のメインコンテンツ部分だけで、右側のサイドバーには上記Controllerクラスの変数 $menu で指定されるメニュー一覧で固定されてしまっている。任意の内容を表示させたくても、それを引き渡すためのパラメータが用意されていない。さて、どうしたものか。

実験として、初期プロジェクトのテーマでのログイン画面は1列、uniquevision テーマのログイン画面は2列の段組みとしてみよう。uniquevision テーマでは、サイドバー側にログイン用入力フォームを設置する。

ではまず、2列にした場合にサイドバーに描画させる内容をビューに引き渡すパラメータをControllerクラスに用意する。

class Controller extends CController
{
    :
    public $sidebarParams = null;
    :


次に、uniquevision テーマの2列用レイアウトのファイルをコピーする。

(basePath)/protected/views/layouts/column2.php

(basePath)/themes/uniquevision/views/layouts/column2.php

2列の場合、サイドバー側はパーシャルで描画するようにしよう。パーシャルの名前と、それに引き渡すパラメータは $sidebarParams で引き渡す。

(basePath)/themes/uniquevision/views/layouts/column2.php

<?php $this->beginContent('//layouts/main'); ?>
<div class="span-19">
    <div id="content">
        <?php echo $content; ?>
    </div><!-- content -->
</div>
<div class="span-5 last">
    <div id="sidebar">
        <?php $this->renderPartial($this->sidebarParams['partial']); ?>
    </div><!-- sidebar -->
</div>
<?php $this->endContent(); ?>


アクションでは以下のようにする。

class SiteController extends Controller
{
    public function actionLogin()
    {
        $model=new LoginForm;
        :

        if ($this->isThemeUsed()) {
            if ('uniquevision' == Yii::app()->theme->name) {
                $this->layout = '//layouts/column2';

                $this->sidebarParams = [
                    'partial' => '_login_form',
                    'model' => $model
                ];
            }
        }

        $this->render('login',array('model'=>$model));
    }

テーマが指定されている場合、2列の段組みにして、Controllerクラスの変数 $sidebarParams にパーシャルの名前と、ログイン用入力フォームで使用するモデルをセットしておく。それぞれ、column2.php と、そこから呼び出されるパーシャル(上記の場合は _login_form.php) で参照される。

左側のメインコンテンツはデフォルトのテーマの場合も、uniquevisionテーマの場合も login という名前のビューを描画する。ただし、デフォルトのテーマではメインコンテンツ側(loginビュー)に入力フォームを設置し、uniquevisionテーマではサイドバー側(_login_formパーシャルビュー)に入力フォールを設置。

これで、段組みが1列の場合でも2列の場合でもログイン用入力フォームを動作させることができた。

なお、layouts/column1.php は uniquevision テーマにコピーしていないが、ログイン画面以外の画面が初期プロジェクトと同様に1列の段組みで表示される。つまり、レイアウトやビューのファイルがテーマに定義されていない場合は、デフォルトのテーマ(protected/views以下のファイル)が使用され、差分となるファイルだけをテーマに定義すればよいことが分かる。


おわり

テーマもレイアウトもWebページの見栄えを制御するものだが、うまく使えば見栄えを切り替えるための分岐処理などは書かなくてもよくなりそうだ。しかし、テーマごとに同じ内容のソースファイルを設置してしまうとメンテナンス性がよくないので、どのように利用するかはプロジェクトごとに検討する必要がある。

ちょうどこの記事を書いている時に、あるプロジェクトで本番環境、ステージング環境、開発環境で画面の背景色を切り替えたい、という要件があり、一部テーマを利用した。活用できるケースは意外に多いのかもしれない。