python - अनुरोधों पर अमान्य लेनदेन जारी है




flask sqlalchemy (2)

एक आश्चर्यजनक बात यह है कि self.session.commit आस-पास कोई अपवाद नहीं है। और एक प्रतिबद्धता विफल हो सकती है, उदाहरण के लिए यदि डीबी से कनेक्शन खो गया है। तो प्रतिबद्धता विफल हो जाती है, session हटाया नहीं जाता है और अगली बार जब विशेष धागा एक अनुरोध को संभालता है तो वह अब भी अमान्य सत्र का उपयोग करने का प्रयास करता है।

दुर्भाग्य से, फ्लास्क-स्क्लाक्लेमी आपके खुद के टियरडाउन फ़ंक्शन के लिए कोई साफ संभावना प्रदान नहीं करता है। एक तरीका SQLALCHEMY_COMMIT_ON_TEARDOWN को गलत पर सेट करना होगा और फिर अपना खुद का टियरडाउन फ़ंक्शन लिखना होगा।

इसे ऐसा दिखना चाहिए:

@app.teardown_appcontext
def shutdown_session(response_or_exc):
    try: 
        if response_or_exc is None:
            sqla.session.commit()
    finally:
        sqla.session.remove()
    return response_or_exc

अब, आपके पास अभी भी आपकी असफल प्रतिबद्धताएं होंगी, और आपको अलग से जांच करनी होगी ... लेकिन कम से कम आपके धागे को पुनर्प्राप्त करना चाहिए।

सारांश

उत्पादन में हमारे धागे में से एक ने एक त्रुटि मारा और अब InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction. उत्पादन कर InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction. InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction. त्रुटियों, हर अनुरोध पर एक प्रश्न के साथ जो इसके कार्य करता है, बाकी के जीवन के लिए! यह दिनों के लिए यह कर रहा है, अब! यह कैसे संभव है, और हम इसे आगे कैसे रोक सकते हैं?

पृष्ठभूमि

हम यूडब्ल्यूएसजीआई (4 प्रोसेस, 2 थ्रेड्स) पर फ्लास्क ऐप का उपयोग कर रहे हैं, फ्लास्क-स्क्लाक्लेमी के साथ हमें एसक्यूएल सर्वर के लिए डीबी कनेक्शन प्रदान करते हैं।

समस्या तब शुरू हुई जब उत्पादन में हमारे धागे में से एक इस अनुरोध को फाड़ रहा था, इस फ्लास्क-स्क्लाक्लेमी विधि के अंदर:

@teardown
def shutdown_session(response_or_exc):
    if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
        if response_or_exc is None:
            self.session.commit()
    self.session.remove()
    return response_or_exc

... और लेनदेन अमान्य होने पर किसी भी तरह self.session.commit() को कॉल करने में कामयाब रहा। इसके परिणामस्वरूप sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back हमारे लॉगिंग कॉन्फ़िगरेशन की अवहेलना में sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back को स्टडआउट पर आउटपुट प्राप्त sqlalchemy.exc.InvalidRequestError: Can't reconnect until invalid transaction is rolled back है, जो एप संदर्भ के दौरान घटने के बाद से समझ में आता है, जिसे कभी नहीं उठाना चाहिए अपवाद नहीं। मुझे यकीन नहीं है कि response_or_exec or_exec सेट होने के बिना लेनदेन को अमान्य कैसे किया जाना चाहिए, लेकिन वास्तव में यह कम समस्या AFAIK है।

बड़ी समस्या यह है कि, जब "तैयार" राज्य "त्रुटियां शुरू हुईं, और तब से नहीं रुक गईं। हर बार यह धागा एक अनुरोध करता है जो डीबी को हिट करता है, यह 500 है। हर दूसरे धागे ठीक लगते हैं: जहां तक ​​मैं कह सकता हूं, यहां तक ​​कि एक ही प्रक्रिया में मौजूद धागा ठीक कर रहा है।

ऐसे ही अनुमान लगाना

SQLAlchemy मेलिंग सूची में "तैयार" स्थिति के बारे में एक प्रविष्टि है "त्रुटि यह कहती है कि ऐसा होता है यदि कोई सत्र शुरू हो रहा है और अभी तक समाप्त नहीं हुआ है, और कुछ और इसका उपयोग करने का प्रयास करता है। मेरा अनुमान है कि इस धागे में सत्र self.session.remove() चरण में कभी नहीं मिला, और अब यह कभी नहीं होगा।

मुझे अभी भी ऐसा लगता है कि यह व्याख्या नहीं करता है कि यह सत्र हालांकि अनुरोधों पर कैसे चल रहा है। हमने फ्लास्क-स्क्लाक्लेमी के अनुरोध-स्कोप्ड सत्रों के उपयोग को संशोधित नहीं किया है, इसलिए सत्र को एसक्लाक्लेमी के पूल में वापस लौटाया जाना चाहिए और अनुरोध के अंत में वापस लुढ़का जाना चाहिए, यहां तक ​​कि जो लोग त्रुटि कर रहे हैं (हालांकि स्वीकार्य रूप से, शायद पहले नहीं, चूंकि ऐप संदर्भ के दौरान उठाया गया है)। रोलबैक क्यों नहीं हो रहे हैं? अगर हम हर समय stdout (uwsgi के लॉग में) पर "अमान्य लेनदेन" त्रुटियां देख रहे थे, तो मैं इसे समझ सकता था, लेकिन हम नहीं हैं: मैंने इसे पहली बार देखा था। लेकिन मैं 500% होने पर हर बार तैयार "राज्य" त्रुटि (हमारे ऐप के लॉग में) देखता हूं।

विन्यास विवरण

हमने session_options में expire_on_commit को बंद कर दिया है, और हमने SQLALCHEMY_COMMIT_ON_TEARDOWN चालू कर दिया है। हम केवल डेटाबेस से पढ़ रहे हैं, अभी तक लिख नहीं रहे हैं। हम अपने सभी प्रश्नों के लिए डॉगपाइल-कैश का भी उपयोग कर रहे हैं (मेमकैच लॉक का उपयोग करके, क्योंकि हमारे पास एकाधिक प्रक्रियाएं हैं, और वास्तव में, 2 लोड-संतुलित सर्वर)। कैश हमारी प्रमुख क्वेरी के लिए हर मिनट समाप्त हो जाती है।

2014-04-28 अपडेट किया गया: संकल्प कदम

ऐसा लगता है कि सर्वर को पुनरारंभ करने से समस्या ठीक हो गई है, जो पूरी तरह से आश्चर्यजनक नहीं है। उस ने कहा, मैं इसे फिर से देखने की उम्मीद करता हूं जब तक कि हम इसे कैसे रोकें। बेन्सेलमे (नीचे) ने प्रतिबद्धता के आस-पास अपवाद के साथ अपने स्वयं के टियरडाउन कॉलबैक को लिखने का सुझाव दिया, लेकिन मुझे लगता है कि बड़ी समस्या यह है कि थ्रेड अपने बाकी के जीवन के लिए गड़बड़ कर रहा था। तथ्य यह है कि यह अनुरोध के बाद दूर नहीं गया या दो वास्तव में मुझे परेशान करता है!


2016-06-05 संपादित करें:

एक पीआर जो इस समस्या को हल करती है उसे 26 मई, 2016 को विलय कर दिया गया है।

फ्लास्क पीआर 1822

2015-04-13 संपादित करें:

रहस्य सुलझ गया!

टीएल; डीआर: 2014-12-11 संपादन में टियरडाउन-रैपिंग रेसिपी का उपयोग करके, अपने टियरडाउन फ़ंक्शन सफल होने पर पूरी तरह से सुनिश्चित रहें!

फ्लास्क का उपयोग करके भी एक नई नौकरी शुरू की, और यह समस्या फिर से पॉप अप हो गई, इससे पहले कि मैं टियरडाउन-रैपिंग रेसिपी डालूं। इसलिए मैंने इस मुद्दे पर फिर से विचार किया और आखिर में पता चला कि क्या हुआ।

जैसा कि मैंने सोचा था, फ्लास्क अनुरोध के संदर्भ में एक नया अनुरोध संदर्भ देता है जब भी कोई नया अनुरोध लाइन के नीचे आता है। इसका उपयोग सत्र की तरह अनुरोध-स्थानीय ग्लोबल्स का समर्थन करने के लिए किया जाता है।

फ्लास्क में "एप्लिकेशन" संदर्भ की धारणा भी है जो अनुरोध संदर्भ से अलग है। इसका मतलब परीक्षण और सीएलआई एक्सेस जैसी चीजों का समर्थन करना है, जहां HTTP नहीं हो रहा है। मुझे यह पता था, और मुझे यह भी पता था कि वह जगह है जहां फ्लास्क-एसक्यूएलए अपने डीबी सत्र रखता है।

सामान्य ऑपरेशन के दौरान, एक अनुरोध की शुरुआत में एक अनुरोध और एक ऐप संदर्भ दोनों धक्का दिया जाता है, और अंत में पॉप किया जाता है।

हालांकि, यह पता चला है कि अनुरोध संदर्भ को दबाते समय, अनुरोध संदर्भ जांचता है कि कोई मौजूदा ऐप संदर्भ है या नहीं, और यदि कोई मौजूद है, तो यह एक नया धक्का नहीं देता है !

इसलिए यदि किसी टियरडाउन फ़ंक्शन को बढ़ाने के कारण अनुरोध के अंत में ऐप संदर्भ पॉप नहीं किया गया है, न केवल यह हमेशा के लिए टिकेगा, इसके ऊपर एक नया ऐप संदर्भ भी नहीं होगा।

यह कुछ जादू बताता है जो मुझे हमारे एकीकरण परीक्षण में नहीं समझा था। आप कुछ परीक्षण डेटा दर्ज कर सकते हैं, फिर कुछ अनुरोध चलाएं और उन अनुरोधों के बावजूद वे अनुरोध उस डेटा तक पहुंच पाएंगे। अनुरोध केवल एक नया अनुरोध संदर्भ है, लेकिन यह केवल इसलिए संभव है, लेकिन परीक्षण अनुप्रयोग संदर्भ का पुन: उपयोग कर रहा है, इसलिए यह मौजूदा डीबी कनेक्शन का पुन: उपयोग कर रहा है। तो यह वास्तव में एक विशेषता है, एक बग नहीं।

उस ने कहा, इसका मतलब यह है कि आपको पूरी तरह से सुनिश्चित होना होगा कि आपके टियरडाउन फ़ंक्शन सफल होते हैं, नीचे दिए गए टियरडाउन-फ़ंक्शन रैपर जैसे कुछ का उपयोग करते हुए। स्मृति और डीबी कनेक्शन को लीक करने से बचने के लिए उस सुविधा के बिना भी यह एक अच्छा विचार है, लेकिन इन निष्कर्षों के प्रकाश में विशेष रूप से महत्वपूर्ण है। मैं इस कारण से फ्लास्क के दस्तावेज़ों में पीआर जमा कर दूंगा। ( यहां यह है )

2014-12-11 संपादित करें:

एक चीज जिसे हम जगह में डाल रहे थे, निम्नलिखित कोड था (हमारे आवेदन कारखाने में), जो यह सुनिश्चित करने के लिए हर टियरडाउन फ़ंक्शन को लपेटता है कि यह अपवाद लॉग करता है और आगे नहीं बढ़ता है। यह सुनिश्चित करता है कि ऐप संदर्भ हमेशा सफलतापूर्वक पॉप हो जाता है। स्पष्ट रूप से यह सुनिश्चित करने के बाद जाना होगा कि सभी टियरडाउन फ़ंक्शंस पंजीकृत हैं।

# Flask specifies that teardown functions should not raise.
# However, they might not have their own error handling,
# so we wrap them here to log any errors and prevent errors from
# propagating.
def wrap_teardown_func(teardown_func):
    @wraps(teardown_func)
    def log_teardown_error(*args, **kwargs):
        try:
            teardown_func(*args, **kwargs)
        except Exception as exc:
            app.logger.exception(exc)
    return log_teardown_error

if app.teardown_request_funcs:
    for bp, func_list in app.teardown_request_funcs.items():
        for i, func in enumerate(func_list):
            app.teardown_request_funcs[bp][i] = wrap_teardown_func(func)
if app.teardown_appcontext_funcs:
    for i, func in enumerate(app.teardown_appcontext_funcs):
        app.teardown_appcontext_funcs[i] = wrap_teardown_func(func)

2014-09-19 संपादित करें:

ठीक है, बाहर --reload-on-exception एक अच्छा विचार नहीं है यदि 1.) आप एकाधिक धागे का उपयोग कर रहे हैं और 2.) थ्रेड मिड-अनुरोध को समाप्त करने से परेशानी हो सकती है। मैंने सोचा था कि यूडब्ल्यूएसजीआई उस कार्यकर्ता के खत्म होने के सभी अनुरोधों की प्रतीक्षा करेगा, जैसे यूडब्ल्यूएसजीआई की "सुंदर रीलोड" सुविधा करता है, लेकिन ऐसा लगता है कि ऐसा नहीं है। हमें समस्याएं शुरू हुईं जहां थ्रेड मेमकैच में कुत्ते के लॉक को लॉन्च करेगा, फिर समाप्त हो जाएगा जब यूडब्ल्यूएसजीआई एक अलग थ्रेड में अपवाद के कारण कार्यकर्ता को फिर से लोड करता है, जिसका अर्थ है कि लॉक कभी जारी नहीं होता है।

SQLALCHEMY_COMMIT_ON_TEARDOWN को हमारी समस्या का हल हल किया गया है, हालांकि हम session.remove() दौरान ऐप टियरडाउन के दौरान कभी-कभी कभी-कभी त्रुटियां प्राप्त कर रहे हैं। ऐसा लगता है कि ये एसक्यूएलकेमी मुद्दे 3043 के कारण हैं, जो कि संस्करण 0.9.5 में तय किया गया था, इसलिए उम्मीद है कि 0.9.5 तक अपग्रेड करने से हम हमेशा ऐप संदर्भ टियरडाउन पर काम करने की अनुमति देंगे।

मूल:

यह पहली जगह में कैसे हुआ यह अभी भी एक खुला प्रश्न है, लेकिन मुझे इसे रोकने का एक तरीका मिला: --reload-on-exception विकल्प।

हमारे फ्लास्क ऐप की त्रुटि हैंडलिंग को कुछ भी पकड़ना चाहिए, इसलिए यह एक कस्टम त्रुटि प्रतिक्रिया प्रदान कर सकता है, जिसका अर्थ है कि केवल सबसे अप्रत्याशित अपवादों को इसे यूडब्ल्यूएसजीआई के लिए सभी तरह से बनाना चाहिए। इसलिए जब भी ऐसा होता है तो पूरे ऐप को फिर से लोड करना समझ में आता है।

हम SQLALCHEMY_COMMIT_ON_TEARDOWN भी बंद कर SQLALCHEMY_COMMIT_ON_TEARDOWN , हालांकि हम शायद ऐप टियरडाउन के लिए अपनी कॉलबैक लिखने के बजाय स्पष्ट रूप से प्रतिबद्ध होंगे, क्योंकि हम डेटाबेस को शायद ही कभी लिख रहे हैं।