読者です 読者をやめる 読者になる 読者になる

テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

S3マルチパートアップロードの実装が難しい点

マルチパートアップロードの実装上やっかいな部分についてまとめる. これはかなりの骨だ.

ひと通りこれを読みましょう. http://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html

まず, マルチパートの基本的な使い方について:

AWS SDKから見ると, highlevel APIとlowlevel APIがある. lowlevel APIREST APIとほぼ1:1対応している.

  1. initiateする. uploadIdを受け取る
  2. partNumberを1から昇順につけて, partを送る
  3. completeする
  4. 途中でabortしてもいい. この場合, 金請求されない.

実装上やっかいな点:

  1. あるファイルに対して並行アクセスされる可能性がある. ("Concurrent Multipart Upload Operations"を見て)
  2. 突然Abortされることがある.
  3. NoSuchUpload: The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed. これはつまり, abortもcompleteも, このエラーの観点からは同じに見えることを意味する. 論理的にはlist partsがcontainsしてるかどうかと一緒にしたいという意図
  4. 突然Deleteされることすらある.
  5. Deleteされたものに対してCompleteされた時, ユーザに対してComplete成功を通知するのはOKとは書いてある. (これを禁じられるとかなりきつい) if another operation deletes a key after you initiate a multipart upload with that key, but before you complete it, the complete multipart upload response might indicate a successful object creation without you ever seeing the object.
  6. For each of these uploads, your application can then upload parts and send a complete upload request to Amazon S3 to create the object. Create時にオブジェクトが作られる?(これは, 結合する. あるいはmultipart uploadがvalidになるということを言ってるだけだろうか?create a objectはこの文脈では曖昧である) 例えば, A, Bの順にcompleteしたのに, Aの方が最新バージョンになるのは禁止?
  7. When the buckets have versioning enabled, completing a multipart upload always creates a new version. ということは, uploadIdから, 書くべきところが一意に決定出来る必要があるということか?
  8. たぶん, uploadIdはランダムであり, 内部を推測しにくいことが要件?そのオブジェクトにライト可能な他のユーザがuploadidを偽造する可能性がある. が, そもそもライト権限を持ってる時点で破壊については信用しているのだから, 今更感はある.

(追記)

  • part uploadを命令した直後に, 他のクライアントからabortされることはあり得ない?abort出来るのはinitiateした人のみということを前提とできる?
  • [考察] 通常, PUTした直後にDELETEしても, PUT自体が途中で否定されることはない. 例えばこれは, versioningをONにしていれば, そのPUTの結果も残すべきということで理解出来る. 従って, uploadを開始したあとにdeleteされた場合も, upload自体は有効であり続けるべきである(NoSuchKeyなどで落ちるべきではない)

(追記)

Completeは非同期である必要がある.

Processing of a Complete Multipart Upload request could take several minutes to complete. After Amazon S3 begins processing the request, it sends an HTTP response header that specifies a 200 OK response. While processing is in progress, Amazon S3 periodically sends whitespace characters to keep the connection from timing out. Because a request could fail after the initial 200 OK response has been sent, it is important that you check the response body to determine whether the request succeeded.

  1. 200OKを返す
  2. 時々whitespaceを送ってconnectionを保ち続ける
  3. 最終的に返すXMLによってクライアントは判断する

はぁ????ネットワークがつながり続けていること前提ならば, どうして同期的に行ってはいけないんだ?

(追記)

あぁ... 本当にcompleteするまでに数分かかったらクライアントがびっくりするでしょ, か. sprayで実装出来るのかこれ?

(追記)

multipartの場合は, MD5の計算がundocumentedなので適当に為すしかない.

s3cmd - What is the algorithm to compute the Amazon-S3 Etag for a file larger than 5GB? - Stack Overflow

(追記)

completeは, stream marshalizerを使うしかない?

(追記)

completeは, Streamを使う.

      path("stream1") {
        // we detach in order to move the blocking code inside the simpleStringStream into a future
        detach() {
          respondWithMediaType(`text/html`) { // normally Strings are rendered to text/plain, we simply override here
            complete(simpleStringStream)
          }
        }
      } ~



  // we prepend 2048 "empty" bytes to push the browser to immediately start displaying the incoming chunks
  lazy val streamStart = " " * 2048 + "<html><body><h2>A streaming response</h2><p>(for 15 seconds)<ul>"
  lazy val streamEnd = "</ul><p>Finished.</p></body></html>"

  def simpleStringStream: Stream[String] = {
    val secondStream = Stream.continually {
      // CAUTION: we block here to delay the stream generation for you to be able to follow it in your browser,
      // this is only done for the purpose of this demo, blocking in actor code should otherwise be avoided
      Thread.sleep(500)
      "<li>" + DateTime.now.toIsoDateTimeString + "</li>"
    }
    streamStart #:: secondStream.take(15) #::: streamEnd #:: Stream.empty
  }

spray/DemoService.scala at release/1.3 · spray/spray · GitHub

(追記)

こんな感じかな?

val completeWork = new CompleteWork // run :: Future
continually(completeWork.run) takeWhile { w => w.completed } map { " " } #::: w.result