DEV Community

Augusts Bautra
Augusts Bautra

Posted on

Buffer all the things

Today I was working on performance optimisations for import logic. One place was operating on an array of arrays of IDs like so:

id_groups = [
  [1, 2],
  [42, 43],
  ...
]

id_groups.each do |ids|
  ids.each_slice(5000) do |portion|
    SomeModel.where(id: portion).update_all(some_field => some_value)
  end
end
Enter fullscreen mode Exit fullscreen mode

While doing an .update_all in reasonable batches is a good start, there's a slight inefficiency here in that if the groups are many and small, we'll be making an update query for each group changing only a handful of records.

We can cut down on the number of update queries made, and thus save on the DB roundtrip overhead by utilising a common pattern - buffered bulk updates.

We will be buffering (collecting in some array) the updates we want to make, and performing an update query (with .upsert_all) only when there's a good number of changes collected.

update_buffer = []
batch_size = 1000

# Collect updates into the buffer and execute upsert_all when reaching batch_size
id_groups.each do |ids|
  ids.each do |id|
    # Add each state update to the buffer
    update_buffer << { id: id, some_field => some_value }

    # Check if buffer has reached the batch size, and perform upsert_all if so
    if update_buffer.size >= batch_size
      SomeModel.upsert_all(update_buffer, unique_by: :id, returning: false)
      update_buffer.clear # Clear buffer after upsert
    end
  end
end

# Perform final upsert_all for any remaining updates in the buffer
SomeModel.upsert_all(update_buffer, unique_by: :id, returning: false) unless update_buffer.empty?
Enter fullscreen mode Exit fullscreen mode

An additional tweak that could be made is avoiding the need to recount the fullness of the buffer for every iteration (the update_buffer.size part), but introducing some counter, incrementing it, and resetting it alongside upsert call. This is useful if the buffer size in your case is large.

Let me know what you think of this technique, have you used something similar?

Top comments (0)