awsパッケージは出来る子でした, 大容量のファイルのS3へのアップロードが出来なくなりました, yesodにpull requestを出しました
バケット名にドットを付けていたのが全て悪かった, awsパッケージは出来る子でした
AWSのS3にhaskellでアクセスするのに苦戦してます, flycheck-stackはもう不要になってました, optparse-applicativeがいい感じ - ncaqに書きましたが, aws :: Stackage Serverで上手くpre signed urlを生成できなかったので, s3-signer :: Stackage Serverを使ってみました.
そしてAmazon S3のバケット名にドットを使ってしまいhttps通信が出来ない環境を作ってしまった, S3のダウンロードファイル名を指定できるライブラリがなくて困っている - ncaqに書きましたが, Amazon S3のバケット名にドットを付け加えるとhttpsなuriが生成できないことがわかったので, 開発用のバケット名の区切りをドットからハイフンに書き換えました.
それで, s3-signerはresponse-content-dispositionに対応していないので, s3-signerを改造するか, amazonka-s3 :: Stackage Serverに移行してみるか迷っていました.
しかし,
もしかしたらawsパッケージでURLを作成できないのはバケット名にドットを使っていたからでは?
と思って書き換えてみたところ,
Aws.awsUri
関数で無事pre signed urlが作られました.
configurationでHTTP
を指定しないと動かなかったのもバケット名にドットを使っていたからでした.
ドットが無ければAws.defServiceConfig
で動きました.
ドット付きバケット名でも問題ないようにs3-signerをforkすることも考えていました. しかし, awsパッケージの関数がバケット名にドットさえなければ動くので, 本番向けのバケットも変更して, 一時的にサービスを止めてsyncした方が良さそうですね.
大量にファイルを生成するテストを削除しました
これまで開発やテストにはローカルのディスクを使って, 本番のみgoofysを使ってディレクトリをマウントしてS3を使用するようにしていました. これからはgoofysは使わずにS3のSDKを使っていくので, テストにもS3を使わざるを得なくなりました.
そうなるとこれまでの大量のファイルを投稿して挙動を調べるテストが, 物凄い時間をかけるようになってしまいました.
これまではローカルで完結していたのをネットワーク越しのS3を使うようになったので当然ですね.
仕方がないのでテストに使うファイルのサイズを大幅に減らして, ファイルを大量投稿するテストは削除することにしました.
ファイル投稿制限のテストが出来なくなってしまいました, どうしましょうか… 開発時のみローカルディスクを使用するようにしておくのも手ではありますが, 本番環境を使わないテストなんて無意味な気がします.
テストにはテスト用のバケットとキーを.travis.yml
に記述します.
テスト時に開発側で弄ってるS3ファイルが書き換えられたりしたらアレなので.
travisの暗号化環境キーを使えば新規アカウントは必要ないかなと思いましたが, 全権限を持つアカウントキーが流出したら大変なので, テスト用のバケットにアクセス出来るだけのアカウントを結局作ることにしました. クローズドソースだからこそ出来る楽というものもある.
AWSのポリシー名って変更できないのかな, わかりにくいから意味のある値に変更しようとしても出来ませんでした. まあいいやと思ってそのまま…
よく考えたらyesodのテスト時設定変数書き換え機能を使えばいいじゃんとなったので.travis.yml
に書き込むのはやめました.
大容量のファイルのS3へのアップロードが出来なくなりました
実装が出来たので実際の動きを確かめてみようと思ったのですが, ちょっと大きいファイルをアップロードしようとするとうまく行かない. サイズは100MBぐらいを超えるとうまく行かなくなります.
エラーログは出ない.
何回かファイルをアップロードしてみようと思ったらメモリを大量に食い始めてswapを食いつぶし始めました.
何も考えずにAWS SDKのAPIを叩くより, goofysを使った方が最適化されていということなのですか…?
たしかにgoofysを使っているときはfileMove
を使って後はファイルシステムに任せて,
こちらの実装はhaskell側で全部をByteString
にして取り扱うようにしたわけですからね,
ロジックが異なる.
S3にクライアントから直接アップロードしても良いのですが, サーバ側との一貫性を取る方法がまるでわからないのでそれは一旦取り止め.
conduitがわからない
ちょっと前に
-- | Bytestring 'fileSource'
fileSourceBytes :: MonadResource m => FileInfo -> m ByteString
fileSourceBytes fileInfo = fileSource fileInfo $$ foldC
を書いてFileInfo
をByteString
にしてしまってしまえばconduitの意味不明な関数群を理解する必要はなくなったと思いましたが効率から逃れられなくなってきました.
このようにS3.putObject
するときに何も考えずにByteString
にしたものをRequestBodyBS
で包んで渡しているのが悪いのかと思ってlow levelにストリームを取り扱ってみようと思いましたがよくわからなくなってきました.
conduitの何がわかりにくいかってSource
, Conduit
, Sink
とか色々な型があるけどそれは結局type
なのでghciで動かしてみるとConduitM
が帰ってきて意味がわからなくなるところですね…
脳内で型エイリアスを変換できない.
厳しい.
要はRequestBody
を効率的に作れば良いのですが,
Network.HTTP.Client.Conduit
のrequestBodySourceChunked
はSource IO ByteString
を求めているため,
YesodのfileSource
が生成するMonadResource m => Source m ByteString
だと渡せない.
http-conduitはconduitを利用しているだけでconduit用の関数を用意しているわけではないのでこれを見てもあまり意味なさそうですね.
最適化の問題では?
Aws.defaultLog Aws.Debug
してみたりputStrLn
デバッグしてみると,
AWSにリクエストを飛ばしているわけではなく,
fileSourceBytes
を実行した段階で応答がなくなっていることがわかるので,
やはり効率の問題っぽい.
効率…ByteString
…待てよ,
そう言えば前に
haskellプログラムがメモリを食いまくって落ちていたのはghcに-O0を指定していたからだった - ncaq
ということがありましたね…
yesod devel
が実行するのはstack build --fast
になっていることを以前見たので,
最適化は前の時と同じく切られているはずだ…
stack clean
した後に,
stack exec -- yesod devel -e --ghc-options="-O2"
を実行してみました.
しかし,
前と同じく失敗してしまいました.
しかしやはり怪しい,
--fast
が--ghc-option="-O2"
より優先されているのかな?
と思ったのでyesodweb/yesod: A RESTful Haskell web framework built on WAI.をcloneして,
yesod-binのソースコードを改変して--fast
を取り除いてインストールし直してみました.
そうすると状況に変化が現れて,
awsへのputリクエストが発生したことがログで確認できました.
しかしやはりネットワークエラーになってしまうので,
最適化が足りないのかと思い,
改変したソースでもう一度yesod devel -e --ghc-options="-O2"
を実行してみました.
しかしやはりダメ.
ですがファイル自体はS3にアップロードされていました.
は?
もう一度やってみると, クライアント側ではネットワークエラーが表示されるのに, サーバサイドではamazon s3へのputが完了するという意味不明な出来事が起きました.
応答が全く無いんですけど…
まあ,
とりあえず,
最適化を有効にすればファイルアップロード自体は出来ることがわかったので,
yesod devel
がstack build
に--fast
を付加するのを消すpull requestを出してみようと思います.
本当はconduitの関数を読み解いて効率の良い実装にした方がもっと良いのでしょうけど,
とりあえずは最適化ビルドが可能なように元を改変したい.
最適化がないと,
ちょっとしたByteString
操作でもサイズが大きくなるとクラッシュしてしまうことがわかったので.
サーバは200 OKを返しているのにクライアントがネットワークエラーを出しているのは何故かわからない. 明日調べたいと思います. fetch apiの仕様なのかな?
yesodにpull requestを出しました
pull requestを出しました. 英語つらい.
google翻訳に頼ったわけですが, せっかくなのでここに原文を置いておきます.
原文
yesod devel
が実行するstack build
から--fast
フラグを取り除く提案です.
私はyesodで, ファイルをS3へとアップロードする機能を持つアプリケーションを書いています. サイズは2GB程度まで対応しています.
ファイルはByteString
へとひも解かれます.
ここで, yesodアプリケーションをビルドする時に最適化が無効になっていると, アプリケーションは大量のメモリを使い, クラッシュしてしまいます. 100MB程度のファイルで問題が起きてしまいます.
つまり,
yesod devel
がstack build
する時に最適化をしてくれるようにすれば良いわけですが,
stack build --fast
がかかっていると,
stack exec -- yesod devel -e --ghc-options="-O2"
しても,
--fast
が優先されて,
最適化が有効になりません.
昔のyesodでは, devel時の最適化の無効化は, 個々のアプリケーションのcabalファイルで書かれていたため, 変更が容易でしたが, 今は私が変更する方法がありません.
なので,
--fast
フラグを削除するpull requestを開きます.
最適化の有無は, 昔のようにユーザが選択できた方が良いと思います.
少なくとも,
私のアプリケーションは今のyesod devel
では動かなくて,
困っています.
10ヶ月前の変更なのにも関わらず, 今問題を報告することをお許しください.
ファイルをByteString
に紐解くのに使っているコードを添付しておきます.
以下のコードは, 最適化フラグがデフォルトだと動きますが, 最適化が無効(-O0)だと動かなくなります.
-- | Bytestring 'fileSource'
fileSourceBytes :: MonadResource m => FileInfo -> m ByteString
fileSourceBytes fileInfo = fileSource fileInfo $$ foldC
最適化を無効にすると動かなくなるようなコードを書くべきではないという方針ならば, それをお伝え下さい.
Data.ByteString.Char8.packを誤ってマルチバイトのStringに使っていた
StringをByteStringに変換したい時Data.ByteString.Char8.pack
を使っていたのだけれど,
これはマルチバイトのString
文字列を壊してしまうのでよろしくない.
誤って使いまくっていて, 日本語ファイルを文字化けさせてしまっていた…
じゃあ何を使うかというとData.ByteString.UTF8.fromString
を使う.
unicode - Using Haskell to output a UTF-8-encoded ByteString - Stack Overflow
utf8-stringは前に使っていたはずなのだけれど, すっかり忘れてしまっていた.
haskell歴はそこそこあるはずだけど,
ByteString
とText
関連のものはよく忘却してしまう,
どうにかならないだろうか.