Skip to content

Commit d9ee537

Browse files
authored
Merge pull request #13 from go-gorm/feature/read-write-connections
2 parents cde328a + 1ac1ef1 commit d9ee537

File tree

7 files changed

+170
-27
lines changed

7 files changed

+170
-27
lines changed

.github/workflows/tests.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,21 @@ jobs:
3535
env:
3636
DIALECTOR: postgres
3737
DATABASE_URL: postgres://gorm:gorm@localhost:5432/sharding-test
38+
DATABASE_READ_URL: postgres://gorm:gorm@localhost:5432/sharding-read-test
39+
DATABASE_WRITE_URL: postgres://gorm:gorm@localhost:5432/sharding-write-test
3840
steps:
3941
- name: Set up Go
4042
uses: actions/setup-go@v1
4143
with:
4244
go-version: 1.17
4345
id: go
4446

47+
- name: Create Read Database
48+
run: PGPASSWORD=gorm psql -h localhost -U gorm -d sharding-test -c 'CREATE DATABASE "sharding-read-test";'
49+
50+
- name: Create Write Databases
51+
run: PGPASSWORD=gorm psql -h localhost -U gorm -d sharding-test -c 'CREATE DATABASE "sharding-write-test";'
52+
4553
- name: Check out code into the Go module directory
4654
uses: actions/checkout@v1
4755

@@ -67,7 +75,7 @@ jobs:
6775
MYSQL_DATABASE: sharding-test
6876
MYSQL_USER: gorm
6977
MYSQL_PASSWORD: gorm
70-
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
78+
MYSQL_ROOT_PASSWORD: gorm
7179
ports:
7280
- 3306:3306
7381
options: >-
@@ -80,13 +88,23 @@ jobs:
8088
env:
8189
DIALECTOR: mysql
8290
DATABASE_URL: gorm:gorm@tcp(127.0.0.1:3306)/sharding-test?charset=utf8mb4&parseTime=True&loc=Local
91+
DATABASE_READ_URL: root:gorm@tcp(127.0.0.1:3306)/sharding-read-test?charset=utf8mb4&parseTime=True&loc=Local
92+
DATABASE_WRITE_URL: root:gorm@tcp(127.0.0.1:3306)/sharding-write-test?charset=utf8mb4&parseTime=True&loc=Local
8393
steps:
8494
- name: Set up Go
8595
uses: actions/setup-go@v1
8696
with:
8797
go-version: 1.17
8898
id: go
8999

100+
- name: Create Read Database
101+
run: mysqladmin -h 127.0.0.1 -uroot -pgorm create sharding-read-test
102+
#run: mysql -e 'CREATE DATABASE sharding-read-test' -ugorm -pgorm
103+
104+
- name: Create Write Database
105+
run: mysqladmin -h 127.0.0.1 -uroot -pgorm create sharding-write-test
106+
#run: mysql -e 'CREATE DATABASE sharding-write-test' -ugorm -pgorm
107+
90108
- name: Check out code into the Go module directory
91109
uses: actions/checkout@v1
92110

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,31 @@ Recommend options:
9494
- [Snowflake](https://github.com/bwmarrin/snowflake)
9595
- [Database sequence by manully](https://www.postgresql.org/docs/current/sql-createsequence.html)
9696
97+
## Combining with dbresolver
98+
99+
> 🚨 NOTE: Use dbresolver first.
100+
101+
```go
102+
dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable"
103+
dsnRead := "host=localhost user=gorm password=gorm dbname=gorm-slave port=5432 sslmode=disable"
104+
105+
conn := postgres.Open(dsn)
106+
connRead := postgres.Open(dsnRead)
107+
108+
db, err := gorm.Open(conn, &gorm.Config{})
109+
dbRead, err := gorm.Open(conn, &gorm.Config{})
110+
111+
db.Use(dbresolver.Register(dbresolver.Config{
112+
Replicas: []gorm.Dialector{dbRead.Dialector},
113+
}))
114+
115+
db.Use(sharding.Register(sharding.Config{
116+
ShardingKey: "user_id",
117+
NumberOfShards: 64,
118+
PrimaryKeyGenerator: sharding.PKSnowflake,
119+
}))
120+
```
121+
97122
## Sharding process
98123
99124
This graph show up how Gorm Sharding works.

conn_pool.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@ type ConnPool struct {
1414
gorm.ConnPool
1515
}
1616

17-
// registerConnPool replace Gorm db.ConnPool as custom
18-
func (s *Sharding) registerConnPool(db *gorm.DB) {
19-
// Avoid assign loop
20-
basePool := db.ConnPool
21-
if _, ok := basePool.(ConnPool); ok {
22-
return
23-
}
24-
25-
s.ConnPool = &ConnPool{ConnPool: basePool, sharding: s}
26-
db.ConnPool = s.ConnPool
27-
db.Statement.ConnPool = s.ConnPool
28-
}
29-
3017
func (pool *ConnPool) String() string {
3118
return "gorm:sharding:conn_pool"
3219
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ require (
88
github.com/longbridgeapp/sqlparser v0.3.1
99
gorm.io/driver/mysql v1.3.2
1010
gorm.io/driver/postgres v1.3.1
11-
gorm.io/gorm v1.23.1
11+
gorm.io/gorm v1.23.2
1212
gorm.io/hints v1.1.0
13+
gorm.io/plugin/dbresolver v1.1.0
1314
)
1415

1516
require (

go.sum

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1313
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1414
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
1515
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
16+
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
1617
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
1718
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
1819
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -76,6 +77,7 @@ github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
7677
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
7778
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
7879
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
80+
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
7981
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
8082
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
8183
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -205,16 +207,22 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
205207
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
206208
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
207209
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
210+
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
208211
gorm.io/driver/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I=
209212
gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U=
210213
gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g=
211214
gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU=
212215
gorm.io/driver/sqlite v1.1.6 h1:p3U8WXkVFTOLPED4JjrZExfndjOtya3db8w9/vEMNyI=
213216
gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8=
217+
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
218+
gorm.io/gorm v1.20.11/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
214219
gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
215220
gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
216-
gorm.io/gorm v1.23.1 h1:aj5IlhDzEPsoIyOPtTRVI+SyaN1u6k613sbt4pwbxG0=
217221
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
222+
gorm.io/gorm v1.23.2 h1:xmq9QRMWL8HTJyhAUBXy8FqIIQCYESeKfJL4DoGKiWQ=
223+
gorm.io/gorm v1.23.2/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
218224
gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw=
219225
gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y=
226+
gorm.io/plugin/dbresolver v1.1.0 h1:cegr4DeprR6SkLIQlKhJLYxH8muFbJ4SmnojXvoeb00=
227+
gorm.io/plugin/dbresolver v1.1.0/go.mod h1:tpImigFAEejCALOttyhWqsy4vfa2Uh/vAUVnL5IRF7Y=
220228
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

sharding.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func (s *Sharding) LastQuery() string {
195195
// Initialize implement for Gorm plugin interface
196196
func (s *Sharding) Initialize(db *gorm.DB) error {
197197
s.DB = db
198-
s.registerConnPool(db)
198+
s.registerCallbacks(db)
199199

200200
for t, c := range s.configs {
201201
if c.PrimaryKeyGenerator == PKPGSequence {
@@ -218,6 +218,20 @@ func (s *Sharding) Initialize(db *gorm.DB) error {
218218
return s.compile()
219219
}
220220

221+
func (s *Sharding) registerCallbacks(db *gorm.DB) {
222+
s.Callback().Create().Before("*").Register("gorm:sharding", s.switchConn)
223+
s.Callback().Query().Before("*").Register("gorm:sharding", s.switchConn)
224+
s.Callback().Update().Before("*").Register("gorm:sharding", s.switchConn)
225+
s.Callback().Delete().Before("*").Register("gorm:sharding", s.switchConn)
226+
s.Callback().Row().Before("*").Register("gorm:sharding", s.switchConn)
227+
s.Callback().Raw().Before("*").Register("gorm:sharding", s.switchConn)
228+
}
229+
230+
func (s *Sharding) switchConn(db *gorm.DB) {
231+
s.ConnPool = &ConnPool{ConnPool: db.Statement.ConnPool, sharding: s}
232+
db.Statement.ConnPool = s.ConnPool
233+
}
234+
221235
// resolve split the old query to full table query and sharding table query
222236
func (s *Sharding) resolve(query string, args ...interface{}) (ftQuery, stQuery, tableName string, err error) {
223237
ftQuery = query
@@ -248,7 +262,6 @@ func (s *Sharding) resolve(query string, args ...interface{}) (ftQuery, stQuery,
248262
}
249263
table = tbl
250264
condition = stmt.Condition
251-
252265
case *sqlparser.InsertStatement:
253266
table = stmt.TableName
254267
isInsert = true

sharding_test.go

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"gorm.io/driver/postgres"
1414
"gorm.io/gorm"
1515
"gorm.io/hints"
16+
"gorm.io/plugin/dbresolver"
1617
)
1718

1819
type Order struct {
@@ -37,35 +38,80 @@ func databaseURL() string {
3738
return databaseURL
3839
}
3940

41+
func databaseReadURL() string {
42+
databaseURL := os.Getenv("DATABASE_READ_URL")
43+
if len(databaseURL) == 0 {
44+
databaseURL = "postgres://localhost:5432/sharding-read-test?sslmode=disable"
45+
if os.Getenv("DIALECTOR") == "mysql" {
46+
databaseURL = "root@tcp(127.0.0.1:3306)/sharding-read-test?charset=utf8mb4"
47+
}
48+
}
49+
return databaseURL
50+
}
51+
52+
func databaseWriteURL() string {
53+
databaseURL := os.Getenv("DATABASE_WRITE_URL")
54+
if len(databaseURL) == 0 {
55+
databaseURL = "postgres://localhost:5432/sharding-write-test?sslmode=disable"
56+
if os.Getenv("DIALECTOR") == "mysql" {
57+
databaseURL = "root@tcp(127.0.0.1:3306)/sharding-write-test?charset=utf8mb4"
58+
}
59+
}
60+
return databaseURL
61+
}
62+
4063
var (
4164
dbConfig = postgres.Config{
4265
DSN: databaseURL(),
4366
PreferSimpleProtocol: true,
4467
}
45-
db *gorm.DB
46-
47-
shardingConfig = Config{
48-
DoubleWrite: true,
49-
ShardingKey: "user_id",
50-
NumberOfShards: 4,
51-
PrimaryKeyGenerator: PKSnowflake,
68+
dbReadConfig = postgres.Config{
69+
DSN: databaseReadURL(),
70+
PreferSimpleProtocol: true,
5271
}
72+
dbWriteConfig = postgres.Config{
73+
DSN: databaseWriteURL(),
74+
PreferSimpleProtocol: true,
75+
}
76+
db, dbRead, dbWrite *gorm.DB
5377

54-
middleware = Register(shardingConfig, &Order{})
55-
node, _ = snowflake.NewNode(1)
78+
shardingConfig Config
79+
middleware *Sharding
80+
node, _ = snowflake.NewNode(1)
5681
)
5782

5883
func init() {
5984
if os.Getenv("DIALECTOR") == "mysql" {
6085
db, _ = gorm.Open(mysql.Open(databaseURL()), &gorm.Config{
6186
DisableForeignKeyConstraintWhenMigrating: true,
6287
})
88+
dbRead, _ = gorm.Open(mysql.Open(databaseReadURL()), &gorm.Config{
89+
DisableForeignKeyConstraintWhenMigrating: true,
90+
})
91+
dbWrite, _ = gorm.Open(mysql.Open(databaseWriteURL()), &gorm.Config{
92+
DisableForeignKeyConstraintWhenMigrating: true,
93+
})
6394
} else {
6495
db, _ = gorm.Open(postgres.New(dbConfig), &gorm.Config{
6596
DisableForeignKeyConstraintWhenMigrating: true,
6697
})
98+
dbRead, _ = gorm.Open(postgres.New(dbReadConfig), &gorm.Config{
99+
DisableForeignKeyConstraintWhenMigrating: true,
100+
})
101+
dbWrite, _ = gorm.Open(postgres.New(dbWriteConfig), &gorm.Config{
102+
DisableForeignKeyConstraintWhenMigrating: true,
103+
})
104+
}
105+
106+
shardingConfig = Config{
107+
DoubleWrite: true,
108+
ShardingKey: "user_id",
109+
NumberOfShards: 4,
110+
PrimaryKeyGenerator: PKSnowflake,
67111
}
68112

113+
middleware = Register(shardingConfig, &Order{})
114+
69115
fmt.Println("Clean only tables ...")
70116
dropTables()
71117
fmt.Println("AutoMigrate tables ...")
@@ -80,6 +126,16 @@ func init() {
80126
user_id bigint,
81127
product text
82128
)`)
129+
dbRead.Exec(`CREATE TABLE ` + table + ` (
130+
id bigint PRIMARY KEY,
131+
user_id bigint,
132+
product text
133+
)`)
134+
dbWrite.Exec(`CREATE TABLE ` + table + ` (
135+
id bigint PRIMARY KEY,
136+
user_id bigint,
137+
product text
138+
)`)
83139
}
84140

85141
db.Use(middleware)
@@ -89,6 +145,8 @@ func dropTables() {
89145
tables := []string{"orders", "orders_0", "orders_1", "orders_2", "orders_3", "categories"}
90146
for _, table := range tables {
91147
db.Exec("DROP TABLE IF EXISTS " + table)
148+
dbRead.Exec("DROP TABLE IF EXISTS " + table)
149+
dbWrite.Exec("DROP TABLE IF EXISTS " + table)
92150
db.Exec(("DROP SEQUENCE IF EXISTS gorm_sharding_" + table + "_id_seq"))
93151
}
94152
}
@@ -264,6 +322,39 @@ func TestPKPGSequence(t *testing.T) {
264322
assert.Equal(t, expected, middleware.LastQuery())
265323
}
266324

325+
func TestReadWriteSplitting(t *testing.T) {
326+
dbRead.Exec("INSERT INTO orders_0 (id, product, user_id) VALUES(1, 'iPad', 100)")
327+
dbWrite.Exec("INSERT INTO orders_0 (id, product, user_id) VALUES(1, 'iPad', 100)")
328+
329+
var db *gorm.DB
330+
if os.Getenv("DIALECTOR") == "mysql" {
331+
db, _ = gorm.Open(mysql.Open(databaseURL()), &gorm.Config{
332+
DisableForeignKeyConstraintWhenMigrating: true,
333+
})
334+
} else {
335+
db, _ = gorm.Open(postgres.New(dbConfig), &gorm.Config{
336+
DisableForeignKeyConstraintWhenMigrating: true,
337+
})
338+
}
339+
340+
db.Use(dbresolver.Register(dbresolver.Config{
341+
Sources: []gorm.Dialector{dbWrite.Dialector},
342+
Replicas: []gorm.Dialector{dbRead.Dialector},
343+
}))
344+
db.Use(middleware)
345+
346+
var order Order
347+
db.Model(&Order{}).Where("user_id", 100).Find(&order)
348+
assert.Equal(t, "iPad", order.Product)
349+
350+
db.Model(&Order{}).Where("user_id", 100).Update("product", "iPhone")
351+
db.Table("orders_0").Where("user_id", 100).Find(&order)
352+
assert.Equal(t, "iPad", order.Product)
353+
354+
dbWrite.Table("orders_0").Where("user_id", 100).Find(&order)
355+
assert.Equal(t, "iPhone", order.Product)
356+
}
357+
267358
func assertQueryResult(t *testing.T, expected string, tx *gorm.DB) {
268359
t.Helper()
269360
assert.Equal(t, toDialect(expected), middleware.LastQuery())

0 commit comments

Comments
 (0)