Ata Günay
Fazla
Published in
2 min readDec 12, 2022

--

ActiveRecord::PreparedStatementCacheExpired Hatası ve Çözümü

Geçtiğimiz günlerde Sentry hesabımıza ilk defa karşılaştığımız bir hata düşmüştü. Araştırmalarım sonucunda bu hatanın çözümünü buldum ve detayları sizinle paylaşmak istedim. Keyifli okumalar :)

ActiveRecord::PreparedStatementCacheExpired Hatası Nedir?

Hatanın ortaya çıkışı ile ilgili bazı bilgiler:

  • Bu hata sadece deploy ortamına migration çalıştırılırken karşınıza çıkabilir.
  • Rails, bizler için SQL sorgularını ön belleğe alarak bu sorguların tekrar tekrar çalıştırıldıklarında daha hızlı yanıt vermesini sağlar.
  • Eğer isterseniz bu özelliği iptal edebilirsiniz ama önerilen bir davranış değildir.
  • Migration çalıştırıldıktan sonra eğer schema dosyası değişirse, önbelleğe alınan ilgili SQL sorguları artık geçersiz kılınır.

Örnek olarak:

  1. Herhangi bir tablodan bir alan (column) silinirse ilgili tablonun ön belleğe alınan SELECT * sorgusu geçersiz hale getirilir
  2. Eğer bu işlem transaction blokları dışında yapıldıysa, Rails önbelleğe alınan sorguyu geçersiz kılar ve güncel sorguyu çalıştırır.
  3. Eğer bu işlem uygulama transaction blokları içindeyken denk geldiyse, Rails bir hata fırlatır ve güncel sorguyu tekrar çalıştırmaz. Çünkü transaction blokları içinde side effects olarak adlandırılan işlemler bulunabilir. Bu işlemlere örnek olarak API isteğinde bulunma veya mail gönderme verilebilir.

1. Çözüm (> Rails 7 )

Eğer projeniz Rails 7' den daha düşük bir versiyonda ise self.ignored_columns = [:__fake_column__]” konfigürasyonunu ekleyebilirsiniz.

class ApplicationRecord < ActiveRecord::Base
# These configuration should update with Rails 7
# config.active_record.enumerate_columns_in_select_statements = true
# src: https://github.com/rails/rails/pull/41718
self.ignored_columns = [:__fake_column__]
end

Peki bu konfigürasyon ne işe yarıyor?

Rails’ in reposunda “activerecord/lib/active_record/relation/query_methods.rb” dosyası içinde şöyle bir method vardır. Buradaki önemli kısım elsif bloğunda da belirtildiği gibi eğer bir ignored_column tanımlanmış ise SELECT * kullanmak yerine o sınıfın sahip olduğu column isimlerini kullanmasıdır. Örnek olarak, SELECT id, name, …

def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values))
elsif klass.ignored_columns.any?
arel.project(*klass.column_names.map { |field| table[field] })
else
arel.project(table[Arel.star])
end
end

Bu ayar sayesinde ActiveRecord::PreparedStatementCacheExpired hatası önlenmiş olur.

2. Çözüm (≥Rails 7)

Projeniz Rails 7 veya üstü bir versiyonda ise “config.active_record.enumerate_columns_in_select_statements = true” konfigürasyonunu ekleyebilirsiniz.

class Application < Rails::Application
config.active_record.enumerate_columns_in_select_statements = true
end

Peki bu konfigürasyon ne işe yarıyor?

Rails 7 işe birlikte “activerecord/lib/active_record/relation/query_methods.rb” dosyasına küçük bir güncelleme gelmiştir

def build_select(arel)
if select_values.any?
arel.project(*arel_columns(select_values))
elsif klass.ignored_columns.any? || klass.enumerate_columns_in_select_statements
arel.project(*klass.column_names.map { |field| table[field] })
else
arel.project(table[Arel.star])
end
end

elsif bloğuna eklenen “klass.enumerate_columns_in_select_statements” koşulu ile yine SELECT * yerine SELECT id, name … kullanılması sağlanmıştır.

--

--