Seaside Laboratory

Post

意外と奥が深い CGI でのページング

うちの掲示板はあまり使われていないので負荷は無いに等しいと思っていたが、スパムボットがクローリングと投稿を繰り返すので、思いのほか負荷が高い。そこで掲示板をより高速に処理できる静的 HTML 版に作り変えることにした。

掲示板は投稿より閲覧の方が多いという性質があるので、投稿が行われた時にログを HTML ファイルに書き出しておけば、閲覧は HTML の転送だけで済み、サーバの負荷を軽減することが出来る。投稿時の負荷が少し高くなってしまうというデメリットはあるが、頻度は低いのでトータルで見れば問題はない。

静的 HTML 化をすると動的に行っていたページング処理はできなくなってしまうので、トップページだけでなく、過去ログも HTML 化しておく必要がある。プログラム的にはトップページも過去ログも連続したページに過ぎないので、要は全てのページを出力しておけばいい。

# ページ数の算出
ページ数 = ceil( 投稿件数 / ページ当たりの件数 );

# 全ページを出力
for ( $page = 1; $page <= ページ数; $page++ )
{
    # 対象のページを出力
    output( $page );
}

一見、問題なさそうなコードに見えるが、投稿件数が 0 だった場合は出力処理を一度も通ることなくプログラムが終了する。トップページが無い状態になると投稿フォームを表示できないので、投稿ができないという困った事態になってしまう。

掲示板のようなプログラムでは投稿が無い状態でもページ数に最低 1 が保証される計算の方が都合が良い。ページ処理に関するノウハウはライブラリから学ぶのが手っ取り早いだろうということで、PHP と Perl 向けに作られたライブラリのソースを解析してみることにした。

まずは、PEAR にある Pager というライブラリ。

function _generatePageData()
{
    // Been supplied an array of data?
    if (!is_null($this->_itemData)) {
        $this->_totalItems = count($this->_itemData);
    }
    $this->_totalPages = ceil((float)$this->_totalItems / (float)$this->_perPage);
    $i = 1;
    if (!empty($this->_itemData)) {
        foreach ($this->_itemData as $key => $value) {
            $this->_pageData[$i][$key] = $value;
            if (count($this->_pageData[$i]) >= $this->_perPage) {
                $i++;
            }
        }
    } else {
        $this->_pageData = array();
    }

    //prevent URL modification
    $this->_currentPage = min($this->_currentPage, $this->_totalPages);
}

$this->_totalPages がページ数を表す変数で、計算方法は先ほどの例と大した違いはなく、投稿がなければページ数は 0 になる。

次は、CPAN にある Data::Page というライブラリ。

sub last_page {
    my $self = shift;

    my $pages = $self->total_entries / $self->entries_per_page;
    my $last_page;

    if ( $pages == int $pages ) {
        $last_page = $pages;
    } else {
        $last_page = 1 + int($pages);
    }

    $last_page = 1 if $last_page < 1;
    return $last_page;
}

$last_page がページ数を表す変数だが、最後の方でページ数が 1 未満の場合は 1 に補正する処理があるので、どんな場合でも 1 ページが保証される作りになっている。

ライブラリは多くの人が使うものなので、言語は違えど同じような内容になると思っていただけに、この方針の違いは興味深い。

perldoc に書かれている Data::Page の覚書が印象的だったので、ついでに引用しておく。

CPAN モジュールにしてはこのコードは "単純すぎやしないか" とは、以前から言われてきたことだ。
だが、私は同意しかねる。この種のコードを何度も何度も書いては、いつも間違えている人々を見てきた。
おそらく、こうしている今も彼らはコードを直すために、さらなる時間を 費やしているだろう……