প্রোগ্রামিং ভাষায় ক্যাশিং বলতে ভবিষ্যতে দ্রুত পুনরুদ্ধারের জন্য একটি মান (বা মানগুলো) সংরক্ষণ করা বোঝায়। সাধারণত আপনি এটা उन মানগুলোর জন্য করবেন যেগুলো কম্পিউট করা ধীর; উদাহরণস্বরূপ, সেগুলো একটি এক্সটার্নাল API হিট করে আনা লাগে, অথবা সেগুলো তৈরি করতে অনেক নাম্বার-ক্রাঞ্চিং দরকার पड़ता है।
ক্যাশড মানগুলো প্রায়শই আলাদা সার্ভারে সংরক্ষণ করা হয়, যেমন মেমক্যাশড (memcached) বা রেডিস (Redis)। এগুলো ডিস্ক বা র্যাম-এও রাখা যায়। কোডে, আমরা প্রায়শই ভেরিয়েবলের ভিতরে ডেটা 'ক্যাশ' করি এক্সপেনসিভ ফাংশন একাধিকবার কল এড়াতে।
data = some_calculation() a(data) b(data)
আপনি যে গতিপ্রাপ্তি পান তার ট্রেড-অফ হলো আপনি পুরনো ডেটা ব্যবহার করছেন। যদি ক্যাশড ডেটা 'স্টেল' (stale) হয়ে যায় আর সঠিক না থাকে? তবে আপনাকে ক্যাশ ক্লিয়ার করতে হবে সেটা 'ইনভ্যালিডেট' (invalidate) করার জন্য।
ক্যাশিং-বিরোধী যুক্তি
পুরনো উক্তি অনুযায়ী, কম্পিউটার সায়েন্সে শুধু ২টা কঠিন সমস্যা আছে: ১. জিনিসগুলো নামकरण ২. ক্যাশ ইনভ্যালিডেশন ৩. অফ-বাই-ওয়ান এরর (Off-by-one errors)
ক্যাশ ইনভ্যালিডেশন কেন এত কঠিন? একটি ক্যাশড মান, তার স্বভাব অনুযায়ী, একটি 'রিয়েল' মানকে 'লুকিয়ে' রাখে। যেকোনো সময় 'রিয়েল' মান পরিবর্তন হলে, আপনাকে (হ্যাঁ, আপনাকে, প্রোগ্রামার) মনে রাখতে হবে ক্যাশটি 'ইনভ্যালিডেট' করতে হবে যাতে সেটা আপডেট হয়ে যায়।
মান लीजিয়ে আপনি একটি টেক্সট এডিটরে 'ওয়ার্ড কাউন্ট' উইজেট যোগ করছেন। ইউজার টাইপ করলে ওয়ার্ড কাউন্ট আপডেট করতে হবে। সবচেয়ে সহজ পদ্ধতি হলো প্রতিটি কীস্ট্রোকে ওয়ার্ড গণনা করা, কিন্তু এটা খুব ধীর। অন্য একটি পদ্ধতি:
- ফাইল লোড করার সময় ওয়ার্ড গণনা করুন।
- এই ওয়ার্ড-কাউন্ট একটি ভেরিয়েবলে সংরক্ষণ করুন (বা 'ক্যাশ করুন')।
- ভেরিয়েবলের কন্টেন্ট স্ক্রিনে দেখান।
এই ইমপ্লিমেন্টেশন খুব দ্রুত, কিন্তু ক্যাশড 'ওয়ার্ড কাউন্ট' টাইপ করার সাথে সাথে পরিবর্তন হয় না। সেটা পরিবর্তন করতে হলে, আমাদের ক্যাশ 'ইনভ্যালিডেট' করতে হবে যখনই আমরা ওয়ার্ড কাউন্ট পরিবর্তন হয়েছে আশা করি।
এখন, কীস্ট্রোক আসলে, আপনি ওয়ার্ড ডিটেক্ট করবেন (যেমন স্পেস) এবং ওয়ার্ড কাউন্টার ইনক্রিমেন্ট করবেন। অবশ্যই, ইউজার ওয়ার্ড ডিলিট করলে ডিক্রিমেন্ট করবেন। সহজ। শেষ। পরবর্তী টিকিট।
...পERO রুকুন, কি আপনি মনে রেখেছিলেন ইউজার টেক্সট কাট করে ক্লিপবোর্ডে কপি করলে ওয়ার্ড কাউন্ট আপডেট করতে? পেস্ট করলে কী? স্পেল-চেকার যদি একটি টাইপোকে দুটি ওয়ার্ডে ভাগ করে দিলে কী?
এখানে সমস্যা মান আপডেট করা নয়, যা काफी সহজ। সমস্যা হলো আপনাকে প্রতিটি জায়গায় আপডেট করতে মনে রাখতে হবে। শুধু একটি আপডেট মিস করলেই ক্যাশ ইনভ্যালিডেশন সমস্যা হবে, অর্থাৎ আপনি ইউজারকে একটি পুরনো (stale) মান দেখাবেন।
এই কথা ধরে রেখে, আপনি বুঝতে পারছেন ক্যাশিং যোগ করলে টেকনিক্যাল জটিলতা এবং পোটেনশিয়াল বাগ আনে। অবশ্যই এই সমস্যাগুলো সমাধান করা যায়, কিন্তু ক্যাশিং কোডকে সমাধান মনে করার আগে এটা মনে রাখা উচিত।
ক্যাশিং ছাড়া গতি
যদি আমরা ক্যাশিং বাদ দিই, তবে অ্যাপ্লিকেশন দ্রুত করার কাজ হলো পারফরম্যান্স বটলনেক চিহ্নিত করা এবং ফিক্স করা - যেসব সিস্টেম তাদের সম্ভাবনার চেয়ে ধীর। আমরা সেগুলো তিনটি মূল ক্যাটাগরিতে ভাগ করতে পারি:
- ডেটাবেস কোয়েরি (খুব বেশি বা খুব ধীর)
- ভিউ রেন্ডারিং (View rendering)
- অ্যাপ্লিকেশন কোড (যেমন, ভারী গণনা করা)
পারফরম্যান্স নিয়ে কাজ করার সময়, দুটি টেকনিক জানা জরুরি যাতে এগিয়ে যাওয়া যায়: প্রোফাইলিং (profiling) এবং বেঞ্চমার্কিং (benchmarking)।
প্রোফাইলিং (Profiling)
প্রোফাইলিং হলো আপনার অ্যাপে সমস্যাগুলো কোথায় সেটা জানার উপায়: এই পেজ ধীর কেন? টেমপ্লেট রেন্ডার করার জন্য? নাকি ডেটাবেস মিলিয়ন বার হিট করার জন্য?
রুবি অন রেইলসের জন্য, আমি rack-mini-profiler রিকমেন্ড করব, যা আপনার অ্যাপের প্রান্তে একটি সুন্দর উইজেট যোগ করে। এটি আপনার দেখ रहे পেজ রেন্ডার করতে কী কী লাগলো সেটার একটা ভালো ওভারভিউ দেয়—যেমন কতগুলো ডেটাবেস কোয়েরি ফায়ার হয়েছে, কতক্ষণ লেগেছে, এবং কতগুলো পার্শিয়াল রেন্ডার হয়েছে।
প্রোডাকশনের জন্য (প্রো-টিপ: rack-mini-profiler প্রোডাকশনে ভালো কাজ করে; শুধু নিশ্চিত করুন এটা শুধু নির্দিষ্ট ইউজারদের জন্য দেখায়, যেমন অ্যাডমিন বা ডেভেলপার), অনলাইন সার্ভিস আছে যেমন স্কাইলাইট (Skylight), নিউ রেলিক (New Relic), এবং স্কাউট (Scout), যা পেজ পারফরম্যান্স মনিটর করে।
সাধারণ টার্গেট <= 100ms পেজ রেন্ডারিংর জন্য খুব ভালো, কারণ এই থেকেও কম সময় ইউজার বাস্তব ইন্টারনেট ব্যবহারে বুঝতে পারেন না। আপনার টার্গেট অনেক ফ্যাক্টরের উপর নির্ভর করবে। একসময়, আমি একটি লিগ্যাসি অ্যাপ্লিকেশনে কাজ করতাম যার পারফরম্যান্স ভয়ানক ছিল, আমি <= 1 second টার্গেট রেখেছিলাম, যা ভালো নয় কিন্তু শুরু থেকে অনেক ভালো।
বেঞ্চমার্কিং (Benchmarking)
একবার আমরা কোথায় সমস্যা আছে সেটা বুঝলে, তারপর বেঞ্চমার্ক ব্যবহার করে দেখতে পারি আমাদের অপ্টিমাইজেশনের কী ইফেক্ট পড়েছে পারফরম্যান্সে। ব্যক্তিগতভাবে আমি benchmark-ips জেম পছন্দ করি এই টাইপের কাজের জন্য, কারণ এটা হিউম্যান-রিডেবল ভাবে পারফরম্যান্স পার্থক্য দেখায়।
একটি טרivial উদাহরণ হিসেবে, স্ট্রিং কনক্যাটেনেশন vs স্ট্রিং ইন্টারপোলেশন তুলনা:
require 'benchmark/ips' @a = "abc" @b = "def" Benchmark.ips do |x| x.report("Concatenation") { @a + @b } x.report("Interpolation") { "#{@a}#{@b}" } x.compare! end
ফলাফল:
Warming up -------------------------------------- Concatenation 316.022k i/100ms Interpolation 282.422k i/100ms Calculating ------------------------------------- Concatenation 10.353M (± 7.4%) i/s - 51.512M in 5.016567s Interpolation 6.615M (± 6.8%) i/s - 33.043M in 5.023636s Comparison: Concatenation: 10112435.3 i/s Interpolation: 6721867.3 i/s - 1.50x slower
এটা একটি সুন্দর হিউম্যান-রিডেবল রেজাল্ট দেয়, এবং ইন্টারপোলেশন কনক্যাটেনেশনের চেয়ে ১.৫ গুণ ধীর (কমপক্ষে আমাদের ছোট স্ট্রিংয়ের জন্য)। এই কারণে, আমি আপনাকে সাজেস্ট করব আপনি যে মেথডটি উন্নত করার চেষ্টা করছেন সেটা কপি করে একটা নতুন নাম দিন। তারপর কুইক কম্প্যারিজন চালিয়ে দেখতে পারেন আপনি পারফরম্যান্স উন্নত করতে পারলেন কিনা।
পারফরম্যান্স সমস্যা ফিক্স করা
এই পর্যায়ে, আমরা জানি অ্যাপের কোন অংশগুলো ধীর। আমাদের বেঞ্চমার্ক রেডি আছে যেকোনো উন্নতি হলে সেটা মাপার জন্য। এখন শুধু পারফরম্যান্স অপ্টিমাইজেশনের আসল কাজটি করা বাকি। আপনি যেসব টেকনিক বেছে নেবেন সেটা নির্ভর করবে আপনার সমস্যা কোথায়: ডেটাবেসে, ভিউতে, অথবা অ্যাপ্লিকেশনে।
ডেটাবেস পারফরম্যান্স
ডেটাবেস-রিলেটেড পারফরম্যান্স ই슈র জন্য, কিছু কথা দেখতে হবে। প্রথমে, ভয়াবহ 'N+1 কোয়েরি' এড়াতে হবে। এই পরিস্থিতিগুলো প্রায়শই কালেকশন রেন্ডার করার সময় ভিউতে ঘটে। উদাহরণস্বরূপ, আপনার একটি ইউজার আছে যার ১০টি ব্লগ পোস্ট, এবং আপনি ইউজার এবং তার সব পোস্ট দেখাতে চান। একটি নেভ প্রথম কাট হতে পারে এমন:
# Controller def show @user = User.find(params[:id]) end # View Name: <%= @user.name %> Posts: <%= @user.posts each do |post| %> <div>Title: <%= post.title %></div> <% end %>
উপরোক্ত পদ্ধতিতে ইউজার পাওয়া যাবে (১ কোয়েরি) এবং তারপর প্রতিটি পোস্টের জন্য আলাদা কোয়েরি ফায়ার হবে (N=১০ কোয়েরি), মোট ১১টি (বা N+1)। সৌভাগ্যে, রেইলস এই সমস্যার একটি সহজ সমাধান দেয় আপনার ActiveRecord কোয়েরিতে .includes(:post) যোগ করে। উপরের উদাহরণে, আমরা কন্ট্রোলার কোড নিচের মতো পরিবর্তন করব:
def show @user = User.includes(:post).find(params[:id]) end
এখন, আমরা ইউজার এবং তার সব পোস্ট একসাথে এক ডেটাবেস কোয়েরিতে ফেচ করব।
একটি আরো বিষয় খেয়াল রাখতে হবে যেখানে আপনি ক্যালকুলেশন ডেটাবেসে পুশ করতে পারেন, যা সাধারণত অ্যাপ্লিকেশনে একই অপারেশন করার চেয়ে দ্রুত। এর একটি সাধারণ রূপ হলো এগريغেশন (aggregation), যেমন:
total = Model.all.map(&:somefield).sum
এটা ডেটাবেস থেকে সব রেকর্ড আনে, কিন্তু ভ্যালুগুলো যোগ করা হয় রুবিতে। আমরা এটা দ্রুত করতে পারি ডেটাবেসকে ক্যালকুলেশন করিয়ে:
total = Model.sum(:somefield)
হয়তো আপনাকে কিছু জটিল কিছুর দরকার, যেমন দুটি কলাম গুণ করা:
total = Model.sum('columna * columnb')
সাধারণ ডেটাবেসগুলো বেসিক গাণিতিক অপারেশন এবং সাধারণ এগ리게েশন যেমন sum এবং average সাপোর্ট করে, তাই আপনার কোডবেসে map(...).sum কল খুঁজে বের করুন।
ভিউ পারফরম্যান্স
যদিও আমি মনে করি টেমপ্লেট-রিলেটেড পারফরম্যান্স সমস্যার জন্য ক্যাশিং আরও উপযুক্ত সমাধান, তবুও কিছু লো-হ্যাঙ্গিং ফ্রুট (low-hanging fruit) আছে যেগুলো আপনি প্রথমে বাদ দিতে পারেন।
সাধারণ পেজ লোড টাইমের জন্য, আপনি চেক করতে পারেন যে আপনি প্রোডাকশন সার্ভারগুলোর জন্য কমপক্ষে যেকোনো জাভাস্ক্রিপ্ট বা CSS লাইব্রেরির মিনিফাইড (minified) সোর্স ব্যবহার করছেন।
এছাড়া, বড় সংখ্যক পার্শিয়াল (partials) ইনক্লুড হওয়া খেয়াল রাখুন। আপনার _widget.html.erb টেমপ্লেট যদি ১ms লাগে প্রসেস হতে, কিন্তু পেজে ১০০টি উইজেট থাকে, তবে সেটা ১০০ms যাবে। একটি সমাধান হলো আপনার UI রিকনসিডার করা। একসাথে ১০০টি উইজেট স্ক্রিনে থাকা সাধারণত ভালো ইউজার এক্সপেরিয়েন্স নয়, এবং আপনি পেজিনেশন (pagination) বা হয়তো আরও radikাল UI/UX ওভারহল দেখতে পারেন।
অ্যাপ্লিকেশন কোড পারফরম্যান্স
আপনার পারফরম্যান্স ইস্যু যদি অ্যাপ্লিকেশন কোডে আছেই (অর্থাৎ ডেটা ম্যানিপুলেশন) ভিউ বা ডেটাবেস লেয়ারের বদলে, তবে আপনার কয়েকটি অপশন আছে। একটি হলো দেখুন কি কাজের কিছু অংশ ডেটাবেসে পুশ করা যায়, যেন উপরে বর্ণিত কোয়েরি হিসেবে, অথবা ডেটাবেস ভিউ হিসেবে, হয়তো scenic জেম দিয়ে।
অন্য অপশন হলো 'ভারী কাজ' (heavy lifting) ব্যাকগ্রাউন্ড জাবে সরিয়ে দেয়া, যদিও এতে আপনার UI-তে পরিবর্তন আনা লাগতে পারে কারণ মানটি এখন অ্যাসিনক্রোনাসলি কম্পিউট হবে।
আমার এখনও ক্যাশিং দরকার; এখন কী?
এতদূর এসে, হয়তো আপনি ঠান করেছেন যে হ্যাঁ, ক্যাশিংই আপনার প্রয়োজনীয় সমাধান। তাহলে কী করবেন? 기다রিন, কারণ এটি একটি সিরিজের প্রথম আর্টিকেল যা রুবি অন রেইলসে উপলব্ধ ক্যাশিং-এর বিভিন্ন ফর্ম কভার করবে।