システム更改では、表面上は同じ機能に見えても、裏側の処理方式が変わることがあります。
画面のボタンを押す。
帳票が印刷される。
ユーザーから見ると、それだけの機能です。
しかし、システムの裏側では、印刷命令をどのサーバから出すのか、どのミドルウェアを使うのか、プリンタまでどうジョブを届けるのか、どこまで確認したら「印刷済み」と判断するのか、といった設計が存在します。
今回扱うのは、まさにその落とし穴です。
Webアプリケーション上では「印刷済み」になっている。
しかし、実際にはプリンタから紙が出てこない。
現場では、プリンタ側のキューが消えたのではないか、プリンタ本体の不具合ではないか、ネットワークの問題ではないか、と大騒ぎになりました。
メーカーにも来てもらい、プリンタ側の状態も確認しました。
しかし、根本原因はもっと手前にありました。
アプリケーションが、lprコマンドの正常終了をもって「印刷済み」と判定していたことでした。
画面上は「印刷済み」なのに、紙が出てこない
今回のシステムでは、ユーザーがブラウザ上で印刷ボタンを押すと、APサーバ側で印刷処理が実行される構成でした。
大まかな流れは、次のようなものです。
ユーザーがWeb画面で印刷ボタンを押す
APサーバに印刷命令が送信される
APサーバ上で lpr コマンドを実行する
lpr が正常終了する
CUPSのキューに印刷ジョブが登録される
CUPSから物理プリンタへジョブが送信される
プリンタ側のキューに登録される
プリンタが順番に印刷する

この流れだけを見ると、特に難しい話には見えません。
しかし、ここに大きな誤解がありました。
アプリケーションは、APサーバ上で実行した lpr コマンドの終了コードを見ていました。 lpr が正常終了すれば、画面上は「印刷済み」と表示する。
一見すると、自然な実装に見えます。
しかし、実際には違います。
lpr の正常終了は、あくまで印刷ジョブの投入に成功したことを示しているにすぎません。 プリンタから紙が出たことを保証しているわけではありません。
lpr正常終了は「印刷完了」ではない
若手SEがこの手のトラブルでつまずきやすいのは、コマンドの正常終了を、業務上の正常終了と同一視してしまうことです。
lpr が正常終了した。 だから印刷できたはず。
これは技術的には雑な判断です。
lpr が正常終了しても、その後にはまだ複数の処理が残っています。
CUPS側のキューに残っているかもしれない
プリンタに送信中かもしれない
プリンタ側で紙詰まりになっているかもしれない
フィニッシャーでエラーになっているかもしれない
プリンタのHDDスプールに登録されていないかもしれない
大量印刷でキューが滞留しているかもしれない

つまり、lpr 正常終了の時点では、まだ「印刷が完了した」とは言えないのです。
にもかかわらず、アプリケーションが lpr の戻り値だけを見て「印刷済み」と表示していた。
これが、今回の直接的な原因でした。
実際にはCUPS側でキューが消えていた
詳細に調査すると、CUPS側のキューが一定時間後に削除されていました。
原因の一つは、CUPSの設定値です。
CUPSにはジョブの最大滞留時間に関する設定があります。
/etc/cups/cupsd.conf
MaxJobTime 1080010800秒、つまり3時間です。
通常の小規模な印刷であれば、3時間もキューに残ることは少ないかもしれません。
しかし、実運用ではそうとは限りません。
大量帳票が流れる。プリンタ側で紙詰まりが起きる。フィニッシャーでエラーが発生する。プリンタが応答しない。
こうなると、CUPS側のキューに印刷ジョブが長時間残ります。
その結果、CUPS側の設定によって、ジョブが削除される。
アプリケーション上では、すでに lpr が正常終了しているため「印刷済み」と表示されている。 しかし、実際にはプリンタまでジョブが届いていない。 当然、紙も出てこない。
ユーザーから見れば、これは完全な不整合です。
プリンタ障害時のリトライも設計対象である
さらに、プリンタ側でエラーが発生した場合のリトライ設定も問題になります。
CUPSには、次のような設定があります。
※設定名や有効なパラメータはCUPSのバージョンやディストリビューションによって異なるため、実際には対象環境のmanページ、設定ファイル、検証結果で確認する必要があります。
JobkillDelay 30
JobRetryLimit 5この場合、送信エラー時に一定回数リトライしたあと、ジョブが削除される可能性があります。
ここで重要なのは、設定値の良し悪しそのものではありません。
大事なのは、その設定値が業務要件と合っているかです。
数ページの印刷であれば、リトライ回数5回でも大きな問題にならないかもしれません。 しかし、業務帳票を大量に印刷するシステムでは話が変わります。
数万ページの帳票を流す。専用プリンタで連続印刷する。紙詰まりやフィニッシャー停止が起きる。復旧後に印刷を再開する必要がある。
このような運用では、プリンタ障害時にジョブが消える設計では困ります。
「プリンタが一時的に止まったら、そのジョブはどうなるのか」 「復旧後に再送されるのか」 「どこまでがサーバ側の責任で、どこからがプリンタ側の責任なのか」 「業務上、二重印刷と未印刷のどちらがより危険なのか」
ここまで考えて、はじめて印刷処理の設計になります。
プリンタ側にも同様の落とし穴がある
CUPSの設定を見直したあと、さらに厄介な問題が浮かび上がりました。
プリンタ側の動作です。
今回利用していたプリンタには、印刷順序に関するモードがありました。
データ受付順
データ処理順
通常は、どちらを選んでも大きな違いはないように見えるかもしれません。
しかし、実際にはモードによって挙動が異なりました。
あるモードでは、プリンタのHDDにスプールされていないにもかかわらず、サーバ側には正常なレスポンスを返す場合がありました。
サーバ側から見ると、送信成功です。 しかし、プリンタ側で確実に保持されているとは限らない。
さらに、プリンタのHDDスプール上限を超えると、メモリ上に印刷ジョブがエントリされる動作がありました。
この状態でプリンタを再起動すると、メモリ上のジョブは消えます。
ところが、サーバ側ではすでに送信済みとして扱っている。 その結果、サーバ側とプリンタ側で状態不一致が発生します。
メーカーとしては仕様の範囲と説明する類の挙動かもしれません。
しかし、業務システムを設計する側から見ると、サーバ側の完了認識とプリンタ側の実態がずれるため、非常に扱いづらい動作です。
こういう事象は、通常の機能試験では見つかりません。少量印刷では発生しないからです。
大量印刷、キュー上限、プリンタ再起動、紙詰まり、フィニッシャー故障、ネットワーク瞬断。 こういった条件を組み合わせて初めて見えてくる問題です。
今回とった対策
対策は、大きく三つです。
一つ目は、CUPS側のキュー滞留時間の見直しです。
MaxJobTime 00は無制限を意味します。大量印刷やプリンタ障害時に、一定時間でジョブが消えてしまうと、業務上の不整合につながります。業務要件に応じて、ジョブを保持する設計に見直しました。
二つ目は、送信リトライに関する設定の見直しです。
JobkillDelay 864000
JobRetryLimit 1000000実質的に、一定期間はジョブを保持し続ける想定です。
ただし、単に無制限にすればよいという話ではありません。ジョブが溜まり続ければ、サーバ側のディスク容量や運用監視にも影響します。リトライ設定を変えるだけでなく、キュー監視、滞留監視、運用手順、障害時の再印刷手順もセットで考える必要があります。
三つ目は、アプリケーションの「印刷済み」判定の見直しです。
lpr の正常終了を見て「印刷済み」とするのは早すぎます。
一方で、CUPS上のジョブ完了を確認したとしても、それだけで物理的に紙が出たことまで完全に保証できるわけではありません。
そのため、業務上どこまでを「印刷完了」とみなすのかを、設計として定義し直しました。
最低限、lpr の戻り値だけで判定する方式はやめ、CUPS上のジョブ状態を確認する方式に変更しました。
具体的には、lpstat コマンドでジョブの状態を取得します。
bash
lpstat -o実行中のジョブ一覧を返します。完了したジョブを含めて確認したい場合は、
bash
lpstat -W completed -oのように指定します。
アプリケーション側では、lpr 実行後にジョブIDを取得し、そのジョブが completed になったことを確認してから「印刷済み」と判定する設計に変更しました。
ただし、lpstat で completed を確認できたとしても、それはCUPS側の完了であり、物理的に紙が出たことの保証ではありません。完全な印刷完了保証を実装するには、プリンタ側のステータス取得やSNMP連携なども視野に入りますが、業務要件とコストのバランスを見ながら、どこまでの確認を「完了」とするかを設計段階で決めておくことが重要です。
「処理方式変更なし」に見えても、実は変更されている
今回の背景には、システム更改があります。
もともとは商用の帳票・印刷関連ソフトウェアを使っていたところから、OSSであるCUPSを利用する方式に変更されました。
ユーザーから見れば、機能は同じです。
印刷ボタンを押す。帳票が出る。それだけです。
そのため、要件としては「既存機能踏襲」と扱われがちです。機能要件も非機能要件も変わらない。だから、印刷処理についても大きな追加試験は不要。
こう判断してしまうと危険です。
画面上の機能は同じでも、裏側の処理方式が変わっているなら、システムの性質は変わっています。
商用ソフトウェアからOSSへ変わる。帳票基盤が変わる。ジョブ管理の仕組みが変わる。キュー管理の考え方が変わる。障害時のリトライや復旧動作が変わる。
これは、単なる部品交換ではなく、設計上は処理方式変更です。
処理方式が変わるなら、非機能要件の確認も必要です。特に、性能、拡張性、運用性、障害時動作は必ず見直すべきです。
大量印刷は、機能試験だけでは見抜けない
印刷機能の単体試験では、数ページの帳票を出して確認することが多いと思います。
ボタンを押す。帳票が出る。レイアウトが正しい。宛先や金額が正しい。文字化けしていない。
これはこれで必要です。
しかし、それだけでは足りません。
実運用では、数万ページの大量印刷が発生することがあります。担当者が一括で帳票を流すこともあります。特定の時間帯に印刷が集中することもあります。プリンタ側のキュー上限に到達することもあります。CUPS側のキューが滞留することもあります。
そうなると、数ページの機能試験では問題が出なかった仕組みが、実運用で破綻します。
今回も、まさにそのパターンでした。
既存機能として帳票が出ることは確認していた。しかし、大量印刷時にCUPSキューとプリンタキューがどう振る舞うかまでは十分に確認できていなかった。
その結果、長時間の印刷待ちが発生し、CUPS側でキューが削除され、画面上の「印刷済み」と実際の印刷結果が不一致になりました。
これは機能不具合というより、非機能要件と試験観点の不足です。
OSSを使うなら「知っている前提」で設計してはいけない
OSSの利用自体が悪いわけではありません。
CUPSも広く使われている印刷システムです。OSSを活用することで、コストを抑えられることもあります。
しかし、OSSを使う場合は、その特性を理解して設計する必要があります。
商用ソフトウェアには、製品として作り込まれた管理機能、保守サポート、導入実績、ナレッジがあります。トラブル時にも、ベンダーが一定の範囲で調査や回答をしてくれます。
一方、OSSでは、利用者側が仕様や設定値を理解し、運用に合わせて設計・検証する比重が大きくなります。
今回で言えば、印刷ジョブの保持、リトライ、キュー滞留、プリンタ障害時の扱い、印刷完了判定がそれにあたります。
OSS化は、設計責任と検証責任が利用側に寄ってくる場合があります。「OSSだから安くなる」だけで考えると、足元をすくわれます。
まとめ
今回のトラブルは、単なるCUPSの設定ミスではありません。
本質は、印刷処理における完了判定の設計ミスです。
lpr が正常終了したからといって、プリンタから印刷されたとは限りません。CUPSのキューに残っているかもしれません。プリンタ側で詰まっているかもしれません。ジョブが削除されているかもしれません。プリンタ再起動で消えているかもしれません。
それでもアプリケーションが「印刷済み」と表示してしまえば、ユーザーは印刷されたものとして業務を進めます。
この不整合は、業務システムでは危険です。
特に、商用ソフトウェアからOSSへ移行する場合、既存機能がそのまま使えるように見えても、裏側の処理方式や非機能要件が変わっている可能性があります。その場合は、単なる機能試験だけでは足りません。
大量印刷、キュー上限、プリンタ障害、復旧後の再送、印刷順序、ジョブ消失、二重印刷、未印刷。こうした観点を入れて試験する必要があります。
システム更改では、「画面上の機能が同じだから大丈夫」と考えてはいけません。
裏側の処理方式が変わるなら、設計も試験も変わります。
そして、若手SEほど覚えておいてほしいことがあります。
コマンドが正常終了したことと、業務が正常完了したことは同じではない。
「どの時点をもって業務上の完了とするのか」
「異常時にジョブをどう保持するのか」
「キュー滞留や印刷失敗をどう検知するのか」
「障害時に誰が、何を見て、どう復旧するのか」
こうした観点は、詳細設計の中でも、運用・監視・ログ・障害時対応に直結します。
詳細設計は、パラメータ表を埋める作業ではありません。
本番で止まったとき、調べられるか。
異常を検知できるか。
復旧できるか。
業務上の完了を説明できるか。
そこまで具体化して初めて、運用に耐える設計になります。
このあたりは、以下の記事でも詳しく書いています。
【前編】詳細設計で決めること|基本設計を「構築・テスト・運用できる形」に落とし込む工程
シリーズ全体は「実務で使えるシステム開発方法論」マガジンにまとめています。


コメント