Skip to Content Skip to Search

Active Storage Mirror Service

Wraps a set of mirror services and provides a single ActiveStorage::Service object that will all have the files uploaded to them. A primary service is designated to answer calls to:

  • download

  • exists?

  • url

  • url_for_direct_upload

  • headers_for_direct_upload

Methods
D
M
N
U

Attributes

[R] mirrors
[R] primary

Class Public methods

new(primary:, mirrors:)

# File activestorage/lib/active_storage/service/mirror_service.rb, line 30
def initialize(primary:, mirrors:)
  @primary, @mirrors = primary, mirrors
  @executor = Concurrent::ThreadPoolExecutor.new(
    name: "ActiveStorage-mirror-service",
    min_threads: 1,
    max_threads: mirrors.size,
    max_queue: 0,
    fallback_policy: :caller_runs,
    idle_time: 60
  )
end

Instance Public methods

delete(key)

Delete the file at the key on all services.

# File activestorage/lib/active_storage/service/mirror_service.rb, line 52
def delete(key)
  perform_across_services :delete, key
end

delete_prefixed(prefix)

Delete files at keys starting with the prefix on all services.

# File activestorage/lib/active_storage/service/mirror_service.rb, line 57
def delete_prefixed(prefix)
  perform_across_services :delete_prefixed, prefix
end

mirror(key, checksum:)

Copy the file at the key from the primary service to each of the mirrors where it doesn’t already exist. Both the existence checks and the uploads run in parallel across mirrors using the internal thread pool.

# File activestorage/lib/active_storage/service/mirror_service.rb, line 67
def mirror(key, checksum:)
  instrument :mirror, key: key, checksum: checksum do
    mirrors_in_need_of_mirroring = mirrors_needing_mirroring(key)
    if mirrors_in_need_of_mirroring.any?
      primary.open(key, checksum: checksum, verify: checksum.present?) do |io|
        io.rewind
        content = io.read.freeze
        tasks = mirrors_in_need_of_mirroring.map do |service|
          Concurrent::Promise.execute(executor: @executor) do
            service.upload key, StringIO.new(content), checksum: checksum
          end
        end
        tasks.each(&:value!)
      end
    end
  end
end

upload(key, io, checksum: nil, **options)

Upload the io to the key specified to all services. The upload to the primary service is done synchronously whereas the upload to the mirrors is done asynchronously. If a checksum is provided, all services will ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError.

# File activestorage/lib/active_storage/service/mirror_service.rb, line 45
def upload(key, io, checksum: nil, **options)
  io.rewind
  primary.upload key, io, checksum: checksum, **options
  mirror_later key, checksum: checksum
end