質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.20%

サーブレットから動的に作成したCSVを遅延なく記録させたい。

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 3
  • VIEW 2,146

itmsd

score 16

前提・実現したいこと

サーブレットでCSVを作成させて遅延なくハードディスクへ記録させたいと思います。

発生している問題・エラーメッセージ

サーブレットを実行し、CSVを作成するのですが、実際にハードディスクに記録
されるまでに5秒程度の遅延が発生しています。

上記と同じことを、jdk1.4とtomcat3の組み合わせで実行しているときは、問題
なく運用できておりましたが、jdk8とtomcat8の環境に変えてから上記の問題が
発生するようになりました。

ソースコード問題、tomcat等の設定でお気づきの点がございましたら、
ご教示いただければ、幸甚にぞんじます。

該当のソースコード

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doAction(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doAction(request, response);
}

private void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession(true);
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma","no-cache");
request.setCharacterEncoding("UTF-8");

/*途中省略*/

synchronized (session) {
sc_ = getServletConfig().getServletContext();
connPool_ = (ConnectionPool)sc_.getAttribute("connPool");
loginUser = (SysUserBeans)session.getAttribute("loginUser");

String sceneStr = request.getParameter("scene");
int scene = -1;
if (sceneStr != null) scene = Integer.parseInt(sceneStr);
switch(scene) {
case 1: // 一覧
response.setContentType(CONTENT_TYPE);
showRows(request, response, session);
break;

/*途中省略*/

case 22:  // CSV作成
downloadCSV(request, response, session);
break;

default:  // 検索条件
response.setContentType(CONTENT_TYPE);
sc_.getRequestDispatcher("/view/sales/salesAggregateFrame.jsp").forward(request, response);
}
rows.setReqPrintUrl(null);
}
}

private void downloadCSV(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws ServletException, IOException {
SalesAggregateBeans rows = (SalesAggregateBeans)session.getAttribute("salesAggregate");
SysUserBeans loginUser = (SysUserBeans)session.getAttribute("loginUser");

String fileName;
fileName = sc_.getRealPath("/data/210.csv");
rows.setReqPrintUrl(response.encodeURL("/example/data/.csv"));

FileOutputStream fo = new FileOutputStream(fileName);
OutputStreamWriter ps = new OutputStreamWriter(fo, "SJIS");

int iMax = rows.getSize();

for (int i = 0; i < iMax; i++) {
rows.getRow(i);
ps.write("\"" + rows.getCd() + "\",\"");
ps.write(rows.getName() + "\",\"");
ps.write(rows.getAddress() + "\",\"");
ps.write(rows.getReadings() + "\",\"");
ps.write(rows.getSalesmanName() + "\",");
ps.write(rows.getCarryOver() + ",");
ps.write(rows.getSum(0) + ",");
ps.write(rows.getSum(1) + ",");
ps.write(rows.getSum(2) + ",");
ps.write(rows.getBalance());
ps.flush();
}
ps.close();
ps = null;

fo.close();
fo = null;
sc_.getRequestDispatcher("/view/sales/salesAggregate.jsp").forward(request, response);
}

試したこと

CSVの出力件数は、わずか5行でも、100行程度でも、5秒程度の遅延が発生しています。

補足情報(言語/FW/ツール等のバージョンなど)

・Windows10
・jdk8
・tomcat8
(サーブレットの設定は、インストール直後の状態です。)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • matobaa

    2016/09/06 18:27 編集

    バックアップも兼ねているのですか、難しい条件ですね……。オンザフライはご認識のとおりです。ところで salesAggregate.jsp からのダウンロードはただのリンクですか、それともDownloadServletのようにJavaで実現していますか? WebブラウザとTomcatサーバ、間に挟まってるキャッシュサーバやProxyサーバの時刻はずれてないでしょうか?

    キャンセル

  • itmsd

    2016/09/07 15:01

    ご返事が遅くなりまして、申し訳ありません。
    salesAggregate.jspからのダウンロードは、作成した210.csvへのリンクです。
    今のところサーバは同じ筐体で動いていますので、時刻のずれはないと思うのですが。

    キャンセル

  • matobaa

    2016/09/07 17:56

    Webブラウザとサーバの時刻ズレはないということでしょうか。だとするとJava以外のところでなにか悪さしているやつがいる可能性も考えないといけないですね。

    キャンセル

回答 2

0

手元の環境で再現できないため、推測による回答となりますが、、

ps.close();直前に以下の1行を追加してみてはいかがでしょうか?

fo.getFD().sync();


API リファレンスによると、OutputStream#flush()は(おそらくclose()も)
データを OS の書き込みバッファに引き渡すことを保証するだけで、デバイスそのものに書き込むことを保証するものではないようです。
https://docs.oracle.com/javase/jp/8/docs/api/java/io/OutputStream.html#flush--

このストリームの目的の転送先が、ベースとなるオペレーティング・システムによって提供される抽象化オブジェクト(ファイルなど)である場合、ストリームをフラッシュすることで、それまでにストリームに書き込まれたバイトがオペレーティング・システムに渡されて書き込まれることは保証されますが、ディスク・ドライブなどの物理デバイスに実際に書き込まれることは保証されません。

一方、FileDescriptor#sync()は、それを保証してくれるようです。
https://docs.oracle.com/javase/jp/8/docs/api/java/io/FileDescriptor.html#sync--

このFileDescriptorがファイル・システムのファイルのような物理記憶メディアを参照する場合、syncはこのFileDescriptorに関連付けられたバッファのメモリー内部での変更事項がすべて物理メディアに書き込まれるまでは復帰しません。


ちなみに、以下も推測にすぎませんが、

この現象が発生する原因は Java または Tomcat のバージョン違いによるものではなく、
アプリケーションからのファイル出力が多すぎて(例えばTomcat のデバッグログなど)
OS のI/O処理が追いついていない、ということも考えられるかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/09/06 14:25

    KiyoshiMotokiさん
    丁寧なご回答をいただき有難うございます。
    ご指示にあったように”ps.close();”の前に”fo.getFD().sync();”を挿入して実行
    しましたが、残念ながら現象に変わりはありませんでした。

    キャンセル

  • 2016/09/06 15:39

    返信ありがとうございます。

    解決しませんでしたか。。

    今のところ、私には他にアイデアはありません。
    何か思いつきましたら、またご連絡させていただきますね。

    キャンセル

  • 2016/09/06 16:49

    お忙しいところ、恐れ入ります。

    キャンセル

-3

OutputStreamWriter.html#flush

try (FileOutputStream fo = new FileOutputStream(fileName); 
     OutputStreamWriter ps = new OutputStreamWriter(fo, "SJIS")) {

    int iMax = rows.getSize();
    for (int i = 0; i < iMax; i++) { 
        // 処理中略
    } 
    ps.flush();
} finally {
    // http://docs.oracle.com/javase/jp/7/technotes/guides/language/try-with-resources.html
    // autocloseable (try-with-resources)
    // この例で、try-with-resources 文には 2 つの宣言 ZipFile および BufferedWriter が含まれており、セミコロンで区切られています。
    // その直後のコードブロックが正常に終了または例外によって終了した場合、BufferedWriter オブジェクトと ZipFile オブジェクトの close メソッドが、この順序で自動的に呼び出されます。リソースの close メソッドは、作成時とは逆の順序で呼び出されます。
}

のように flush したらどうでしょう?

 追記

っと Javadoc (close) みると

ストリームを最初にフラッシュして、閉じます。ストリームが閉じられたあとにwrite()またはflush()を呼び出すと、IOExceptionがスローされます。すでに閉じられているストリームを閉じても、何の影響もありません。

で実行が冗長でしたね。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • この投稿は削除されました

  • 2016/09/06 10:16

    haruka-kanataさん、ご回答有難うございます。
    ご指示のように、forループ中にflushを記述しましたが、改善されませんでした。
    コメント中にありました、明示的に閉じるとはどのようなコードの記述に
    なりますでしょうか。

    キャンセル

  • 2016/09/06 10:24

    やっぱ close で flush 実行されてましたね。

    キャンセル

  • 2016/09/06 10:54

    恐れ入りますが、当方のソースコードのどの箇所が該当しますでしょうか?

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.20%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる