
Ketika bekerja dengan database (khususnya dengan PostgreSQL), saya punya ide untuk memilih data dari tabel secara paralel (menggunakan Go GoP). Dan saya bertanya-tanya, "apakah mungkin untuk memindai garis sampel di masing-masing goroutine?"
Ternyata,
pemindaian func (* Rows) tidak dapat dipanggil secara bersamaan di gourutins. Berdasarkan batasan ini, saya memutuskan untuk melakukan proses lain secara paralel dengan baris pemindaian, khususnya, persiapan data yang dihasilkan.
Karena Pindai tumpukan data sesuai dengan petunjuk, saya memutuskan untuk membuat dua irisan (saya akan menjelaskan mengapa dua nanti), di antaranya saya akan beralih Pindai, sedangkan gourutin lainnya akan menangani data yang sudah dipilih.
Awalnya, saya perlu tahu jumlah kolom sampel:
columns, err = rows.Columns() count := len(columns)
Selanjutnya, saya membuat dua irisan dengan nilai dan dengan pointer ke nilai-nilai ini (di mana saya akan menambahkan data selama pemindaian baris):
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] }
Dalam contoh ini, saya akan menambahkan hasil pemilihan untuk memetakan string [string], di mana nama kolom akan menjadi kunci. Anda dapat menggunakan struktur spesifik yang menunjukkan tipe, tetapi karena Tujuan dari publikasi ini adalah untuk mencari tahu dari habrasociety seberapa layak pendekatan yang diusulkan, mari kita memikirkan pemilihan dalam peta.
Selanjutnya, saya pisahkan dua gorutin, salah satunya akan membentuk peta yang dihasilkan:
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 }
Dan yang kedua akan beralih antara dua irisan dengan nilai yang dihasilkan oleh Pindai dan mengirimkannya ke saluran untuk gourutin sebelumnya (yang membentuk hasilnya):
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 } } } }
Proses utama akan beralih di antara irisan pointer dan memilih data:
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 }
Dalam database, saya membentuk tabel dengan 32 kolom dan menambahkan 100 ribu baris ke dalamnya.
Sebagai hasil dari tes (saat pengambilan sampel data 50 kali), saya mendapat data berikut:
Waktu yang dihabiskan: 1m8.022277124s - mencicipi hasilnya menggunakan sepotong tunggal
Waktu yang dihabiskan: 1m7.806109441s - mencicipi hasilnya menggunakan dua irisan
Dengan peningkatan jumlah iterasi menjadi 100:
Waktu yang dihabiskan: 2m15.973344023s - pemilihan hasil menggunakan sepotong tunggal
Waktu yang dihabiskan: 2m15.057413845s - mencicipi hasilnya menggunakan dua irisan
Perbedaannya meningkat dengan meningkatnya volume data dan meningkatnya kolom dalam tabel.
Namun, hasil yang berlawanan diamati dengan penurunan jumlah data atau dengan penurunan jumlah kolom tabel, yang, pada prinsipnya, dapat dimengerti, karena overhead dari langkah persiapan dan departemen gourutin “menghabiskan” waktu yang berharga dan hasilnya diratakan.
Adapun dua irisan dan dua gorutin: Saya melakukan tes dengan sejumlah besar irisan, tetapi waktu pengambilan sampel meningkat, karena, jelas, fungsi getData dan transferData memproses data lebih cepat daripada memindai nilai dari database. Oleh karena itu, bahkan dengan jumlah core yang lebih besar, tidak masuk akal untuk menambahkan irisan baru untuk Pindai dan goroutine tambahan (kecuali untuk volume data yang sangat liar).
Dalam kode github, saya memberikan
contoh kerja dari pendekatan ini. Tugas saya juga menggunakan paket lain, yang saya bersihkan dari waktu di atas, tetapi ide utama tidak harus menderita dari ini.
Secara umum, saya mengharapkan kritik konstruktif dari komunitas yang tertarik. Terima kasih