503 使用 Carrierwave 上传到 S3 时速度变慢

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的背景下实现它。

Dan*_*n M 6

这里

例如,您的应用程序可以实现存储桶中每个前缀每秒至少 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/ 中。 ..等等。这意味着您必须存储对象与其保存位置之间的关系。另一方面,如果可以搜索所有地点来寻找所需的对象,则您甚至可能不需要这样做。

我强烈认为这会解决你的问题。

  • AWS 的消息非常有道理。您访问前缀 /uploads/audio/fr/audio_uploader/word/audio_file/ 的速度超出了限制。您会看到,/uploads/audio/fr/audio_uploader/word/audio_file/ 是所有数据库 ID 的前缀,并且您正在以非常快的速度创建数据库 ID。我觉得我提出的解决方案会起作用,最坏的情况下你甚至可以并行 100 个以上;如果 100 仍然给出 503 错误,您可以转到 1000。 (3认同)

ard*_*igh 0

我尝试使用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)