কম্পিউটার

কিভাবে ActiveRecord ডাটাবেসে অপ্রয়োজনীয় ট্রিপ এড়াতে ক্যাশিং ব্যবহার করে

ক্যাশিং বর্ণনা করার একটি সাধারণ উপায় হল কিছু কোডের ফলাফল সংরক্ষণ করা যাতে আমরা পরে দ্রুত এটি পুনরুদ্ধার করতে পারি। কিছু ক্ষেত্রে, এর অর্থ হল একটি গণনা করা মান সংরক্ষণ করা যাতে পরে এটি পুনরায় গণনার প্রয়োজন না হয়। যাইহোক, আমরা হার্ড ড্রাইভ থেকে পড়তে বা নেটওয়ার্ক অনুরোধ সম্পাদন করতে এড়াতে কোনও গণনা না করেই কেবল মেমরিতে রেখে ডেটা ক্যাশে করতে পারি।

এই পরবর্তী ফর্মটি ActiveRecord-এর জন্য বিশেষভাবে প্রাসঙ্গিক, যেখানে ডাটাবেস প্রায়ই একটি পৃথক সার্ভারে চলে। এইভাবে, সমস্ত অনুরোধের জন্য নেটওয়ার্ক-ট্রাফিক ওভারহেড হয়, যখন প্রশ্নটি আবার করা হয় তখন ডাটাবেস সার্ভারে রাখা লোডের কথা উল্লেখ না করে৷

সৌভাগ্যবশত, Rails ডেভেলপারদের জন্য, ActiveRecord নিজেই আমাদের জন্য এটির অনেক কিছু পরিচালনা করে, সম্ভবত আমরা এটি সম্পর্কে সচেতন না হয়েও। এটি উত্পাদনশীলতার জন্য চমৎকার, তবে কখনও কখনও, পর্দার পিছনে কী ক্যাশে করা হচ্ছে তা জানা গুরুত্বপূর্ণ। উদাহরণস্বরূপ, যখন আপনি জানেন (বা আশা করেন) একটি মান অন্য প্রক্রিয়ার দ্বারা পরিবর্তিত হচ্ছে, অথবা আপনার কাছে অবশ্যই সবচেয়ে আপ-টু-ডেট মান থাকতে হবে। এই ধরনের ক্ষেত্রে, ActiveRecord একটি আনক্যাশড ডেটা পড়ার জন্য জোর করে কয়েকটি 'এসকেপ হ্যাচ' প্রদান করে।

ActiveRecords-এর অলস মূল্যায়ন

ActiveRecord-এর অলস মূল্যায়ন স্বতঃপ্রণোদিতভাবে ক্যাশিং নয়, কিন্তু আমরা পরবর্তীতে কোড উদাহরণে এটির সম্মুখীন হব, তাই আমরা একটি সংক্ষিপ্ত ওভারভিউ প্রদান করব। আপনি যখন একটি ActiveRecord ক্যোয়ারী তৈরি করেন, অনেক ক্ষেত্রে, কোডটি ডাটাবেসে তাৎক্ষণিক কল জারি করে না। এটিই আমাদের একাধিক .where চেইন করতে দেয় প্রতিবার ডাটাবেসে আঘাত না করেই ক্লজ:

@posts = Post.where(published: true)
# no DB hit yet
@posts = @posts.where(publied_at: Date.today)
# still nothing
@posts.count
# SELECT COUNT(*) FROM "posts" WHERE...

এর কিছু ব্যতিক্রম আছে। উদাহরণস্বরূপ, .find ব্যবহার করার সময় , .find_by , .pluck , .to_a , অথবা .first , অতিরিক্ত ধারা চেইন করা অসম্ভব। নীচের বেশিরভাগ উদাহরণে, আমি .to_a ব্যবহার করব একটি ডিবি কল জোর করার একটি সহজ উপায় হিসাবে৷

মনে রাখবেন যে আপনি যদি এটি একটি Rails কনসোলে পরীক্ষা করে থাকেন তবে আপনাকে 'ইকো' মোড বন্ধ করতে হবে। অন্যথায়, কনসোল (irb বা pry) কল করে .inspect অবজেক্টে একবার আপনি 'এন্টার' টিপুন, যা একটি DB ক্যোয়ারীকে বাধ্য করে। ইকো মোড নিষ্ক্রিয় করতে, আপনি নিম্নলিখিত কোডটি ব্যবহার করতে পারেন:

conf.echo = false # for irb
pry_instance.config.print = proc {} # for pry

ActiveRecord সম্পর্ক

ActiveRecord এর বিল্ট-ইন ক্যাশিংয়ের প্রথম অংশটি আমরা দেখব সম্পর্ক। উদাহরণস্বরূপ, আমাদের একটি সাধারণ User-Posts আছে সম্পর্ক:

# app/models/user.rb
class User < ApplicationRecord
  has_many :posts
end

# app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
end

এটি আমাদের সহজ user.posts দেয় এবং post.user সম্পর্কিত রেকর্ড (গুলি) খুঁজে পেতে একটি ডাটাবেস ক্যোয়ারী সঞ্চালনের পদ্ধতি। ধরা যাক আমরা এগুলি একটি নিয়ামক এবং ভিউতে ব্যবহার করছি:

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    @user = User.find(params[:user_id])
    @posts = @user.posts
  end
...

# app/views/posts/index.html.erb
...
<%= render 'shared/sidebar' %>
<% @posts.each do |post| %>
  <%= render post %>
<% end %>

# app/views/shared/_sidebar.html.erb
...
<% @posts.each do |post| %>
  <li><%= post.title %></li>
<% end %>

আমাদের একটি মৌলিক index আছে অ্যাকশন যা @user.posts দখল করে . পূর্ববর্তী বিভাগের অনুরূপ, ডাটাবেস কোয়েরি এই সময়ে চালানো হয়নি। রেল তারপর আমাদের index রেন্ডার করে ভিউ, যা, ঘুরে, সাইডবার রেন্ডার করে। সাইডবার @posts.each ... কল করে , এবং এই মুহুর্তে, ActiveRecord ডাটা পেতে ডাটাবেস কোয়েরি বন্ধ করে দেয়।

তারপর আমরা আমাদের বাকি index-এ ফিরে আসি টেমপ্লেট, যেখানে আমাদের আরেকটি আছে @posts.each; যাইহোক, এই সময়, কোন ডাটাবেস কল নেই. যা ঘটছে তা হল ActiveRecord আমাদের জন্য এই সমস্ত পোস্টগুলি ক্যাশ করছে এবং ডাটাবেস থেকে আবার পড়ার চেষ্টা করে বিরক্ত করে না৷

এস্কেপ হ্যাচ

এমন অনেক সময় আছে যখন আমরা ActiveRecord-কে আবার সংশ্লিষ্ট রেকর্ডগুলি আনতে বাধ্য করতে চাই; সম্ভবত, আমরা জানি এটি অন্য একটি প্রক্রিয়া দ্বারা পরিবর্তিত হচ্ছে (উদাহরণস্বরূপ একটি ব্যাকগ্রাউন্ড কাজ)। আরেকটি সাধারণ পরিস্থিতি হল স্বয়ংক্রিয় পরীক্ষায় যেখানে আমরা কোডটি সঠিকভাবে আপডেট করেছে তা যাচাই করার জন্য আমরা ডাটাবেসের সর্বশেষ মান পেতে চাই।

পরিস্থিতির উপর নির্ভর করে এটি করার দুটি সাধারণ উপায় রয়েছে। আমি মনে করি সবচেয়ে সাধারণ উপায় হল .reload কল করা অ্যাসোসিয়েশনে, যেটি ActiveRecord কে বলে যে আমরা যা কিছু ক্যাশ করেছে তা উপেক্ষা করতে চাই এবং ডাটাবেস থেকে সর্বশেষ সংস্করণ পেতে চাই:

@user = User.find(1)
@user.posts # DB Call
@user.posts # Cached, no DB call
@user.posts.reload # DB call
@user.posts # Cached new version, no DB call

আরেকটি বিকল্প হল ActiveRecord মডেলের একটি নতুন উদাহরণ পাওয়া (যেমন, find কল করে আবার):

@user = User.find(1)
@user.posts # DB Call
@user.posts # Cached, no DB call
@user = User.find(1) # @user is now a new instance of User
@user.posts # DB Call, no cache in this instance

ক্যাশিং সম্পর্ক ভাল, কিন্তু আমরা প্রায়ই জটিল .where(...) দিয়ে শেষ করি। সহজ সম্পর্ক লুকআপের বাইরে প্রশ্ন। এখানেই ActiveRecord এর SQL ক্যাশে আসে।

ActiveRecord এর SQL ক্যাশে

ActiveRecord কর্মক্ষমতা দ্রুত করার জন্য সঞ্চালিত প্রশ্নের একটি অভ্যন্তরীণ ক্যাশে রাখে। উল্লেখ্য, যাইহোক, এই ক্যাশে নির্দিষ্ট কর্মের সাথে আবদ্ধ; এটি কর্মের শুরুতে তৈরি হয় এবং কর্মের শেষে ধ্বংস হয়। এর মানে হল আপনি এটি শুধুমাত্র তখনই দেখতে পাবেন যদি আপনি একই ক্যোয়ারীটি একটি কন্ট্রোলার অ্যাকশনের মধ্যে দুইবার করেন। এটি এছাড়াও৷ মানে রেল কনসোলে ক্যাশে ব্যবহার করা হয় না। ক্যাশে হিটগুলি একটি CACHE সহ রেল লগে দেখানো হয়৷ . উদাহরণস্বরূপ,

class PostsController < ApplicationController
  def index
    ...
    Post.all.to_a # to_a to force DB query

    ...
    Post.all.to_a # to_a to force DB query

নিম্নলিখিত লগ আউটপুট উত্পাদন করে:

  Post Load (2.1ms)  SELECT "posts".* FROM "posts"
  ↳ app/controllers/posts_controller.rb:11:in `index'
  CACHE Post Load (0.0ms)  SELECT "posts".* FROM "posts"
  ↳ app/controllers/posts_controller.rb:13:in `index'

আপনি ActiveRecord::Base.connection.query_cache প্রিন্ট করে একটি অ্যাকশনের জন্য ক্যাশের ভিতরে কী আছে তা দেখতে পারেন (বা ActiveRecord::Base.connection.query_cache.keys শুধুমাত্র এসকিউএল কোয়েরির জন্য)।

এস্কেপ হ্যাচ

SQL ক্যাশে বাইপাস করার জন্য সম্ভবত অনেক কারণ নেই, কিন্তু তবুও, আপনি uncached ব্যবহার করে ActiveRecord কে তার SQL ক্যাশে বাইপাস করতে বাধ্য করতে পারেন। ActiveRecord::Base এ পদ্ধতি :

class PostsController < ApplicationController
  def index
    ...
    Post.all.to_a # to_a to force DB query

    ...
    ActiveRecord::Base.uncached do
      Post.all.to_a # to_a to force DB query
    end

যেহেতু এটি ActiveRecord::Base এ একটি পদ্ধতি , আপনি এটিকে আপনার মডেল ক্লাসগুলির একটির মাধ্যমেও কল করতে পারেন যদি এটি পঠনযোগ্যতা উন্নত করে; উদাহরণস্বরূপ,

  Post.uncached do
    Post.all.to_a
  end

কাউন্টার ক্যাশে

ওয়েব অ্যাপ্লিকেশনগুলিতে একটি সম্পর্কের রেকর্ড গণনা করতে চাওয়া খুব সাধারণ (যেমন, একজন ব্যবহারকারীর X পোস্ট আছে বা একটি টিম অ্যাকাউন্টের Y ব্যবহারকারী রয়েছে)। এটি কতটা সাধারণ হওয়ার কারণে, ActiveRecord স্বয়ংক্রিয়ভাবে একটি কাউন্টার আপ-টু-ডেট রাখার একটি উপায় অন্তর্ভুক্ত করে যাতে আপনার কাছে একগুচ্ছ .count না থাকে। ডাটাবেস সম্পদ ব্যবহার করে কল. এটি সক্ষম করার জন্য এটি শুধুমাত্র কয়েকটি পদক্ষেপ নেয়। প্রথমে, আমরা counter_cache যোগ করি সম্পর্কের জন্য যাতে ActiveRecord আমাদের জন্য গণনা ক্যাশে করতে জানে:

class Post < ApplicationRecord
  belongs_to :user, counter_cache: true
end

আমাদের User এ একটি নতুন কলাম যোগ করতে হবে , যেখানে গণনা সংরক্ষণ করা হবে। আমাদের উদাহরণে, এটি হবে User.posts_count . আপনি counter_cache-এ একটি প্রতীক পাঠাতে পারেন প্রয়োজনে কলামের নাম উল্লেখ করতে।

rails generate migration AddPostsCountToUsers posts_count:integer
rails db:migrate

কাউন্টারগুলি এখন 0 এ সেট করা হবে (ডিফল্ট)। আপনার অ্যাপ্লিকেশানে ইতিমধ্যে কিছু পোস্ট থাকলে, আপনাকে সেগুলি আপডেট করতে হবে৷ ActiveRecord একটি reset_counters প্রদান করে নিটি-কঠোর বিবরণ পরিচালনা করার পদ্ধতি, তাই আপনাকে কেবল এটি আইডি পাস করতে হবে এবং কোন কাউন্টার আপডেট করতে হবে তা বলতে হবে:

User.all.each do |user|
  User.reset_counters(user.id, :posts)
end

অবশেষে, আমাদের সেই স্থানগুলি পরীক্ষা করতে হবে যেখানে এই গণনাটি ব্যবহার করা হচ্ছে। এটি .count কল করার কারণে কাউন্টারটিকে বাইপাস করবে এবং সর্বদা একটি COUNT() চালাবে এসকিউএল কোয়েরি। পরিবর্তে, আমরা .size ব্যবহার করতে পারি , যা কাউন্টার ক্যাশে ব্যবহার করতে জানে যদি এটি বিদ্যমান থাকে। একটি বাদ দিয়ে, আপনি ডিফল্ট .size ব্যবহার করতে চাইতে পারেন সর্বত্র, কারণ এটি অ্যাসোসিয়েশনগুলিকে পুনরায় লোড করে না যদি তারা ইতিমধ্যে উপস্থিত থাকে, সম্ভাব্যভাবে ডাটাবেসে একটি ট্রিপ সংরক্ষণ করে৷

উপসংহার

বেশিরভাগ অংশের জন্য, ActiveRecord এর অভ্যন্তরীণ ক্যাশিং "শুধু কাজ করে"। আমি বলতে পারি না যে আমি অনেকগুলি কেস দেখেছি যেগুলি এটিকে বাইপাস করতে হবে, তবে সমস্ত জিনিসের মতো, "আন্ডার-দ্য-হুড" কী চলছে তা জানা আপনাকে কিছু সময় এবং যন্ত্রণা বাঁচাতে পারে যখন আপনি এমন পরিস্থিতিতে হোঁচট খাবেন যার জন্য কিছু প্রয়োজন সাধারণের বাইরে।

অবশ্যই, ডাটাবেস একমাত্র জায়গা নয় যেখানে রেল আমাদের জন্য কিছু পর্দার পিছনে ক্যাশিং করছে। HTTP স্পেসিফিকেশনে এমন শিরোনাম রয়েছে যা ক্লায়েন্ট এবং সার্ভারের মধ্যে পাঠানো যেতে পারে যাতে পরিবর্তিত হয়নি এমন ডেটা পুনরায় পাঠাতে না হয়। ক্যাশিং সম্পর্কিত এই সিরিজের পরবর্তী নিবন্ধে, আমরা 304 (Not Modified) দেখব। এইচটিটিপি স্ট্যাটাস কোড, কীভাবে রেল আপনার জন্য এটি পরিচালনা করে এবং আপনি কীভাবে এই হ্যান্ডলিংকে টুইক করতে পারেন।


  1. কিভাবে MS SQL সার্ভারে ডাটাবেস পুনরুদ্ধার করবেন

  2. এমএস এসকিউএল সার্ভারে ডাটাবেস কীভাবে মুছবেন

  3. দুর্ঘটনাজনিত সাহায্য এড়াতে Windows 10-এ F1 সহায়তা কী কীভাবে নিষ্ক্রিয় করবেন

  4. কিভাবে অ্যাপস দিয়ে বিলম্ব এড়ানো যায়?