प्रौद्योगिकी साझेदारी

[ruby on rails] ActiveRecord::PreparedStatementCache परिनियोजनस्य समये समाप्तदोषाणां कारणानि समाधानं च

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. प्रश्नः : १.

  • कदाचित् Postgres इत्यत्र Rails अनुप्रयोगं परिनियोजयन्, भवान् ActiveRecord::PreparedStatementCacheExpired त्रुटिं द्रष्टुं शक्नोति । एतत् केवलं तदा एव भवति यदा परिनियोजने प्रवासाः चालिताः भवन्ति ।
  • एतत् भवति यतोहि Rails प्रदर्शनं सुधारयितुम् Postgres इत्यस्य सज्जीकृतस्य कथनसञ्चयस्य (PreparedStatementCache) सुविधायाः लाभं लभते । इदं विशेषता रेलेषु पूर्वनिर्धारितरूपेण सक्षमम् अस्ति ।

2. समस्यायाः पुनरावृत्तिः : १.

  • एतत् त्रुटिं पुनः प्रदर्शयितुं वयं rspec इत्यस्य उपयोगं कर्तुं शक्नुमः
 it 'not raise ActiveRecord::PreparedStatementCacheExpired' do
    create(:user)
    User.first
    User.find_by_sql('ALTER TABLE users ADD new_metric_column integer;')
    ActiveRecord::Base.transaction { User.first }
  end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

अत्र चित्रविवरणं सम्मिलितं कुर्वन्तु

3. उत्पादनस्य सिद्धान्तः : १.

  • रेल्स् प्रश्नकथनानि यथाUser.allसक्रिय_अभिलेख द्वारा sql कथने पार्स करेंतदनन्तरं दत्तांशकोशं प्रति प्रेषयन्तु,प्रथमं PREPARE इति निष्पादयन्तु सज्जीकृतानि कथनानि, SQL कथनानि विश्लेषितानि, विश्लेषणं, अनुकूलितं, पुनर्लेखनं च भविष्यति ।यदा अनुवर्तनम्EXECUTE आदेशं निर्गच्छतु, सज्जीकृतं वक्तव्यं योजनाकृतं निष्पादितं च भविष्यति।
  • रेल्स् क्वेरी स्टेट्मेण्ट् इत्यत्र सेव् करिष्यतिpg_prepared_statementsin to facilitate अग्रिमवारं भवन्तः समानवाक्यानि आह्वयन्ति।प्रत्यक्षतया निष्पादयन्तु कथनानि, कार्यस्य द्वितीयकं परिहरन् कार्यक्षमतां च सुधारयित्वा पार्सिंग्, विश्लेषणं, अनुकूलनं च कर्तुं स्थाने ।
User.first
User.all
# 执行上面的2个查询后,用connection.instance_variable_get(:@statements)就可以看到缓存的准备语句
ActiveRecord::Base.connection.instance_variable_get(:@statements)
==> <ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::StatementPool:0x00000001086b13c8 
@cache={78368=>{""$user", public-SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 
$1"=>"a7", ""$user", public-SELECT "users".* FROM "users" /* loading for inspect */ LIMIT $1"=>"a8"}},
@statement_limit=1000, @connection=#<PG::Connection:0x00000001086b31a0>, @counter=8>

# 这个也可以看到,会在数据库中去查询
ActiveRecord::Base.connection.execute('select * from pg_prepared_statements').values
(0.5ms) select * from pg_prepared_statements
==> [["a7", "SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1", "2024-07-
11T07:03:06.891+00:00", "{bigint}", false], ["a8", "SELECT "users".* FROM "users" /* loading for inspect 
*/ LIMIT $1", "2024-07-11T07:04:47.772+00:00", "{bigint}", false]]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • Postgres मध्ये, यदि सारणीयाः स्कीमा परिवर्तनं भवति यत् प्रत्यागतपरिणामान् प्रभावितं करोति तर्हि सज्जीकृतं कथनसञ्चयं अमान्यं भविष्यति । विशेषतः, एतत् DDL-क्रियाः सन्ति यथा सारणीयां क्षेत्राणि योजयितुं विलोपयितुं च, अथवा क्षेत्राणां प्रकारं दीर्घतां च परिवर्तयितुं ।

यथा निम्नलिखित उदाहरणे, क्षेत्राणि योजयित्वा वा विलोपयित्वा वा SELECT निष्पादयन्,pg databaseक्षिपतिcached plan must not change result type, रेलेषुसक्रिय_अभिलेखएतत् दोषं प्राप्य ततः क्षिपतुActiveRecord::PreparedStatementCacheExpired

ALTER TABLE users ADD COLUMN new_column integer;
ALTER TABLE users DROP COLUMN old_column;
添加或删除列,然后执行 SELECT *
删除 old_column 列然后执行 SELECT users.old_column
  • 1
  • 2
  • 3
  • 4
  • परिनियोजनसेवायां क्षेत्राणि योजयितुं, घटयितुं, परिवर्तयितुं वा प्रवासनं चालयति सति, उपयोक्त्रा निर्गतं क्वेरी स्टेट्मेण्ट् प्रत्यक्षतया सज्जीकृत स्टेट्मेण्ट् कैशतः SQL आनयिष्यति तथा च excute प्रत्यक्षतया निष्पादयिष्यति तथापि, यतः अस्मिन् समये सारणीसंरचना परिवर्तते, सज्जीकृतं कथनसञ्चयं अमान्यं भविष्यति ।pg databaseक्षिपतिcached plan must not change result typeत्रुटि
  • active_record स्रोतसङ्केतः पश्यन्तुexec_cacheविधिः, मया ज्ञातं यत् pg कृते रेलस्य त्रुटिनियन्त्रणविधिः अस्ति :
    1. व्यवहारे साक्षात् क्षिप्यते raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
    2. व्यवहारात् बहिः ये सन्ति ते @statements इत्यस्य संग्रहणं करिष्यन्तिएतत् वाक्यं विलोप्य प्रयतस्व, पुनः प्रयासस्य अनन्तरं, sql कथनं पुनः विश्लेषणं, विश्लेषणं, अनुकूलितं, निष्पादनं च भविष्यति ।prepare_statementविधिः सज्जीकृते कथनसञ्चये
module ActiveRecord
  module ConnectionHandling
    def exec_cache(sql, name, binds)
      materialize_transactions
      mark_transaction_written_if_write(sql)
      update_typemap_for_default_timezone

      stmt_key = prepare_statement(sql, binds)
      type_casted_binds = type_casted_binds(binds)

      log(sql, name, binds, type_casted_binds, stmt_key) do
        ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
          @connection.exec_prepared(stmt_key, type_casted_binds)
        end
      end
    rescue ActiveRecord::StatementInvalid => e
      raise unless is_cached_plan_failure?(e)

      # Nothing we can do if we are in a transaction because all commands
      # will raise InFailedSQLTransaction
      if in_transaction?
        raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
      else
        @lock.synchronize do
          # outside of transactions we can simply flush this query and retry
          @statements.delete sql_key(sql)
        end
        retry
      end
    end
  end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • अतः व्यवहारे यः दोषः दृश्यते सः व्यवहारस्य पुनः रोल करणं करिष्यति इति अर्थः यत् अनुरोधः विफलः अभवत् अतः अस्माभिः स्वयमेव तत् सम्पादयितुं आवश्यकम् ।

4. समाधानम् : १.

1. cache prepared statement function निष्क्रियं कुर्वन्तु (अनुशंसितं नास्ति)

Rails6 अपि च उपरि दत्तांशकोशे prepared_statements इत्येतत् false इति सेट् कृत्वा एतत् विशेषतां निष्क्रियं कर्तुं शक्नोति ।

default: &default
  adapter: postgresql
  encoding: unicode
  prepared_statements: false
  • 1
  • 2
  • 3
  • 4

Rails6 तथा अधः परीक्षणं न कृतम् यदि उपरिष्टाद् कार्यं न करोति तर्हि भवान् नूतनं आरम्भसञ्चिकां निर्मातुं प्रयतितुं शक्नोति ।

# config/initializers/disable_prepared_statements.rb:
db_configuration = ActiveRecord::Base.configurations[Rails.env]
db_configuration.merge!('prepared_statements' => false)
ActiveRecord::Base.establish_connection(db_configuration)
  • 1
  • 2
  • 3
  • 4

सत्यापन:

User.all
ActiveRecord::Base.connection.execute('select * from pg_prepared_statements').values
==> []
  • 1
  • 2
  • 3

निष्कर्षः - लघु परियोजनासु, भवान् एतत् कार्यं अक्षमं करोति चेत् तस्य महत्त्वं नास्ति, तथा च, बृहत् परियोजनासु, यावन्तः उपयोक्तारः, अधिकजटिलप्रश्नवक्तव्यं च, तावत् अधिकं लाभं एतत् कार्यं आनयिष्यति, तथा च, कार्यप्रदर्शनं प्रायः अप्रभावितं भविष्यति । अतः वास्तविकस्थित्यानुसारं निष्क्रियं कर्तव्यं वा इति निर्णयं कर्तुं शक्नुवन्ति ।

२ करणे त्रि०select * जातःselect id, nameएतादृशानि विशिष्टानि क्षेत्राणि, रेलमार्गेषु7आधिकारिक समाधानइति

  • enumerate_columns_in_select_statements इत्येतत् rails7 इत्यत्र true इति सेट् कुर्वन्तु
# config/application.rb
module MyApp
  class Application < Rails::Application
    config.active_record.enumerate_columns_in_select_statements = true
  end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • rails7 इत्यस्य अधः एतादृशं विन्यासः नास्ति, तत् प्राप्तुं भवान् ignored_columns इत्यस्य उपयोगं कर्तुं शक्नोति
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
  #__fake_column__是自定义的,不要是某个表中的字段就行,如果是[:id],那么 User.all就会被解析为select name from users,没有id了
  self.ignored_columns = [:__fake_column__] 
end
  • 1
  • 2
  • 3
  • 4
  • 5

निष्कर्षः- अस्य समाधानस्य समस्या अस्ति यत् क्षेत्राणि योजयित्वा सम्यक् समाधानं कर्तुं शक्यते, परन्तु क्षेत्राणि विलोपनेन अद्यापि दोषाः भविष्यन्ति यथा, नामक्षेत्रं विलोपनस्य अनन्तरं यदि सज्जीकृते कथने नाम select id, उपयोक्तृभ्यः नाम नास्ति, तर्हि एकः त्रुटिः निवेदिता भविष्यति Rails7 आधिकारिकसमाधानस्य अपि एषा समस्या अस्ति

3. rails अनुप्रयोगं पुनः आरभत

  • सज्जीकृतस्य कथनसञ्चयस्य जीवनचक्रं केवलं एकस्मिन् दत्तांशकोशसत्रे विद्यते यदि दत्तांशकोशसंयोजनं बन्दं भवति (अनुप्रयोगं पुनः आरभ्य मूलसंयोजनं बन्दं करिष्यति तथा च नूतनं संयोजनं पुनः स्थापयति), मूलसज्जितं कथनसञ्चयं स्वच्छं भविष्यति, तथा च पुनः आरम्भस्य अनन्तरं SQL अनुरोधः पुनः आरब्धः भविष्यति तथा च भवान् सामान्यतया आँकडान् प्राप्तुं शक्नोति ।

निष्कर्षः - अनुप्रयोगस्य पुनः आरम्भेण अस्थायी सेवा 502 अनुपलब्धा भविष्यति अवश्यं, अनुप्रयोगस्य परिनियोजने भवद्भिः सेवां पुनः आरभ्यत इति अपि आवश्यकम्, 502 अपि दृश्यते, अतः यदा कोऽपि तस्याः अभिगमनं न करोति तदा परिनियोजनं सर्वोत्तमम् मध्यरात्रौ?), येन यथाशक्ति अल्पाः दोषाः भविष्यन्ति AppearPreparedStatementCacheExpiredत्रुटिं निवेदयन्तु

4. पुनः लेखनम् transaction प्रक्रिया

class ApplicationRecord < ActiveRecord::Base
  class << self
    def transaction(*args, &block)
      retried ||= false
      super
    rescue ActiveRecord::PreparedStatementCacheExpired
      if retried
        raise
      else
        retried = true
        retry
      end
    end
  end
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • पुनर्लेखनानन्तरं कोडमध्ये यत्र व्यवहाराः लिखिताः सन्ति तत् स्थानं उपयोगाय परिवर्तितं भवति ApplicationRecord.transaction do ... end वाMyModel.transaction

निष्कर्षः महत्त्वपूर्णः टिप्पणी: यदि भवान् ईमेल प्रेषयति, एपिआइ-मध्ये पोस्ट् करोति, अथवा अन्ये कार्याणि करोति ये व्यवहारस्य भागरूपेण बहिः जगति सह अन्तरक्रियां कुर्वन्ति, तर्हि एतेन एतेषु केचन कार्याणि यदा कदा द्विवारं भवितुं शक्नुवन्ति अत एव रेल्स् आधिकारिकतया स्वयमेव पुनः प्रयासं न करोति, अपितु अनुप्रयोगविकासकानाम् उपरि त्यजति ।

&gt;&gt;&gt;&gt;&gt;&gt;&gt;यदा अहं स्वयमेव एतस्य पद्धतेः परीक्षणं करोमि तदापि मम त्रुटयः प्राप्यन्ते ।

5. सज्जीकृतं कथनसञ्चयं मैन्युअल् रूपेण स्वच्छं कुर्वन्तु

 ActiveRecord::Base.connection.clear_cache!
  • 1

5. अन्तिमम् उत्तरम्

सम्यक् समाधानं न प्राप्नोत्