কম্পিউটার টিউটোরিয়াল

মেমোইজেশন দিয়ে রেইলস অ্যাপ দ্রুত করুন

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

একটা হলো, আমরা ডেটাকে একটি ভেরিয়েবলে অ্যাসাইন করে রিইউজ করতে পারি, যা প্রসেসকে স্পিড আপ করবে। একটি সম্ভাব্য সমাধান হলেও, ম্যানুয়ালি সেই ভেরিয়েবল ম্যানেজ করা দ্রুত বিরক্তিকর হতে পারে।

কিন্তু, যদি বদলে, সেই "স্লো ওয়ার্ক" করার মেথডটি সেটা ভেরিয়েবল হ্যান্ডেল করতে পারত? এটি আমাদের অনুমতি দিবে মেথডকে একইভাবে কল করতে, কিন্তু মেথডটি ডেটা সেভ এবং রিইউজ করবে। একটু সিম্পল কথায়, মেমোইজেশন হলো একটি মেথডের রিটার্ন ভ্যালু সংরক্ষণ করা যাতে প্রতিবার সেটা রিকম্পিউট করতে না হয়। সকল ক্যাশিং-এর মতো, আপনি কার্যকরভাবে মেমরি ট্রেড করছেন টাইমের জন্য (অর্থাৎ, আপনি ভ্যালু স্টোর করার জন্য প্রয়োজনীয় মেমরি ছেড়ে দিচ্ছেন, কিন্তু মেথড প্রসেস করার প্রয়োজনীয় টাইম বাঁচাচ্ছেন)।

একটি ভ্যালু মেমোইজ কিভাবে করবেন

রুবি একটি খুব ক্লিন ইডিয়ম প্রদান করে ভ্যালু মেমোইজ করার জন্য অর-ইকুয়ালস অপারেটর দিয়ে: ||=। এটি লেফট এবং রাইট ভ্যালুর মাঝে একটি লজিক্যাল OR (||) করে, তারপর রেজাল্টটিকে লেফট সাইডের ভেরিয়েবলে অ্যাসাইন করে। একশনে:

value ||= expensive_method(123) #লজিক্যালি ইকুইভ্যালেন্ট: value = (value || expensive_method(123)) 

মেমোইজেশন কীভাবে কাজ করে

এটি কীভাবে কাজ করে বুঝতে আপনাকে দুটি কনসেপ্ট বুঝতে হবে: "ফলসি" ভ্যালু এবং লেজি ইভ্যালুয়েশন। আমরা ট্রুথি-ফলসি দিয়ে শুরু করি।

ট্রুথি এবং ফলসি

রুবি (প্রায় সব অন্যান্য ল্যাঙ্গুয়েজের মতো) বুলিয়ান true এবং false ভ্যালুর জন্য বিল্ট-ইন কিওয়ার্ডস রাখে। সেগুলো ঠিক যেমন আপনি আশা করবেন কাজ করে:

if true #আমরা সবসময় এটি রান করব end if false # এটি কখনো রান করবে না end 

তবে, রুবি (এবং অন্যান্য অনেক ল্যাঙ্গুয়েজ) "ট্রুথি" এবং "ফলসি" ভ্যালুর কনসেপ্টও রাখে। এর মানে ভ্যালুগুলোকে "যেন" সেগুলো true বা false হয় তেন ট്രീট করা যায়। রুবিতে শুধু nil এবং false ফলসি। অন্যান্য সকল ভ্যালু (শূন্য সহ) true-র মতো ট്രീট হয় (নোট: অন্যান্য ল্যাঙ্গুয়েজ ভিন্ন চয়েস নেয়। উদাহরণস্বরূপ C শূন্যকে false হিসেবে ট್ರೀট করে)। উপরের উদাহরণটি রিইউজ করে, আমরা এভাবেও লিখতে পারি:

value = "abc123" # একটি স্ট্রিং if value # আমরা সবসময় এটি রান করব end value = nil if value # এটি কখনো রান করবে না end 

লেজি ইভ্যালুয়েশন

লেজি ইভ্যালুয়েশন হলো একটি অপ্টিমাইজেশনের ফর্ম যা প্রোগ্রামিং ল্যাঙ্গুয়েজে খুব কমন। এটি প্রোগ্রামকে অনুমতি দেয় অপ্রয়োজনীয় অপারেশন স্কিপ করতে।

লজিক্যাল OR অপারেটর (||) ট্রু রিটার্ন করে যদি লেফট বা রাইট-হ্যান্ড সাইড যেকোনো একটি ট্রু হয়। এর মানে যদি লেফট-হ্যান্ড আর্গুমেন্ট ট্রু হয়, তবে রাইট-হ্যান্ড সাইড ইভ্যালুয়েট করার কোনো পয়েন্ট নেই কারণ আমরা ইতিমধ্যে জানি রেজাল্ট হবে ট্রু। আমরা যদি এটা নিজেরা ইমপ্লিমেন্ট করতাম তবে এভাবে হতো:

def logical_or (lhs, rhs) return lhs if lhs rhs end 

যদি lhs এবং rhs ফাংশন হতো (যেমন, ল্যামডা) তবে আপনি দেখতে পাবেন rhs শুধু এক্সিকিউট হবে যদি lhs ফলসি হয়।

অর-ইকুয়ালস

ট্রুথি-ফলসি ভ্যালু এবং লেজি ইভ্যালুয়েশন এই দুটি কনসেপ্ট কম্বাইন করলে আমরা বুঝতে পারি ||= অপারেটর কী করছে:

value #ডিফল্টভাবে nil value ||= "test" value ||= "blah" puts value => test 

আমরা শুরু করি value nil দিয়ে কারণ এটি ইনিশিয়ালাইজড ছিল না। পরপর, আমরা প্রথম ||= অপারেটর مواجه করি। এই স্টেজে value ফলসি তাই আমরা রাইট-হ্যান্ড সাইড ("test") ইভ্যালুয়েট করি এবং রেজাল্ট value-এ অ্যাসাইন করি। এখন আমরা দ্বিতীয় ||= অপারেটর مواجه করি, কিন্তু এই বার value ট্রুথি কারণ এর ভ্যালু "test"। আমরা রাইট-হ্যান্ড সাইডের ইভ্যালুয়েশন স্কিপ করি এবং value অপরিবর্তিত রেখে এগিয়ে যাই।

মেমোইজেশন কখন ব্যবহার করবেন সেটা ঠিক করা

মেমোইজেশন ব্যবহার করার সময় আমাদের কিছু প্রশ্ন নিজেদের থেকে করতে হবে: ভ্যালুটা কতবার অ্যাক্সেস হয়? কী কারণে এটি চেঞ্জ হয়? কতবার এটি চেঞ্জ হয়?

যদি ভ্যালুটা শুধু একবার অ্যাক্সেস হয় তবে ক্যাশিং ভ্যালু খুব উপকারী হবেনা, যত বেশি ভ্যালুটা অ্যাক্সেস হবে, তত বেশি বেনিফিট আমরা ক্যাশিং থেকে পাব।

কী কারণে এটি চেঞ্জ হয় সেটা দেখার জন্য আমাদের মেথডে কোন ভ্যালুগুলো ইউজ হচ্ছে সেটা দেখতে হবে। কি আর্গুমেন্ট নেয়? যদি হয়, তবে মেমোইজেশনকে সম্ভবত এটাকে একাউন্ট করতে হবে। ব্যক্তিগতভাবে, আমি এই কাজের জন্য memoist জেম পছন্দ করি কারণ এটি আর্গুমেন্টস হ্যান্ডেল করে আপনার জন্য।

শেষে, আমাদের বিবেচনা করতে হবে ভ্যালুটা কতবার চেঞ্জ হয়। কি ইনস্ট্যান্স ভেরিয়েবল আছে যা এটাকে চেঞ্জ করে? কি আমাদের ক্যাশড ভ্যালু ক্লিয়ার করতে হবে যখন সেগুলো চেঞ্জ হয়? ভ্যালুটা অবজেক্ট লেভেলে ক্যাশ হবে নাকি ক্লাস লেভেলে?

এই প্রশ্নগুলোর উত্তর জানতে আসুন একটি সিম্পল উদাহরণ দেখি এবং সিদ্ধান্তের ধাপগুলো ওয়াক থ্রু করি:

class ProfitLossReport def initialize(title, expenses, invoices) @expenses = expenses @invoices = invoices @title = title end def title "#{@title} #{Time.current}" end def cost @expenses.sum(:amount) end def revenue @invoices.sum(:amount) end def profit revenue - cost end def average_profit(months) profit / months.to_f end end 

কলিং কোড এখানে দেখানো হয়নি, তবে একটা ভালো গেস হলো title মেথডটি সম্ভবত শুধু একবার কল হবে, এছাড়া এটি Time.current ব্যবহার করে তাই মেমোইজ করা মানে ভ্যালু ইনস্ট্যান্টলি স্টেইল হয়ে যাবে।

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

শেষে, у нас average_profit আছে। এখানে ভ্যালু আর্গুমেন্টের ওপর নির্ভরশীল তাই আমাদের মেমোইজেশনের এটাকে একাউন্ট করতে হবে। revenue-এর মত একটি সিম্পল কেসের জন্য আমরা শুধু এভাবে করতে পারি:

def revenue @revenue ||= @invoices.sum(:amount) end 

average_profit জন্য, আমাদের প্রতিটি আর্গুমেন্টের জন্য আলাদা ভ্যালু দরকার। আমরা memoist ব্যবহার করতে পারতাম, কিন্তু ক্লেরিটির জন্য আমরা নিজেরা রোল করব এখানে:

def average_profit(months) @average_profit ||= {} @average_profit[months] ||= profit / months.to_f end 

এখানে আমরা একটি হ্যাশ ব্যবহার করছি আমাদের কম্পিউটেড ভ্যালুগুলো ট্র্যাক রাখতে। প্রথমে আমরা নিশ্চিত করি @average_profit ইনিশিয়ালাইজড হয়েছে, তারপর আর্গুমেন্ট পাস করা হয়েছে সেটাকে হ্যাশ কি হিসেবে ব্যবহার করি।

ক্লাস লেভেলে নাকি ইনস্ট্যান্স লেভেলে মেমোইজেশন

বেশির সময় মেমোইজেশন ইনস্ট্যান্স লেভেলে করা হয়, অর্থাৎ আমরা একটি ইনস্ট্যান্স ভেরিয়েবল ব্যবহার করি কম্পিউটেড ভ্যালু ধারণ করার জন্য। এর মানে যেকোনো সময় আমরা অবজেক্টের একটি নতুন ইনস্ট্যান্স তৈরি করি, সেটি "ক্যাশড" ভ্যালু থেকে বেনিফিট পায় না। এখানে একটি খুব সিম্পল ইলাস্ট্রেশন:

class MemoizedDemo def value @value ||= computed_value end def computed_value puts "Crunching Numbers" rand(100) end end 

এই অবজেক্ট ব্যবহার করে আমরা রেজাল্ট দেখতে পাই:

demo = MemoizedDemo.new => #<MemoizedDemo:0x00007f95e5d9d398> demo.value Crunching Numbers => 19 demo.value => 19 MemoizedDemo.new.value Crunching Numbers => 93 

আমরা এটাকে চেঞ্জ করতে পারি সিম্পলি একটি ক্লাস-লেভেল ভেরিয়েবল ( @@ সহ) ব্যবহার করে আমাদের মেমোইজড ভ্যালুর জন্য:

 def value @@value ||= computed_value end 

তবে রেজাল্ট হবে:

demo = MemoizedDemo.new => #<MemoizedDemo:0x00007f95e5d9d398> demo.value Crunching Numbers => 60 demo.value => 60 MemoizedDemo.new.value => 60 

আপনি ক্লাস-লেভেল মেমোইজেশন অনেক সময় চাওয়া নাও হতে পারে, কিন্তু এটি একটি অপশন হিসেবে রয়েছে। তবে, আপনি যদি একটি ভ্যালু এই লেভেলে ক্যাশ করতে চান তবে এটি ওয়ার্ধ লুক করতে লायक একটি এক্সটার্নাল স্টোরে ক্যাশ করা যেমন Redis বা memcached।

রুবি অন রেইলস অ্যাপ্লিকেশনে মেমোইজেশনের কমন ইউজ কেস

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

 def current_user @current_user ||= User.find(params[:user_id]) end 

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

মেমোইজেশনের গ্যাচা

সবচেয়ে বড় গ্যাচা হলো সেসময় মেমোইজ করা যখন সেটা আসলেই প্রয়োজনীয় নয়। স্ট্রিং ইন্টারপোলেশন এরকম জিনিস মেমোইজেশনের জন্য সহজ ক্যান্ডিডেট লাগতে পারে, কিন্তু বাস্তবে, সেগুলো আপনার সাইটের পারফরম্যান্সে কোনো নোটিশেবল ইম্প্যাক্ট ডালে না (যদি অবশ্য আপনি এক্সেপশনালি বড় স্ট্রিং বা খুব বড় পরিমাণ স্ট্রিং ম্যানিপুলেশন না করেন), উদাহরণস্বরূপ:

 def title # এখানে মেমোইজেশন আমাদের পারফরম্যান্সে মাচের ইম্প্যাক্ট দেবে না @title ||= "#{@object.published_at} - #{@object.title}" end 

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

 # এর বদলে এটি করুন def profit # ক্যাশড নয়, কিন্তু সাবট্র্যাকশন একটি ফাস্ট ক্যালকুলেশন revenue - losses end def revenue @revenue ||= Invoice.all.sum(:amount) end def losses @losses ||= Purchase.all.sum(:amount) end 

শেষ গ্যাচা হলো লেজি ইভ্যালুয়েশন কীভাবে কাজ করে তার কারণে - আপনাকে কিছু কাস্টম করতে হবে যদি আপনাকে একটি ফলসি ভ্যালু মেমোইজ করতে হয় (অর্থাৎ nil বা false), কারণ ||= ইডিয়ম সবসময় রাইট-হ্যান্ড সাইড এক্সিকিউট করবে যদি আপনার সেভড ভ্যালু ফলসি হয়। আমার এক্সপেরিয়েন্সে, এই ভ্যালুগুলো ক্যাশ করার প্রয়োজন কম পড়ে, কিন্তু আপনি যদি করেন, তবে আপনাকে একটি বুলিয়ান ফ্ল্যাগ যোগ করতে হতে পারে ইন্ডিকেট করার জন্য এটি ইতিমধ্যে কম্পিউটেড, অথবা অন্য একটি ক্যাশিং মেকানিজম ব্যবহার করতে হবে।

 def last_post # যদি ইউজারের কোনো পোস্ট না থাকে, তবে আমরা ডেটাবেস হিট করব প্রতিবার এই মেথড কল হলে @last_post ||= Post.where(user: current_user).order_by(created_at: :desc).first end # একটি সিম্পল ওয়ার্কঅরাউন্ড হিসেবে আমরা করতে পারি: def last_post return @last_post if @last_post_checked @last_post_checked = true @last_post ||= Post.where(user: current_user).order_by(created_at: :desc).first end 

যখন মেমোইজেশন যথেষ্ট নয়

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

ক্লাস-লেভেল মেমোইজেশন এই সমস্যা সহায়তা করতে পারে, কিন্তু ক্যাশ ইনভ্যালিডেশন ম্যানেজ করা আরও কঠিন হয়। সেটাও না করেই, যদি আপনার সার্ভার রিবুট হয় তবে সেই ক্যাশড ভ্যালুগুলো হারিয়ে যাবে, এবং সেগুলো মাল্টিপল ওয়েব সার্ভারের মধ্যে শেয়ার করা যাবে না।

এই সিরিজের ক্যাশিং-এ পরবর্তী ইস্যুতে, আমরা রেইলসের সমাধানটি দেখব এই সমস্যা গুলোর - লো-লেভেল ক্যাশিং। যা আপনাকে অনুমতি দেয় ভ্যালু এক্সটার্নাল স্টোরে ক্যাশ করার যেটি সার্ভারগুলোর মধ্যে শেয়ার করা যায়, এবং এক্সপায়ারি টাইমআউট এবং ডাইনামিক ক্যাশ কী দিয়ে ক্যাশ ইনভ্যালিডেশন ম্যানেজ করতে।

  1. স্ট্রাইপ চেকআউট (Stripe Checkout) দিয়ে রেইলসে সিমলেস ওয়ান-টাইম পারচেজ ইন্টিগ্রেশন

  2. Hotwire দিয়ে Rails অ্যাপস দ্রুত করুন: JavaScript ছাড়া সার্ভার-সাইড রেন্ডারিং

  3. রুবি অন রেইলসে টেইলউইন্ড CSS যোগ করা: একটি প্র্যাকটিক্যাল ধাপে ধাপে গাইড

  4. Angular এবং Rails 5 ইন্টিগ্রেশন: একটি স্মুথ ফুল-স্ট্যাক পদ্ধতি