अधिकांश वेब ऐप्स बैकग्राउंड कतार से लाभान्वित हो सकते हैं, जिसका उपयोग अक्सर त्रुटि-प्रवण या समय लेने वाले साइड जॉब्स को संसाधित करने के लिए किया जाता है। ये बैकग्राउंड जॉब्स ईमेल भेजने से, कैश अपडेट करने तक, कोर व्यावसायिक तर्क करने तक भिन्न हो सकते हैं।
जैसे-जैसे कोई बैकग्राउंड कतार प्रणाली संसाधित करने के लिए आवश्यक जॉब्स की संख्या को बढ़ाती है, उन जॉब्स को संसाधित करने वाले श्रमिकों के पूल को भी बढ़ाने की आवश्यकता होती है। उन मामलों में जहां कतार में डाले जा रहे जॉब्स की दर भिन्न होती है, कतार श्रमिकों की संख्या को बढ़ाना प्रसंस्करण गति बनाए रखने में एक महत्वपूर्ण पहलू बन जाता है। इसके अतिरिक्त, कम कतार थ्रूपुट के दौरान श्रमिकों को कम करने से महत्वपूर्ण बचत हो सकती है!
दुर्भाग्य से, कई कतार बैकएंड श्रमिकों को चालू या बंद करने के लिए स्केलिंग तर्क के साथ सुसज्जित नहीं आते हैं। लेकिन हम अपने इष्टतम श्रमिक संख्या को कतार में प्रतीक्षा कर रहे काम के आधार पर खोजने के लिए कुछ सरल गणित और प्रदर्शन डेटा का उपयोग कर सकते हैं।
कतार नियम का अंगूठा
यदि जॉब्स को कतार श्रमिकों द्वारा संसाधित किए जाने की तुलना में उच्च दर पर एनक्यू किया जाता है, तो कतार की गहराई बढ़ेगी और प्रत्येक जॉब द्वारा कतार में बिताया गया समय भी बढ़ेगा। आम तौर पर, हम प्रत्येक जॉब के लिए प्रतीक्षा समय (कतार में समय की मात्रा) को यथासंभव कम चाहते हैं - 0 सेकंड से कुछ स्वीकार्य सीमा तक। कतार में प्रतीक्षा कर रहे काम के आधार पर वांछित प्रतीक्षा समय को पूरा करने के लिए आवश्यक श्रमिकों की संख्या का अनुमान लगाने के लिए, हम कतार नियम के अंगूठे (QROT) का उपयोग कर सकते हैं। आमतौर पर, QROT को एक असमानता के रूप में व्यक्त किया जाता है जो जॉब्स की एक कतार को सेवा देने के लिए आवश्यक सर्वरों की संख्या का वर्णन करती है, लेकिन एक रूप को इस प्रकार लिखा जा सकता है:
workers = (number_of_jobs * avg_service_time_per_job) / time_to_finish_queue तो, अगर हम 30 सेकंड के वांछित समय में अपनी कतार को सेवा देने के लिए आवश्यक श्रमिकों की संख्या का पता लगाना चाहते हैं, तो हमें केवल जॉब्स की संख्या (कतार का आकार) और प्रत्येक जॉब को निष्पादित करने में लगने वाला औसत समय जानने की आवश्यकता है। उदाहरण के लिए, यदि हमारे पास 7500 जॉब्स की एक कतार है और प्रत्येक जॉब को निष्पादित करने में औसतन 0.3 सेकंड लगते हैं, तो हम 75 श्रमिकों के साथ 30 सेकंड में उस कतार को समाप्त कर सकते हैं।
प्रदर्शन मीट्रिक तक पहुंचना
कतार में जॉब्स के लिए औसत सेवा समय का अनुमान लगाने के लिए, हमें प्रत्येक जॉब क्लास के लिए प्रदर्शन मीट्रिक तक पहुंच की आवश्यकता है। सौभाग्य से, AppSignal सामान्य कतार बैकएंड के लिए बॉक्स से बाहर प्रदर्शन डेटा रिकॉर्ड करता है, प्रत्येक बार मीट्रिक रिकॉर्ड करता है जब एक जॉब निष्पादित किया गया हो।
हम आगामी AppSignal GraphQL API का उपयोग करके पिछले 24 घंटों में प्रत्येक जॉब प्रकार की औसत अवधि प्राप्त कर सकते हैं। यह API अभी तक पूरी तरह से सार्वजनिक नहीं है, हालांकि यह वर्तमान में AppSignal के प्रदर्शन ग्राफ़ और अन्य डेटा डिस्प्ले के लिए उपयोग किया जाता है। सौभाग्य से, GraphQL APIs का इरादा स्व-दस्तावेज़ीकरण होना है, और हम GraphiQL जैसे टूल का उपयोग करके API का आत्मनिरीक्षण कर सकते हैं और पता लगा सकते हैं कि यह किन डेटा ऑब्जेक्ट्स को उजागर करता है।
GraphQL क्वेरी बनाने की प्रक्रिया इस पोस्ट के दायरे से बाहर है, लेकिन नीचे एक उदाहरण रूबी क्लास है जो लोकप्रिय Faraday HTTP क्लाइंट लाइब्रेरी का उपयोग करके AppSignal GraphQL API से जुड़ती है ताकि एक बुनियादी मीट्रिक एकत्रीकरण के लिए क्वेरी की जा सके।
require 'json' require 'faraday' class AppsignalClient BASE_URL = 'https://appsignal.com/' DEFAULT_APP_ID = ENV['APPSIGNAL_APP_ID'] DEFAULT_TOKEN = ENV['APPSIGNAL_API_TOKEN'] # चयनित ऐप के लिए "mean" मीट्रिक लाने के लिए GraphQL क्वेरी। 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 # किसी जॉब क्लास के परफॉर्म एक्शन के लिए औसत अवधि लाएं # डिफ़ॉल्ट टाइमफ्रेम पिछले 24 घंटे हैं 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) # चयनित टाइमफ्रेम में कोई मीट्रिक नहीं हो सकती है 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) में बदलना चाह सकते हैं ताकि अभी निष्पादित होने पर ऐसे जॉब की अवधि का बेहतर अनुमान लगाया जा सके।
ध्यान दें कि यह प्रदर्शन डेटा हमारे सर्वर उपयोग डेटा से अलग है। यह डेटा हमें बताता है कि प्रत्येक जॉब के लिए आवश्यक काम करने में वास्तव में कितना समय लगेगा। यह हमारे श्रमिकों को स्केल करने में बाहरी माप जैसे उपयोग मीट्रिक की तुलना में अधिक उपयोगी होगा।
कतार का आत्मनिरीक्षण करना
अगला, हमें अपनी कतार का आत्मनिरीक्षण करने की आवश्यकता है ताकि सेवा करने के लिए जॉब्स निर्धारित किए जा सकें। एक सामान्य रूबी कतार बैकएंड Resque है, जो ActiveJob के साथ भी अच्छी तरह से इंटीग्रेट करता है। हम Resque में दिए गए कतार के लिए एनक्यू किए गए जॉब्स तक पहुंच सकते हैं और फिर हमारे ऊपर से 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+Resque इंटीग्रेशन के लिए जॉब आर्ग्यूमेंट्स पार्स करने के लिए ActiveJob-विशिष्ट मेथड 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 # प्लेटफॉर्म-विशिष्ट स्केलिंग इंटरफ़ेस का उपयोग करके, desired_workers तक स्केल करें end endहम अपने श्रमिकों को नियमित अंतराल पर स्केल करना चाहेंगे ताकि हम मांग के आधार पर ऊपर और नीचे स्केल कर रहे हों। हम एक Rake कार्य बना सकते हैं जो हमारे ResqueWorkerScaler क्लास को श्रमिकों को स्केल करने के लिए कॉल करता है:
# lib/tasks/resque_workers.rake के अंदर namespace :resque_workers do desc 'एनक्यू किए गए जॉब्स के आधार पर वर्कर पूल स्केल करें' task :scale, [:queue] => [:environment] do |_t, args| queue = args[:queue] || 'default' ResqueWorkerScaler.new(queue: queue).scale end endऔर फिर हम इस Rake कार्य को नियमित अंतराल पर चलाने के लिए एक क्रॉन जॉब सेट कर सकते हैं:
*/5 * * * * /path/to/our/rake resque_workers:scale # गैर-डिफ़ॉल्ट कतार स्केल करें: */5 * * * * /path/to/our/rake resque_workers:scale['my_queue'] ध्यान दें कि हमने स्केलिंग कार्य को हर 5 मिनट पर चलाने के लिए सेट किया है। प्रत्येक नया श्रमिक ऑनलाइन आने और जॉब्स संसाधित करना शुरू करने में कुछ समय लेगा - संभवतः 10-40 सेकंड के बीच कहीं भी, हमारे कोडबेस के आकार और हमारे द्वारा उपयोग किए जाने वाले जेम्स की संख्या के आधार पर। इसलिए, यदि हम हर मिनट अपने श्रमिकों को स्केल करने का प्रयास करते हैं, तो हम संभवतः फिर से ऊपर या नीचे स्केल करेंगे इससे पहले कि हमारे वांछित परिवर्तन प्रभावी हुए हों। यदि हमारा ऐप केवल दिन के अलग-अलग समय पर कतार उपयोग में उतार-चढ़ाव देख रहा है, तो हम अपने Rake कार्य को प्रति घंटा अंतराल पर कॉल कर सकते हैं। लेकिन यदि हमारा कतार आकार घंटे के भीतर उतार-चढ़ाव करता है, तो हम अपनी कतार का आत्मनिरीक्षण अधिक बार करना चाहेंगे, जैसे ऊपर 5 मिनट।
अगले कदम
ऐसी प्रणाली जहां वास्तविक प्रदर्शन डेटा का उपयोग इन्फ्रास्ट्रक्चर को स्केल करने के लिए किया जाता है, मांग के प्रति बहुत संवेदनशील और विविध उपयोग के प्रति लचीला हो सकती है। विशेष रूप से बैकग्राउंड प्रोसेसिंग जैसे वातावरण में, जहां होस्ट मीट्रिक जैसे मेमोरी उपयोग और लोड औसत के भिन्न होने की संभावना नहीं है, प्रदर्शन मीट्रिक का उपयोग करके स्केल करना बहुत अधिक उपयुक्त है।
वैकल्पिक कतार स्केलिंग कार्यान्वयन प्रति जॉब औसत प्रतीक्षा समय को माप सकते हैं बजाय पूरी कतार का आत्मनिरीक्षण करने के, लेकिन जब कतार सामग्री और आकार तेजी से बदलते हैं तो वह मीट्रिक अप्रतिनिधि हो सकता है। यदि हमारा सिस्टम व्यापक रूप से परिवर्तनीय लोड का अनुभव करता है, जिसमें एक साथ बहुत सारे जॉब्स एनक्यू हो रहे हैं, या व्यापक रूप से परिवर्तनीय जॉब निष्पादन समय, तो कतार आत्मनिरीक्षण प्रतिक्रिया देने के लिए बहुत तेज और विश्वसनीय रूप से सही है।
लेकिन हमारे कतार आत्मनिरीक्षण प्रणाली में कुछ सीमाओं पर विचार करना होगा। यदि कतार पर्याप्त रूप से बड़ी है, तो निष्पादन अनुमान के लिए प्रत्येक जॉब को देखना निषेधात्मक रूप से धीमा होगा। ऐसे मामलों में, कुल जॉब काउंट ढूंढना बेहतर हो सकता है, फिर कतार से जॉब्स का एक यादृच्छिक प्रतिनिधि नमूना चुनें और उस नमूने से औसत निष्पादन की गणना करें। वैकल्पिक रूप से, यदि किसी जॉब क्लास के साथ अभी तक कोई प्रदर्शन डेटा जुड़ा नहीं है, तो हमें तब तक एक माना हुआ निष्पादन समय उपयोग करना होगा जब तक कि यह निष्पादित न हो जाए और कुछ बार रिकॉर्ड न हो जाए।
ऊपर उल्लिखित प्रणाली को कुछ सुधारों के साथ भी काफी बेहतर बनाया जा सकता है। प्रत्येक जॉब क्लास के लिए निष्पादन समय का अनुमान समानांतर में लगाने पर विचार करें, क्योंकि प्रत्येक अनुमान अलग और आइडेम्पोटेंट है। हम अपने कतार आत्मनिरीक्षण को उन जॉब्स को शामिल करने के लिए भी अपडेट कर सकते हैं जो वर्तमान में एक श्रमिक द्वारा निष्पादित किए जा रहे हैं ताकि हमारे कुल सेवा समय अनुमान की सटीकता में सुधार हो सके। एकाधिक कतारों के साथ बैकग्राउंड प्रोसेसिंग आर्किटेक्चर के लिए, हम प्रत्येक कतार को कतार प्राथमिकता के आधार पर एक वांछित प्रतीक्षा समय असाइन कर सकते हैं और श्रमिकों को उचित रूप से स्केल कर सकते हैं।
कतार प्रणालियां किसी भी प्रोजेक्ट में अत्यधिक परिवर्तनीय काम का एक बहुत कुछ इकट्ठा करती हैं। कतार से जॉब्स के निष्पादन पर प्रदर्शन डेटा के साथ, हम उन सभी कामों को एक संवेदनशील, कुशल तरीके से सेवा करने के लिए संसाधनों को प्रभावी ढंग से स्केल कर सकते हैं।
हैप्पी स्केलिंग!
P.S. यदि आप रूबी मैजिक पोस्ट को प्रेस से निकलते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूज़लेटर की सदस्यता लें और कभी भी एक भी पोस्ट मिस न करें!