Update millions of records in Rails
blog.eq8.eu
blog.eq8.eu
Also, this is another example of why normalizing your schemas is important - in a normalized schema, you would only have fiftyish state records, and a few tens of thousands of city records. (Yes, this is just an example, I'm sure they wouldn't actually update city and state on every record this way, but it's a useful example of why normalized data is easier to work with. Imagine if you missed a few records - now you have a random assortment of Cleveland, CLEVELAND, and cleveland, where if your schema were normalized with a constraint, you'd have one city_id pointing to Cleveland, OH)
Would you please share this useful gem's name? I have hand-rolled some variety of this solution so many times that it feels like just a part of life, like doing the laundry. If a gem can do that setup for me, I would gladly use it.
and for seeding data https://github.com/pboling/seed_migration
https://github.com/OffgridElectric/rails-data-migrations
This is maybe a more modern one I found:
The most common I've seen relate to replication. If your database has replicas and uses row-based replication (as opposed to statement-based replication) running "update huge_table set title = upper(title)" could create a billion updated rows, which then need to be copied to all of those replicas. This can cause big problems.
In those cases, it's better to update 1,000 rows at a time using some kind of batching mechanism, similar to the one described in the article.
N.B. postgresql can use parallel execution of workers in batches above a certain size, Depending On Your Settings™ and version
I have no problem with Rails or ActiveRecord but bulk upload of data through any ORM is a big no for me.
You just need to use batches (that `.in_batches` in the link), it's an easy-peasy approach which is way more acceptable for your database than a single update of 500m records. I've been using it for years and it has never raised a single issue.
Anyways. Before blindly doing string manipulation with postgres make sure that all the locale stuff is set correctly.
[1] https://api.rubyonrails.org/classes/ActiveRecord/Relation.ht...
Is there a way to send low priority updates that wont interfere with other requests?
But not out of the box or on something like RDS - then your best bet is to write a script or function that uses sleep or pg_sleep to wait between calls to the database - I tend to do it in the database itself by using `CREATE FUNCTION` to write it, but depending on your comfort with postgres it may be better to do it in a scripting language or shell script (as in the parent article using a background job)
> processes per-query
Minor nit, this is probably what you meant but it’s process per _connection_.After discussing with a friend that frequently works with large data sets, he suggested that single very large upserts/inserts (50k to 100k) records would be handled much better by Postgresql, and they were.
Our final setup still has all those Sidekiq workers, but they dump the records as JSON blobs into Kafka. From there a single consumer pulls them out and buffers. Once it has 50k, it dumps them all in a single ActiveRecord Upsert. Its been working very well for us for a while now.
> You need to schedule few thousand record samples and monitor how well/bad will your worker perform
I find it staggering how many senior developers don’t know how to profile or performance test their software. Just throwing a few million rows into a local db and measuring your app performance is so basic, yet I’ve seen many teams not even consider this, and others farm it to a “QA”.
- Fear of the database. For some folks keeping the logic in the app which is fully under their control is comforting. I find this to be common by folks who've been burnt by their db in the past. It's a lot easier to assert (quality) control over your own software.
- Difficulty in modeling data. This is more tricky, but IMO separates code monkeys from developers.
Those milliseconds, N+1 queries, nested queries, large joins (owing to incongruence between logical data structure and how that data is stored on disk) and poorly-indexed tables combine into lots of sad times.
It all depends on your database, which is why you need an actual DBA role on your team instead of just using your database like an abstract storage entity.
People seem to be getting along alright without it, no?
I mean ideally you would want someone on your team who understands how the technology behind your product actually works and how to use it efficiently.
"We built this awesome tower. The foundation? It's great so far, I guess. But we really are tower builders, not foundation builders." - The team behind the Leaning Tower of Pisa
The Leaning Tower stands today, so obviously they built their tower really well. But the problems with the foundation takes away from it somewhat.
One could suppose that it's just a contrived simple example to show the point, but the headline suggests otherwise.
I’m working on a codebase where someone had the amazing idea to implement some of the multi-model business logic using the “Interactor” gem. I’ve just spent two days rewriting one of these interactor abominations into a simple 250 LOC module of module_function-ed methods with clear dependencies and data flow, as a temporary solution to be able to see what’s even happening there and build some actual domain models from it.
I can’t imagine anyone with some CS background and a bit of experience with other languages to look at the interactor stuff and go: omg this is perfect, exactly what this codebase needs. It’s like a normal Ruby class but it:
- hides all instance variables into an opaque “context” bag
- has methods which don’t take arguments (they reach into the “context” bag instead)
- has methods that don’t have return values (side effects go into the “context” bag)
- has no constructor and just chucks everything you pass to #new into the “context” bag
- is not understood by RubyMine, arguably the most intelligent Ruby IDE, at all>>> MyService.new.call(address_batch)
The call method doesn't hold state past execution, so don't instantiate a new object for no reason. Just call a static method.
WhenEVER I make an insert, update, or delete -- I always use a transaction.
That's what we should always do, right?
Right?
> For our setup/task (just update few fields on a table) the process of probing different batch sizes & Sidekiq thread numbers with couple of thousands/millions records took about 5 hours [...]
You want to commit a set of statements that is a business transaction.
---
In this case, it's okay for individual records to fail. They can either be manually processed, re-run, or root caused for failure and handled individually.
A lot of the work I do involves analyzing and improving Ruby applications' performance. Often, simply wrapping transactions around the database operations will help - without making other (often much needed) structural changes.
Typically, these get run during off-peak hours. This running in 30 minutes vs 12 hours simply isn't important.
Generate and send SQL (wrapped in transactions) from C bindings via a proper background job processor like beanstalkd in batches of say 50k. Getting Ruby involved in millions of records is asking for trouble.
It's extremely well-designed and battle-hardened code, and it recently had a major release.
Most of the non-enterprise customers pay because we want to support Mike Perham, not because we need paid-tier features.
Beanstalkd is simple, scales, and is platform agnostic.
At scale, simple wins.
I don't think anyone here suggested that Beanstalkd is bad or should not be used, if it fits your use case like a glove.
Speed-wise, let's not forget that Redis is also written in C.
Bigger picture, folks outside of the Rails community love to sidestep the simple fact that something like 75% of the total value created by YC-backed companies was created by the subset who built with Rails.
Clearly, we're connecting the dots in a way others are not.