0

I have two Rails Apps that need to share some state. Each App needs to run background jobs during their execution lifetimes. I added Heroku Redis to each App, and Sidekiq. So when I run background jobs, everything is great.

I do not have shared state until I point one App's REDIS_URL at the other Apps REDIS_URL and then both Apps are using the same shared Redis instance, I assume, and hence I have some shared state.

Now both Sidekiq instances share the same Redis, and one App is seeing the other Apps jobs and puking, because it has no idea about the other Apps jobs.

What is a clearer way of sharing state and keeping the jobs separate like they should be? I guess I need to just buy a cache I can use as shared state separate from the jobs?

Heroku App 1. Owns Redis Instance. Has Sidekiq. Runs Job AaaBbbJob Heroku App 2. Points at Redis Instance from App1. Has Sidekiq

I notice the logs for App 2 show job AaaBbbJob tried running and failed as AaaBbbJob was not found.

In the logs of App 1, Job AaaBbbJob ran fine...

So my confusion lies in trying to assure myself that this is all OK. When App 1 through a job via Sidekiq into Redis, for whatever reason, App 2 saw that job in Redis too, and tried to run it.

So my question is, since throwing Jobs into Redis is now shared between two Apps, they both try and run ANY jobs they see?

David Lazar
  • 10,865
  • 3
  • 25
  • 38
  • How do you represent your app state ? Sidekiq is running your slug in a different machine / instance. You should pass any required params to your job, whereas it is a model instance or appwide data. Sidekiq has no clue what your webserver constants or class variable created dynamically can be. (Would be easier if you have actual code to add to your question) – Maxence Mar 02 '21 at 00:48

2 Answers2

1

You have two problems here :

  • 2 different apps use Sidekiq and share the same Redis instance
  • need to exchange data between two apps

For the first question, your 2 apps may have different Sidekiq Jobs but because of the same Redis instance, each of the Background app (which runs respective apps slug) pick all Sidekiq Jobs and only understand the jobs that they know about.

You can't really help as Sidekiq is done like this. It is a feature that allows to run multiple dynos with Sidekiq for the same app.

What you can do to separate the two types of Sidekiq jobs is to assign a redis Database to each app : What's the Point of Multiple Redis Databases?

Redis can segregate data into 16 different databases.

For example my Redis config in one of my app is :

config/redis.yml

development:
  url: redis://localhost:6379/
  db: 2  

production:
  url: <%= ENV["REDIS_URL"] %>
  db: 2

test: 
  url: redis://localhost:6379/
  db: 2 

In another app I am using the database number 1. Then there is no collision.

The matching Sidekiq initializer looks like this then :

conf = Rails.application.config_for(:redis)
confid = conf.merge({id: "Sidekiq-server-PID-#{::Process.pid}"})
confservid = conf.merge({id: "Sidekiq-server"})

if Rails.env.production?

  Sidekiq.configure_client do |config|    
    config.redis = conf    
  end

  Sidekiq.configure_server do |config|    
    config.redis = conf    
  end

end


if Rails.env.development?
  Sidekiq.configure_client do |config|         
    config.redis = confid    
  end

  Sidekiq.configure_server do |config|    
    config.redis = confservid        
  end

end

As you can see I am naming all redis clients (in development) on top of what I have explained. It is very useful to understand Redis clients consumption.

Regarding your second question: sharing "app state" I think it is about sharing global values such as constants or class variables.

It is probably possible to share this data through Redis but this is definitely ugly. You turn 2 apps a single monolith. Even if you manage to do it properly, you have to rewrite everything when you want each app to have its own Redis instance.

The only way to do this is through a controller behaving as an API that would answer requests and deliver data to the other app.

Maxence
  • 2,029
  • 4
  • 18
  • 37
  • Thanks for All that. Actually, the state to share between Apps is remarkably clean and safe. One App, App1 for example, generates a JSON data structure { "foomanchu": "greatbeard"} and in App2 I need to consume this key "foomanuchu" sometimes. So there is no ugly as far as I can tell, just a need to share some JSON between Apps! – David Lazar Mar 02 '21 at 15:57
  • 1
    Just do it as API by pinging a route in either app. This is definitely the cleanest way. – Maxence Mar 02 '21 at 16:14
1

Each app will need a Redis connection pool for the other Redis instance. Then App A can use those connections to send data or jobs to App B or push jobs to its sidekiq. Using multiple Redises is known as sharding.

https://github.com/mperham/sidekiq/wiki/Sharding

Mike Perham
  • 21,300
  • 6
  • 59
  • 61
  • So in my Rails App A, at Heroku, I pay for a Super Redis from Heroku. I setup a namespace in Redis, for the REDIS_URL I get as 'AppA'. Now Sidekiq should also point at namespace 'AppA' and magically my ActiveJobs will execute in the correct Redis DB, no crosstalk, all is well. As for AppB, it has a REDIS_URL pointing at AppA Redis, and in the Rail Initialization here, the namespace is called AppB, and the Sidekiq in AppB is pointing at this namespace 'AppB' so that jobs execute in Redis, without polluting what is going on in AppA. – David Lazar Mar 03 '21 at 17:47
  • Now as far as just having a key in Redis, that can be seen by both Apps, I suppose that means both Apps would have to see the same database in Redis, so whoa... all of a sudden I am in trouble again. I think I might just step back. Run AppA on its Redis, AppB on its Redis, and for shared state, figure out another solution – David Lazar Mar 03 '21 at 17:48
  • You need to have a separate Redis pool in AppA that is configured to use AppB's namespace and vice versa. Or you can have a pool in both Apps with no namespace which is a shared space for both. – Mike Perham Mar 03 '21 at 19:16