Nginxで「pread() read only xxxx of xxxx from xxxx」 エラーが表示されたときの対応と、open_file_cacheとの関連

OpenRestyで動的生成されたコンフィグファイルをLuaで読み込んで云々、という処理を行っているのですが、
その際Nginx側で表題の「pread() read only xxxx of xxxx from xxxx」といったエラーが表示される事案がありました。

状況まとめ

  • 特定の環境のみ表示
  • 突然エラーが出始めた
    • 同じロジックで、過去にエラー表示されたことはない
  • しばらく(1分くらい)するとエラーでなくなる

ググる

以下の2つのリンクが引っかかりました。

引用ですが

Investigating my Nginx configuration I found this may be related to the files cached using open_file_cache option on Nginx. According to Igor Sysoev (creator of Nginx) on this forum it was caused because the file was not updated atomically.

Temporary disabling open_file_cache fixed the issue.

とありますね。
どうやら「Nginxでopen_file_cacheを利用していて、かつそのキャッシュの対象ファイルがアトミックに処理されないとエラーになる」ようです。
open_file_cacheを無効にすれば解消しそうですが、無効にはしたくないですし原因不明のまま対応するのも気持ち悪いのでもう少し調べてみます。

コンフィグ生成コード

言われてみれば、その辺りコンフィグファイルを動的生成するときにはあまり気にしていませんでした。
Consulでイベント受信して生成しているのですが、ファイルを出力する現状のコードは以下のような感じです。

def write_file(self, file, data):
        directory = "/path/to/config"
        if not os.path.exists(directory):
            os.makedirs(directory)

        fname = directory + file    
        with open(fname, mode = 'w') as fh:
            fh.write(data)

そこで、以下のようにアトミックにファイルを生成するように変更してみました。

import tempfile
from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IROTH

def write_file(self, file, data):
      directory = "/path/to/config"
      if not os.path.exists(directory):
          os.makedirs(directory)

      fname = directory + file
      with tempfile.NamedTemporaryFile(delete=False) as tfh:
           tfh.write(data)
           temp_fname = tfh.name

      # 新しくファイル作っているので、適切なパーミッション付与しとく
      os.chmod(temp_fname, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
      # move
      os.rename(temp_fname, fname)

この対応でエラー無く処理されるようになりました! 🙆‍

急にエラーが発生した原因

単純に、いままではファイルバッファ内に収まっていて問題にはならなかったのですが、
コンフィグファイル肥大化によりバッファが溢れてアトミックに処理されなくなっただけでしょう。

まとめ

あんまり見たことがないエラーだったので久々に記事にしてみました。
この問題はLuaに限らず普通のファイルアップロードなどでも発生するので、open_file_cacheを利用する際には気をつけましょう!