Worker pool to handle queries - mysql

I'm pretty new to Go and looking for a way to handle 3000 queries using 100 workers and ensuring a connection for every worker (MySQL is already configured with more than 100 connections). This is my attempt:
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
var query *sql.Stmt
func worker(jobs <-chan int, results chan<- int) {
for _ = range jobs {
_, e := query.Exec("a")
if e != nil {
panic(e.Error())
}
results <- 1
}
}
func main() {
workers := 100
db, e := sql.Open("mysql", "foo:foo#/foo")
if e != nil {
panic(e.Error())
}
db.SetMaxOpenConns(workers)
db.SetMaxIdleConns(workers)
defer db.Close()
query, e = db.Prepare("INSERT INTO foo (foo) values(?)")
if e != nil {
panic(e.Error())
}
total := 30000
jobs := make(chan int, total)
results := make(chan int, total)
for w := 0; w < workers; w++ {
go worker(jobs, results)
}
for j := 0; j < total; j++ {
jobs <- j
}
close(jobs)
for r := 0; r < total; r++ {
<-results
}
}
It's working, but I'm not sure if is the best way of doing it.
Please, if you think this is opinion based or is not a good question at all, just mark it to be closed and leave a comment explaining why.

What you've got fundamentally works, but to get rid of buffering, you need to be writing to jobs and reading from results at the same time. Otherwise, your process ends up stuck--workers can't send results because nothing is receiving them, and you can't insert jobs because workers are blocked.
Here's a boiled-down example on the Playground of how to do a work queue that pushes jobs in the background as it receives results in main:
package main
import "fmt"
func worker(jobs <-chan int, results chan<- int) {
for _ = range jobs {
// ...do work here...
results <- 1
}
}
func main() {
workers := 10
total := 30
jobs := make(chan int)
results := make(chan int)
// start workers
for w := 0; w < workers; w++ {
go worker(jobs, results)
}
// insert jobs in background
go func() {
for j := 0; j < total; j++ {
jobs <- j
}
}()
// collect results
for i := 0; i < total; i++ {
<-results
fmt.Printf(".")
}
close(jobs)
}
For that particular code to work, you have to know how many results you'll get. If you don't know that (say, each job could produce zero or multiple results), you can use a sync.WaitGroup to wait for the workers to finish, then close the result stream:
package main
import (
"fmt"
"sync"
)
func worker(jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
for _ = range jobs {
// ...do work here...
results <- 1
}
wg.Done()
}
func main() {
workers := 10
total := 30
jobs := make(chan int)
results := make(chan int)
wg := &sync.WaitGroup{}
// start workers
for w := 0; w < workers; w++ {
wg.Add(1)
go worker(jobs, results, wg)
}
// insert jobs in background
go func() {
for j := 0; j < total; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
// all workers are done so no more results
close(results)
}()
// collect results
for _ = range results {
fmt.Printf(".")
}
}
There are many other more complicated tricks one can do to stop all workers after an error happens, put results into the same order as the original jobs, or do other things like that. Sounds as if the basic version works here, though.

Related

Running aggregate join queries on mysql from golang returns no values

I have the following query which returns results when run directly from mysql.
The same query returns 0 values, when run from golang program.
package main
import (
"github.com/rs/zerolog/log"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
var DB *sqlx.DB
func main() {
DB, err := sqlx.Connect("mysql", "root:password#(localhost:3306)/jsl2")
if err != nil {
log.Error().Err(err)
}
sqlstring := `SELECT
salesdetails.taxper, sum(salesdetails.gvalue),
sum(salesdetails.taxamt)
FROM salesdetails
Inner Join sales ON sales.saleskey = salesdetails.saleskey
where
sales.bdate >= '2021-12-01'
and sales.bdate <= '2021-12-31'
and sales.achead IN (401975)
group by salesdetails.taxper
order by salesdetails.taxper`
rows, err := DB.Query(sqlstring)
for rows.Next() {
var taxper int
var taxableValue float64
var taxAmount float64
err = rows.Scan(&taxper, &taxableValue, &taxAmount)
log.Print(taxper, taxableValue, taxAmount)
}
err = rows.Err()
if err != nil {
log.Error().Err(err)
}
}
On the console, running the program returns the following values.
In SQL browser, it returns 4 rows which is correct.
The result from the sql browser for the same query is
0 1278.00 0.00
5 89875.65 4493.78
12 3680.00 441.60
18 94868.73 17076.37
But in the program also return 4 rows with 0 value.
{"level":"debug","time":"2022-01-13T17:07:39+05:30","message":"0 0 0"}
{"level":"debug","time":"2022-01-13T17:07:39+05:30","message":"0 0 0"}
{"level":"debug","time":"2022-01-13T17:07:39+05:30","message":"0 0 0"}
{"level":"debug","time":"2022-01-13T17:07:39+05:30","message":"0 0 0"}
How to set the datatype for the aggregate functions.
I changed the datatype of taxper to float and it worked.
I found this after checking the err from rows.Scan( as suggested by #mkopriva

sql.Query truncated or incomplete results

I've the following code:
const qInstances = `
SELECT
i.uuid,
i.host,
i.hostname
FROM
db.instances AS i
WHERE
i.deleted_at IS NULL
GROUP BY i.uuid;
`
...
instancesMap := make(map[string]*models.InstanceModel)
instances := []models.Instance{}
instancesCount := 0
instancesRow, err := db.Query(qInstances)
if err != nil {
panic(err.Error())
}
defer instancesRow.Close()
for instancesRow.Next() {
i := models.Instance{}
err = instancesRow.Scan(&i.UUID, &i.Host, &i.Hostname)
if err != nil {
log.Printf("[Error] - While Scanning Instances Rows, error msg: %s\n", err)
panic(err.Error())
} else {
if i.UUID.String != "" {
instancesCount++
}
if _, ok := instancesMap[i.UUID.String]; !ok {
instancesMap[i.UUID.String] = &models.InstanceModel{}
inst := instancesMap[i.UUID.String]
inst.UUID = i.UUID.String
inst.Host = i.Host.String
inst.Hostname = i.Hostname.String
} else {
inst := instancesMap[i.UUID.String]
inst.UUID = i.UUID.String
inst.Host = i.Host.String
inst.Hostname = i.Hostname.String
}
instances = append(instances, i)
}
}
log.Printf("[Instances] - Total Count: %d\n", instancesCount)
The problem that I'm facing is that if run the SQL query directly to the database (mariadb) it returns 7150 records, but the total count inside the program outputs 5196 records. I also check the SetConnMaxLifetime parameter for the db connection and set it to 240 seconds, and it doesn't show any errors or broken connectivity between the db and the program. Also I try to do some pagination (LIMIT to 5000 records each) and issue two different queries and the first one returns the 5000 records, but the second one just 196 records. I'm using the "github.com/go-sql-driver/mysql" package. Any ideas?

Mysql missing data inserted with goroutines

I'm updating records from another resource into the database.
// Turn into batches with batchSize == 10
batches := service.TurnIntoBatches(cModels, 10)
// Sync to our db
var wg sync.WaitGroup
customerChannel := make(chan []*model.Customer, len(batches))
for _, batch := range batches {
wg.Add(1)
go UpdateCustomers(batch, &wg, uc.customerRp, customerChannel)
}
wg.Wait()
close(customerChannel)
func UpdateCustomers(customers []*model.Customer,
wg *sync.WaitGroup,
rp repository.CustomerRepositoryInterface,
channel chan []*model.Customer) {
defer wg.Done()
for _, c := range customers {
//err := Update logic
if err != nil {
logger.WithFields(logrus.Fields{
"error": err,
}).WithError(err).Error("Updating failed!")
}
}
}
If I got 50 records, that's 5 batches of 10.
There are 50 insert query, as expected. All of them returned [1 rows affected or returned ]
However, if I query from the database, there are sometimes 45 or 46 records.
What is the problem and how to fix it?

How to use results from a sql query as a variable

I'm trying to use a result from a query as an integer, so i can use it in some different calculations.
I'm fairly new at Go and programming in general(really new, just started school a few weeks back). For an assignment for school I need to calculate the 'doorlooptijd'(nr of months a customer has to pay) based on the customers age.
When i run below code i keep getting the error: 'cannot use leeftijdAlsText (type *sql.Rows) as type string in argument to strconv.Atoi'
leeftijd := "SELECT TIMESTAMPDIFF(YEAR, k.geboortedatum, NOW()) AS leeftijd FROM klant k WHERE k.klantnummer = ?"
leeftijdAlsText, err := db.Query(leeftijd, nummerKlant)
if err != nil {
fmt.Println("Error found.")
panic(err)
}
var huidigeLeeftijd int
if leeftijdAlsText.Next() {
err := leeftijdAlsText.Scan(&leeftijdAlsText)
if err != nil {
fmt.Println("Error found")
panic(err)
}
}
huidigeLeeftijd, _ = strconv.Atoi(leeftijdAlsText)
var doorlooptijd int
if huidigeLeeftijd < 45 {
doorlooptijd = 120
} else if huidigeLeeftijd > 45 && huidigeLeeftijd < 55 {
doorlooptijd = 90
} else if huidigeLeeftijd > 55 {
doorlooptijd = 60
}
When this works, i need to insert the doorlooptijd in a new row in my database, together with some other information about the customer.

Combining data from multiple cells into one JSON object

I am trying to combine data from multiple cells from an excel spreadsheet into one JSON encoded string. I cannot figure out how to do so, the code below is creating a new JSON object per cell. How do I differentiate the cells to combine into the same JSON string?
package main
import (
"fmt"
"github.com/tealeg/xlsx"
"encoding/json"
)
func main() {
excelFileName := "/Users/isaacmelton/Desktop/Test_Data.xlsx"
xlFile, err := xlsx.OpenFile(excelFileName)
if err != nil {
fmt.Printf("Cannot parse data")
}
for _, sheet := range xlFile.Sheets {
for _, row := range sheet.Rows {
fmt.Printf("\n")
for x, cell := range row.Cells {
if x == 3 || x == 5 {
data := map[string]string{"d_name": cell.String(), "name": cell.String()}
json_data, _ := json.Marshal(data)
fmt.Println(string(json_data))
}
}
}
}
}
Running the above code results in the following:
{"foo":"cell1","bar":"cell1"}
{"foo":"cell2","bar":"cell2"}
I expect something like this:
{"foo":"cell1", "bar":"cell2"}
If I right understand your request you just need to define root element, add cells into it and marshal this element rather than individual cells.
root := []map[string]string{}
for x, cell := range row.Cells {
if x == 3 || x == 5 {
root = append(root, map[string]string{"d_name": cell.String(), "name": cell.String()})
}
}
json_data, _ := json.Marshal(root)
fmt.Println(string(json_data))
http://play.golang.org/p/SHnShHvW_0
You may use
a, err := row.Cells[3].String()
b, err := row.Cells[5].String()
Like this working code:
package main
import (
"encoding/json"
"fmt"
"github.com/tealeg/xlsx"
)
func main() {
xlFile, err := xlsx.OpenFile(`Test_Data.xlsx`)
if err != nil {
panic(err)
}
for _, sheet := range xlFile.Sheets {
for _, row := range sheet.Rows {
//for x, cell := range row.Cells {
//if x == 3 || x == 5 {
a, err := row.Cells[3].String()
if err != nil {
panic(err)
}
b, err := row.Cells[5].String()
if err != nil {
panic(err)
}
data := map[string]string{"d_name": a, "name": b}
json_data, err := json.Marshal(data)
if err != nil {
panic(err)
}
fmt.Println(string(json_data))
//}
//}
}
}
}
output:
{"d_name":"1000","name":"a"}
{"d_name":"2000","name":"b"}
{"d_name":"3000","name":"c"}
{"d_name":"4000","name":"d"}
{"d_name":"5000","name":"e"}
input file content:
1 10 100 1000 10000 a
2 20 200 2000 20000 b
3 30 300 3000 30000 c
4 40 400 4000 40000 d
5 50 500 5000 50000 e