فرق تسد


عند العمل مع قاعدة بيانات (خاصة مع PostgreSQL) ، كان لدي فكرة لتحديد البيانات من جدول بالتوازي (باستخدام Go GoP). وتساءلت ، "هل من الممكن مسح خطوط عينة في goroutines الفردية؟"

كما اتضح فيما بعد ، لا يمكن استدعاء func (* Rows) Scan في وقت واحد في gourutins. بناءً على هذا القيد ، قررت تنفيذ عمليات أخرى بالتوازي مع مسح الصفوف ، وعلى وجه الخصوص ، إعداد البيانات الناتجة.

لأن مسح كدمات البيانات وفقًا للمؤشرات ، قررت إنشاء شريحتين (سأشرح سبب شريحتين لاحقًا) ، حيث سأقوم بالتبديل بينهما ، بينما سيتعامل باقي gourutins مع البيانات المحددة بالفعل.

في البداية ، أحتاج إلى معرفة عدد أعمدة النماذج:

columns, err = rows.Columns() count := len(columns) 

بعد ذلك ، أقوم بإنشاء شريحتين لهما قيم مع مؤشرات لهذه القيم (حيث سأضيف بيانات أثناء المسح الضوئي للصف):

 values := make([]interface{}, count) valuesPtrs := make([]interface{}, count) values_ := make([]interface{}, count) valuesPtrs_ := make([]interface{}, count) for i := range columns { valuesPtrs[i] = &values;[i] valuesPtrs_[i] = &values;_[i] } 

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

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

 func getData(deleteNullValues bool, check, finish chan bool, dbData chan interface{}, columns []string, data *[]map[string]string) { lnc := len(columns) for <-check { row := make(map[string]string) for i := 0; i < lnc; i++ { el := <-dbData b, ok := el.([]byte) if ok { row[columns[i]] = string(b) } else { if el == nil { if deleteNullValues == false { row[columns[i]] = "" } } else { row[columns[i]] = fmt.Sprint(el) } } } *data = append(*data, row) } finish <- true } 

والثاني سيتم التبديل بين شريحتين باستخدام القيم التي تم إنشاؤها بواسطة Scan وإرسالها إلى القناة لـ gourutin السابق (الذي يشكل النتيجة):

 func transferData(values, values_ []interface{}, dbData chan interface{}, swtch, working, check chan bool) { for <-working { check <- true switch <-swtch { case false: for _, v := range values { dbData <- v } default: for _, v := range values_ { dbData <- v } } } } 

ستنتقل العملية الرئيسية بين شرائح المؤشرات وحدد البيانات:

 for rows.Next() { switch chnl { case false: if err = rows.Scan(valuesPtrs...); err != nil { fmt.Printf("rows.Scan: %s\n%s\n%#v\n", err, query, args) return nil, nil, err } default: if err = rows.Scan(valuesPtrs_...); err != nil { fmt.Printf("rows.Scan: %s\n%s\n%#v\n", err, query, args) return nil, nil, err } } working <- true swtch <- chnl chnl = !chnl } 

في قاعدة البيانات ، قمت بتكوين جدول به 32 عمودًا وأضفت عليه 100 صف.
نتيجة للاختبار (عند أخذ عينات البيانات 50 مرة) ، حصلت على البيانات التالية:
الوقت المستغرق: 1m8.022277124s - أخذ عينات من النتيجة باستخدام شريحة واحدة
الوقت المستغرق: 1m7.806109441s - أخذ عينات من النتيجة باستخدام شريحتين

مع زيادة عدد التكرارات إلى 100:
الوقت المستغرق: 2m15.973344023s - اختيار النتيجة باستخدام شريحة واحدة
الوقت المستغرق: 2m15.057413845s - أخذ عينات من النتيجة باستخدام شريحتين

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

بالنسبة لشريحتين وجورتين: لقد أجريت اختبارات مع عدد كبير من الشرائح ، ولكن زاد وقت أخذ العينات ، لأنه من الواضح أن وظيفتي getData و transferData تعالجان البيانات بشكل أسرع من مسح القيم من قاعدة البيانات. لذلك ، حتى مع وجود عدد أكبر من النوى ، لا معنى لإضافة شرائح جديدة لـ Scan و goroutines إضافية (باستثناء وحدات تخزين البيانات شديدة البرودة).

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

بشكل عام ، أتوقع انتقادات بناءة من المجتمع المهتم. شكرا لك

Source: https://habr.com/ru/post/ar485056/


All Articles