अधिकांश वेब ऐप्स पृष्ठभूमि कतार से लाभान्वित हो सकते हैं, जिसका उपयोग अक्सर त्रुटि-प्रवण या समय लेने वाली साइड जॉब को संसाधित करने के लिए किया जाता है। ये पृष्ठभूमि कार्य ईमेल भेजने से लेकर कैश अपडेट करने तक, मुख्य व्यावसायिक तर्क प्रदर्शन करने तक भिन्न हो सकते हैं।
चूंकि कोई भी पृष्ठभूमि कतार प्रणाली उन नौकरियों की संख्या को मापती है जिन्हें संसाधित करने की आवश्यकता होती है, उन नौकरियों को संसाधित करने वाले श्रमिकों के पूल को भी स्केल करने की आवश्यकता होती है। ऐसे मामलों में जहां नौकरियों की दर अलग-अलग होती है, कतार श्रमिकों की संख्या को बढ़ाना एक महत्वपूर्ण पहलू बन जाता है। प्रसंस्करण गति बनाए रखने में। इसके अतिरिक्त, कम कतार थ्रूपुट के दौरान श्रमिकों को कम करने से महत्वपूर्ण बचत मिल सकती है!
दुर्भाग्य से, कई कतारबद्ध बैकएंड श्रमिकों को चालू या बंद करने के लिए स्केलिंग लॉजिक से सुसज्जित नहीं होते हैं। लेकिन हम कतार में प्रतीक्षा कर रहे काम के आधार पर अपनी इष्टतम कार्यकर्ता गणना खोजने के लिए कुछ सरल गणित और प्रदर्शन डेटा का उपयोग कर सकते हैं।
अंगूठे का कतार नियम
यदि नौकरियों को कतार के श्रमिकों द्वारा संसाधित किए जाने की तुलना में उच्च दर पर कतारबद्ध किया जाता है, तो कतार की गहराई बढ़ेगी और कतार में प्रत्येक कार्य द्वारा खर्च किया जाने वाला समय भी बढ़ेगा। आम तौर पर, हम प्रतीक्षा समय चाहते हैं (समय की मात्रा में प्रत्येक कार्य को यथासंभव कम करने के लिए - 0 सेकंड से कुछ स्वीकार्य सीमा तक। वांछित प्रतीक्षा समय को पूरा करने के लिए आवश्यक श्रमिकों की संख्या का अनुमान लगाने के लिए, हम अंगूठे के कतार नियम (QROT) का उपयोग कर सकते हैं। आमतौर पर, क्यूआरओटी को नौकरियों की कतार की सेवा के लिए आवश्यक सर्वरों की संख्या का वर्णन करने वाली असमानता के रूप में व्यक्त किया जाता है, लेकिन एक फॉर्म को इस प्रकार लिखा जा सकता है:
workers = (number_of_jobs * avg_service_time_per_job) / time_to_finish_queue
इसलिए, यदि हम 30 सेकंड के वांछित समय में अपनी कतार की सेवा के लिए आवश्यक श्रमिकों की संख्या का पता लगाना चाहते हैं, तो हमें केवल नौकरियों की संख्या (कतार का आकार) और औसत समय जानने की आवश्यकता है। प्रत्येक कार्य को निष्पादित करें। उदाहरण के लिए, यदि हमारे पास 7500 नौकरियों की कतार है और प्रत्येक कार्य को निष्पादित करने में औसतन 0.3 सेकंड का समय लगता है, तो हम उस कतार को 75 श्रमिकों के साथ 30 सेकंड में समाप्त कर सकते हैं।
प्रदर्शन मीट्रिक एक्सेस करना
कतार में नौकरियों के लिए औसत सेवा समय का अनुमान लगाने के लिए, हमें प्रत्येक नौकरी वर्ग के लिए प्रदर्शन मीट्रिक तक पहुंच की आवश्यकता होती है। सौभाग्य से, ऐपसिग्नल सामान्य कतार बैकएंड के प्रदर्शन डेटा को आउट-ऑफ-द-बॉक्स रिकॉर्ड करता है, प्रत्येक बार के लिए मीट्रिक रिकॉर्ड करता है कार्य निष्पादित किया गया है।
हम पिछले 24 घंटों में प्रत्येक कार्य प्रकार की औसत अवधि प्राप्त करने के लिए आगामी ऐपसिग्नल ग्राफ़क्यूएल एपीआई का उपयोग कर सकते हैं। यह एपीआई अभी तक पूरी तरह से सार्वजनिक नहीं है, हालांकि वर्तमान में इसका उपयोग ऐपसिग्नल के प्रदर्शन ग्राफ और अन्य डेटा डिस्प्ले के लिए किया जाता है। सौभाग्य से, ग्राफक्यूएल एपीआई हैं स्व-दस्तावेजीकरण करने का इरादा है, और हम एपीआई का आत्मनिरीक्षण करने के लिए ग्राफीक्यूएल जैसे टूल का उपयोग कर सकते हैं और यह पता लगा सकते हैं कि यह किन डेटा ऑब्जेक्ट्स को उजागर करता है।
ग्राफ़क्यूएल क्वेरी बनाने की प्रक्रिया इस पोस्ट के दायरे से बाहर है, लेकिन नीचे रूबी क्लास का एक उदाहरण दिया गया है जो बेसिक मेट्रिक्स एग्रीगेशन के लिए क्वेरी करने के लिए लोकप्रिय फैराडे एचटीटीपी क्लाइंट लाइब्रेरी का उपयोग करके ऐपसिग्नल ग्राफक्यूएल एपीआई से जुड़ता है।
require 'json'
require 'faraday'
class AppsignalClient
BASE_URL = 'https://appsignal.com/'
DEFAULT_APP_ID = ENV['APPSIGNAL_APP_ID']
DEFAULT_TOKEN = ENV['APPSIGNAL_API_TOKEN']
# GraphQL query to fetch the "mean" metric for the selected app.
METRICS_QUERY = <<~GRAPHQL.freeze
query($appId: String!, $query: [MetricAggregation!]!, $timeframe: TimeframeEnum!) {
app(id: $appId) {
metrics {
list(timeframe: $timeframe, query: $query) {
start
end
rows {
fields {
key
value
}
}
}
}
}
}
GRAPHQL
def initialize(app_id: DEFAULT_APP_ID, client_secret: DEFAULT_TOKEN)
@app_id = app_id
@client_secret = client_secret
end
# Fetch the average duration for a job class's perform action
# Default timeframe is last 24 hours
def average_job_duration(job_class, timeframe: 'R24H')
response =
connection.post(
'graphql',
JSON.dump(
query: METRICS_QUERY,
variables: {
appId: @app_id,
timeframe: timeframe,
query: [
name: 'transaction_duration',
headerType: legacy
tags: [
{ key: 'namespace', value: 'background' },
{ key: 'action', value: "#{job_class.name}#perform" },
],
fields: [{ field: 'MEAN', aggregate: 'AVG' }],
],
}
)
)
data = JSON.parse(response.body, symbolize_names: true)
rows = data.dig(:data, :app, :metrics, :list, :rows)
# There may be no metrics in the selected timeframe
return 0.0 if rows.empty?
rows.first[:fields].first[:value]
end
private
def connection
@connection ||= Faraday.new(
url: BASE_URL,
params: { token: @client_secret },
headers: { 'Content-Type' => 'application/json' },
request: { timeout: 10 }
) do |faraday|
faraday.response :raise_error
faraday.adapter Faraday.default_adapter
end
end
end
इस वर्ग के साथ, हम किसी दिए गए ActiveJob वर्ग के लिए औसत कार्य अवधि प्राप्त कर सकते हैं, जो हमें मिलीसेकंड में लौटा दी जाती है:
AppsignalClient.new.average_job_duration(MyMailerJob)
# => 233.1
डिफ़ॉल्ट रूप से, यह डेटा के पिछले 24 घंटों में कार्य की औसत लेनदेन अवधि की मांग करता है। यदि हमारे कार्य (कार्यों) को उससे अधिक बार निष्पादित किया जाता है, तो हम उस विंडो को छोटा करना चाह सकते हैं, जो हमारे हाल के निष्पादनों को अधिक महत्व देता है। औसत। उदाहरण के लिए, यदि हमारे पास ऐसे कार्य हैं जो एक घंटे में सैकड़ों बार चलते हैं, तो हम अपनी timeframe
बदलना चाह सकते हैं। एक घंटे तक (R1H
) इस तरह के एक काम की अवधि का बेहतर अनुमान लगाने के लिए अगर अभी निष्पादित किया जाता है।
ध्यान दें कि यह प्रदर्शन डेटा हमारे सर्वर उपयोग डेटा से अलग है। यह डेटा हमें बताता है कि प्रत्येक कार्य के लिए आवश्यक कार्य करने में वास्तव में कितना समय लगेगा। यह हमारे लिए उपयोग मेट्रिक्स जैसे बाहरी मापों की तुलना में हमारे कर्मचारियों को स्केल करने में अधिक उपयोगी होगा। ।
कतार का आत्मनिरीक्षण करना
इसके बाद, हमें सेवित की जाने वाली नौकरियों को निर्धारित करने के लिए अपनी कतार का आत्मनिरीक्षण करने की आवश्यकता है। एक सामान्य रूबी कतारबद्ध बैकएंड रेस्क है, जो एक्टिवजॉब के साथ अच्छी तरह से एकीकृत है। हम रेस्क में किसी दिए गए कतार के लिए संलग्न नौकरियों तक पहुंच सकते हैं और फिर निष्पादन समय का अनुमान लगा सकते हैं हमारे AppsignalClient
. का उपयोग करके प्रत्येक कार्य अपनी कक्षा के आधार पर ऊपर से कक्षा।
require 'resque'
class ResqueEstimator
def initialize(queue: 'default')
@queue = queue
@cache = {}
@appsignal_client = AppsignalClient.new
end
def enqueued_duration_estimate
Resque.data_store.everything_in_queue(queue).map do |job|
estimate_job_duration decode_activejob_args(job)
end.sum
end
def estimate_job_duration(job)
@cache[job['job_class']] ||= @appsignal_client
.average_job_duration job['job_class']
end
private
# ActiveJob-specific method for parsing job arguments
# for ActiveJob+Resque integration
def decode_activejob_args(job)
decoded_job = job
decoded_job = Resque.decode(job) if job.is_a? String
decoded_job['args'].first
end
end
इस वर्ग का उपयोग करना उतना ही सरल है:
ResqueEstimator.new(queue: 'my_queue').enqueued_duration_estimate
# => 23000 (ms)
ध्यान दें कि हम अपने estimate_job_duration
में नौकरी की अवधि के एक साधारण संस्मरण का उपयोग करते हैं AppSignal API पर डुप्लिकेट कॉल से बचने के लिए विधि। सबसे अधिक संभावना है, हमारी कतार में एक ही वर्ग के कई कार्य होंगे और हम केवल एक बार प्रत्येक वर्ग के निष्पादन का अनुमान लगाकर अपने ओवरहेड को कम कर सकते हैं।
प्रदर्शन डेटा को स्केल करने के लिए उपयोग करना
यह सब एक साथ खींचकर, हम अब हमारे कतार की सामग्री के आधार पर हमारे कतार कार्यकर्ताओं को ऊपर या नीचे स्केल करने के लिए हमारे हालिया प्रदर्शन डेटा का उपयोग कर सकते हैं! किसी भी समय, हम अपनी कतार में नौकरियों को देख सकते हैं और आवश्यक श्रमिकों का अनुमान प्राप्त कर सकते हैं हमारी वांछित समय सीमा में इसकी सेवा करने के लिए।
हमें एक वांछित कतारबद्ध समय सीमा (किसी भी कार्य को कतार में प्रतीक्षा करने के लिए अधिकतम समय) पर निर्णय लेने की आवश्यकता होगी, उदा। 30 सेकंड। हमें न्यूनतम और अधिकतम कार्यकर्ता संख्या भी निर्दिष्ट करने की आवश्यकता होगी। कतार के खाली होने के बाद कतार में पहली नौकरी को संभालने के लिए, कतार के लिए कम से कम एक कार्यकर्ता को चलाने में मददगार है। हम करेंगे हमारे डेटाबेस कनेक्शन और/या सर्वर उपयोग लागत को बहुत अधिक श्रमिकों के साथ बढ़ाने से बचने के लिए अधिकतम कार्यकर्ता संख्या भी चाहते हैं।
हम अपने लिए इस तर्क को संभालने के लिए एक वर्ग बना सकते हैं, जो मूल रूप से पहले से केवल हमारे अंगूठे के कतार नियम का कार्यान्वयन है।
class ResqueWorkerScaler
def initialize(queue: 'default', workers_range: 1..100, desired_wait_ms: 300_000)
@queue = queue
@workers_range = workers_range
@desired_wait_ms = desired_wait_ms
@estimator = ResqueEstimator.new(queue: @queue)
end
def desired_workers
total_time_ms = @estimator.enqueued_duration_estimate
workers_required = [(total_time_ms / desired_wait_ms).ceil, workers_range.last].min
[workers_required, workers_range.first].max
end
def scale
# using platform-specific scaling interface, scale to desired_workers
end
end
हम अपने कर्मचारियों को नियमित अंतराल पर स्केल करना चाहेंगे ताकि हम मांग के आधार पर ऊपर और नीचे स्केलिंग कर सकें। हम एक रेक टास्क बना सकते हैं जो हमारे ResqueWorkerScaler
. को कॉल करता है वर्ग से पैमाने के कर्मचारी:
# inside lib/tasks/resque_workers.rake
namespace :resque_workers do
desc 'Scale worker pool based on enqueued jobs'
task :scale, [:queue] => [:environment] do |_t, args|
queue = args[:queue] || 'default'
ResqueWorkerScaler.new(queue: queue).scale
end
end
और फिर हम इस रेक कार्य को नियमित अंतराल पर चलाने के लिए क्रॉन जॉब सेट कर सकते हैं:
*/5 * * * * /path/to/our/rake resque_workers:scale
# scale a non-default queue:
*/5 * * * * /path/to/our/rake resque_workers:scale['my_queue']
ध्यान दें कि हमने स्केलिंग कार्य को हर 5 मिनट में चलाने के लिए सेट किया है। प्रत्येक नए कार्यकर्ता को ऑनलाइन आने और कार्य संसाधित करने में कुछ समय लगेगा - हमारे कोडबेस के आकार और रत्नों की संख्या के आधार पर 10-40 सेकंड से कहीं भी संभव है। हम उपयोग करते हैं। इसलिए, यदि हम अपने कर्मचारियों को हर मिनट स्केल करने का प्रयास करते हैं, तो हमारे वांछित परिवर्तनों के प्रभावी होने से पहले हम फिर से ऊपर या नीचे स्केलिंग करेंगे। यदि हमारा ऐप दिन के अलग-अलग समय में केवल कतार के उपयोग में उतार-चढ़ाव देख रहा है, तो हम कर सकते हैं हमारे रेक कार्य को एक घंटे के अंतराल पर कॉल करने की संभावना है। लेकिन अगर हमारी कतार का आकार घंटे के भीतर बदलता रहता है, तो हम अपनी कतार को अधिक लगातार अंतराल पर आत्मनिरीक्षण करना चाहेंगे, जैसे कि ऊपर 5 मिनट।
अगले चरण
ऐसी प्रणाली जहां वास्तविक प्रदर्शन डेटा का उपयोग बुनियादी ढांचे को स्केल करने के लिए किया जाता है, मांग के प्रति बहुत प्रतिक्रियाशील हो सकता है और विभिन्न उपयोगों के लिए लचीला हो सकता है। विशेष रूप से पृष्ठभूमि प्रसंस्करण जैसे वातावरण में, जहां मेमोरी उपयोग और लोड औसत जैसे मेजबान मीट्रिक भिन्न होने की संभावना नहीं है, प्रदर्शन मेट्रिक्स का उपयोग करके पैमाना अधिक उपयुक्त है।
वैकल्पिक कतार स्केलिंग कार्यान्वयन पूर्ण कतार का आत्मनिरीक्षण करने के बजाय प्रति कार्य औसत प्रतीक्षा समय को माप सकता है, लेकिन जब कतार की सामग्री और आकार तेजी से बदलते हैं तो वह मीट्रिक अप्रस्तुत हो सकता है। यदि हमारा सिस्टम व्यापक रूप से परिवर्तनशील भार का अनुभव करता है, जिसमें बहुत सारी नौकरियां एक साथ होती हैं, या व्यापक रूप से परिवर्तनशील कार्य निष्पादन समय, तो कतार आत्मनिरीक्षण प्रतिक्रिया देने और मज़बूती से सही करने के लिए बहुत तेज़ है।
लेकिन हमारी कतार आत्मनिरीक्षण प्रणाली में विचार करने के लिए कुछ सीमाएँ हैं। यदि कतार पर्याप्त रूप से बड़ी है, तो निष्पादन अनुमान के लिए प्रत्येक कार्य को देखना निषेधात्मक रूप से धीमा होगा। ऐसे मामलों में, कुल नौकरी की संख्या का पता लगाना बेहतर हो सकता है, फिर चयन करें कतार से नौकरियों का एक यादृच्छिक प्रतिनिधि नमूनाकरण और उस नमूने से औसत निष्पादन की गणना करें। वैकल्पिक रूप से, यदि किसी नौकरी वर्ग के पास अभी तक कोई प्रदर्शन डेटा नहीं है, तो हमें एक अनुमानित निष्पादन समय का उपयोग करने की आवश्यकता होगी जब तक कि इसे निष्पादित और रिकॉर्ड नहीं किया जाता है कुछ बार।
ऊपर उल्लिखित प्रणाली को कुछ बदलावों के साथ भी काफी सुधार किया जा सकता है। समानांतर में प्रत्येक नौकरी वर्ग के लिए निष्पादन समय का अनुमान लगाने पर विचार करें, क्योंकि प्रत्येक अनुमान अलग और बेकार है। हम उन नौकरियों को शामिल करने के लिए अपनी कतार आत्मनिरीक्षण को भी अपडेट कर सकते हैं जो वर्तमान में एक द्वारा निष्पादित की जा रही हैं। हमारे कुल सेवा समय अनुमान की सटीकता में सुधार करने के लिए कार्यकर्ता। कई कतारों के साथ पृष्ठभूमि प्रसंस्करण वास्तुकला के लिए, हम प्रत्येक कतार को एक वांछित प्रतीक्षा समय, कतार प्राथमिकता के आधार पर, और स्केल श्रमिकों को उचित रूप से असाइन कर सकते हैं।
कतारबद्ध प्रणालियाँ किसी भी परियोजना में बहुत अधिक परिवर्तनशील कार्य एकत्र करती हैं। कतार से कार्यों के निष्पादन पर प्रदर्शन डेटा के साथ, हम एक प्रतिक्रियाशील, कुशल तरीके से सभी काम करने के लिए संसाधनों को प्रभावी ढंग से माप सकते हैं।
हैप्पी स्केलिंग!
पी.एस. यदि आप रूबी मैजिक की पोस्ट प्रेस से छूटते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूजलेटर की सदस्यता लें और एक भी पोस्ट मिस न करें!