لماذا تكون وظائف SQL المجمعة أبطأ بكثير من Python وJava (أو Poor Man's OLAP)

StackOverflow https://stackoverflow.com/questions/51553

سؤال

أحتاج إلى رأي DBA الحقيقي.يستغرق Postgres 8.3 200 مللي ثانية لتنفيذ هذا الاستعلام على جهاز Macbook Pro الخاص بي بينما تقوم Java وPython بإجراء نفس الحساب في أقل من 20 مللي ثانية (350.000 صف):

SELECT count(id), avg(a), avg(b), avg(c), avg(d) FROM tuples;

هل هذا السلوك الطبيعي عند استخدام قاعدة بيانات SQL؟

المخطط (يحتوي الجدول على ردود على استطلاع):

CREATE TABLE tuples (id integer primary key, a integer, b integer, c integer, d integer);

\copy tuples from '350,000 responses.csv' delimiter as ','

لقد كتبت بعض الاختبارات في Java وPython للسياق وهي تسحق SQL (باستثناء لغة python النقية):

java   1.5 threads ~ 7 ms    
java   1.5         ~ 10 ms    
python 2.5 numpy   ~ 18 ms  
python 2.5         ~ 370 ms

حتى sqlite3 يتنافس مع Postgres على الرغم من افتراضه أن جميع الأعمدة عبارة عن سلاسل (على النقيض من ذلك:حتى استخدام التبديل إلى الأعمدة الرقمية بدلاً من الأعداد الصحيحة في Postgres يؤدي إلى تباطؤ 10x)

تتضمن عمليات الضبط التي جربتها دون نجاح (اتباع بعض نصائح الويب بشكل أعمى):

increased the shared memory available to Postgres to 256MB    
increased the working memory to 2MB
disabled connection and statement logging
used a stored procedure via CREATE FUNCTION ... LANGUAGE SQL

لذا فإن سؤالي هو، هل تجربتي هنا عادية، وهذا ما يمكنني توقعه عند استخدام قاعدة بيانات SQL؟أستطيع أن أفهم أن ACID يجب أن يأتي مع تكاليف، ولكن هذا نوع من الجنون في رأيي.أنا لا أطلب سرعة اللعبة في الوقت الفعلي، ولكن نظرًا لأن Java يمكنها معالجة ملايين الضربات في أقل من 20 مللي ثانية، أشعر ببعض الغيرة.

هل هناك طريقة أفضل لإجراء OLAP بسيط بسعر رخيص (سواء من حيث المال أو تعقيد الخادم)؟لقد بحثت في Mondrian وPig + Hadoop ولكني لم أكن متحمسًا جدًا للحفاظ على تطبيق خادم آخر ولست متأكدًا مما إذا كان ذلك سيساعدني أم لا.


لا يقوم كود Python ورمز Java بكل العمل في المنزل إذا جاز التعبير.لقد قمت فقط بإنشاء 4 مصفوفات تحتوي كل منها على 350.000 قيمة عشوائية، ثم خذ المتوسط.أنا لا أدرج الجيل في التوقيتات، فقط خطوة المتوسط.يستخدم توقيت سلاسل جافا 4 سلاسل رسائل (واحدة لكل صفيف في المتوسط)، وهي مبالغة ولكنها بالتأكيد الأسرع.

يتم تشغيل توقيت sqlite3 بواسطة برنامج Python ويتم تشغيله من القرص (وليس :memory:)

أدرك أن Postgres يفعل الكثير خلف الكواليس، لكن معظم هذا العمل لا يهمني نظرًا لأنه عبارة عن بيانات للقراءة فقط.

لا يغير استعلام Postgres التوقيت في عمليات التشغيل اللاحقة.

لقد قمت بإعادة تشغيل اختبارات بايثون لتشمل تخزينها مؤقتًا خارج القرص.يتباطأ التوقيت بشكل كبير إلى ما يقرب من 4 ثوانٍ.لكنني أعتقد أن كود التعامل مع ملفات Python موجود إلى حد كبير في لغة C (على الرغم من أنه ربما ليس csv lib؟) لذا فإن هذا يشير لي إلى أن Postgres لا يتدفق من القرص أيضًا (أو أنك على صواب ويجب أن أنحني قبل من كتب طبقة التخزين الخاصة به!)

هل كانت مفيدة؟

المحلول

يقوم Postgres بأكثر مما يبدو (الحفاظ على تناسق البيانات كبداية!)

إذا لم يكن من الضروري أن تكون القيم صحيحة بنسبة 100%، أو إذا كان يتم تحديث الجدول نادرًا، ولكنك تقوم بهذه العملية الحسابية كثيرًا، فقد ترغب في البحث في طرق العرض المادية لتسريع العملية.

(ملاحظة، لم أستخدم طرق عرض مادية في Postgres، فهي تبدو مبتذلة بعض الشيء، ولكنها قد تناسب موقفك).

وجهات نظر مادية

ضع في اعتبارك أيضًا تكلفة الاتصال الفعلي بالخادم ورحلة الذهاب والإياب المطلوبة لإرسال الطلب إلى الخادم والعودة.

سأعتبر 200 مللي ثانية لشيء مثل هذا أمرًا جيدًا جدًا، يستغرق الاختبار السريع على خادم أوراكل الخاص بي، نفس بنية الجدول التي تحتوي على حوالي 500 ألف صف ولا توجد فهارس، حوالي 1 - 1.5 ثانية، وكل ذلك تقريبًا مجرد امتصاص أوراكل للبيانات خارج القرص.

السؤال الحقيقي هو: هل سرعة 200 مللي ثانية كافية؟

-------------- أكثر --------------------

لقد كنت مهتمًا بحل هذه المشكلة باستخدام وجهات النظر المادية، حيث أنني لم ألعب بها مطلقًا.هذا في أوراكل.

أولاً قمت بإنشاء فيديو موسيقي يتم تحديثه كل دقيقة.

create materialized view mv_so_x 
build immediate 
refresh complete 
START WITH SYSDATE NEXT SYSDATE + 1/24/60
 as select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

أثناء تحديثه، لم يتم إرجاع أي صفوف

SQL> select * from mv_so_x;

no rows selected

Elapsed: 00:00:00.00

بمجرد التحديث، يكون أسرع بكثير من إجراء الاستعلام الأولي

SQL> select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:05.74
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

إذا قمنا بإدراجه في الجدول الأساسي، فلن تكون النتيجة قابلة للعرض على الفور لعرض MV.

SQL> insert into so_x values (1,2,3,4,5);

1 row created.

Elapsed: 00:00:00.00
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

لكن انتظر دقيقة أو نحو ذلك، وسيتم تحديث الفيديو الموسيقي خلف الكواليس، وسيتم إرجاع النتيجة بسرعة كما تريد.

SQL> /

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899460 7495.35823 22.2905352 5.00276078 2.17647059

Elapsed: 00:00:00.00
SQL> 

هذا ليس مثاليا.كبداية، ليس الوقت الفعلي، ولن تكون الإدخالات/التحديثات مرئية على الفور.أيضًا، لديك استعلام قيد التشغيل لتحديث MV سواء كنت في حاجة إليه أم لا (يمكن ضبط هذا على أي إطار زمني، أو عند الطلب).لكن هذا يوضح مدى السرعة التي يمكن أن تجعلها MV تبدو للمستخدم النهائي، إذا كان بإمكانك التعايش مع قيم لا تصل إلى الدقة الثانية تمامًا.

نصائح أخرى

أود أن أقول إن مخطط الاختبار الخاص بك ليس مفيدًا حقًا.لتنفيذ استعلام قاعدة البيانات، يمر خادم قاعدة البيانات بعدة خطوات:

  1. تحليل SQL
  2. العمل على خطة الاستعلام، أنا.ه.تحديد المؤشرات التي سيتم استخدامها (إن وجدت)، وتحسينها، وما إلى ذلك.
  3. إذا تم استخدام فهرس، ابحث فيه عن المؤشرات إلى البيانات الفعلية، ثم انتقل إلى الموقع المناسب في البيانات أو
  4. إذا لم يتم استخدام أي فهرس، قم بالمسح الضوئي الطاولة بأكملها لتحديد الصفوف المطلوبة
  5. تحميل البيانات من القرص إلى موقع مؤقت (نأمل، ولكن ليس بالضرورة، الذاكرة)
  6. إجراء العمليات الحسابية count() و avg()

لذا، فإن إنشاء مصفوفة في بايثون والحصول على المتوسط ​​يتخطى كل هذه الخطوات باستثناء الخطوة الأخيرة.نظرًا لأن عمليات الإدخال/الإخراج للقرص تعد من بين العمليات الأكثر تكلفة التي يجب على البرنامج تنفيذها، فهذا يعد عيبًا كبيرًا في الاختبار (راجع أيضًا الإجابات على هذا السؤال لقد سألت هنا من قبل).حتى لو قرأت البيانات من القرص في اختبارك الآخر، فإن العملية مختلفة تمامًا ومن الصعب معرفة مدى ملاءمة النتائج.

للحصول على مزيد من المعلومات حول المكان الذي يقضي فيه Postgres وقته، أود أن أقترح الاختبارات التالية:

  • قارن وقت تنفيذ استعلامك بـ SELECT بدون وظائف التجميع (i.ه.قطع الخطوة 5)
  • إذا وجدت أن التجميع يؤدي إلى تباطؤ كبير، فحاول أن تفعله Python بشكل أسرع، وتحصل على البيانات الأولية من خلال SELECT العادي من المقارنة.

لتسريع الاستعلام، قم بتقليل الوصول إلى القرص أولاً.أشك كثيرًا في أن التجميع هو الذي يستغرق وقتًا.

هناك عدة طرق للقيام بذلك:

  • تخزين البيانات مؤقتًا (في الذاكرة!) للوصول إليها لاحقًا، إما عبر إمكانات محرك قاعدة البيانات الخاصة أو باستخدام أدوات مثل memcached
  • تقليل حجم البيانات المخزنة الخاصة بك
  • تحسين استخدام المؤشرات.في بعض الأحيان قد يعني هذا تخطي استخدام الفهرس تمامًا (بعد كل شيء، فهو الوصول إلى القرص أيضًا).بالنسبة إلى MySQL، يبدو أنني أتذكر أنه من المستحسن تخطي المؤشرات إذا افترضت أن الاستعلام يجلب أكثر من 10% من جميع البيانات الموجودة في الجدول.
  • إذا كان استعلامك يستخدم المؤشرات بشكل جيد، فأنا أعلم أنه بالنسبة لقواعد بيانات MySQL، من المفيد وضع المؤشرات والبيانات على أقراص فعلية منفصلة.ومع ذلك، لا أعرف ما إذا كان هذا ينطبق على Postgres.
  • قد تكون هناك أيضًا مشكلات أكثر تعقيدًا مثل تبديل الصفوف إلى القرص إذا تعذرت معالجة مجموعة النتائج بالكامل في الذاكرة لسبب ما.لكنني سأترك هذا النوع من البحث حتى أواجه مشكلات خطيرة في الأداء لا أستطيع العثور على طريقة أخرى لإصلاحها، لأنها تتطلب معرفة الكثير من التفاصيل الصغيرة الموجودة أسفل الغطاء في العملية الخاصة بك.

تحديث:

لقد أدركت للتو أنه يبدو أنك لا تستخدم المؤشرات الخاصة بالاستعلام أعلاه وعلى الأرجح لا تستخدم أيًا منها أيضًا، لذا ربما لم تكن نصيحتي بشأن المؤشرات مفيدة.آسف.ومع ذلك، أود أن أقول إن التجميع ليس هو المشكلة ولكن الوصول إلى القرص هو المشكلة.سأترك عناصر الفهرس، على أي حال، ربما لا يزال لها بعض الفائدة.

لقد قمت بإعادة الاختبار باستخدام MySQL مع تحديد ENGINE = MEMORY ولم يغير شيئًا (لا يزال 200 مللي ثانية).يوفر Sqlite3 باستخدام قاعدة بيانات في الذاكرة توقيتات مماثلة أيضًا (250 مللي ثانية).

الرياضيات هنا يبدو صحيحًا (على الأقل الحجم، حيث أن هذا هو حجم قاعدة البيانات sqlite :-)

أنا فقط لا أشتري حجة القرص الذي يسبب البطء نظرًا لوجود كل المؤشرات على أن الجداول موجودة في الذاكرة (يحذر جميع رجال postgres من محاولة تثبيت الجداول في الذاكرة بجهد شديد لأنهم أقسموا أن نظام التشغيل سيفعل ذلك بشكل أفضل من المبرمج )

لتوضيح التوقيت، لا تتم قراءة كود Java من القرص، مما يجعلها مقارنة غير عادلة تمامًا إذا كان Postgres يقرأ من القرص ويحسب استعلامًا معقدًا، ولكن هذا في الحقيقة بجانب النقطة، يجب أن تكون قاعدة البيانات ذكية بما يكفي لجلب صغير الجدول في الذاكرة وتجميع الإجراء المخزن مسبقًا IMHO.

تحديث (ردًا على التعليق الأول أدناه):

لست متأكدًا من كيفية اختبار الاستعلام دون استخدام وظيفة التجميع بطريقة عادلة، لأنه إذا قمت بتحديد جميع الصفوف، فسوف أقضي الكثير من الوقت في إجراء تسلسل وتنسيق لكل شيء.أنا لا أقول أن البطء يرجع إلى وظيفة التجميع، فمن الممكن أن يكون مجرد عبء من التزامن والنزاهة والأصدقاء.أنا لا أعرف كيفية عزل التجميع باعتباره المتغير المستقل الوحيد.

هذه إجابات مفصلة للغاية، لكنها في الغالب تطرح السؤال، كيف يمكنني الحصول على هذه الفوائد دون مغادرة Postgres نظرًا لأن البيانات تتلاءم بسهولة مع الذاكرة، وتتطلب قراءات متزامنة ولكن لا توجد عمليات كتابة، ويتم الاستعلام عنها بنفس الاستعلام مرارًا وتكرارًا.

هل من الممكن ترجمة الاستعلام وخطة التحسين مسبقًا؟كنت أعتقد أن الإجراء المخزن سيفعل ذلك، لكنه لا يساعد حقًا.

لتجنب الوصول إلى القرص، من الضروري تخزين الجدول بأكمله مؤقتًا في الذاكرة، هل يمكنني إجبار Postgres على القيام بذلك؟أعتقد أنه يفعل ذلك بالفعل، حيث يتم تنفيذ الاستعلام خلال 200 مللي ثانية فقط بعد التشغيل المتكرر.

هل يمكنني إخبار Postgres أن الجدول للقراءة فقط حتى يتمكن من تحسين أي رمز قفل؟

أعتقد أنه من الممكن تقدير تكاليف إنشاء الاستعلام باستخدام جدول فارغ (تتراوح التوقيتات من 20 إلى 60 مللي ثانية)

ما زلت لا أستطيع معرفة سبب عدم صلاحية اختبارات Java/Python.لا يقوم Postgres بهذا القدر من العمل (على الرغم من أنني لم أتناول بعد جانب التزامن، فقط إنشاء التخزين المؤقت والاستعلام)

تحديث:لا أعتقد أنه من العدل مقارنة التحديدات كما هو مقترح عن طريق سحب 350.000 من خلال برنامج التشغيل وخطوات التسلسل إلى Python لتشغيل التجميع، ولا حتى حذف التجميع حيث يصعب فصل النفقات العامة في التنسيق والعرض عن التوقيت.إذا كان كلا المحركين يعملان في بيانات الذاكرة، فيجب أن تكون هذه مقارنة بين تفاحتين، لست متأكدًا من كيفية ضمان حدوث ذلك بالفعل.

لا أستطيع معرفة كيفية إضافة التعليقات، ربما ليس لدي سمعة كافية؟

أنا نفسي رجل MS-SQL، وكنا نستخدم DBCC قابل للطباعة للاحتفاظ بجدول مخبأ، و تعيين الإحصائيات IO لنرى أنه يقرأ من ذاكرة التخزين المؤقت، وليس من القرص.

لا يمكنني العثور على أي شيء في Postgres لتقليد PINTABLE، ولكن pg_buffercache يبدو أنه يقدم تفاصيل حول ما هو موجود في ذاكرة التخزين المؤقت - قد ترغب في التحقق من ذلك ومعرفة ما إذا كان جدولك قد تم تخزينه مؤقتًا بالفعل.

إن العودة السريعة لحساب المغلف تجعلني أشك في أنك تقوم بالترحيل من القرص.بافتراض أن Postgres يستخدم أعدادًا صحيحة ذات 4 بايت، فإن لديك (6 * 4) بايت لكل صف، لذا فإن جدولك يبلغ الحد الأدنى (24 * 350,000) بايت ~ 8.4 ميجابايت.بافتراض أن الإنتاجية المستمرة تبلغ 40 ميجابايت/ثانية على محرك الأقراص الثابتة لديك، فإنك تنظر إلى حوالي 200 مللي ثانية لقراءة البيانات (والتي، كما أشار, ، يجب أن يكون المكان الذي يتم فيه قضاء معظم الوقت تقريبًا).

ما لم أفشل في حساباتي في مكان ما، لا أرى كيف من الممكن أن تكون قادرًا على قراءة 8 ميجا بايت في تطبيق Java الخاص بك ومعالجته في الأوقات التي تعرضها - ما لم يكن هذا الملف مخزنًا مؤقتًا بالفعل بواسطة محرك الأقراص أو جهاز الكمبيوتر الخاص بك نظام التشغيل.

لا أعتقد أن نتائجك مثيرة للدهشة - إذا كان هناك أي شيء فهو أن Postgres سريع جدًا.

هل يعمل استعلام Postgres بشكل أسرع مرة ثانية بمجرد أن تتاح له فرصة تخزين البيانات مؤقتًا؟لكي تكون أكثر عدالة، يجب أن يغطي اختبار Java وPython تكلفة الحصول على البيانات في المقام الأول (من الأفضل تحميلها من القرص).

إذا كان مستوى الأداء هذا يمثل مشكلة لتطبيقك في الممارسة العملية ولكنك تحتاج إلى نظام إدارة قواعد البيانات (RDBMS) لأسباب أخرى، فيمكنك الاطلاع عليه com.memcached.سيكون لديك بعد ذلك وصول أسرع إلى البيانات الأولية المخزنة مؤقتًا ويمكنك إجراء العمليات الحسابية في التعليمات البرمجية.

هل تستخدم TCP للوصول إلى Postgres؟في هذه الحالة، يعبث Nagle بتوقيتك.

أحد الأشياء الأخرى التي يقوم بها نظام RDBMS عمومًا هو توفير التزامن من خلال حمايتك من الوصول المتزامن من خلال عملية أخرى.ويتم ذلك عن طريق وضع الأقفال، وهناك بعض النفقات العامة من ذلك.

إذا كنت تتعامل مع بيانات ثابتة تمامًا لا تتغير أبدًا، وخاصة إذا كنت في سيناريو "مستخدم واحد" بشكل أساسي، فإن استخدام قاعدة بيانات علائقية لا يحقق لك بالضرورة فائدة كبيرة.

تحتاج إلى زيادة ذاكرة التخزين المؤقت لـ postgres إلى النقطة التي تتناسب فيها مجموعة العمل بأكملها مع الذاكرة قبل أن تتوقع رؤية أداء مشابه للقيام بذلك في الذاكرة باستخدام برنامج.

شكرًا على توقيت Oracle، هذا هو نوع الأشياء التي أبحث عنها (على الرغم من أنها مخيبة للآمال :-)

من المحتمل أن تكون العروض المادية جديرة بالاهتمام لأنني أعتقد أنه يمكنني إجراء حساب مسبق للنماذج الأكثر إثارة للاهتمام لهذا الاستعلام لمعظم المستخدمين.

لا أعتقد أن وقت الاستعلام ذهابًا وإيابًا يجب أن يكون مرتفعًا جدًا لأنني أقوم بتشغيل الاستعلامات على نفس الجهاز الذي يقوم بتشغيل Postgres، لذا لا يمكن إضافة الكثير من زمن الوصول؟

لقد قمت أيضًا ببعض التحقق من أحجام ذاكرة التخزين المؤقت، ويبدو أن Postgres يعتمد على نظام التشغيل للتعامل مع التخزين المؤقت، وقد ذكروا على وجه التحديد BSD باعتباره نظام التشغيل المثالي لهذا الغرض، لذلك أعتقد أن نظام التشغيل Mac OS يجب أن يكون ذكيًا جدًا في جلب الجدول إلى ذاكرة.ما لم يكن لدى شخص ما معلمات أكثر تحديدًا في الاعتبار، أعتقد أن التخزين المؤقت الأكثر تحديدًا هو خارج عن إرادتي.

في النهاية، ربما يمكنني تحمل أوقات استجابة تبلغ 200 مللي ثانية، ولكن معرفة أن 7 مللي ثانية هو هدف محتمل يجعلني أشعر بعدم الرضا، حيث إن 20 إلى 50 مللي ثانية ستمكن المزيد من المستخدمين من الحصول على المزيد من الاستعلامات الحديثة والتخلص من الكثير من التخزين المؤقت والاختراقات المحسوبة مسبقًا.

لقد تحققت للتو من التوقيت باستخدام MySQL 5 وهي أسوأ قليلاً من Postgres.لذا، باستثناء بعض الاختراقات الرئيسية في التخزين المؤقت، أعتقد أن هذا هو ما يمكنني توقعه من خلال اتباع مسار قواعد البيانات العلائقية.

أتمنى أن أتمكن من التصويت على بعض إجاباتك، ولكن ليس لدي ما يكفي من النقاط حتى الآن.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top