ard*_*igh 5 ruby-on-rails amazon-s3
我在后台作业中使用 Rails 和 Carrierwave 将许多小文件上传到 S3,并且达到了 S3 速率限制。我的直接想法是在每次上传之前添加一个sleep 0.1,但这似乎不是一个很好的解决方案。
关于如何通过 S3 API 和某种类型的退避处理这个问题有什么建议吗?
正在执行上传的 Ruby 代码,该方法在循环中被调用数千次:
def attach_audio(object:, audio_field:, attachment:)
return true if Rails.env.test?
language_code, voice_id = language_and_voice(object)
resp = polly.synthesize_speech(
output_format: 'mp3',
voice_id: voice_id,
text: audio_field.to_s,
language_code: language_code
)
audio_filename = "#{object.class.to_s.downcase}_#{attachment}_#{object.id}_#{voice_id}.mp3"
audio_path = "#{Rails.root}/db/audio/#{audio_filename}"
IO.copy_stream(resp.audio_stream, audio_path)
object.send(attachment + '=', Pathname.new(audio_path).open)
object.save!
end
Run Code Online (Sandbox Code Playgroud)
上传者类
class AudioUploader < BaseUploader
def store_dir
"uploads/audio/#{model.target_language}/#{self.class.to_s.underscore}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_whitelist
%w[mp3]
end
end
Run Code Online (Sandbox Code Playgroud)
class BaseUploader < CarrierWave::Uploader::Base
if Rails.env.test?
storage :file
else
storage :fog
end
def store_dir
"uploads/#{self.class.to_s.underscore}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
Run Code Online (Sandbox Code Playgroud)
AWS 的回应
Message
Excon::Error::ServiceUnavailable: Expected(200) <=> Actual(503 Service Unavailable) excon.error.response :body => "<Error><Code>SlowDown</Code><Message>Please reduce your request rate.</Message><RequestId>176C22715A856A29</RequestId><HostId>L/+
Traceback
Excon::Error::ServiceUnavailable: Expected(200) <=> Actual(503 Service Unavailable)
excon.error.response
:body => "<Error><Code>SlowDown</Code><Message>Please reduce your request rate.</Message><RequestId>176C22715A856A29</RequestId><HostId>xxxxxxxxxxxxxxxxxxxxxxxxx</HostId></Error>"
:cookies => [
]
:headers => {
"Connection" => "close"
"Content-Type" => "application/xml"
"Date" => "Wed, 18 Nov 2020 07:31:29 GMT"
"Server" => "AmazonS3"
"x-amz-id-2" => "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"x-amz-request-id" => "176C22715A856A29"
}
:host => "example-production.s3-eu-west-1.amazonaws.com"
:local_address => "xxx.xx.xxx.xxx"
:local_port => 50276
:path => "/uploads/audio/fr/audio_uploader/word/audio_file/8015423/word_audio_file_8015423_Mathieu.mp3"
:port => 443
:reason_phrase => "Slow Down"
:remote_ip => "xx.xxx.xx.x"
:status => 503
:status_line => "HTTP/1.1 503 Slow Down\r\n"
File "/app/vendor/bundle/ruby/2.6.0/gems/excon-0.71.1/lib/excon/middlewares/expects.rb", line 13, in response_call
File "/app/vendor/bundle/ruby/2.6.0/gems/excon-0.71.1/lib/excon/middlewares/response_parser.rb", line 12, in response_call
File "/app/vendor/bundle/ruby/2.6.0/gems/excon-0.71.1/lib/excon/connection.rb", line 448, in response
File "/app/vendor/bundle/ruby/2.6.0/gems/excon-0.71.1/lib/excon/connection.rb", line 279, in request
File "/app/vendor/bundle/ruby/2.6.0/gems/fog-xml-0.1.3/lib/fog/xml/sax_parser_connection.rb", line 35, in request
etc
Run Code Online (Sandbox Code Playgroud)
编辑
链接的AWS文档引用了前缀,这似乎可以解决问题
Amazon S3 自动扩展到高请求率。例如,您的应用程序可以实现存储桶中每个前缀每秒至少 3,500 个 PUT/COPY/POST/DELETE 或 5,500 个 GET/HEAD 请求。桶中的前缀数量没有限制。您可以通过并行读取来提高读取或写入性能。例如,如果您在 Amazon S3 存储桶中创建 10 个前缀来并行读取,则可以将读取性能扩展到每秒 55,000 个读取请求。
但我不明白如何在Carrierwave的背景下实现它。
从这里
例如,您的应用程序可以实现存储桶中每个前缀每秒至少 3,500 个 PUT/COPY/POST/DELETE 或 5,500 个 GET/HEAD 请求。
你会知道自己的极限是什么。现在您需要了解什么是前缀,这很简单。考虑一下:
/uploads/audio/fr/audio_uploader/word/audio_file/8015423/word_audio_file_8015423_Mathieu.mp3
这里的前缀是什么?回答:
/上传/音频/fr/audio_uploader/word/audio_file/8015423
前缀是除对象名称之外的所有内容。因此,问题的答案在于您设计方案的能力,以便您永远不会超出亚马逊为每个前缀定义的限制。
例如,您可以使用旋转计数器,假设从 0 到 99,并将要保存的对象与存储它的旋转计数器点之间的关系存储在某处[以便您稍后可以读取]。如果你要实现这个,你的问题将减少到现在的 1/100;实际上,您可能不需要一直达到 100,如果需要,您可以在将来随时增加它。所以现在,这个:
/uploads/audio/fr/audio_uploader/word/audio_file/8015423/word_audio_file_8015423_Mathieu.mp3
将存储在:
/uploads/audio/fr/audio_uploader/word/audio_file/ 00 /8015423/word_audio_file_8015423_Mathieu.mp3
下一个对象存储在.../01/...中,依此类推,第 100 个对象存储在.../99/...中,然后第 101 个对象存储在.../00/ 中。 .. [显然你不必使用这两个字符]。
此过程给您的逻辑带来的额外步骤是,出于检索目的,您需要知道word_audio_file_8015423_Mathieu.mp3位于.../00/...中,例如,word_audio_file_8015424_Mark.mp3位于.../01/ 中。 ..等等。这意味着您必须存储对象与其保存位置之间的关系。另一方面,如果可以搜索所有地点来寻找所需的对象,则您甚至可能不需要这样做。
我强烈认为这会解决你的问题。
我尝试使用https://github.com/nickelser/activejob-traffic_control,但无法让工作正常工作。
最后,我找到了一个超级简单的解决方案:我将 S3 中每个单词的音频创建和存储移至新的 ActiveJob 类中。然后只要调用它 1000 次,它就会被 Sidekiq 并发设置自动限制。
配置/sidekiq.yml
---
:concurrency: 10
:max_retries: 3
:queues:
- [urgent, 4]
- [nlp, 3]
- [default, 2]
- [low]
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4146 次 |
| 最近记录: |