rocketlaunchr / dbq

Zero boilerplate database operations for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Congratulations and benchmarks project

frederikhors opened this issue · comments

My happiest congratulations on this project! Very good!

Can I ask you for an opinion on this benchmark project?

What do you think about adding dbq too?

It's very easy, just copy one of these files and edit it with dbq code.

I found dbq today and I still need to understand it.

Again, thanks for your amazing project!

commented

Thanks for the compliment.

I won't have time but here is a sample of how to benchmark dbq

https://medium.com/@rocketlaunchr.cloud/how-to-benchmark-dbq-vs-sqlx-vs-gorm-e814caacecb5

You should be able to add it easily. Dbq is really just a an extension to database/SQL - not an orm (yet, but v3 will be)

Let me tag the tip first because I believe there is some changes in master that have not been tagged

Can you please help me with this?

I'm trying to add dbq to this benchmark project: https://github.com/frederikhors/orm-benchmark/tree/add-dbq.

But I'm getting this error:

dbq
pq: syntax error at or near ","

Why?

Here the relevant code: https://github.com/frederikhors/orm-benchmark/blob/add-dbq/benchs/dbq.go

Thanks.

commented

https://godoc.org/github.com/rocketlaunchr/dbq/v2#INSERTStmt

add dbq.PostgreSQL as final argument to INSERTStmt. The default is mysql.

commented

Model will also need to implement https://godoc.org/github.com/rocketlaunchr/dbq/v2#ScanFaster for maximum performance (when querying/unmarshaling)

Thanks. Updated.

Now the error is pq got 8 parameters but the statement requires 7.

I think I don't have to add "id" in columns, right?

Relevant code: https://github.com/frederikhors/orm-benchmark/blob/add-dbq/benchs/dbq.go.

commented
type Model struct {
	Id      int `orm:"auto" gorm:"primary_key" db:"id" dbq:"id"`
	Name    string   `dbq:"name"`
	Title   string  `dbq:"title"`
	Fax     string   `dbq:"fax"`
	Web     string    `dbq:"web"`
	Age     int    `dbq:"age"`
	Right   bool   `dbq:"\"right\""`
	Counter int64   `dbq:"counter"`
}

stmt := dbq.INSERTStmt("models", []string{"id", "name", "title", "fax", "web", "age", "\"right\"", "counter"}, 1, dbq.PostgreSQL)

Done. Now the error is:

pq: duplicate key value violates unique constraint "models_pkey".

commented

That means that you have a unique key in the table + you are inserting the same value twice.

Are you deleting all rows for each package you are benchmarking?

This is not a dbq issue.

I'm using this code for each orm:

for i := 0; i < b.N; i++ {
    m.Id = 0

    stmt := dbq.INSERTStmt("models", []string{"id", "name", "title", "fax", "web", "age", "\"right\"", "counter"}, 1, dbq.PostgreSQL)

    _, err := dbq.E(context.Background(), dbqdb, stmt, nil, dbq.Struct(m))

    if err != nil {
        fmt.Println(err)
        b.FailNow()
    }
}

Each orm works creating a new row with progressive ID.

Dbq gives that error.

Am I wrong?

commented

ahhh i see.

type Model struct {
	Id      int `orm:"auto" gorm:"primary_key" db:"id" dbq:"-"` // replace with dash
	Name    string   `dbq:"name"`
	Title   string  `dbq:"title"`
	Fax     string   `dbq:"fax"`
	Web     string    `dbq:"web"`
	Age     int    `dbq:"age"`
	Right   bool   `dbq:"\"right\""`
	Counter int64   `dbq:"counter"`
}

stmt := dbq.INSERTStmt("models", []string{"name", "title", "fax", "web", "age", "\"right\"", "counter"}, 1, dbq.PostgreSQL)

If you are going to query into the model (which I suspect you will), then:

Just do this instead when inserting:

stmt := dbq.INSERTStmt("models", []string{"name", "title", "fax", "web", "age", "\"right\"", "counter"}, 1, dbq.PostgreSQL)
_, err := dbq.E(context.Background(), dbqdb, stmt, nil,   dbq.Struct(m)[1:] )
commented

The second way is probably what you actually want.

Now it works, but to my surprise it is much slower than go-pg and gorm.

Where am I doing wrong?

200 times - Insert
    gorm:     0.85s      4249336 ns/op    7006 B/op     90 allocs/op
    pg:     0.85s      4254043 ns/op    2077 B/op     11 allocs/op
    dbq:     1.20s      5991529 ns/op    1857 B/op     60 allocs/op

You can try it with:

git clone https://github.com/frederikhors/orm-benchmark.git
cd orm-benchmark
git checkout add-dbq
docker-compose up -d
go run . -orm=pg -orm=dbq -orm=gorm
commented

That's surprising to me too. I expect it to be more or less the same as gorm. I'm not familiar with pg.

temporarily, can you try directly inserting the stmt from dbq.INSERTStmt instead of using that function.
And also directly inserting the args instead of dbq.Struct() to see if the issue is with dbq.E

It's interesting that mine uses significantly less memory that gorm and slightly less than pg.

temporarily, can you try directly inserting the stmt from dbq.INSERTStmt instead of using that function.
And also directly inserting the args instead of dbq.Struct() to see if the issue is with dbq.E

I do not understand what to do.

commented

well dbq.E is just a simple wrapper over database/sql Exec function. That's the limiting case. No package can get faster than directly using Exec function. dbq.E() is only a very light wrapper over it.

  1. dbq.INSERTStmt will return a string. Instead of using dbq.INSERTStmt, just use the string directly.
  2. replace dbq.Struct() with : "Orm Benchmark", "Just a Benchmark for fun", "99909990", "http://blog.milkpod29.me", 100, true, 1000

If that is still slower, then there is something very seriously wrong. Something mysterious.

Done:

stmt := "INSERT INTO models ( name,title,fax,web,age,\"right\",counter ) VALUES ($1,$2,$3,$4,$5,$6,$7)"
_, err := dbq.E(context.Background(), dbqdb, stmt, nil, "Orm Benchmark", "Just a Benchmark for fun", "99909990", "http://blog.milkpod29.me", 100, true, 1000)
200 times - Insert
    pg:     0.83s      4126598 ns/op    2735 B/op     11 allocs/op
    raw:     1.00s      5005248 ns/op    1335 B/op     20 allocs/op
    dbq:     1.01s      5074871 ns/op     920 B/op     16 allocs/op

Very strange.

I used raw too: https://github.com/frederikhors/orm-benchmark/blob/add-dbq/benchs/raw.go.

commented

For raw and dbq you are using: https://github.com/lib/pq (which is not as performant): https://github.com/frederikhors/orm-benchmark/blob/add-dbq/main.go#L13
gorm2 is using this driver: https://github.com/jackc/pgx (superior performance)
go-pg is using their own custom driver which seems to be superior to github.com/lib/pq.

My suggestion is for dbq and raw, switch to https://github.com/jackc/pgx/tree/master/stdlib (it is a database/sql adapter for github.com/jackc/pgx: https://github.com/jackc/pgx#compatibility-with-databasesql

sql.Open("pgx", "user=postgres password=secret host=localhost port=5432 database=pgx_test sslmode=disable")

See: https://github.com/jackc/pgx#comparison-with-alternatives

That will make your benchmark fairer.

Here we are with pgx, wow!

2000 times - Insert
    raw:     8.17s      4084892 ns/op     750 B/op     13 allocs/op
    dbq:     8.23s      4117260 ns/op    1711 B/op     54 allocs/op
    pg:      8.44s      4218307 ns/op    1015 B/op     10 allocs/op
    gorm:    8.47s      4233492 ns/op    6732 B/op     90 allocs/op

Now I have to complete the other tests.

commented
stmt := dbq.INSERTStmt("models", []string{"name", "title", "fax", "web", "age", "\"right\"", "counter"}, 1, dbq.PostgreSQL)

I will make a new helper function to make extracting []string{"name", "title", "fax", "web", "age", "\"right\"", "counter"} this from the model directly.

so you can then do: dbq.StructFields(m) => return []string{...}

This dbq.Struct(m)[1:] is the other strange code we need to have. Do we need?

commented

That is only for inserting. It is just to remove the id field (which is first value). When you are inserting, you aren't including id because its set to auto_increment/serial.

I'll add a new optional param to it so you can chose to exclude a value:

like this dbq.Struct(m, "id")

I'm trying multinsert now with this code:

func DbqInsertMulti(b *B) {
	var ms []*Model
	wrapExecute(b, func() {
		initDB()
		ms = make([]*Model, 0, 100)
		for i := 0; i < 100; i++ {
			ms = append(ms, NewModel())
		}
	})

	for i := 0; i < b.N; i++ {
		//for _, m := range ms {
		//	m.Id = 0
		//}

		stmt := dbq.INSERTStmt("models", []string{"name", "title", "fax", "web", "age", "\"right\"", "counter"}, len(ms), dbq.PostgreSQL)
		_, err := dbq.E(context.Background(), dbqdb, stmt, nil, ms)

		if err != nil {
			fmt.Println(err)
			b.FailNow()
		}
	}
}

but the error is: expected 700 arguments, got 100.

Why?

commented

Interesting. I'll add functionality to make your code work as is. it's a simple change.

In the mean time:

var ms []interface{}
ms = append(ms, dbq.Struct(NewModel())[1:]...)

Depending on what you are trying to benchmark, you can move stmt = ... outside of for loop

I think I have no more time for this test.

I had set the deadline for October 29. I realized that the differences between the ORMs are minimal and that dbq has a great potential which however does not affect the comfort / speed / ram / allocations ratio of go-pg.

Will you agree with me?

Do you want to continue with this test?

commented

dbq isn't really a ORM. It just a small layer over database/sql which I believe is all you need (provided you know what you are doing). I'm no longer a fan of ORM. I was when I used PHP/Laravel. Then I realised that the value of ORM's are actually not that great because they don't provide an enormous amount of benefits in terms of convenience.

I don't have time either for tests. It's not really important either. The reality is that Go is so fast that it doesn't matter what you use for standard web applications.

I hope you learnt about the different drivers for your exercise. It can make a difference.

commented

Thanks for showing me some speed bumps of dbq in terms of user-friendliness. When I get some time, I'll add some helper functions in to make life slightly easier for developers.

I agree.

But I have only one thing to clarify: I would gladly go without an ORM if I could find a significantly faster alternative.

In this case go-pg is both fast and comfortable even for much more complex queries!

commented

I regularly deal with super complex queries that ORM's can't handle. Some ORMS provide an "escape hatch" for executing complex queries, which then makes it just easier to not use a ORM.

For basic insert and select with a model, then ORM's provide some convenience only in terms of not making errors in writing the query. I find that for basic inserts and selects, you can write the query manually very easily. You can see that philosophy in dbq.

Keep working on it because maybe I'll make the next project with dbq! 🤣