Reputation: 409
Currently I am facing a problem in which I am getting the Timeout::Error. Here is my detailed scenario
I have following tables
Model - Client
class Client < ActiveRecord::Base
has_many :users
has_many :scheme_transactions
end
Model - User
class User < ActiveRecord::Base
has_many :scheme_transactions
end
Model - SchemeTransaction
class SchemeTransaction < ActiveRecord::Base
belongs_to :client
belongs_to :user
end
Consider an example, I have a client (stored in the clients table) say ABCL which has around 1000 users (stored in the users table) and there are around 50000 transactions (stored in the scheme_transactions table) done by all of the respective client users.
Now I have to find total of the first transaction of each user. For this I have written following logic and it is working fine on the rails console but getting Time out error in the browser.
users = User.joins(:client).where(clients: {id: clients})
first_purchase = Array.new
users.each do |user|
user_transaction = SchemeTransaction.joins(:user).where(scheme_transactions: {user_id: user.id}).first
first_purchase << user_transaction.points unless user_transaction.nil?
end
total_first_purchase = first_purchase.inject(:+)
So is there any other way to avoid the problem or how can I simplify my logic.
I am using ruby 1.9.3 and rails 3.2.11
EDIT Also tried using find_each method
users.find_each(:batch_size => 1000) do |user|
user_transaction = SchemeTransaction.joins(:user).where(scheme_transactions: {user_id: user.id}).first
first_purchase << user_transaction.points unless user_transaction.nil?
end
Upvotes: 1
Views: 263
Reputation: 1902
If user model has client_id, I'm not sure why are using 'joins' in following query
users = User.joins(:client).where(clients: {id: clients})
Why don't you use
users = User.where(client_id: clients)
Second thing is in each loop, As scheme_transactions has user_id, you don't need use 'joins' for following query. Is following query working for you?, In rails 4.1.6 its not working for me.
user_transaction = SchemeTransaction.joins(:user).where(scheme_transactions: {user_id: user.id}).first
Simply replace this as
user_transaction = SchemeTransaction.where(user_id: user.id).first
Or
user_transaction = user.scheme_transactions.first
Upvotes: 0
Reputation: 1124
There are three joins here, each join will be using increasing amounts of memory. The third joins a 50000 row table to the result of clients * 1000 rows. Thats 50,000,000 x clients rows held in memory and temporary hard disk memory (page swaps are slow).
Retrieve just the users for this client
users = Client.find(client_id).users
In your users loop retrieve only the transaction you want by sorting and taking the first transaction. You may need to consider specifying DESC in the sort_by if you get the last transaction rather than the first.
user_transaction = user.scheme_transactions.sort_by(:date).first
Your models appear to be set up ok, so this should be all you need.
Upvotes: 0
Reputation: 5279
You can run a SQL query:
SELECT SUM(points)
FROM scheme_transactions
WHERE id in (
SELECT MIN(id)
FROM scheme_transactions st
INNER JOIN users u ON st.user_id = u.id
WHERE u.client_id = :client_id
)
Your method will queries the database for each User
, hence the timeout.
Upvotes: 0