Rails Strict Loading: Vang N+1 Queries Voordat Ze Productie Bereiken
N+1 queries zijn de performancebug die je niet opmerkt tot je database in de fik staat. Rails heeft al jaren includes en eager_load, maar je moet ze wel onthouden. Strict loading draait dat om: lazy loading wordt een error, tenzij je het expliciet toestaat.
Beschikbaar sinds Rails 6.1 en verfijnd in Rails 7 en 8 — strict loading vangt ontbrekende eager loads tijdens development, in plaats van ze stilletjes je productie-responstijden te laten slopen.
Hoe Strict Loading Werkt
Wanneer strict loading actief is op een record of associatie, gooit elke poging tot lazy loading een ActiveRecord::StrictLoadingViolationError. In plaats van stilletjes extra queries af te vuren, vertelt je app precies waar je een eager load vergeten bent.
# Zonder strict loading — stilletjes N+1 queries
posts = Post.all
posts.each { |p| p.comments.count } # N+1, geen waarschuwing
# Met strict loading — gooit meteen een error
posts = Post.strict_loading.all
posts.each { |p| p.comments.count }
# => ActiveRecord::StrictLoadingViolationError:
# `Post` is marked for strict_loading. The Comment association
# named `:comments` cannot be lazily loaded.
De fix is een eager load toevoegen:
posts = Post.strict_loading.includes(:comments).all
posts.each { |p| p.comments.count } # Eén query, geen error
Drie Manieren om Het In te Schakelen
1. Per Query met .strict_loading
De meest gerichte aanpak. Voeg het toe aan specifieke queries waarvan je weet dat ze performance-gevoelig zijn:
@dashboard_posts = Post.strict_loading
.includes(:author, :comments, :tags)
.where(published: true)
.limit(50)
2. Per Model als Standaard
Stel het in op de modelklasse om strict loading af te dwingen op elke query voor dat model:
class Post < ApplicationRecord
self.strict_loading_by_default = true
belongs_to :author
has_many :comments
has_many :tags, through: :taggings
end
Dit is agressief, maar werkt goed voor modellen die vaak betrokken zijn bij N+1 problemen.
3. Per Associatie
Markeer individuele associaties als strict:
class Post < ApplicationRecord
has_many :comments, strict_loading: true
has_many :audit_logs # Deze kan nog steeds lazy loaden
end
De meest fijnmazige optie. Gebruik het wanneer één specifieke associatie duur is maar andere associaties prima lazy kunnen laden.
Globale Strict Loading in Development
De meest praktische setup: schakel strict loading globaal in tijdens development en test, maar laat het uit in productie.
In Rails 6.1 en 7.x gebruik je een initializer:
# config/initializers/strict_loading.rb
if Rails.env.local? # .local? dekt development + test in Rails 7.1+
ActiveRecord::Base.strict_loading_by_default = true
end
Rails 8 heeft config.active_record.strict_loading_by_default toegevoegd aan de frameworkconfiguratie:
# config/environments/development.rb
config.active_record.strict_loading_by_default = true
Strict Loading Mode: :log vs :raise
Rails 7.1 introduceerde strict_loading_mode, waarmee je violations kunt loggen in plaats van een exception te gooien:
ActiveRecord::Base.strict_loading_mode = :log
Met :log mode verschijnen violations als waarschuwingen in je Rails-log zonder de uitvoering te stoppen. Handig tijdens de migratieperiode wanneer je strict loading inschakelt op een bestaande codebase met honderden queries.
De Transitie Aanpakken
Strict loading inschakelen op een grote codebase levert tientallen of honderden violations op. Een praktische aanpak:
Stap 1: Schakel globaal in met :log mode. Draai je testsuite en noteer elke violation.
Stap 2: Sorteer violations op frequentie. Queries die bij elk request afvuren zijn belangrijker dan die in een zelden gebruikt adminpaneel.
Stap 3: Fix de hoge-frequentie violations eerst. De meeste fixes zijn simpele includes-aanroepen:
# Voor
def index
@posts = Post.where(published: true)
end
# Na
def index
@posts = Post.includes(:author, :tags)
.where(published: true)
end
Stap 4: Voor queries waar eager loading geen zin heeft, gebruik strict_loading!(false) om expliciet uit te schakelen:
post = Post.strict_loading.find(params[:id])
post.strict_loading!(false)
Documenteer waarom je het uitschakelt.
Stap 5: Schakel over naar :raise zodra violations beheersbaar zijn.
Veelvoorkomende Patronen die Breken
Serializers die associaties doorlopen. ActiveModel Serializers of Blueprinter wandelen vaak door associaties. Eager load alles wat de serializer aanraakt op queryniveau.
Callbacks die associaties benaderen. Een after_save die post.author aanroept gooit een error als de post met strict loading geladen is.
Delegatie via associaties. delegate :name, to: :author op een strict-loaded Post gooit een error bij aanroep van :name.
View helpers en partials. Een partial die ergens drie lagen diep post.comments.count aanroept is een klassieke N+1 bron. Strict loading maakt deze direct zichtbaar.
Performance-Impact
Strict loading voegt verwaarloosbare overhead toe — een simpele boolean check bij toegang tot een associatie. Bij benchmarks op Rails 8.0 met Ruby 3.3 voegde strict loading op 10.000 records minder dan 1ms totale overhead toe.
De performance die je wint door ontbrekende eager loads te vangen weegt ruim op tegen deze kosten.
Strict Loading en Counter Caches
Als je counter caches gebruikt, interfereert strict loading er niet mee. Counter cache kolommen staan op het parent model, dus post.comments_count triggert geen associatie-load. Maar post.comments.size kan dat wel — size checkt of de associatie geladen is en valt terug op een COUNT query.
Gebruik post.comments_count in plaats van post.comments.size onder strict loading.
FAQ
Werkt strict loading met preload en eager_load of alleen includes?
Alle drie werken. includes, preload en eager_load voldoen allemaal aan strict loading. Het verschil zit in hoe Rails de SQL opbouwt, maar strict loading controleert alleen of de associatiedata al geladen is wanneer je ze benadert.
Kan ik strict loading in productie gebruiken?
Dat kan, maar de meeste teams doen het niet. Als een lazy load er doorheen glipt in productie, krijg je een 500 error in plaats van een trage pagina. Het veiligere patroon is :raise in development/test en :log of uitgeschakeld in productie.
Hoe werkt strict loading met includes op geneste associaties?
Geneste eager loading werkt zoals verwacht. Met includes(comments: :author) zijn zowel post.comments als comment.author voldaan. Maar als je alleen includes(:comments) doet, gooit het benaderen van comment.author op een strict-loaded record nog steeds een error.
Wat is het verschil tussen strict_loading en strict_loading!?
strict_loading is de querymethode die een nieuwe relatie retourneert: Post.strict_loading.all. strict_loading! is de instantiemethode die een record ter plekke wijzigt. De querymethode heeft de voorkeur omdat deze declaratief is.
About the Author
Roger Heykoop is een senior Ruby on Rails ontwikkelaar met 19+ jaar Rails ervaring en 35+ jaar ervaring in softwareontwikkeling. Hij is gespecialiseerd in Rails modernisering, performance optimalisatie, en AI-ondersteunde ontwikkeling.
Get in TouchRelated Articles
Need Expert Rails Development?
Let's discuss how we can help you build or modernize your Rails application with 19+ years of expertise
Schedule a Free Consultation