2016-04-14 11 views
6

मैं रेल 4.0.0 और रूबी 2.0.0 का उपयोग कर रहा हूं। मेरा Post (जैसा कि ब्लॉग पोस्ट में है) मॉडल उपयोगकर्ता के उपयोगकर्ता_नाम, first_name, last_name के संयोजन के साथ उपयोगकर्ता से जुड़ा हुआ है। मैं डेटा माइग्रेट करना चाहता हूं ताकि पोस्ट विदेशी उपयोगकर्ताओं द्वारा उपयोगकर्ताओं से जुड़ी हो, जो उपयोगकर्ता की आईडी है।मैं ActiveRecord find_in_batches क्वेरी को कैसे अनुकूलित करूं?

मेरे पास posts तालिका में लगभग 11 मिलियन रिकॉर्ड हैं।

मैं लिनक्स सर्वर पर एक रेक कार्य का उपयोग कर डेटा माइग्रेट करने के लिए नीचे कोड चला रहा हूं। हालांकि, मेरा काम अलग-अलग मेमोरी का उपभोग करने के कारण, विशेष रूप से नीचे दिए गए कोड के कारण, सेवर द्वारा "मार डाला" रहता है।

मैंने पाया है कि 20 के लिए batch_size कम करने और sleep(60) को sleep(10) बढ़ती कार्य मार डाला जा रहा है बिना कुल में अधिक रिकॉर्ड को अपडेट करते, अब चलाने के लिए अनुमति देता है, लेकिन काफी अधिक समय लगता है।

मैं इस कोड को गति और स्मृति उपयोग के लिए कैसे अनुकूलित कर सकता हूं?

Post.where(user_id: nil).find_in_batches(batch_size: 1000) do |posts| 
    puts "*** Updating batch beginning with post #{posts.first.id}..." 
    sleep(10) # Hopefully, saving some memory usage. 
    posts.each do |post| 
    begin 
     user = User.find_by(user_name: post.user_name, first_name: post.first_name, last_name: post.last_name) 
     post.update(user_id: user.id) 
    rescue NoMethodError => error # user could be nil, so user.id will raise a NoMethodError 
     puts "No user found." 
    end 
    end 
    puts "*** Finished batch." 
end 
+0

वहाँ 'find_in_batches' तुलना में एक बेहतर तरीका है एआर Iff अद्यतन, कि एक स्वागत योग्य जवाब होगा! – sealocal

+0

आप मॉडल परत के माध्यम से जाने के बजाय इसे एक 'अद्यतन' कथन के साथ कर सकते हैं। इसके अलावा 'नींद' का उपयोग स्मृति उपयोग पर शून्य प्रभाव पड़ता है, यह बस इसे धीमा कर देता है। यदि आप स्मृति उपयोग पर दीवार के खिलाफ वास्तव में हैं, तो प्रत्येक पुनरावृत्ति के बाद 'जीसी.स्टार्ट' पर कॉल करें। बहुत यकीन है कि आप यह सब एक साधारण प्रवासन में कर सकते हैं। – tadman

उत्तर

9

डेटाबेस में सभी काम करें जो डेटा को आगे और आगे बढ़ने से तेज़ तरीके से करें।

यह ActiveRecord के साथ पूरा किया जा सकता है। बेशक कृपया महत्वपूर्ण डेटा पर इसे खोलने से पहले कृपया इसका परीक्षण करें।

Post 
    .where(user_id: nil) 
    .joins("inner join users on posts.user_name = users.user_name") 
    .update_all("posts.user_id = users.id") 

इसके अलावा, अगर पदों user_id पर एक सूचकांक है, और उन user_name पर एक सूचकांक है, तो यह है कि इस विशेष क्वेरी रन और अधिक तेजी से मदद मिलेगी।

+0

इसने डेटा पर स्क्रिप्ट को लगभग आधा तक चलाने के लिए समय कम कर दिया! बहुत बहुत धन्यवाद। अपने आप पर, यह मेरी स्क्रिप्ट को सर्वर द्वारा मारने से नहीं रोका। मुझे अभी भी बैच प्रश्नों का उपयोग करने की आवश्यकता है। – sealocal

+0

खुशी हुई इससे मदद मिली। यदि पोस्ट में user_id अनुक्रमणिका है, और उपयोगकर्ताओं के पास user_name अनुक्रमणिका है, तो यह विशेष क्वेरी तेज़ी से कर देगी। सुनिश्चित नहीं है कि आपके पास क्या है। – z5h

2

बाहर चेक एआर मॉडल पर #uncached विधि। असल में, अनुरोध अनुकूलन के लिए, एआर बहुत सारे क्वेरी डेटा को कैश करेगा क्योंकि यह #find_in_batches कर रहा है, लेकिन यह इस तरह की बड़ी प्रोसेसिंग स्क्रिप्ट्स में बाधा है।

Post.uncached do 
    # perform all your heavy query magic here 
end 

अंत में, यह है कि अगर काम नहीं करता, mysql2 मणि का उपयोग कर एआर भूमि के ऊपर से बचने के लिए, जब तक कि आप अद्यतन में कोई कॉलबैक/व्यापार तर्क के आधार पर नहीं कर रहे हैं के रूप में विचार करें।

+0

आश्चर्यजनक रूप से, इससे मुझे सर्वर द्वारा मारने के बिना अपना कोड चलाने में मदद मिली! हालांकि, यह गति में एक उल्लेखनीय प्रभाव नहीं था। – sealocal

2

यदि कोई जुड़ाव संभव है तो मैं z5h से दृष्टिकोण के साथ जाऊंगा। नहीं तो आप उपयोगकर्ता मॉडल (संभवतः एक अलग प्रवास में) करने के लिए एक सूचकांक जोड़ सकते हैं और जब प्रत्येक पोस्ट को अद्यतन करने के भी सत्यापन, कॉलबैक और सामान को छोड़:

add_index :users, [:user_name, :first_name, :last_name] # Speed up search queries 
Post.where(user_id: nil).find_each do |post| 
    if user = User.find_by(user_name: post.user_name, 
         first_name: post.first_name, 
         last_name: post.last_name) 
    post.update_columns(user_id: user.id) # ...to skip validations and callbacks. 
    end 
end 

कृपया ध्यान दें कि find_eachfind_in_batches + से अधिक पुनरावृत्ति के बराबर है प्रत्येक पोस्ट, लेकिन संभवतः तेज़ नहीं है (Active Record Query Interface पर रेल गाइड देखें)

शुभकामनाएँ!

+0

धन्यवाद। ऐसा लगता है कि कोई बुरा विचार नहीं है, लेकिन मैं अपने डेटाबेस को उस तीन कॉलम कॉम्बो द्वारा उपयोगकर्ता को देखने से दूर करने की कोशिश कर रहा हूं, इसलिए अगर मैं इससे बच सकता हूं तो मैं इसे इंडेक्स नहीं करना चाहता था। – sealocal

0

अन्य उत्तरों को जोड़कर, मैं तालिकाओं में शामिल होने में सक्षम था, और 1000 पंक्तियों के बैचों में, एकाधिक गतियों को अपडेट करता था, गति में कमी के साथ और सर्वर द्वारा मेरी प्रक्रिया को मारने के बिना।

यहां उन दृष्टिकोणों को जोड़ दिया गया है जो मुझे सबसे अच्छा काम करने के लिए मिला है, जितना संभव हो सके ActiveRecord API के भीतर कोड को रखते हुए।

Post.uncached do 
    Post.where(user_id: nil, organization_id: nil).find_each do |posts| 
    puts "** Updating batch beginning with post #{posts.first.id}..." 

    # Update 1000 records at once 
    posts.map!(&:id) # posts is an array, not a relation 
    Post.where(id: posts). 
     joins("INNER JOIN users ON (posts.user_name = users.user_name)"). 
     joins("INNER JOIN organizations ON (organizations.id = users.organization_id)"). 
     update_all("posts.user_id = users.id, posts.organization_id = organizations.id") 

    puts "** Finished batch." 
    end 
end 
0

जोड़ें नए अस्थायी बूलियन विशेषता

Post.where(updated: false).find_in_batches(batch_size: 1000) do |posts| 
    ActiveRecord::Base.transaction do 
    puts "*** Updating batch beginning with post #{posts.first.id}..." 
    posts.each do |post| 
     user = User.find_by(user_name: post.user_name, first_name: post.first_name, last_name: post.last_name) 
     if user 
     post.update_columns(user_id: user.id, updated: true) 
     else 
     post.update_columns(updated: true) 
     end 
    end 
    puts "*** Finished batch." 
    end 
end 
संबंधित मुद्दे