Rails balance withdraw action threading issue - ruby-on-rails

To allow users create balance withdrawal request, I have a WithdrawalsController#create action. The code checks if the user has sufficient balance before proceeding to creating the withdrawal.
def create
if amount > current_user.available_balance
error! :bad_request, :metadata => {code: "002", description: "insufficient balance"}
return
end
withdrawal = current_user.withdrawals.create(amount: amount, billing_info: current_user.billing_info)
exposes withdrawal
end
This can pose a serious issue in a multi-threaded server. When two create requests arrive simultaneously, and both requests pass the the balance check before creating the withdrawal, then both withdrawal can be created even if the sum of two could be exceeding the original balance.
Having a class variable of Mutex will not be a good solution because that will lock this action for all users, where a lock on a per-user level is desired.
What is the best solution for this?
The following diagram illustrates my suspected threading issue, could it be occurring in Rails?

As far as I can tell your code is safe here, mutlithreading is not a much of a problem. Even with more app instances generate by your app server, each instance will end up testing amount > current_user.available_balance.
If you are really paranoiac about it. you could wrap the all with a transacaction:
ActiveRecord::Base.transaction do
withdrawal = current_user.withdrawals.create!(
amount: amount,
billing_info: current_user.billing_info
)
# if after saving the `available_balance` goes under 0
# the hole transaction will be rolled back and any
# change happened to the database will be undone.
raise ActiveRecord::Rollback if current_user.available_balance < 0
end

Related

Send email to only certain Users, Rails 5

I'm having trouble sending an email blast to only certain users who have a boolean set to true and to not send the email to those users who have it set to false.
In my app I have Fans following Artists through Artists Relationships. Inside my ArtistRelationship model I have a boolean that fans can set to true or false based on if they want email blasts from Artists or not when the Artist makes a post.
So far, I have this:
artist.rb
class Artist < ApplicationRecord
def self.fan_post_email
Artist.inlcudes(:fans).find_each do |fan|
fan.includes(:artist_relationships).where(:post_email => true).find_each do |fan|
FanMailer.post_email(fan).deliver_now
end
end
end
end
posts_controller.rb
class Artists::PostsController < ApplicationController
def create
#artist = current_artist
#post = #artist.artist_posts.build(post_params)
if #post.save
redirect_to artist_path(#artist)
#artist.fan_post_email
else
render 'new'
flash.now[:alert] = "You've failed!"
end
end
end
I'm having trouble getting the fan_post_email method to work. I'm not entirely sure how to find the Fans that have the boolean set to true in the ArtistRelationship model.
You want to send mails to fans of a particular artist. Therefore you call
#artist.fan_post_email
That is you call a method on an instance of the Artist class. Instance methods are not defined with a self.[METHOD_NAME]. Doing so defines class methods (if you where to call e.g. Artist.foo).
First part then is to remove the self. part, second is adapting the scope. The complete method should look like this:
def fan_post_email
artists_relationships
.includes(:fan)
.where(post_email: true)
.find_each do |relationship|
FanMailer.post_email(relationship.fan).deliver_now
end
end
end
Let's walk through this method.
We need to get all fans in order to send mails to them. This can be done by using the artist_relationships association. But as we only want to have those fans having checked the e-mail flag, we limit those by the where statement.
The resulting SQL condition will give us all such relationships. But we do it in batches (find_each) in order to not have to load all of the records into memory upfront.
The block provided to find_each is yielded with an artists_relationships instance. But we need the fan instances and not the artists_relationships instances to send the mail in our block and thus call post_email with the fan instance associated with the relationship. In order to avoid N+1 queries (a query for the fan record of every artists_relationships record one by one) there, we eager load the fan association on the artists_relationships.
Unrelated to the question
The usage of that method within the normal request/response cycle of a user's request will probably slow down the application quite a lot. If an artists has many fans, the application will send an e-mail to every one of them before rendering the response for the user. If it is a popular artist, I can easily imagine this taking minutes.
There is a counterpart to deliver_now which is deliver_later (documentation. Jobs, like sending an e-mail, can be queued and resolved independent from the request/response cycle. It will require setting up a worker like Sidekiq or delayed_job but the increase in performance is definitely worth it.
If the queueing mechanism is set up, it probably makes sense to move the call to fan_post_email there as well as the method itself might also take some time.
Additionally, it might make sense to send e-mail as BCC which would allow you to send one e-mail to multiple fans at the same time.

Can recursion be used to guarantee an object gets saved to the DB in Ruby?

I am making payouts to my sub-merchants and wanted to know if I could use recursion to guarantee that when a payout is made to them, we save that data to our DB.
Example:
def make_payout
result = object.process_payout_through_gateway
if result.success?
payout = Payout.new
payout[:paid] = true
save_payout(payout)
end
end
def save_payout(payout)
begin
payout.save!
rescue e
save_payout(payout)
end
end
If you want to gaunrentee it gets saved without validating the object:
payout.save(validate: false)
The short answer is no. Unless the error is transient, retrying the save operation over and over again will not help you and in some cases it could even exacerbate the problem.
If you expect certain errors to routinely occur during normal operation, you can choose to selectively handle those errors by retrying the operation, for example. The keyword here is selectiveness - if you blindly rescue everything, you risk retrying the operation even when the error is caused by something you cannot routinely recover from, like a programming bug. Therefore, you should always specify a specific exception class that you would like to rescue:
begin
payout.save!
rescue ExceptionThatHappensSometimesButIsNoBigDeal
# ignore error + maybe log it, etc.
end
Catching certain exceptions and retrying the operation later is a strategy frequently used when talking to non-critical external APIs, since those can have transient outages at any time. If you don't ignore these non-critical exceptions, they bubble up and bring down your app with them as well.
However, retrying failed operations that only touch systems which you have control over (such as saving to the local database) is usually counterproductive - if you silently ignore these errors, you risk not finding out about them in time.
Thanks guys, all great answers. I personally went with:
def save_payout(payout)
begin
payout.save!
rescue e
puts "Payment to User-ID: #{current_user.id} FAILED TO SAVE! #{e}"
UserMailer.send_payout_failure_email(current_user.id).deliver_now
end
end
I won't mark a "right" answer and just let everyone up-vote their favorite route.

Using an if statement within a transaction to delete multiple objects

I'm a junior dev trying to write code that separates out beeps that are owned by a user and beeps that another user is authorized for.
The beeps come from a device that a user owns. An owner can authorize other users to use that device and receive their own beep alerts. This gives us two different types of beeps: owned beeps and authorized beeps. I want the authorized user to be able to delete multiple beep events at the same time and only their own, while I want the owner to be able to delete multiple beeps and those beep's corresponding events.
The application I'm working on is very large and architecturally has been set up with this functionality for the owner to delete only single beeps.
The beep events go through a method that talks to a separate application that handles the deletion of events through the device_broker method.
What I ended up doing was a transaction with an if statement and then looping through an each iterator, checking each beep that's passing through if it was owned or authorized by the user.
My problem is that my broker_mock test isn't receiving any arguments. When I use a debugger on the code, the methods all seem to be working, so I'm very puzzled. Is using an if statement with an each loop a good idea? Also wrapping it all in a transaction means that if one tiny thing breaks, it doesn't work. Is there a better alternative?
Thanks for any wisdom or light you can shed.
beeps_controller.rb
def bulk_deletion
BulkBeepRemover.run!(params[:beep_ids], current_user)
end
bulk_beep_remover.rb
class BulkBeepRemover
class Unauthorized < StandardError; end;
attr_reader :beep_ids, :user, :beeps
attr_accessor :owned_beeps, :unowned_beeps
def self.run!(beep_ids, user)
new(beep_ids, user).run!
end
def initialize(beep_ids, user)
#beep_ids = beep_ids
raise Unauthorized if user.nil?
#user = user
end
def beeps
#beeps ||= Beep.finished.find(beep_ids)
end
def device_broker
#device_broker ||= $device_broker
end
attr_writer :device_broker
def run!
#handles deletion of the owned beeps
Beep.transaction do
beeps.each do |beep|
if beep.device.is_owner?(user)
beep.destroy
remove_beep_event(beep.id)
end
# elsif beep.device.is_authorized?(user) logic goes here
end
end
end
private
def remove_beep_event(beep_id)
device_broker.publish('beep_deleted', { beep_id: beep_id })
end
dings_spec.rb
describe "POST /clients_api/beeps/bulk_deletion" do
let(:user_id) { user.id }
let!(:shared_beep_1) { create(:beep, id: 33, device: authorized_device, state: :completed) }
let!(:shared_beep_2) { create(:beep, id: 34, device: authorized_device, state: :completed) }
let!(:owned_beep_1) { create(:beep, id: 35, device: owned_device, state: :completed) }
let!(:owned_beep_2) { create(:beep, id: 36, device: owned_device, state: :completed) }
before do
post "/clients_api/beeps/:beep_id/bulk_deletion", default_params.merge(beep_ids: [shared_beep_1.id, shared_beep_2.id, owned_beep_1.id, owned_beep_2.id], user_id: user.id)
allow_any_instance_of(BulkBeepRemover).to receive(:device_broker).and_return(broker_mock)
end
context "the owner's array of beep ids" do
let(:beep_id) { owned_beep_2.id }
it "deletes the beeps" do
expect { Beep.find(beep_id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it "publishes the owner beep_deleted event" do
expect(broker_mock).to have_received(:publish).with("beep_deleted", { beep_id: beep_id })
end
end
end
end
It looks like your /clients_api/beeps/:beep_id/bulk_deletion route doesn't take a :user_id argument. As such, there's no parameter for the user_id arguments to be passed.
Also, the parameter name :beep_id is mismatched from the usage in the test, which calls it :beep_ids; this may or may not play an issue, but it's worth making sure that the names are aligned to avoid conflicts.
You asked about the if statement inside an each block, and there's no problem with that at all; this is very common in Ruby code. You've done just find with the if statement.
Regarding the transaction, you can protect the transaction block with a rescue, but whether you should depends entirely on a few consideration.
1) The purpose of the transaction block is important, and not at all obvious from the code. You'll have to determine if the transaction is strictly for efficiency in updating the database (to reduce the number of SQL calls), or if it's to maintain transactional integrity.
2) You have to find out whether using rescue in this way is considered acceptable practice in your organization; it's very much verboten in some organizations. When it's not allowed, you must take steps to ensure that the exception that occurs will be avoided before it can happen. One such way is to query whether it's safe to perform an operation prior to performing it, and avoid doing so if it's not safe.
3) You have to know whether it's acceptable to disregard deleting beeps within the transaction, for any reason, without giving proper feedback; or, on the flip side, determine what the proper feedback mechanism is. Processes that silently fail are not well-prized. For instance, if someone asks for a beep to be deleted, and the deletion returns successfully, it can be assumed that the beep has been deleted; if not, the user of the process should be informed in some way that it was not, so that they may take appropriate action, even if that action is simply relaying the message upstream.
Realistically, with questions about the use of the transaction and whether the if can (or should) be used, you may be treading into software design territory, and you may need clarification on the goals and restrictions that you have to work with. This is life of a software developer; every simple question has a number of complex considerations. Learn to recognize and deal with them appropriately, and you'll be well ahead of the pack!

ActiveRecord and Postgres row locking

API clients in a busy application are competing for existing resources. They request 1 or 2 at a time, then attempt actions upon those record. I am trying to use transactions to protect state but am having trouble getting a clear picture of row locks, especially where nested transactions (I guess savepoints, since PG doesn't really do transactions within transactions?) are concerned.
The process should look like this:
Request N resources
Remove those resources from the pool to prevent other users from attempting to claim them
Perform action with those resources
Roll back the entire transaction and return resources to pool if an error occurs
(Assume happy path for all examples. Requests always result in products returned.)
One version could look like this:
def self.do_it(request_count)
Product.transaction do
locked_products = Product.where(state: 'available').lock('FOR UPDATE').limit(request_count).to_a
Product.where(id: locked_products.map(&:id)).update_all(state: 'locked')
do_something(locked_products)
end
end
It seems to me that we could have a deadlock on that first line if two users request 2 and only 3 are available. So, to get around it, I'd like to do...
def self.do_it(request_count)
Product.transaction do
locked_products = []
request_count.times do
Product.transaction(requires_new: true) do
locked_product = Product.where(state: 'available').lock('FOR UPDATE').limit(1).first
locked_product.update!(state: 'locked')
locked_products << locked_product
end
end
do_something(locked_products)
end
end
But from what I've managed to find online, that inner transaction's end will not release the row locks -- they'll only be released when the outermost transaction ends.
Finally, I considered this:
def self.do_it(request_count)
locked_products = []
request_count.times do
Product.transaction do
locked_product = Product.where(state: 'available').lock('FOR UPDATE').limit(1).first
locked_product.update!(state: 'locked')
locked_products << locked_product
end
end
Product.transaction { do_something(locked_products) }
ensure
evaluate_and_cleanup(locked_products)
end
This gives me two completely independent transactions followed by a third that performs the action, but I am forced to do a manual check (or I could rescue) if do_something fails, which makes things messier. It also could lead to deadlocks if someone were to call do_it from within a transaction, which is very possible.
So my big questions:
Is my understanding of the release of row locks correct? Will row locks within nested transactions only be released when the outermost transaction is closed?
Is there a command that will change the lock type without closing the transaction?
My smaller question:
Is there some established or totally obvious pattern here that's jumping out to someone to handle this more sanely?
As it turns out, it was pretty easy to answer these questions by diving into the PostgreSQL console and playing around with transactions.
To answer the big questions:
Yes, my understanding of row locks was correct. Exclusive locks acquired within savepoints are NOT released when the savepoint is released, they are released when the overall transaction is committed.
No, there is no command to change the lock type. What kind of sorcery would that be? Once you have an exclusive lock, all queries that would touch that row must wait for you to release the lock before they can proceed.
Other than committing the transaction, rolling back the savepoint or the transaction will also release the exclusive lock.
In the case of my app, I solved my problem by using multiple transactions and keeping track of state very carefully within the app. This presented a great opportunity for refactoring and the final version of the code is simpler, clearer, and easier to maintain, though it came at the expense of being a bit more spread out than the "throw-it-all-in-a-PG-transaction" approach.

ActiveRecord - working within one connection

For example, suppose there is the code in Rails 3.2.3
def test_action
a = User.find_by_id(params[:user_id])
# some calculations.....
b = Reporst.find_by_name(params[:report_name])
# some calculations.....
c = Places.find_by_name(params[:place_name])
end
This code does 3 requests to database and opens 3 different connections. Most likely it's going to be a quite long action.
Is there any way to open only one connection and do 3 requests within it? Or I want to control which connection to use by myself.
You would want to bracket the calls with transaction:
Transactions are protective blocks where SQL statements are only
permanent if they can all succeed as one atomic action. The classic
example is a transfer between two accounts where you can only have a
deposit if the withdrawal succeeded and vice versa. Transactions
enforce the integrity of the database and guard the data against
program errors or database break-downs. So basically you should use
transaction blocks whenever you have a number of statements that must
be executed together or not at all.
def test_action
User.transaction do
a = User.find_by_id(params[:user_id])
# some calculations.....
b = Reporst.find_by_name(params[:report_name])
# some calculations.....
c = Places.find_by_name(params[:place_name])
end
end
Even though they invoke different models the actions are encapsulated into one call to the DB. It is all or nothing though. If one fails in the middle then the entire capsule fails.
Though the transaction class method is called on some Active Record
class, the objects within the transaction block need not all be
instances of that class. This is because transactions are per-database
connection, not per-model.
You can take a look at ActiveRecord::ConnectionAdapters::ConnectionPool documentation
Also AR doesn't open a connection for each model/query it reuses the existent connection.
[7] pry(main)> [Advertiser.connection,Agent.connection,ActiveRecord::Base.connection].map(&:object_id)
=> [70224441876100, 70224441876100, 70224441876100]

Resources