簡単なことから始めましょう
公式の(つまり、Amazonが開発した)gem aws-sdkを使用してファイルのアップロードを実装するのは非常に簡単です。 Amazon Web Services(AWS)承認パラメーターの作成の準備部分を省略すると、コードは3行になります。
def upload_to_s3(config, src, dst) # S3, config s3 = AWS::S3.new( :access_key_id => config['access_key_id'], :secret_access_key => onfig['secret_access_key']) # , 'dst' S3 ( ) s3_file = s3.buckets[config['bucket_name']].objects[dst] # src ( ) s3_file.write(:file => src, :acl => :public_read) end
この方法は機能し、5 Mbを超えるファイルでは最大5 Mb / sの平均ダウンロード速度を示します(小さいファイルでは平均速度が低下します)。
ただし、複数のファイルを同時にダウンロードする合計速度は、一度に1つのファイルをダウンロードする速度よりも速いことが実験から簡単にわかります。 ストリームごとに何らかの帯域幅制限がありますか? マルチスレッドとmultipart_uploadメソッドを使用してダウンロードを並列化し、何が起こるかを見てみましょう。
パーツ単位でファイルをダウンロードする
aws-sdkの一部のファイルをダウンロードするには、multipart_uploadメソッドがあります。 その典型的なアプリケーションを見てみましょう。
def multipart_upload_to_s3(src, dst, config) s3 = AWS::S3.new( :access_key_id => config['access_key_id'], :secret_access_key => onfig['secret_access_key']) s3_file = s3.buckets[config['bucket_name']].objects[dst] # src src_io = File.open(src, 'rb') # read_size = 0 uploaded_size = 0 parts = 0 # src_size = File.size(src) # s3_file = s3_file.multipart_upload({:acl => :public_read}) do |upload| while read_size < src_size # buff = src_io.readpartial(config['part_size']) # read_size += buff.size part_number = parts += 1 # , S3 upload.add_part :data => buff, :part_number => part_number end end # src_io.close s3_file end
単純なダウンロードよりも複雑に見えます。 ただし、新しい方法には大きな利点があります。
- 部品はランダムにダウンロードできます
- ある部分のダウンロードが完了するのを待って別の部分のダウンロードを開始する必要はありません
これら2つのプロパティにより、ダウンロード時にマルチスレッドを使用できます。
部分的なマルチスレッドファイルのダウンロード
この問題を解決するには、いくつかのアプローチがあります。
- 固定スレッド数:ファイルは(スレッド数Nで)N個の部分に分割され、各部分は並行してダウンロードされます。
- 固定パーツサイズ:ファイルはS値を超えないサイズのパーツに分割され、各パーツは並行してアップロードされます。
- mixed:ファイルはS値を超えないサイズの部分に分割され、受信した部分は同時にダウンロードされますが、一度にNスレッド以下になります。
実用的な観点から、最も便利なのは混合アプローチです:
- API S3では、部品の最小サイズ(5Mb)に制限があります。
- 多数のスレッドを使用すると、効率が低下します。
次に、マルチスレッドファイルアップロードのコードを示します。
def threaded_upload_to_s3(src, dst, config) s3 = AWS::S3.new( :access_key_id => config['access_key_id'], :secret_access_key => onfig['secret_access_key']) s3_file = s3.buckets[config['bucket_name']].objects[dst] src_io = File.open(src, 'rb') read_size = 0 uploaded_size = 0 parts = 0 src_size = File.size(src) s3_file = s3_file.multipart_upload({:acl => :public_read}) do |upload| # upload_threads = [] # ( ), “” mutex = Mutex.new max_threads = [config['threads_count'], (src_size.to_f / config['part_size']).ceil].min # max_threads.times do |thread_number| upload_threads << (Thread.new do # while true # , # mutex.lock # , break unless read_size < src_size # buff = src_io.readpartial(config['part_size']) # read_size += buff.size part_number = parts += 1 # mutex.unlock # , S3 upload.add_part :data => buff, :part_number => part_number end end) end # upload_threads.each{|thread| thread.join} end src_io.close s3_file end
スレッドを作成するには、標準のThreadクラスを使用します(Rubyでマルチスレッドを使用する基本は、記事habrahabr.ru/post/94574で説明されています)。 相互例外を実装するには、標準のMutexクラスで実装されている最も単純なバイナリセマフォ(mutex)を使用します。
セマフォが必要なのはなぜですか? 彼らの助けを借りて、一度に1つのスレッドしか実行できないコードのセクション(クリティカルと呼ばれる)をマークします。 残りのスレッドは、セマフォをオンにしたスレッドがクリティカルセクションを離れるまで待機する必要があります。 通常、セマフォは共有リソースへの適切なアクセスを提供するために使用されます。 この場合、共通リソースは次のとおりです。入力オブジェクトsrc_io、変数read_sizeおよびparts。 buffおよびpart_number変数は、スレッド(つまり、Thread.new do ... endブロック)内でローカルに宣言されているため、共有されません。
Rubyでのセマフォとマルチスレッドの詳細については、 www.tutorialspoint.com / ruby / ruby_multithreading.htmの記事をご覧ください。
比較結果
複数のテストファイル(サイズが1MB、10MB、50MB、150MB)で異なる方法を使用してダウンロード速度を測定し、表に要約します。
ファイル
| パーツ
| ストリーム
| アップロード、Mb / s
| multipart_upload、Mb / s
| threaded_upload、Mb / s
| |
1
| 64k
| 1
| 1
| 0.78
| 0.29(37%)
| 0.29(37%)
|
2
| 512k
| 1
| 1
| 2.88
| 1.84(64%)
| 1.65(57%)
|
3
| 1 Mb
| 1
| 1
| 3.39
| 2.38(70%)
| 2.58(76%)
|
4
| 10 Mb
| 2
| 2
| 5.06
| 4.50(89%)
| 7.69(152%)
|
5
| 50 Mb
| 10
| 5
| 4.48
| 4.41(98%)
| 9.02(201%)
|
6
| 50 Mb
| 10
| 10
| 4.33
| 4.44(103%)
| 8.49(196%)
|
7
| 150Mb
| 30
| 5
| 4.34
| 4.43(102%)
| 9.22(212%)
|
8
| 150Mb
| 30
| 10
| 4.48
| 4.52(101%)
| 8.90(199%)
|
テストは、EU西部(アイルランド)リージョンのマシンから同じリージョンのS3ストレージにファイルをダウンロードすることで実行されました。 各ファイルに対して一連の10回の順次テストが実行されました。
4〜8をテストするための簡単な方法で注入率を評価すると、測定誤差は約8%であり、これはかなり許容範囲です。
小さいファイルでの部分(multipart_upload)のダウンロードは 、単純なものと比較して最悪の結果を示し、大きなファイルで同じでした。
マルチスレッドダウンロード(threaded_upload)は、1つの部分からのファイルに対して、部分ごとのダウンロードと同じ効率を示しました(これは明らかです)。 ただし、大きなファイルには大きな利点があります-最大2倍(通常のダウンロードに比べて)。
パーツの最適なサイズとストリーム数を見つけるタスクはありませんでしたが、大きなファイルでストリームを5から10に増やしても大きな効果はありませんでした。
おわりに
マルチスレッドファイルのアップロードは、複数の部分で構成されるファイルに対して通常よりも効果的であることが判明し、速度が最大2倍になりました。
ちなみに、ファイルサイズに応じて最適なダウンロード方法を選択する方法を作成すると便利です。
例のソースコードはGithubに投稿されています: github.com/whisk/s3up