構文解析の過程では、生成されるページの複雑性を把握するためにいくつかのカウンターが使用されます。カウンターは解析開始時点でゼロに設定され、解析中に該当するプロセスがあると数値が計上されていきます。カウンターには上限値が設定されており、上限値を越えると展開ができなくなります。
極端に長いページや[[Help:テンプレート|テンプレート]]をたくさん呼び出しているような複雑なページは解析に時間がかかります。このことは利用者にとって不便なだけでなく、処理不可能なほど巨大なデータを MediaWiki に解析させることで[[DoS攻撃]]の手段として悪用される可能性もあります。このような攻撃を予防し、ページの読み込みが常識的なスピードで行われることを保証するために、このような制限が設定されています。読み込みデータの制限は、2006年8月より導入されました。なお、2008年12月から英語版などで新しいプリプロセッサが導入され、制限の方法が変更されてました。今後他のプロジェクトにも漸次拡大が予定されています([[meta:Migration to the new preprocessor]]参照)。日本語版ではURLで指定することでこのプリプロセッサを一時的に使うことができます([[#読み込み量]]参照)。
== 制限がおこりやすい事例 ==
生成されたページ本体のHTMLソース中には、ソースの比較的末尾にHTMLコメントによってカウンターの数値が描き込まれています。そこで、ブラウザのHTMLソースを表示する機能を利用してHTMLソースを確認することで、そのページのカウンターの数値を知る事ができます。
例えば、[[沖縄県]](<small>[http://ja.wikipedia.org/w/index.php?title=沖縄県&oldid=1808022618235452 2008年2月1826日 (月火) 14:3615 (UTC) の版]</small>)のページの生成されたHTMLソースにはつぎのコメントが含まれています。
<pre>
<!--
Pre-expand include size: 68326/2048000 bytes ▼
Preprocessor node count: 1346181477/1000000 ▼
Post-expand include size: 43390/2048000 bytes
TemplatePost-expand argumentinclude size: 1360145830/2048000 bytes
▲Pre-expandTemplate includeargument size: 6832610079/2048000 bytes
#ifexist count: 1/500
--></pre>
</pre>
/の前の数値が計上された数値、/の後の数値が制限値です。カウンターの仕様により、表示される数値は常に制限値よりも小さくなります。もし/の前の数値が制限値(/の後の数値)に近ければ、読み込まれなかったテンプレートがある可能性があります。呼び込まれなかったテンプレートがある場合、この旨を伝えるエラーメッセージがHTMLソース中に表示されます。例えば、[http://ja.wikipedia.org/w/index.php?title=皇室の系図一覧&oldid=15529665 皇室の系図一覧 2008年1月16日 (水) 07:10 (UTC) の版] を見てみましょう。カウンターの数値は
<pre>
<!--
Pre-expand include size: 2047788/2048000 bytes
Post-expand include size: 395045/2048000 bytes
Template argument size: 717271/2048000 bytes
#ifexist count: 1/500
-->
</pre>
となっており、読み込みに失敗してテンプレートへのリンクだけが示されている部分のHTMLソースにはそれぞれ “<tt><!-- WARNING: template omitted, pre-expand include size too large--></tt>” のコメントが書き込まれていますね。
== 読み込み制限の仕組み ==
解析の過程で使われる制限には、「展開前読み込み量プリプロセッサ・ノード数」と、「展開後読み込み量」、「テンプレート引数量」、「#ifexixt 使用回数」の4種があります(新プリプロセッサでは「展開前読み込み量」の代わりに「プリプロセッサ・ノード数」を使用)。
新プリプロセッサで採用されている「プリプロセッサ・ノード数」(Preprocessor node count) のカウンターは新プリプロセッサから採用されました。これは、データの量ではなく、ページの複雑性を計測しています。解析によってページが展開される時には、HTMLの構造に対応する「ツリー」と呼ばれるデータ構造を生成しますが、このツリーのノードの数を計るものです。このカウンターが制限値を越えると、解析が中止され、生成HTMLページ中に "Node-count limit exceeded" のエラーメッセージを生成します。ノード数にはテンプレートだけでなく、リンク、セクション見出し、HTML要素などがすべて計上されています。 ▼
ページ内のソースコードに従って、テンプレートの展開を行うための構文解析がはじまると、ソフトウェアはまず、展開前のテンプレートのソースコードの長さ(展開前読み込み量(pre-expand include size))を、読み込み先のページの「展開前カウンター」に計上します。このとき、もし、計上後の数値が「展開前読み込み制限値」を越えていれば、テンプレートは展開されず、生成されたHTMLソース中にコメントとしてエラーメッセージが出力されます。 ▼
計上後の数値が「展開前読み込み制限値」内であれば、「展開前カウンター」の数値が新しい値に設定され、テンプレートが展開されます。テンプレート展開後には、テンプレートによって生成されるHTMLソースコードの長さ(展開後読み込み両(post-expand include size))が、「展開後カウンター」に計上されます。もし計上後の数値が「展開後読み込み制限値」を越えていれば、テンプレートはページ内に読み込まれず、生成されたHTMLソース中に別のエラーメッセージが出力されます。数値が「展開後読み込み制限値」内であれば、テンプレートによって生成されたHTMLコードがページ本体のHTMLの中に組み込まれ、「展開後カウンター」の数値が新しい値に設定されます。
▲ソフトウェアはページ内のソースコードに従って 構文解析を行い、テンプレート の展開を 行うための構文解析がはじまると、ソフトウェアはまず、展開 前します。この 時、テンプレート のによって生成されるHTMLソースコードの長さ(展開 前後読み込み量 (pre(post-expand include size)) をが、 読み込み先のページの「展開 前後カウンター」に計上 しされます。 このとき、もし 、計上後の数値が「展開 前後読み込み制限値」を越えていれば、テンプレートは 展開さページ内に読み込まれず、生成されたHTMLソース中に コメントとしてエラーメッセージが出力 されます。数値が「展開後読み込み制限値」内であれば、テンプレートによって生成されたHTMLコードがページ本体のHTMLの中に組み込まれ、「展開後カウンター」の数値が新しい値に設定されます。
テンプレートは再帰的に展開されるため、もし読み込まれているテンプレート自体に他のテンプレートが呼び出されている場合、呼び出されている下位のテンプレートの値も、最終的に呼び出されているページのカウンターの数値に影響します。そのため、下位のテンプレートを呼び出している途中に制限値を超過した場合、テンプレートの展開が途中でとまってしまうこともありえます。
#例えば、テンプレート 上記の仮定は<code><nowiki>{{A}}</nowiki></code> に <code><nowiki>{{B}}{{B}}{{B}}{{B}}{{B}}</nowiki></code> が含まれており、 「展開後カウテン タプレー 」ト <code><nowiki>{{B}}</nowiki></code> の ことを考慮ソースに いは、100バイトのプレインテキストが含まれてい るとしま せんす。 ここで、テンプレート <code><nowiki>{{A}}</nowiki></code> を呼び出す前の段階で、展開後カウンターに余裕が150バイトしかなかったと仮定します。この時、テンプレート <code><nowiki>{{B}}</nowiki></code> の展開が2回行われた後のテンプレート <code><nowiki>{{A}}</nowiki></code> が生成するHTMLソースは200バイトを越えますので、結果としてテンプレート <code><nowiki>{{A}}</nowiki></code> は呼び出し先のページに表示されません。 ▼
また、カウンターはテンプレートがページに読み込まれるたびに増加しますので、1ページ内に何度も使用されているテンプレートがあった場合、読み込まれている回数分、カウンターの数値が増加することになります。
新プリプロセッサの導入前は、テンプレートの展開前のデータ量も計測しており、これがテンプレートの制限でもっとも問題になっていました。[[特別:ParserDiffTest]]は古いプリプロセッサと新しいプリプロセッサの出力結果の違いを差分表示する特別ページです。/を使って直接解析ページを指定したリンクも作れます(例:[[特別:ParserDiffTest/皇室の系図一覧]]。リンク先は非常に重いページとなっていますので、クリックする際にはご注意下さい)。 ▼
例えば、テンプレート <code><nowiki>{{A}}</nowiki></code> に <code><nowiki>{{B}}{{B}}{{B}}{{B}}{{B}}</nowiki></code> が含まれており、テンプレート <code><nowiki>{{B}}</nowiki></code> のソースには、100バイトのプレインテキストが含まれているとします。この時、
# テンプレート <code><nowiki>{{A}}</nowiki></code> の呼び出しは、呼び出し先のページの「展開前カウンター」に25バイトを計上します。25を計上すると展開前カウンターが制限値を越えてしまう場合、テンプレート <code><nowiki>{{A}}</nowiki></code> は展開されません。
# テンプレート <code><nowiki>{{A}}</nowiki></code> を呼び出す前の段階で、展開前カウンターに余裕が250バイトあったと仮定します。この時、テンプレート <code><nowiki>{{A}}</nowiki></code> が展開されると、カウンターの残りは225バイトですから、最初の2回のテンプレート <code><nowiki>{{B}}</nowiki></code> は展開されますが、残りの3回は制限値を越えるため、展開されません。
▲# 上記の仮定は、「展開後カウンター」のことを考慮にいれていません。ここで、テンプレート <code><nowiki>{{A}}</nowiki></code> を呼び出す前の段階で、展開後カウンターに余裕が150バイトしかなかったと仮定します。この時、テンプレート <code><nowiki>{{B}}</nowiki></code> の展開が2回行われた後のテンプレート <code><nowiki>{{A}}</nowiki></code> が生成するHTMLソースは200バイトを越えますので、結果としてテンプレート <code><nowiki>{{A}}</nowiki></code> は呼び出し先のページに表示されません。
テンプレートの制限でもっとも問題になりやすいのが、「展開前読み込み量」の制限です。英語版で導入されている新プリプロセッサでは「展開前読み込み量」の制限はなく、「展開後読み込み量」のプロセスだけが行われます。このため、現在日本語版でテンプレートの読み込みに失敗している場合、 新しいプリプロセッサを使えば読み込める場合があります。これは <code>&timtest=newpp</code> の引数をURLに追加するか、[[特別:ParserDiffTest]]を使って確認できます({{[[Template:Npp|Npp]]}}のテンプレートを使用すると、<code>&timtest=newpp</code> の引数を追加したURLへのリンクを簡便に表示させることができます)。
例えば上の読み込みに失敗している例で取り上げた皇室の系図一覧のページを <code>http://ja.wikipedia.org/w/index.php?title=皇室の系図一覧&oldid=17394772&timtest=newpp </code>というURLを使って表示させると、新しいプリプロセッサが一時的に使われ、テンプレートはすべて読み込みに成功しています。ちなみにカウンターの数値は
<pre>
<!--
▲Preprocessor node count: 134618/1000000
Post-expand include size: 711668/2048000 bytes
Template argument size: 325381/2048000 bytes
#ifexist count: 1/500
-->
</pre>
となっています。
▲[[特別:ParserDiffTest]]は古いプリプロセッサと新しいプリプロセッサの出力結果の違いを差分表示する特別ページです。/を使って直接解析ページを指定したリンクも作れます(例:[[特別:ParserDiffTest/皇室の系図一覧]]。リンク先は非常に重いページとなっていますので、クリックする際にはご注意下さい)。
また、新プリプロセッサでは、条件文を用いたテンプレートの実行されない部分を展開しません。例えば、<code><nowiki>{{#if:yes|{{bar}}|{{foo}} }}</nowiki></code>というコードがあったとすると、テンプレート <nowiki>{{bar}}</nowiki> は展開され、テンプレート <nowiki>{{foo}}</nowiki> は展開されません。ただし、最終的な出力結果には表れないテンプレートの引数が展開後読み込み量に計上されることがあります。例えば <code><nowiki>{{#if:{{foo}}|yes|no}}</nowiki></code> というコードがあったとすると、解析の際に条件文の決定のためにテンプレート <nowiki>{{foo}}</nowiki> の展開が必要なため、 <nowiki>{{foo}}</nowiki> の展開量が展開後カウンターに計上されます。
=== #ifexist 使用回数 ===
<nowiki>#</nowiki>ifexist 使用回数の制限は、[[Help:条件文|条件文]]中の #ifexist の使用回数を制限するものです。この機能は、指定されたページが存在するかどうかで出力を切り替えるのに使用されます。使用回数が制限値を超えると、それより後の #ifexist では、ページの存在はすべてないものとして返されます。
▲新プリプロセッサで採用されている「プリプロセッサ・ノード数」(Preprocessor node count)は、データの量ではなく、ページの複雑性を計測しています。解析によってページが展開される時には、HTMLの構造に対応する「ツリー」と呼ばれるデータ構造を生成しますが、このツリーのノードの数を計るものです。このカウンターが制限値を越えると、解析が中止され、生成HTMLページ中に "Node-count limit exceeded" のエラーメッセージを生成します。ノード数にはテンプレートだけでなく、リンク、セクション見出し、HTML要素などがすべて計上されています。
== 制限内でやりくりするには ==
=== noinclude と onlyinclude タグおよびコメントの使用 ===
テンプレートの展開時には、テンプレートのウィキソースの全体量が展開前カウンターに計上され、この時、データが<noinclude> や <includeonly> 、 <onlyinclude> タグに囲まれているかどうかは無関係です。しかし、<noinclude> タグに囲まれている部分、もしくは <onlyinclude> タグの外にある部分は呼び出し先に展開されませんから。またHTMLコメントも展開されません。したがって、これらの部分は「展開後カウンター」の数値には一切影響を与えませんが、新プリプロセッサ導入前に用いられていた、「展開前カウンター」には計上される時ていました。このカウンターの制限にはよりテンプレートが展開前されない場合が少なくなかったため、[[Help:テンプレートのデ説明文|テンプレートの説明文]]を /doc というサブページに切り離し、<noinclude> タ量分だけが計上グで囲んでテンプレート本体に読み込むようにすることで、テンプレートの読み込みサイズを小さくすることが行われ、ていまた「す。新プリプロセッサでは展開後前カウンター」を使用しないの数値には一で、説明文が切り離されているかどうかは読み込み制限にほとんど影響を与えがなくなりませんす。詳しくは[[Help:テンプレートの説明文]]を参照してください。
例えば、<code><nowiki>{{C}}</nowiki></code> には <code><nowiki><noinclude>{{D}}</noinclude></nowiki></code> が含まれ、 <code><nowiki>{{D}}</nowiki></code> には500バイトのデータが含まれており、呼び出し先のページの「展開前カウンター」に250バイトの余裕がある時、テンプレート <code><nowiki>{{C}}</nowiki></code> は問題なく読み込まれ、ページの「展開前カウンター」には28バイトしか計上されません。
[[Help:テンプレートの説明文|テンプレートの説明文]]は、多くの場合、<noinclude> タグで囲んで提供されます。テンプレート呼び出しの際には、これらの部分の展開前のウィキテキストのデータ量しか展開前カウンターには計上されませんから、テンプレートの説明文を /doc というサブページに切り離し、テンプレート本体に読み込むようにすることで、テンプレートの読み込みサイズを小さくすることができます。詳しくは[[Help:テンプレートの説明文]]を参照してください。ただし新プリプロセッサでは展開前カウンターを使用しないので、説明文が切り離されているかどうかは読み込み制限にほとんど影響がなくなります。
HTMLコメントも展開前カウンターには計上されますが、展開後カウンターには計上されません。
=== 条件文の使用 ===
<nowiki>{{#ifexpr}}</nowiki> などの[[Help:条件文]]を用いて、テンプレートの読み込みを制限することが可能です。この時、条件文の書き方によっては、条件文の結果によってテンプレートが読み込まれなくても、テンプレートのソースのデータ量は呼び出し先のページのカウンターに計上されます。これは、条件文を展開する前に、条件文の内容が読み込まれるためです。例えば、
<nowiki>{{#ifexpr:0|{{:123}}}}</nowiki>
では、結果は自明に偽なので、ページ [[123]]は読み込まれませんが、ページ[[123]]のデータ量は、「展開前カウンター」に計上されます。
この問題を解決するためには、呼び出しタグを条件文の外に出します。
<nowiki>{{ {{#ifexpr:0|:123|x0}} }}</nowiki>
この場合も、ページ[[123]]は読み込まれませんが、条件文中に呼び出しタグがないため、条件文の計算前に[[123]]が読み込まれることはなく、中身が空のテンプレート<nowiki>{{</nowiki>[[:en:Template:x0|x0]]}}が呼び出されるだけのため、結果としてカウンターにはなにも計上されません。
=== 配列の各要素にテンプレートを使う ===
巨大な[[配列]](例えば要素が100以上のもの)を用いる時には、各要素に別のテンプレートを用意した方がよいでしょう(例:[[:meta:Template:Eln de]])。このようにしておけば、配列に含まれている要素を1回ずつ呼び出しても、その展開前読み込みサイズは配列全体のサイズの一次に留まります。しかし、もし全ての要素を #switch を用いて1つのテンプレートに収めると(例えば[[:meta:Template:N en]])、各要素を呼び出した時の展開前読み込みサイズは配列全体のサイズの倍数になってしまい、あっというまに制限値に達してしまうでしょう。
ただし、100の要素を含む1つのテンプレートのかわりに、1つの要素を持つ100のテンプレートを作るのも大変ですので、例えば10の要素を持つテンプレートを10作る、というような方策もあるでしょう。
=== 同じテンプレートの呼び出しを避ける ===
|