Skip to content

Commit e15a286

Browse files
authored
Merge pull request #5 from go-gorm/feature/pk
2 parents 81e8137 + 6736eca commit e15a286

File tree

6 files changed

+191
-109
lines changed

6 files changed

+191
-109
lines changed

README.md

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Gorm Sharding 是一个高性能的数据库分表中间件。
1313
- Non-intrusive design. Load the plugin, specify the config, and all done.
1414
- Lighting-fast. No network based middlewares, as fast as Go.
1515
- Multiple database support. PostgreSQL tested, MySQL and SQLite is coming.
16-
- Allows you custom the Primary Key generator ([Longkey](https://github.com/longbridgeapp/longkey), Sequence, Snowflake ...).
16+
- Integrated primary key generator (Snowflake, PostgreSQL Sequence, Custom, ...).
1717

1818
## Sharding process
1919

@@ -40,28 +40,13 @@ Config the sharding middleware, register the tables which you want to shard. See
4040

4141
```go
4242
db.Use(sharding.Register(sharding.Config{
43-
ShardingKey: "user_id",
44-
ShardingAlgorithm: func(value interface{}) (suffix string, err error) {
45-
if user_id, ok := value.(int64); ok {
46-
return fmt.Sprintf("_%02d", user_id%64), nil
47-
}
48-
return "", errors.New("invalid user_id")
49-
},
50-
PrimaryKeyGenerate: func(tableIdx int64) int64 {
51-
// use LongKey for generate a sequence primary key with table index
52-
return longkey.Next(tableIdx)
53-
}
43+
ShardingKey: "user_id",
44+
NumberOfShards: 64,
45+
PrimaryKeyGenerator: sharding.PKSnowflake,
5446
}, "orders").Register(sharding.Config{
55-
ShardingKey: "user_id",
56-
ShardingAlgorithm: func(value interface{}) (suffix string, err error) {
57-
if user_id, ok := value.(int64); ok {
58-
return fmt.Sprintf("_%02d", user_id%256), nil
59-
}
60-
return "", errors.New("invalid user_id")
61-
},
62-
PrimaryKeyGenerate: func(tableIdx int64) int64 {
63-
return snowflake_node.Generate().Int64()
64-
}
47+
ShardingKey: "user_id",
48+
NumberOfShards: 256,
49+
PrimaryKeyGenerator: sharding.PKSnowflake,
6550
// This case for show up give notifications, audit_logs table use same sharding rule.
6651
}, Notification{}, AuditLog{}))
6752
```
@@ -107,9 +92,8 @@ When you sharding tables, you need consider how the primary key generate.
10792

10893
Recommend options:
10994

110-
- [LongKey](https://github.com/longbridgeapp/longkey)
111-
- [Database sequence by manully](https://www.postgresql.org/docs/current/sql-createsequence.html)
11295
- [Snowflake](https://github.com/bwmarrin/snowflake)
96+
- [Database sequence by manully](https://www.postgresql.org/docs/current/sql-createsequence.html)
11397

11498
## License
11599

examples/order.go

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
package main
22

33
import (
4-
"errors"
54
"fmt"
65

7-
"github.com/bwmarrin/snowflake"
86
"gorm.io/driver/postgres"
97
"gorm.io/gorm"
108
"gorm.io/sharding"
@@ -33,22 +31,10 @@ func main() {
3331
)`)
3432
}
3533

36-
node, err := snowflake.NewNode(1)
37-
if err != nil {
38-
panic(err)
39-
}
40-
4134
middleware := sharding.Register(sharding.Config{
42-
ShardingKey: "user_id",
43-
ShardingAlgorithm: func(value interface{}) (suffix string, err error) {
44-
if uid, ok := value.(int64); ok {
45-
return fmt.Sprintf("_%02d", uid%64), nil
46-
}
47-
return "", errors.New("invalid user_id")
48-
},
49-
PrimaryKeyGenerate: func(tableIdx int64) int64 {
50-
return node.Generate().Int64()
51-
},
35+
ShardingKey: "user_id",
36+
NumberOfShards: 64,
37+
PrimaryKeyGenerator: sharding.PKSnowflake,
5238
}, "orders")
5339
db.Use(middleware)
5440

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.17
55
require (
66
github.com/bwmarrin/snowflake v0.3.0
77
github.com/longbridgeapp/assert v0.1.0
8-
github.com/longbridgeapp/longkey v0.1.0
98
github.com/longbridgeapp/sqlparser v0.2.0
109
gorm.io/driver/postgres v1.1.0
1110
gorm.io/gorm v1.21.16

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
221221
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
222222
github.com/longbridgeapp/assert v0.1.0 h1:KkQlHUJSpuUFkUDjwBJgghFl31+wwSDHTq/WRrvLjko=
223223
github.com/longbridgeapp/assert v0.1.0/go.mod h1:ew3umReliXtk1bBG4weVURxdvR0tsN+rCEfjnA4YfxI=
224-
github.com/longbridgeapp/longkey v0.1.0 h1:FW7I89nQNVYal3n0RBSy1eusQQmtTHNLcPsiXHDrEFM=
225-
github.com/longbridgeapp/longkey v0.1.0/go.mod h1:Wt5u8YLL9HThTU3ecmu+BgXxsq73CQxbWYr5ssEvTuA=
226224
github.com/longbridgeapp/sqlparser v0.2.0 h1:A6gvcqGYWpLrLbD2OoXoiMQsyUc9bg24aah902dXgN8=
227225
github.com/longbridgeapp/sqlparser v0.2.0/go.mod h1:GIHaUq8zvYyHLCLMJJykx1CdM6LHtkUih/QaJXySSx4=
228226
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=

sharding.go

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,34 @@ package sharding
33
import (
44
"errors"
55
"fmt"
6+
"hash/crc32"
67
"strconv"
78
"strings"
89
"sync"
910

11+
"github.com/bwmarrin/snowflake"
1012
"github.com/longbridgeapp/sqlparser"
1113
"gorm.io/gorm"
1214
"gorm.io/gorm/schema"
1315
)
1416

17+
const (
18+
PKSnowflake = iota // Use Snowflake primary key generator
19+
PKPGSequence // Use PostgreSQL sequence primary key generator
20+
PKCustom // Use custom primary key generator
21+
)
22+
1523
var (
1624
ErrMissingShardingKey = errors.New("sharding key or id required, and use operator =")
1725
ErrInvalidID = errors.New("invalid id format")
1826
)
1927

2028
type Sharding struct {
2129
*gorm.DB
22-
ConnPool *ConnPool
23-
configs map[string]Config
24-
querys sync.Map
30+
ConnPool *ConnPool
31+
configs map[string]Config
32+
querys sync.Map
33+
snowflakeNodes []*snowflake.Node
2534
}
2635

2736
// Config specifies the configuration for sharding.
@@ -33,6 +42,12 @@ type Config struct {
3342
// For example, for a product order table, you may want to split the rows by `user_id`.
3443
ShardingKey string
3544

45+
// NumberOfShards specifies how many tables you want to sharding.
46+
NumberOfShards uint
47+
48+
// tableFormat specifies the sharding table suffix format.
49+
tableFormat string
50+
3651
// ShardingAlgorithm specifies a function to generate the sharding
3752
// table's suffix by the column value.
3853
// For example, this function implements a mod sharding algorithm.
@@ -47,26 +62,27 @@ type Config struct {
4762

4863
// ShardingAlgorithmByPrimaryKey specifies a function to generate the sharding
4964
// table's suffix by the primary key. Used when no sharding key specified.
50-
// For example, this function use the LongKey library to generate the suffix.
65+
// For example, this function use the Snowflake library to generate the suffix.
5166
//
5267
// func(id int64) (suffix string) {
53-
// return fmt.Sprintf("_%02d", longkey.TableIdx(id))
68+
// return fmt.Sprintf("_%02d", snowflake.ParseInt64(id).Node())
5469
// }
5570
ShardingAlgorithmByPrimaryKey func(id int64) (suffix string)
5671

57-
// PrimaryKeyGenerate specifies a function to generate the primary key.
72+
// PrimaryKeyGenerator specifies the primary key generate algorithm.
5873
// Used only when insert and the record does not contains an id field.
59-
// We recommend you use the
60-
// [LongKey](https://github.com/longbridgeapp/longkey) component,
61-
// it is a distributed primary key generator.
74+
// Options are PKSnowflake, PKPGSequence and PKCustom.
75+
// When use PKCustom, you should also specify PrimaryKeyGeneratorFn.
76+
PrimaryKeyGenerator int
77+
78+
// PrimaryKeyGeneratorFn specifies a function to generate the primary key.
6279
// When use auto-increment like generator, the tableIdx argument could ignored.
63-
//
64-
// For example, this function use the LongKey library to generate the primary key.
80+
// For example, this function use the Snowflake library to generate the primary key.
6581
//
6682
// func(tableIdx int64) int64 {
67-
// return longkey.Next(tableIdx)
83+
// return nodes[tableIdx].Generate().Int64()
6884
// }
69-
PrimaryKeyGenerate func(tableIdx int64) int64
85+
PrimaryKeyGeneratorFn func(tableIdx int64) int64
7086
}
7187

7288
func Register(config Config, tables ...interface{}) *Sharding {
@@ -87,6 +103,75 @@ func (s *Sharding) Register(config Config, tables ...interface{}) *Sharding {
87103
}
88104
}
89105

106+
for t, c := range s.configs {
107+
if c.NumberOfShards > 1024 && c.PrimaryKeyGenerator == PKSnowflake {
108+
panic("Snowflake NumberOfShards should less than 1024")
109+
}
110+
111+
if c.PrimaryKeyGenerator == PKSnowflake {
112+
c.PrimaryKeyGeneratorFn = func(index int64) int64 {
113+
return s.snowflakeNodes[index].Generate().Int64()
114+
}
115+
} else if c.PrimaryKeyGenerator == PKPGSequence {
116+
c.PrimaryKeyGeneratorFn = func(index int64) int64 {
117+
var id int64
118+
err := s.DB.Raw("SELECT nextval('" + pgSeqName(t) + "')").Scan(&id).Error
119+
if err != nil {
120+
panic(err)
121+
}
122+
return id
123+
}
124+
} else if c.PrimaryKeyGenerator == PKCustom {
125+
if c.PrimaryKeyGeneratorFn == nil {
126+
panic("PrimaryKeyGeneratorFn not configured")
127+
}
128+
} else {
129+
panic("PrimaryKeyGenerator can only be one of PKSnowflake, PKPGSequence and PKCustom")
130+
}
131+
132+
if c.ShardingAlgorithm == nil {
133+
if c.NumberOfShards == 0 {
134+
panic("specify NumberOfShards or ShardingAlgorithm")
135+
}
136+
if c.NumberOfShards < 10 {
137+
c.tableFormat = "_%01d"
138+
} else if c.NumberOfShards < 100 {
139+
c.tableFormat = "_%02d"
140+
} else if c.NumberOfShards < 1000 {
141+
c.tableFormat = "_%03d"
142+
} else if c.NumberOfShards < 10000 {
143+
c.tableFormat = "_%04d"
144+
}
145+
c.ShardingAlgorithm = func(value interface{}) (suffix string, err error) {
146+
id := 0
147+
switch value := value.(type) {
148+
case int:
149+
id = value
150+
case int64:
151+
id = int(value)
152+
case string:
153+
id, err = strconv.Atoi(value)
154+
if err != nil {
155+
id = int(crc32.ChecksumIEEE([]byte(value)))
156+
}
157+
default:
158+
return "", fmt.Errorf("default algorithm only support integer and string column," +
159+
"if you use other type, specify you own ShardingAlgorithm")
160+
}
161+
return fmt.Sprintf(c.tableFormat, id%int(c.NumberOfShards)), nil
162+
}
163+
}
164+
165+
if c.ShardingAlgorithmByPrimaryKey == nil {
166+
if c.PrimaryKeyGenerator == PKSnowflake {
167+
c.ShardingAlgorithmByPrimaryKey = func(id int64) (suffix string) {
168+
return fmt.Sprintf(c.tableFormat, snowflake.ParseInt64(id).Node())
169+
}
170+
}
171+
}
172+
s.configs[t] = c
173+
}
174+
90175
return s
91176
}
92177

@@ -108,6 +193,25 @@ func (s *Sharding) LastQuery() string {
108193
func (s *Sharding) Initialize(db *gorm.DB) error {
109194
s.DB = db
110195
s.registerConnPool(db)
196+
197+
for t, c := range s.configs {
198+
if c.PrimaryKeyGenerator == PKPGSequence {
199+
err := s.DB.Exec("CREATE SEQUENCE IF NOT EXISTS " + pgSeqName(t)).Error
200+
if err != nil {
201+
return fmt.Errorf("init postgresql sequence error, %w", err)
202+
}
203+
}
204+
}
205+
206+
s.snowflakeNodes = make([]*snowflake.Node, 1024)
207+
for i := int64(0); i < 1024; i++ {
208+
n, err := snowflake.NewNode(i)
209+
if err != nil {
210+
return fmt.Errorf("init snowflake node error, %w", err)
211+
}
212+
s.snowflakeNodes[i] = n
213+
}
214+
111215
return nil
112216
}
113217

@@ -208,7 +312,7 @@ func (s *Sharding) resolve(query string, args ...interface{}) (ftQuery, stQuery,
208312
if err != nil {
209313
return ftQuery, stQuery, tableName, err
210314
}
211-
id := r.PrimaryKeyGenerate(int64(tblIdx))
315+
id := r.PrimaryKeyGeneratorFn(int64(tblIdx))
212316
insertNames = append(insertNames, &sqlparser.Ident{Name: "id"})
213317
insertValues = append(insertValues, &sqlparser.NumberLit{Value: strconv.FormatInt(id, 10)})
214318
}
@@ -349,3 +453,7 @@ func getBindValue(value interface{}, args []interface{}) (interface{}, error) {
349453
}
350454
return args[pos-1], nil
351455
}
456+
457+
func pgSeqName(table string) string {
458+
return fmt.Sprintf("gorm_sharding_%s_id_seq", table)
459+
}

0 commit comments

Comments
 (0)