Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enqueue jobs inside a connected_to block #369

Closed
bubiche opened this issue Oct 2, 2024 · 32 comments · Fixed by rails/rails#53865
Closed

Enqueue jobs inside a connected_to block #369

bubiche opened this issue Oct 2, 2024 · 32 comments · Fixed by rails/rails#53865

Comments

@bubiche
Copy link

bubiche commented Oct 2, 2024

My rails application connects to multiple databases with the configs below:

database.yml
production:
  primary_shard:
    ...
  secondary_shard:
    ...
  queue:
    ...
config/initializers/database.rb
# This is needed due to us using another gem whose model writes to both primary_shard and secondary_shard and its model inherits from ActiveRecord::Base
ActiveRecord::Base.instance_eval do
    connects_to shards: {
      primary_shard: {
        writing: :primary_shard,
      },
      secondary_shard: {
        writing: :secondary_shard,
      },
    }
end

SolidQueue has been working well, the only time where we cannot enqueue is if we have a block of code like below:

ActiveRecord::Base.connected_to(role: :writing, shard: :secondary_shard) do
  # ...
  SomeJob.perform_later(some_argument)
  # ...
end

I think it is because SolidQueue is trying to insert the job into secondary_shard instead of the queue database. Is there any way to circumvent this besides rewriting the code to call perform_later outside of the connected_to block? SomeJob.perform_later might be called inside a function with the connected_to block so it's quite a big refactor for us.

@rosa
Copy link
Member

rosa commented Oct 5, 2024

Ohhh, totally! Let me look into this one to see how to fix, I think it's something Solid Queue should handle. Thanks for the report!

@djpate
Copy link

djpate commented Oct 18, 2024

Same issue here. is there a workaround? my entire controller is in a connected_to block?
I tried wrapping the perform_later in another connection switch but no luck.

ActiveRecord::Base.connected_to(role: :writing, shard: :queue) do
  Workers::Foobar.perform_later(account_id: account_id, id: id)
end

@thibaudgg
Copy link

thibaudgg commented Nov 2, 2024

Same problem for me, I'm working on switching my multi-tenant app to SQLite (one database per tenant) using sharding, while keep the SolidQueue database separated.

Please let me know how I could help, or if sharing more code would be helpful. I'm happy to help fix the issue as well.

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  connects_to shards: Tenant.shards.map { |shard|
    [ shard, { writing: shard } ]
  }.to_h
end

Production config:

  config.active_job.queue_adapter = :solid_queue
  config.solid_queue.connects_to = { database: { writing: :queue } }

Tenant switching logic (simplified):

module Tenant
  extend self
  def switch(tenant)
    ActiveRecord::Base.connected_to(shard: tenant.to_sym) do
        yield
    end
  end
  # ...
end

But trying to enqueue jobs gives me that error trace:

Stacktrace
ActiveRecord::ConnectionNotDefined: No database connection defined for SolidQueue::Record with 'TENANT_A' shard. (ActiveRecord::ConnectionNotDefined)
  from active_record/connection_adapters/abstract/connection_handler.rb:230:in `retrieve_connection_pool'
  from active_record/connection_handling.rb:343:in `connection_pool'
  from active_record/connection_handling.rb:310:in `with_connection'
  from active_record/transactions.rb:410:in `with_transaction_returning_status'
  from active_record/transactions.rb:366:in `save!'
  from active_record/suppressor.rb:56:in `save!'
  from active_record/persistence.rb:55:in `create!'
  from bundle/ruby/3.3.0/bundler/gems/solid_queue-51c75bec01c8/app/models/solid_queue/job.rb:41:in `create_from_active_job'
  from bundle/ruby/3.3.0/bundler/gems/solid_queue-51c75bec01c8/app/models/solid_queue/job.rb:31:in `enqueue'
  from active_job/queue_adapters/solid_queue_adapter.rb:16:in `enqueue'
  from active_job/enqueuing.rb:132:in `raw_enqueue'
  from active_job/enqueue_after_transaction_commit.rb:40:in `raw_enqueue'
  from active_job/enqueuing.rb:117:in `block in enqueue'
  from active_support/callbacks.rb:120:in `block in run_callbacks'
  from active_job/instrumentation.rb:40:in `block in instrument'
  from active_support/notifications.rb:210:in `block in instrument'
  from active_support/notifications/instrumenter.rb:58:in `instrument'
  from sentry/rails/tracing.rb:56:in `instrument'
  from active_support/notifications.rb:210:in `instrument'
  from active_job/instrumentation.rb:39:in `instrument'
  from active_record/railties/job_runtime.rb:18:in `instrument'
  from active_job/instrumentation.rb:21:in `block (2 levels) in <module:Instrumentation>'
  from active_support/callbacks.rb:129:in `instance_exec'
  from active_support/callbacks.rb:129:in `block in run_callbacks'
  from active_support/tagged_logging.rb:143:in `block in tagged'
  from active_support/tagged_logging.rb:38:in `tagged'
  from active_support/tagged_logging.rb:143:in `tagged'
  from active_support/broadcast_logger.rb:241:in `method_missing'
  from active_job/logging.rb:39:in `tag_logger'
  from active_job/logging.rb:28:in `block (2 levels) in <module:Logging>'
  from active_support/callbacks.rb:129:in `instance_exec'
  from active_support/callbacks.rb:129:in `block in run_callbacks'
  from active_support/callbacks.rb:140:in `run_callbacks'
  from active_job/enqueuing.rb:116:in `enqueue'
  from active_job/configured_job.rb:15:in `perform_later'
  from action_mailer/parameterized.rb:148:in `enqueue_delivery'
  from action_mailer/message_delivery.rb:103:in `deliver_later'
  from app/controllers/sessions_controller.rb:26:in `create'
  from action_controller/metal/basic_implicit_render.rb:8:in `send_action'
  from abstract_controller/base.rb:226:in `process_action'
  from action_controller/metal/rendering.rb:193:in `process_action'
  from abstract_controller/callbacks.rb:261:in `block in process_action'
  from active_support/callbacks.rb:120:in `block in run_callbacks'
  from turbo-rails.rb:24:in `with_request_id'
  from bundle/ruby/3.3.0/gems/turbo-rails-2.0.11/app/controllers/concerns/turbo/request_id_tracking.rb:10:in `turbo_tracking_request_id'
  from active_support/callbacks.rb:129:in `block in run_callbacks'
  from action_text/rendering.rb:25:in `with_renderer'
  from action_text/engine.rb:71:in `block (4 levels) in <class:Engine>'
  from active_support/callbacks.rb:129:in `instance_exec'
  from active_support/callbacks.rb:129:in `block in run_callbacks'
  from sentry/rails/controller_transaction.rb:21:in `block in sentry_around_action'
  from sentry/hub.rb:115:in `block in with_child_span'
  from sentry/span.rb:232:in `with_child_span'
  from sentry/hub.rb:113:in `with_child_span'
  from sentry-ruby.rb:499:in `with_child_span'
  from sentry/rails/controller_transaction.rb:18:in `sentry_around_action'
  from active_support/callbacks.rb:129:in `block in run_callbacks'
  from active_support/callbacks.rb:140:in `run_callbacks'
  from abstract_controller/callbacks.rb:260:in `process_action'
  from action_controller/metal/rescue.rb:27:in `process_action'
  from action_controller/metal/instrumentation.rb:76:in `block in process_action'
  from active_support/notifications.rb:210:in `block in instrument'
  from active_support/notifications/instrumenter.rb:58:in `instrument'
  from sentry/rails/tracing.rb:56:in `instrument'
  from active_support/notifications.rb:210:in `instrument'
  from action_controller/metal/instrumentation.rb:75:in `process_action'
  from action_controller/metal/params_wrapper.rb:259:in `process_action'
  from active_record/railties/controller_runtime.rb:39:in `process_action'
  from abstract_controller/base.rb:163:in `process'
  from action_view/rendering.rb:40:in `process'
  from action_controller/metal.rb:252:in `dispatch'
  from action_controller/metal.rb:335:in `dispatch'
  from action_dispatch/routing/route_set.rb:67:in `dispatch'
  from action_dispatch/routing/route_set.rb:50:in `serve'
  from action_dispatch/journey/router.rb:53:in `block in serve'
  from action_dispatch/journey/router.rb:133:in `block in find_routes'
  from action_dispatch/journey/router.rb:126:in `each'
  from action_dispatch/journey/router.rb:126:in `find_routes'
  from action_dispatch/journey/router.rb:34:in `serve'
  from action_dispatch/routing/route_set.rb:908:in `call'
  from lib/tenant/middleware.rb:12:in `block in call'
  from lib/tenant.rb:56:in `block (2 levels) in switch'
  from active_record/connection_handling.rb:214:in `prohibit_shard_swapping'
  from lib/tenant.rb:55:in `block in switch'
  from active_record/connection_handling.rb:398:in `with_role_and_shard'
  from active_record/connection_handling.rb:149:in `connected_to'
  from lib/tenant.rb:54:in `switch'
  from lib/tenant/middleware.rb:12:in `call'
  from rack/tempfile_reaper.rb:20:in `call'
  from rack/etag.rb:29:in `call'
  from rack/conditional_get.rb:43:in `call'
  from rack/head.rb:15:in `call'
  from action_dispatch/http/permissions_policy.rb:38:in `call'
  from action_dispatch/http/content_security_policy.rb:35:in `call'
  from rack/session/abstract/id.rb:272:in `context'
  from rack/session/abstract/id.rb:266:in `call'
  from action_dispatch/middleware/cookies.rb:706:in `call'
  from action_dispatch/middleware/callbacks.rb:31:in `block in call'
  from active_support/callbacks.rb:100:in `run_callbacks'
  from action_dispatch/middleware/callbacks.rb:30:in `call'
  from sentry/rails/rescued_exception_interceptor.rb:14:in `call'
  from action_dispatch/middleware/debug_exceptions.rb:31:in `call'
  from sentry/rack/capture_exceptions.rb:30:in `block (2 levels) in call'
  from sentry/hub.rb:265:in `with_session_tracking'
  from sentry-ruby.rb:412:in `with_session_tracking'
  from sentry/rack/capture_exceptions.rb:21:in `block in call'
  from sentry/hub.rb:59:in `with_scope'
  from sentry-ruby.rb:392:in `with_scope'
  from sentry/rack/capture_exceptions.rb:20:in `call'
  from action_dispatch/middleware/show_exceptions.rb:32:in `call'
  from lograge/rails_ext/rack/logger.rb:18:in `call_app'
  from rails/rack/logger.rb:29:in `call'
  from rails/rack/silence_request.rb:28:in `call'
  from action_dispatch/middleware/remote_ip.rb:96:in `call'
  from request_store/middleware.rb:19:in `call'
  from action_dispatch/middleware/request_id.rb:34:in `call'
  from rack/method_override.rb:28:in `call'
  from rack/runtime.rb:24:in `call'
  from action_dispatch/middleware/executor.rb:16:in `call'
  from action_dispatch/middleware/static.rb:27:in `call'
  from rack/sendfile.rb:114:in `call'
  from action_dispatch/middleware/assume_ssl.rb:24:in `call'
  from rack/cors.rb:102:in `call'
  from rails/engine.rb:535:in `call'
  from rack/status.rb:13:in `call'
  from puma/configuration.rb:272:in `call'
  from puma/request.rb:100:in `block in handle_request'
  from puma/thread_pool.rb:378:in `with_force_shutdown'
  from puma/request.rb:99:in `handle_request'
  from puma/server.rb:464:in `process_client'
  from puma/server.rb:245:in `block in run'
  from puma/thread_pool.rb:155:in `block in spawn_thread'

@thibaudgg
Copy link

Added a comment on this other ticket (#353), that might help here.

@rosa
Copy link
Member

rosa commented Nov 2, 2024

Sorry for the delay on this one, I haven't managed to look into it yet, but it's definitely on my radar. I'll try next week 🤞

@thibaudgg
Copy link

Thanks @rosa!

I just gave a tried to this patch on production, and it seems to work like a charm.

# config/initializers/solid_queue_patch.rb
Rails.application.config.to_prepare do
  module CurrentShardPatch
    def current_shard; :queue end
  end

  SolidQueue::Record.send(:extend, CurrentShardPatch)
end

My app is open-source, so you can give it a look if that's helpful to you.
Here some relevant parts:

@rosa
Copy link
Member

rosa commented Nov 2, 2024

Oh, awesome! I'm sure this is going to be super helpful! I'll look into all those next week, too 🙏 Thank you so much!

@hubertjakubiak
Copy link

Hi @rosa, just checking in to see if there have been any updates on this issue. I’m still encountering the same problem with enqueuing jobs inside a connected_to block. Would love to hear if there's any progress on a solution. Thanks!

@rosa
Copy link
Member

rosa commented Dec 5, 2024

@hubertjakubiak so sorry for the delay! I did look into it but was immediately pulled into other directions before I could figure out anything, but this is definitely high on my list.

@rosa
Copy link
Member

rosa commented Dec 5, 2024

Hey @hubertjakubiak, @thibaudgg, @bubiche, I'm back again with something I did find back then, and that just revisited... and it was that things work fine as long as you don't pollute ActiveRecord::Base with the shard configuration. In fact, I couldn't get it to work if I tried to do that.

I configured some sharded records as follows:

database.yml config:

  primary:
    <<: *default
    database: <%= database_name_from("development") %>
  shard_one:
    <<: *default
    database: <%= database_name_from("development_shard_one") %>
    migrations_paths: db/migrate_shards
  shard_two:
    <<: *default
    database: <%= database_name_from("development_shard_two") %>
    migrations_paths: db/migrate_shards
  queue:
    <<: *default
    database: <%= database_name_from("development_queue") %>
    migrations_paths: db/queue_migrate

Then in the app:

class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class
end

class ShardedRecord < ApplicationRecord
  self.abstract_class = true

  connects_to shards: {
    shard_one: { writing: :shard_one },
    shard_two: { writing: :shard_two }
  }
end

class JobResult < ApplicationRecord
end

class ShardedJobResult < ShardedRecord
end

Then, the following doesn't work (this might have been a result of changes to connection pool management since the Rail guides for horizontal sharding were written):

ActiveRecord::Base.connected_to(role: :writing, shard: :shard_two) do
  JobResult.create!
  ShardedJobResult.create!(value: "in shard two")
end

No connection pool for 'ActiveRecord::Base' found for the 'shard_two' shard. (ActiveRecord::ConnectionNotEstablished)

The following works:

ShardedRecord.connected_to(role: :writing, shard: :shard_two) do
  JobResult.create!(value: "right place")
  ShardedJobResult.create!(value: "in shard two")
end

  TRANSACTION (1.5ms)  BEGIN
  JobResult Create (3.4ms)  INSERT INTO `job_results` (`queue_name`, `status`, `value`, `created_at`, `updated_at`) VALUES (NULL, NULL, 'right place', '2024-12-05 20:41:54.697692', '2024-12-05 20:41:54.697692')
  TRANSACTION (4.0ms)  COMMIT
  TRANSACTION (0.9ms)  BEGIN
  ShardedJobResult Create (2.0ms)  INSERT INTO `sharded_job_results` (`value`, `created_at`, `updated_at`) VALUES ('in shard two', '2024-12-05 20:41:54.720816', '2024-12-05 20:41:54.720816')
  TRANSACTION (2.1ms)  COMMIT
=> #<ShardedJobResult:0x000000011fc70c98 id: 2, value: "in shard two", created_at: Thu, 05 Dec 2024 20:41:54.720816000 UTC +00:00, updated_at: Thu, 05 Dec 2024 20:41:54.720816000 UTC +00:00>

And the following also works, with everything created in the corresponding DB:

ShardedRecord.connected_to(role: :writing, shard: :shard_two) do
  JobResult.create!(value: "right place")
  ShardedJobResult.create!(value: "in shard two")
  AddToBufferJob.perform_later(2)
end

  TRANSACTION (1.7ms)  BEGIN
  JobResult Create (4.3ms)  INSERT INTO `job_results` (`queue_name`, `status`, `value`, `created_at`, `updated_at`) VALUES (NULL, NULL, 'right place', '2024-12-05 20:42:53.607259', '2024-12-05 20:42:53.607259')
  TRANSACTION (4.4ms)  COMMIT
  TRANSACTION (1.1ms)  BEGIN
  ShardedJobResult Create (2.3ms)  INSERT INTO `sharded_job_results` (`value`, `created_at`, `updated_at`) VALUES ('in shard two', '2024-12-05 20:42:53.626346', '2024-12-05 20:42:53.626346')
  TRANSACTION (2.4ms)  COMMIT
  TRANSACTION (0.7ms)  BEGIN
  SolidQueue::Job Create (2.3ms)  INSERT INTO `solid_queue_jobs` (`queue_name`, `class_name`, `arguments`, `priority`, `active_job_id`, `scheduled_at`, `finished_at`, `concurrency_key`, `created_at`, `updated_at`) VALUES ('background', 'AddToBufferJob', '{\"job_class\":\"AddToBufferJob\",\"job_id\":\"3f2b5646-5905-43e0-ad97-7fab2c425494\",\"provider_job_id\":null,\"queue_name\":\"background\",\"priority\":null,\"arguments\":[2],\"executions\":0,\"exception_executions\":{},\"locale\":\"en\",\"timezone\":\"UTC\",\"enqueued_at\":\"2024-12-05T20:42:53.666927000Z\",\"scheduled_at\":\"2024-12-05T20:42:53.666125000Z\"}', 0, '3f2b5646-5905-43e0-ad97-7fab2c425494', '2024-12-05 20:42:53.666125', NULL, NULL, '2024-12-05 20:42:53.702207', '2024-12-05 20:42:53.702207')
  TRANSACTION (0.6ms)  SAVEPOINT active_record_1
  SolidQueue::Job Load (1.7ms)  SELECT `solid_queue_jobs`.* FROM `solid_queue_jobs` WHERE `solid_queue_jobs`.`id` = 3 LIMIT 1
  SolidQueue::ReadyExecution Create (0.8ms)  INSERT INTO `solid_queue_ready_executions` (`job_id`, `queue_name`, `priority`, `created_at`) VALUES (3, 'background', 0, '2024-12-05 20:42:53.740160')
  TRANSACTION (0.5ms)  RELEASE SAVEPOINT active_record_1
  TRANSACTION (2.6ms)  COMMIT
Enqueued AddToBufferJob (Job ID: 3f2b5646-5905-43e0-ad97-7fab2c425494) to SolidQueue(background) with arguments: 2

So, I wonder if things would work fine if you switched from using ActiveRecord::Base to the abstract class that actually has the connects_to shard: ... 🤔

    ApplicationRecord.connected_to(shard: tenant.to_sym) do
        yield
    end

I feel I'm missing something here 😳

@thibaudgg
Copy link

Hey @rosa, thanks for looking into this and for your detailed answer.

Your suggestion to switch from ActiveRecord::Base to an abstract class is definitely worth considering. However, in my multi-tenant setup, I also rely on models like ActiveStorage::Record (blobs and attachments models) that must connect to the correct tenant shard and inherit directly from ActiveRecord::Base (and not ApplicationRecord). This makes it tricky to apply your proposed solution as is.

It might just be a unique aspect of my environment, where I have no primary shard and every model (besides SolidQueue::Record models) must connect to their respective tenant shards. If you have any other approaches or insights, I’d be happy to explore them.

So far, the patch I mentioned above has been working like a charm on production.

@rosa
Copy link
Member

rosa commented Dec 6, 2024

@thibaudgg good point! I think in that case, you could rely on ActiveRecord::Base.connected_to_many, with both your ApplicationRecord and ActiveStorage::Record. Something like

    ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: tenant.to_sym) do
        yield
    end

The more I've been looking into the connection handling code, the more I think the guides are wrong/outdated and that ActiveRecord::Base.connected_to should never be used directly 🤔

I'm going to propose a PR to update the guides shortly.

@robacarp
Copy link

robacarp commented Dec 6, 2024

connected_to_many is what we have found we need to use as well. Unfortunately, it assumes that role is the same for all connections, so we have this type of thing going on in our middleware:

      ActiveRecord::Base.connected_to_many(GlobalDB, role: :reading) do
        ActiveRecord::Base.connected_to_many(CustomerDB, role: :writing, shard: "customer_NN") do
          @app.call(env)
        end
      end

There's a discussion about activestorage and sharded databases too: #53602

@rosa
Copy link
Member

rosa commented Dec 6, 2024

Ohhhh, great point @robacarp. Thanks a lot for sharing that. Going to read that carefully and the forum thread linked there. It seems this area has still some open questions 🤔

@rosa
Copy link
Member

rosa commented Dec 6, 2024

Hmm... wait, @robacarp, I think your example above would be expected in any case, if you have different models with different DB configurations 🤔 It could be written like:

GlobalDB.connected_to(role: :reading) do
  CustomerDB.connected_to(role: :writing, shard: "customerNN") do
    @app.call(env)
  end
end

but I think this is expected and how it's supposed to work 🤔 The problem with ActiveStorage::Record in that case is that it wouldn't know about the customerNN shard, but it seems that would be fixed quite easily with a configuration connects_to in ActiveStorage::Record and nothing else? Then, wouldn't this work? This is an example from your linked issue, where it seems you want the same shard and role for your StateModel and Active Storage models:

ActiveRecord::Base.connected_to_many(StateModel, ActiveStorage::Record, shard: shard_for_subdomain, role: :writing) do
  @app.call(env)
end

@robacarp
Copy link

robacarp commented Dec 6, 2024

@rosa We're veering off topic of solid_queue here a bit but I really appreciate you paying such close attention. My intent above was primarily to link the active storage issue because the solution here and there will probably be similar if not related.

The problem we have with ActiveStorage::Record is that we want to store and attach blobs both in the shards, and in the global database -- models which "attach" and are subclasses of a Shard should use tables (and controllers!) associated with the shard, and models which are subclasses of a non-sharded abstract table should use tables connected to that named database.

Nested connected_to or connected_to_many blocks are indeed the only way I was able to resolve this. I think our Rails version didn't or doesn't support the full specifier of shard+role in connected_to? I'm getting fuzzy remembering the details now.

As mentioned on that issue, there are workarounds for some of this. Notably missing is a workaround to properly use the activestorage front-end utilities.

@rosa
Copy link
Member

rosa commented Dec 6, 2024

Ahhhh! Thanks for the extra explanation, that makes sense 🙏 No problem at all with going off-topic, this was super helpful for me as I try to figure out the best way to fix this and also the best way to add sharding support to Solid Queue itself, so yeah, super super helpful and relevant! ❤️ Thank you so much.

@thibaudgg
Copy link

Hey @rosa, thanks for pointing the ActiveRecord::Base.connected_to_many method, I wasn't aware of it. 👍🏻

I tried it out, but I’m getting an ActiveRecord::StatementTimeout: SQLite3::BusyException: database is locked error (similar to #133). I assume this is happening because you can’t connect to the same SQLite database twice in the same thread, once through ApplicationRecord and another time through ActiveStorage::Record. I might be misunderstanding something, though 😅.

I want with something similar to:

ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: tenant.to_sym) do
  yield
end

@rosa
Copy link
Member

rosa commented Dec 9, 2024

I assume this is happening because you can’t connect to the same SQLite database twice in the same thread, once through ApplicationRecord and another time through ActiveStorage::Record

Ohh, interesting! I thought that error happened when you were trying to write concurrently, and one write needs to wait to obtain a lock, and it times out 🤔 This doesn't happen when you do

ActiveRecord::Base.connected_to(role: :writing, shard: tenant.to_sym) do
...
end

I'd imagine it happening in the same way 🤔

@thibaudgg
Copy link

Yeah, I'm a bit confused about why am I getting this error as well. This is certainly happening on reading (e.g., loading a page with an attachment), not only writing. 🤔

Yes, when using simply ActiveRecord::Base.connected_to (with the SolidQueue::Record#current_shard patch), it works just fine.

@rosa
Copy link
Member

rosa commented Dec 9, 2024

Yes, it's strange! I think this should have gotten a different error, but have you tried increasing Active Record's pool size?

@thibaudgg
Copy link

@rosa yes, I did already, but sadly it doesn't resolve the issue (no change at all).

Are you confident that having twice the same SQLite DB (with the :writing role) in the connected_to_stack array is supported?

@rosa
Copy link
Member

rosa commented Dec 10, 2024

Are you confident that having twice the same SQLite DB (with the :writing role) in the connected_to_stack array is supported?

Nope, not confident, it seems it should work just from reading the docs, but I haven't tested this with SQLite or dug into the code. I'll check it out.

@rosa
Copy link
Member

rosa commented Dec 10, 2024

Ok, I've tested this with Active Storage in a new app and it seems to be working fine 🤔 I have two shards:

development:
  shard_one:
    <<: *default
    database: storage/development_shard_one.sqlite3
    migrations_paths: db/migrate
  shard_two:
    <<: *default
    database: storage/development_shard_two.sqlite3
    migrations_paths: db/migrate
class ApplicationRecord < ActiveRecord::Base
  primary_abstract_class

  connects_to shards: {
    shard_one: { writing: :shard_one },
    shard_two: { writing: :shard_two }
  }
end

Then, this initializer so Active Storage knows about the shards. @thibaudgg, I haven't found something like this in your app 🤔 If you're using ActiveRecord::Base.connected_to everywhere it wouldn't matter because that's global, but if you don't want to set this globally, ActiveStorage::Record needs to know about the DB config. So I've created this initializer in config/initializers/active_storage.rb:

ActiveSupport.on_load(:active_storage_record) do
  connects_to shards: {
    shard_one: { writing: :shard_one },
    shard_two: { writing: :shard_two }
  }
end

Then I have just a single model:

class Dog < ApplicationRecord
  has_one_attached :photo

  def attach_test_photo
    photo.attach(io: File.open("/tmp/photo.png"), filename: "photo.png")
  end
end

Then the following all works as expected:

>> Dog.first.attach_test_photo

This uses the first shard by default. Checking the DB directly:

$ sqlite3 development_shard_one.sqlite3
sqlite> select * from active_storage_attachments;
2|photo|Dog|1|2|2024-12-10 12:39:37.669209
sqlite> select * from active_storage_blobs;
2|n8zho7o4sojy0tzn4wguehevwoua|photo.png|image/png|{"identified":true,"analyzed":true}|local|1620147|GiSsNzp7B+EFOaS+vBTojg==|2024-12-10 12:39:37.662174

$ sqlite3 development_shard_two.sqlite3
sqlite> select count(*) from active_storage_attachments;
0
sqlite> select count(*) from active_storage_blobs ;
0

Then reading from each shard:

>> ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: :shard_two) do
  ActiveStorage::Blob.count
end
  ActiveStorage::Blob Count (2.8ms)  SELECT COUNT(*) FROM "active_storage_blobs" /*application='PhantomDbSqliteRepro'*/
=> 0

>> ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: :shard_one) do
  ActiveStorage::Blob.count
end

  ActiveStorage::Blob Count (0.1ms)  SELECT COUNT(*) FROM "active_storage_blobs" /*application='PhantomDbSqliteRepro'*/
=> 1

So it seems this works as expected 🤔 Not sure if you can see anything fundamentally different from your app here (besides ActiveStorage::Record.connects_to).

@thibaudgg
Copy link

@rosa thanks again taking the time and your detailed answer.

Then, this initializer so Active Storage knows about the shards. @thibaudgg, I haven't found something like this in your app 🤔 If you're using ActiveRecord::Base.connected_to everywhere it wouldn't matter because that's global, but if you don't want to set this globally, ActiveStorage::Record needs to know about the DB config. So I've created this initializer in config/initializers/active_storage.rb:

Yes, I have set a similar initializer in my local branch to test connected_to_many. 👍🏻

Reading with connected_to_many seems fine, but writing to both record types inside the same connected_to_many block is throwing the ActiveRecord::StatementTimeout: SQLite3::BusyException: database is locked error for me.

Giving your code example, you should be able to reproduce it with something like that:

ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: :shard_two) do
  # Should work fine
  Dog.count
  ActiveStorage::Blob.count
  # Should raise an error
  Dog.create!
  Dog.first.attach_test_photo
end

Is this snippet failing for you too?

PS: As you are most likely well aware 😅, running this from the Rails console in the development environment makes use of the :async queue adapter and not :solid_queue. Not sure it that's relevant here, but ActiveStorage::PurgeJob will be run using the Tenant.switch context in my codebase.

@rosa
Copy link
Member

rosa commented Dec 10, 2024

Ah! I tried reading as you said you were getting the error on reading too, but testing writing, this works fine 😕

>> ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: :shard_two) do
  # Should work fine
  Dog.count
  ActiveStorage::Blob.count
  # Should raise an error
  Dog.create!
  Dog.first.attach_test_photo
end
  Dog Count (3.3ms)  SELECT COUNT(*) FROM "dogs" /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Count (0.6ms)  SELECT COUNT(*) FROM "active_storage_blobs" /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Dog Create (0.4ms)  INSERT INTO "dogs" ("name", "features", "nickname", "created_at", "updated_at") VALUES (NULL, NULL, NULL, '2024-12-10 15:53:00.722864', '2024-12-10 15:53:00.722864') RETURNING "id" /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.7ms)  COMMIT TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Dog Load (0.1ms)  SELECT "dogs".* FROM "dogs" ORDER BY "dogs"."id" ASC LIMIT 1 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Load (0.1ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = 1 AND "active_storage_attachments"."record_type" = 'Dog' AND "active_storage_attachments"."name" = 'photo' LIMIT 1 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Attachment Load (0.0ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 1 AND "active_storage_attachments"."record_type" = 'Dog' AND "active_storage_attachments"."name" = 'photo' LIMIT 1 /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Create (0.2ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "service_name", "byte_size", "checksum", "created_at") VALUES ('0rawu5e1vvs4dquf80be7ub0ekaj', 'photo.png', 'image/png', '{"identified":true}', 'local', 1620147, 'GiSsNzp7B+EFOaS+vBTojg==', '2024-12-10 15:53:00.755736') RETURNING "id" /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Attachment Create (0.0ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('photo', 'Dog', 1, 1, '2024-12-10 15:53:00.758157') RETURNING "id" /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.1ms)  COMMIT TRANSACTION /*application='PhantomDbSqliteRepro'*/
Enqueued ActiveStorage::AnalyzeJob (Job ID: 9a249c26-59a5-4e6a-91f0-058f84ba8324) to Async(default) with arguments: #<GlobalID:0x000000014b8f4cc8 @uri=#<URI::GID gid://phantom-db-sqlite-repro/ActiveStorage::Blob/1>>
↳ app/models/dog.rb:5:in `attach_test_photo'
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Dog Update (0.2ms)  UPDATE "dogs" SET "updated_at" = '2024-12-10 15:53:00.761401' WHERE "dogs"."id" = 1 /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.0ms)  COMMIT TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Disk Storage (4.6ms) Uploaded file to key: 0rawu5e1vvs4dquf80be7ub0ekaj (checksum: GiSsNzp7B+EFOaS+vBTojg==)

And these are visible in the shard_two DB:

sqlite> select count(*) from active_storage_blobs ;
1
sqlite> select count(*) from active_storage_attachments;
1

How strange! And yes, this is using the async adapter. I wonder if this is making a difference at all...

Not sure it that's relevant here, but ActiveStorage::PurgeJob will be run using the Tenant.switch context in my codebase.

@thibaudgg
Copy link

How strange indeed! The only change I was able to think of is that in my case, I attach a PDF file and not a PNG. I tried with a PNG, and it works fine for me too! 🤯

For science, could you give a try to a PDF file? I can also try to reproduce on a fresh new Rails app, some custom logic might be in cause here, but so fare I haven't been able to put my finger in.

I wonder if some ActiveStorage internals (preview or analyzer ???) would behave differently for PDF file format, here my error backtrace, it seems to fail when executing ActiveStorage::PurgeJob async.

  ActiveStorage::Blob Load (0.2ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = 1 AND "active_storage_attachments"."record_type" = 'Organization' AND "active_storage_attachments"."name" = 'logo' LIMIT 1 /*application='CSAAdmin'*/
  ActiveStorage::Attachment Load (0.3ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 1 AND "active_storage_attachments"."record_type" = 'Organization' AND "active_storage_attachments"."name" = 'logo' LIMIT 1 /*application='CSAAdmin'*/
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='CSAAdmin'*/
  ActiveStorage::Attachment Destroy (0.4ms)  DELETE FROM "active_storage_attachments" WHERE "active_storage_attachments"."id" = 6757 /*application='CSAAdmin'*/
  ActiveStorage::Blob Create (0.1ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "byte_size", "checksum", "created_at", "service_name") VALUES ('4ss9gew82vu7xiy5p2ttdnpn0quv', 'logo.png', 'application/pdf', '{"identified":true}', 115794, 'yH/1hu7C1fxR/YSsPIb5wA==', '2024-12-10 17:04:26.883000', 'tenant_disk') RETURNING "id" /*application='CSAAdmin'*/
  ActiveStorage::Attachment Create (0.0ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('logo', 'Organization', 1, 6772, '2024-12-10 17:04:26.883581') RETURNING "id" /*application='CSAAdmin'*/
  TRANSACTION (0.1ms)  COMMIT TRANSACTION /*application='CSAAdmin'*/
  ActiveStorage::Blob Load (0.0ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = 6771 LIMIT 1 /*application='CSAAdmin'*/
Enqueued ActiveStorage::PurgeJob (Job ID: 30f9c4b2-dac7-476b-9bf7-1484fe6141b5) to Async(default) with arguments: #<GlobalID:0x000000012f371060 @uri=#<URI::GID gid://csa-admin/ActiveStorage::Blob/6771>>, {"tenant"=>"ragedevert", "current"=>{}}
↳ (csa-admin):26:in `block in <main>'
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='CSAAdmin'*/
  ActiveStorage::Blob Update (0.1ms)  UPDATE "active_storage_blobs" SET "metadata" = '{"identified":true,"analyzed":true}' WHERE "active_storage_blobs"."id" = 6772 /*application='CSAAdmin'*/
  ActiveStorage::Attachment Load (0.0ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."blob_id" = 6772 /*application='CSAAdmin'*/
  ActiveStorage::Blob Load (1.1ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = 6771 LIMIT 1 /*application='CSAAdmin'*/
Performing ActiveStorage::PurgeJob (Job ID: 30f9c4b2-dac7-476b-9bf7-1484fe6141b5) from Async(default) enqueued at 2024-12-10T17:04:26.884725000Z with arguments: #<GlobalID:0x0000000139e1c4b0 @uri=#<URI::GID gid://csa-admin/ActiveStorage::Blob/6771>>, {"tenant"=>"ragedevert", "current"=>{}}
  TRANSACTION (5000.9ms)  BEGIN immediate TRANSACTION /*application='CSAAdmin'*/
  Organization Load (5001.0ms)  SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = 1 /*application='CSAAdmin'*/
  TRANSACTION (0.0ms)  ROLLBACK TRANSACTION /*application='CSAAdmin'*/
SQLite3::BusyException: database is locked
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:100:in `step'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:100:in `perform_query'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:556:in `block (2 levels) in raw_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:1011:in `block in with_raw_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:983:in `with_raw_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:555:in `block in raw_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:1129:in `log'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:554:in `raw_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:591:in `internal_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:71:in `internal_begin_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:33:in `begin_db_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:455:in `materialize!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:584:in `block (2 levels) in materialize_transactions'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:584:in `each'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:584:in `block in materialize_transactions'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:581:in `materialize_transactions'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `materialize_transactions'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:986:in `block in with_raw_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:983:in `with_raw_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:555:in `block in raw_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:1129:in `log'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:554:in `raw_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:591:in `internal_execute'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:547:in `internal_exec_query'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:693:in `select'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:73:in `select_all'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/query_cache.rb:251:in `select_all'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/querying.rb:68:in `_query_by_sql'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1449:in `block (2 levels) in exec_main_query'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:406:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1448:in `block in exec_main_query'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1470:in `skip_query_cache_if_necessary'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1432:in `exec_main_query'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1410:in `block in exec_queries'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1470:in `skip_query_cache_if_necessary'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1404:in `exec_queries'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1181:in `load'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:56:in `load_records_for_keys'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:92:in `load_records'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:71:in `records'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:29:in `records_for'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:33:in `load_records_in_batch'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:42:in `block in group_and_load_similar'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:41:in `each_pair'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:41:in `group_and_load_similar'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:27:in `call'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader.rb:121:in `call'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1326:in `block in preload_associations'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1325:in `each'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1325:in `preload_associations'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1414:in `block in exec_queries'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1470:in `skip_query_cache_if_necessary'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1404:in `exec_queries'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/association_relation.rb:44:in `exec_queries'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1181:in `load'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:343:in `records'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation/delegation.rb:101:in `each'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/blob.rb:386:in `touch_attachments'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:361:in `block in make_lambda'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:207:in `call'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `block in invoke_after'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `each'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `invoke_after'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:110:in `run_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_update_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:449:in `_update_record'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/timestamp.rb:122:in `_update_record'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:894:in `create_or_update'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:441:in `block in create_or_update'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:120:in `block in run_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/autosave_association.rb:396:in `around_save_collection_association'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:129:in `block in run_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:140:in `run_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_save_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:441:in `create_or_update'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/timestamp.rb:127:in `create_or_update'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:424:in `save!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/validations.rb:54:in `save!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:366:in `block in save!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:418:in `block (2 levels) in with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:357:in `transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:414:in `block in with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:406:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:410:in `with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:366:in `save!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/suppressor.rb:56:in `save!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:579:in `block in update!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:418:in `block (2 levels) in with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:626:in `block in within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:414:in `block in with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:406:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:410:in `with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:577:in `update!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/blob/analyzable.rb:30:in `analyze'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/blob/analyzable.rb:41:in `analyze_later'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/attachment.rb:127:in `analyze_blob_later'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:361:in `block in make_lambda'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:207:in `call'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `block in invoke_after'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `each'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `invoke_after'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:110:in `run_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_commit_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:385:in `committed!'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:307:in `block in commit_records'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:344:in `run_action_on_records'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:306:in `commit_records'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:606:in `block in commit_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:594:in `commit_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:638:in `block in within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:234:in `block in transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:412:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:233:in `transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/has_one_association.rb:127:in `transaction_if'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/has_one_association.rb:68:in `replace'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/singular_association.rb:26:in `writer'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/builder/association.rb:113:in `logo_attachment='
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/changes/create_one.rb:49:in `public_send'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/changes/create_one.rb:49:in `save'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/model.rb:142:in `block in has_one_attached'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:406:in `instance_exec'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:406:in `block in make_lambda'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:207:in `call'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `block in invoke_after'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `each'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `invoke_after'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:110:in `run_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_save_callbacks'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:441:in `create_or_update'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/timestamp.rb:127:in `create_or_update'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:391:in `save'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/validations.rb:48:in `save'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:362:in `block in save'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:418:in `block (2 levels) in with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:626:in `block in within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:414:in `block in with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:412:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:410:in `with_transaction_returning_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:362:in `save'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/suppressor.rb:52:in `save'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/one.rb:61:in `attach'
(csa-admin):26:in `block in <main>'
/Users/thibaudgg/Codes/csa-admin/lib/tenant.rb:51:in `block (2 levels) in switch'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:214:in `prohibit_shard_swapping'
/Users/thibaudgg/Codes/csa-admin/lib/tenant.rb:50:in `block in switch'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:175:in `connected_to_many'
/Users/thibaudgg/Codes/csa-admin/lib/tenant.rb:49:in `switch'
(csa-admin):22:in `<main>'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/workspace.rb:121:in `eval'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/workspace.rb:121:in `evaluate'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/context.rb:622:in `evaluate_expression'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/context.rb:590:in `evaluate'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/mission_control-jobs-1.0.1/lib/mission_control/jobs/console/context.rb:8:in `evaluate'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1051:in `block (2 levels) in eval_input'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1363:in `signal_status'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1043:in `block in eval_input'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1122:in `block in each_top_level_statement'
<internal:kernel>:187:in `loop'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1119:in `each_top_level_statement'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1042:in `eval_input'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1023:in `block in run'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1022:in `catch'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1022:in `run'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/irb_console.rb:122:in `start'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/console_command.rb:59:in `start'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/console_command.rb:8:in `start'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/console_command.rb:87:in `perform'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/thor-1.3.2/lib/thor/command.rb:28:in `run'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/thor-1.3.2/lib/thor/invocation.rb:127:in `invoke_comma  TRANSACTION (4976.6ms)  BEGIN immediate TRANSACTION /*application='CSAAdmin',job='ActiveStorage%3A%3APurgeJob'*/
nd'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command/base.rb:178:in `invoke_command'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/thor-1.3.2/lib/thor.rb:538:in `dispatch'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command/base.rb:73:in `perform'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command.rb:65:in `block in invoke'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command.rb:143:in `with_argv'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command.rb:63:in `invoke'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands.rb:18:in `<main>'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/3.3.0/bundled_gems.rb:69:in `require'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/3.3.0/bundled_gems.rb:69:in `block (2 levels) in replace_require'
/Users/thibaudgg/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
bin/rails:4:in `<main>'
=> nil
  ActiveStorage::Attachment Exists? (4977.4ms)  SELECT 1 AS one FROM "active_storage_attachments" WHERE "active_storage_attachments"."blob_id" = 6771 LIMIT 1 /*application='CSAAdmin',job='ActiveStorage%3A%3APurgeJob'*/
  ActiveStorage::VariantRecord Load (0.1ms)  SELECT "active_storage_variant_records".* FROM "active_storage_variant_records" WHERE "active_storage_variant_records"."blob_id" = 6771 /*application='CSAAdmin',job='ActiveStorage%3A%3APurgeJob'*/
  ActiveStorage::Attachment Load (0.9ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 6771 AND "active_storage_attachments"."record_type" = 'ActiveStorage::Blob' AND "active_storage_attachments"."name" = 'preview_image' LIMIT 1 /*application='CSAAdmin',job='ActiveStorage%3A%3APurgeJob'*/
                               ActiveStorage::Blob Destroy (0.1ms)  DELETE FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = 6771 /*application='CSAAdmin',job='ActiveStorage%3A%3APurgeJob'*/
                                                                                                                                                                                                              TRANSACTION (0.1ms)  COMMIT TRANSACTION /*application='CSAAdmin',job='ActiveStorage%3A%3APurgeJob'*/
                                                                                                                                                                                                                                                                                                                    TenantDisk Storage (0.4ms) Deleted file from key: 26u34bozkmb5olxjh7woptprjpn4
Performed ActiveStorage::PurgeJob (Job ID: 30f9c4b2-dac7-476b-9bf7-1484fe6141b5) from Async(default) in 4985.13ms

@rosa
Copy link
Member

rosa commented Dec 10, 2024

I attach a PDF file and not a PNG. I tried with a PNG, and it works fine for me too! 🤯

Oh wow! Great catch! 🔍 💯 Going to check with a PDF file as well!

@rosa
Copy link
Member

rosa commented Dec 10, 2024

Aha! I can indeed reproduce with a PDF file:

class Dog < ApplicationRecord
  has_one_attached :photo

  def attach_test_photo
    photo.attach(io: File.open("/tmp/sample.pdf"), filename: "sample.pdf")
  end
end

And then:

>> ActiveRecord::Base.connected_to_many(ApplicationRecord, ActiveStorage::Record, role: :writing, shard: :shard_two) do
  # Should work fine
  Dog.count
  ActiveStorage::Blob.count
  # Should raise an error
  Dog.create!
  Dog.first.attach_test_photo
end
  Dog Count (1.7ms)  SELECT COUNT(*) FROM "dogs" /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Count (2.2ms)  SELECT COUNT(*) FROM "active_storage_blobs" /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Dog Create (0.4ms)  INSERT INTO "dogs" ("name", "features", "nickname", "created_at", "updated_at") VALUES (NULL, NULL, NULL, '2024-12-10 17:58:52.790575', '2024-12-10 17:58:52.790575') RETURNING "id" /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.0ms)  COMMIT TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Dog Load (1.7ms)  SELECT "dogs".* FROM "dogs" ORDER BY "dogs"."id" ASC LIMIT 1 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Load (0.0ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = 1 AND "active_storage_attachments"."record_type" = 'Dog' AND "active_storage_attachments"."name" = 'photo' LIMIT 1 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Attachment Load (0.0ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 1 AND "active_storage_attachments"."record_type" = 'Dog' AND "active_storage_attachments"."name" = 'photo' LIMIT 1 /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.1ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Attachment Destroy (0.5ms)  DELETE FROM "active_storage_attachments" WHERE "active_storage_attachments"."id" = 1 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Create (0.1ms)  INSERT INTO "active_storage_blobs" ("key", "filename", "content_type", "metadata", "service_name", "byte_size", "checksum", "created_at") VALUES ('snj1kva4hbk4dcdft8ei550ph0o5', 'sample.pdf', 'application/pdf', '{"identified":true}', 'local', 84574, '0zqKIzSeM5UfB0flo3IyGw==', '2024-12-10 17:58:52.851668') RETURNING "id" /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Attachment Create (0.0ms)  INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('photo', 'Dog', 1, 2, '2024-12-10 17:58:52.853486') RETURNING "id" /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.2ms)  COMMIT TRANSACTION /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Load (0.7ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = 1 LIMIT 1 /*application='PhantomDbSqliteRepro'*/
Enqueued ActiveStorage::PurgeJob (Job ID: 7092254f-4ee4-49db-aca7-8ccd7837a59a) to Async(default) with arguments: #<GlobalID:0x0000000128e36150 @uri=#<URI::GID gid://phantom-db-sqlite-repro/ActiveStorage::Blob/1>>
↳ app/models/dog.rb:5:in `attach_test_photo'
  TRANSACTION (0.0ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Update (0.9ms)  UPDATE "active_storage_blobs" SET "metadata" = '{"identified":true,"analyzed":true}' WHERE "active_storage_blobs"."id" = 2 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Attachment Load (0.1ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."blob_id" = 2 /*application='PhantomDbSqliteRepro'*/
  ActiveStorage::Blob Load (1.8ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = 1 LIMIT 1 /*application='PhantomDbSqliteRepro'*/
Discarded ActiveStorage::PurgeJob (Job ID: 7092254f-4ee4-49db-aca7-8ccd7837a59a) due to a ActiveRecord::RecordNotFound (Couldn't find ActiveStorage::Blob with 'id'=1).
Performed ActiveStorage::PurgeJob (Job ID: 7092254f-4ee4-49db-aca7-8ccd7837a59a) from Async(default) in 6.1ms
  TRANSACTION (5025.9ms)  BEGIN immediate TRANSACTION /*application='PhantomDbSqliteRepro'*/
  Dog Load (5026.2ms)  SELECT "dogs".* FROM "dogs" WHERE "dogs"."id" = 1 /*application='PhantomDbSqliteRepro'*/
  TRANSACTION (0.1ms)  ROLLBACK TRANSACTION /*application='PhantomDbSqliteRepro'*/
app/models/dog.rb:5:in `attach_test_photo': SQLite3::BusyException: database is locked (ActiveRecord::StatementTimeout)
	from (phantom-db-sqlite-repro):7:in `block in <main>'
	from (phantom-db-sqlite-repro):1:in `<main>'
/Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:100:in `step': database is locked (SQLite3::BusyException)
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:100:in `perform_query'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:556:in `block (2 levels) in raw_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:1011:in `block in with_raw_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:983:in `with_raw_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:555:in `block in raw_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:1129:in `log'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:554:in `raw_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:591:in `internal_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:71:in `internal_begin_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/sqlite3/database_statements.rb:33:in `begin_db_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:455:in `materialize!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:584:in `block (2 levels) in materialize_transactions'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:584:in `each'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:584:in `block in materialize_transactions'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:581:in `materialize_transactions'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `materialize_transactions'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:986:in `block in with_raw_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:983:in `with_raw_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:555:in `block in raw_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/notifications/instrumenter.rb:58:in `instrument'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract_adapter.rb:1129:in `log'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:554:in `raw_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:591:in `internal_execute'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:547:in `internal_exec_query'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:693:in `select'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:73:in `select_all'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/query_cache.rb:251:in `select_all'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/querying.rb:68:in `_query_by_sql'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1449:in `block (2 levels) in exec_main_query'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:406:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1448:in `block in exec_main_query'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1470:in `skip_query_cache_if_necessary'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1432:in `exec_main_query'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1410:in `block in exec_queries'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1470:in `skip_query_cache_if_necessary'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1404:in `exec_queries'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1181:in `load'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:56:in `load_records_for_keys'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:92:in `load_records'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:71:in `records'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:29:in `records_for'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/association.rb:33:in `load_records_in_batch'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:42:in `block in group_and_load_similar'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:41:in `each_pair'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:41:in `group_and_load_similar'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader/batch.rb:27:in `call'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/preloader.rb:121:in `call'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1326:in `block in preload_associations'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1325:in `each'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1325:in `preload_associations'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1414:in `block in exec_queries'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1470:in `skip_query_cache_if_necessary'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1404:in `exec_queries'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/association_relation.rb:44:in `exec_queries'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:1181:in `load'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation.rb:343:in `records'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/relation/delegation.rb:101:in `each'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/blob.rb:386:in `touch_attachments'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:361:in `block in make_lambda'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:207:in `call'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `block in invoke_after'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `each'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `invoke_after'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:110:in `run_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_update_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:449:in `_update_record'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/timestamp.rb:122:in `_update_record'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:894:in `create_or_update'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:441:in `block in create_or_update'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:120:in `block in run_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/autosave_association.rb:396:in `around_save_collection_association'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:129:in `block in run_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:140:in `run_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_save_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:441:in `create_or_update'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/timestamp.rb:127:in `create_or_update'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:424:in `save!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/validations.rb:54:in `save!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:366:in `block in save!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:418:in `block (2 levels) in with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:357:in `transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:414:in `block in with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:406:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:410:in `with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:366:in `save!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/suppressor.rb:56:in `save!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:579:in `block in update!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:418:in `block (2 levels) in with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:626:in `block in within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:414:in `block in with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:406:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:410:in `with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:577:in `update!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/blob/analyzable.rb:30:in `analyze'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/blob/analyzable.rb:41:in `analyze_later'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/app/models/active_storage/attachment.rb:127:in `analyze_blob_later'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:361:in `block in make_lambda'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:207:in `call'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `block in invoke_after'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `each'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `invoke_after'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:110:in `run_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_commit_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:385:in `committed!'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:307:in `block in commit_records'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:344:in `run_action_on_records'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:306:in `commit_records'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:606:in `block in commit_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:594:in `commit_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:638:in `block in within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:234:in `block in transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:412:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:233:in `transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/has_one_association.rb:127:in `transaction_if'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/has_one_association.rb:68:in `replace'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/singular_association.rb:26:in `writer'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/associations/builder/association.rb:113:in `photo_attachment='
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/changes/create_one.rb:49:in `public_send'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/changes/create_one.rb:49:in `save'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/model.rb:142:in `block in has_one_attached'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:406:in `instance_exec'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:406:in `block in make_lambda'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:207:in `call'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `block in invoke_after'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `each'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:562:in `invoke_after'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:110:in `run_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/callbacks.rb:912:in `_run_save_callbacks'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/callbacks.rb:441:in `create_or_update'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/timestamp.rb:127:in `create_or_update'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/persistence.rb:391:in `save'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/validations.rb:48:in `save'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:362:in `block in save'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:418:in `block (2 levels) in with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:626:in `block in within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activesupport-8.0.0/lib/active_support/concurrency/null_lock.rb:9:in `synchronize'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/transaction.rb:623:in `within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in `within_new_transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in `transaction'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:414:in `block in with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_adapters/abstract/connection_pool.rb:412:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:310:in `with_connection'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:410:in `with_transaction_returning_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/transactions.rb:362:in `save'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/suppressor.rb:52:in `save'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activestorage-8.0.0/lib/active_storage/attached/one.rb:61:in `attach'
	from /Users/rosa/Work/rosa/phantom-db-sqlite-repro/app/models/dog.rb:5:in `attach_test_photo'
	from (phantom-db-sqlite-repro):7:in `block in <main>'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/activerecord-8.0.0/lib/active_record/connection_handling.rb:175:in `connected_to_many'
	from (phantom-db-sqlite-repro):1:in `<main>'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/workspace.rb:121:in `eval'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/workspace.rb:121:in `evaluate'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/context.rb:622:in `evaluate_expression'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb/context.rb:590:in `evaluate'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1051:in `block (2 levels) in eval_input'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1363:in `signal_status'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1043:in `block in eval_input'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1122:in `block in each_top_level_statement'
	from <internal:kernel>:187:in `loop'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1119:in `each_top_level_statement'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1042:in `eval_input'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1023:in `block in run'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1022:in `catch'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/irb-1.14.1/lib/irb.rb:1022:in `run'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/irb_console.rb:122:in `start'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/console_command.rb:59:in `start'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/console_command.rb:8:in `start'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands/console/console_command.rb:87:in `perform'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/thor-1.3.2/lib/thor/command.rb:28:in `run'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/thor-1.3.2/lib/thor/invocation.rb:127:in `invoke_command'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command/base.rb:178:in `invoke_command'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/thor-1.3.2/lib/thor.rb:538:in `dispatch'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command/base.rb:73:in `perform'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command.rb:65:in `block in invoke'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command.rb:143:in `with_argv'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/command.rb:63:in `invoke'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/railties-8.0.0/lib/rails/commands.rb:18:in `<main>'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/3.3.0/bundled_gems.rb:69:in `require'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/3.3.0/bundled_gems.rb:69:in `block (2 levels) in replace_require'
	from /Users/rosa/.local/share/mise/installs/ruby/3.3.6/lib/ruby/gems/3.3.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
	from bin/rails:4:in `<main>'
>>

But in this case, looks like the purge job ran fine, though it couldn't find a blob with id=1, which is super weird because there was a blob with id=1, the one I created in the previous test 🤔 This suggests that perhaps the async job is not connecting to the right shard 🤔 Although TBH I'm not sure why a purge job is enqueued in this case, as the dog record is new, so there was no previous attachment or anything 😕

Discarded ActiveStorage::PurgeJob (Job ID: 7092254f-4ee4-49db-aca7-8ccd7837a59a) due to a ActiveRecord::RecordNotFound (Couldn't find ActiveStorage::Blob with 'id'=1).
Performed ActiveStorage::PurgeJob (Job ID: 7092254f-4ee4-49db-aca7-8ccd7837a59a) from Async(default) in 6.1ms

The SQLite3::BusyException: database is locked (ActiveRecord::StatementTimeout) error itself seems to have happened in a callback... I need to investigate a bit more here! But this does look like a bug in Rails. I'd expect this to work exactly as if ActiveRecord::Base.connected_to were used, and across file types as well 🤔

@rosa
Copy link
Member

rosa commented Dec 10, 2024

Ok, I found the reason why this happens with a PDF and not with a PNG. The reason is that, when attaching the blob, this method gets called. The analyzer_class for the PNG file is ActiveStorage::Analyzer::ImageAnalyzer::Vips which doesn't change the default for analyze_later?, so that's true, and a ActiveStorage::AnalyzeJob gets enqueued. However, when using a PDF, the analyzer_class by default is ActiveStorage::Analyzer::NullAnalyzer, which just returns empty metadata, it doesn't perform any analysis. Because of this, it sets analyze_later? to false, which means we run this update, and this callback. In the touch_attachments callback is where I get the error. For the PNG, that callback happens together with the update in the AnalyzeJob, so not there 😅

I don't know yet why the callback works just fine when using ActiveRecord::Base.connected_to, but I'll continue investigating. I think this should be also reproducible without Active Storage, just with a similar callback, ensuring it's triggered. I'll look into that as well, since the simpler the reproduction, the simpler it should be to narrow down the problem.

@rosa
Copy link
Member

rosa commented Dec 10, 2024

Aha! I think I found the reason why this doesn't happen when using ActiveRecord::Base.connected_to! It's because during touch_attachments, we reach over the other connection because of ActiveStorage.touch_attachment_records, which is true by default. This means we'd try to fetch blob.attachments.record, which is Dog in this case, but we'll do that from the ActiveStorage::Record connection if that makes sense 🤔 It's a bit hand-wavy because I haven't ironed out the details about that yet, but setting

config.active_storage.touch_attachment_records = false

makes the problem disappear.

@thibaudgg
Copy link

@rosa so happy you were able to reproduce the error 🙌🏻

I can confirm that setting config.active_storage.touch_attachment_records = false makes the problem disappear in my app when testing in the Rails console. Awesome detective job! 🔍 👍🏻

That said, running my specs still raise SQLite3::BusyException: database is locked when the spec is attaching a PDF. Not sure what would be different here, the :test queue adapter maybe?

My test config is:

# config/environments/test.rb
config.active_storage.service = :test
config.active_storage.touch_attachment_records = false

Please let me know if I can help debugging it further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants