Coder Social home page Coder Social logo

sharding's Introduction

Gorm Sharding

Go

Gorm Sharding plugin using SQL parser and replace for splits large tables into smaller ones, redirects Query into sharding tables. Give you a high performance database access.

Gorm Sharding 是一个高性能的数据库分表中间件。

它基于 Conn 层做 SQL 拦截、AST 解析、分表路由、自增主键填充,带来的额外开销极小。对开发者友好、透明,使用上与普通 SQL、Gorm 查询无差别,只需要额外注意一下分表键条件。

Features

  • Non-intrusive design. Load the plugin, specify the config, and all done.
  • Lighting-fast. No network based middlewares, as fast as Go.
  • Multiple database (PostgreSQL, MySQL) support.
  • Integrated primary key generator (Snowflake, PostgreSQL Sequence, Custom, ...).

Install

go get -u gorm.io/sharding

Usage

Config the sharding middleware, register the tables which you want to shard.

import (
  "fmt"

  "gorm.io/driver/postgres"
  "gorm.io/gorm"
  "gorm.io/sharding"
)

db, err := gorm.Open(postgres.New(postgres.Config{DSN: "postgres://localhost:5432/sharding-db?sslmode=disable"))

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKSnowflake,
}, "orders", Notification{}, AuditLog{}))
// This case for show up give notifications, audit_logs table use same sharding rule.

Use the db session as usual. Just note that the query should have the Sharding Key when operate sharding tables.

// Gorm create example, this will insert to orders_02
db.Create(&Order{UserID: 2})
// sql: INSERT INTO orders_2 ...

// Show have use Raw SQL to insert, this will insert into orders_03
db.Exec("INSERT INTO orders(user_id) VALUES(?)", int64(3))

// This will throw ErrMissingShardingKey error, because there not have sharding key presented.
db.Create(&Order{Amount: 10, ProductID: 100})
fmt.Println(err)

// Find, this will redirect query to orders_02
var orders []Order
db.Model(&Order{}).Where("user_id", int64(2)).Find(&orders)
fmt.Printf("%#v\n", orders)

// Raw SQL also supported
db.Raw("SELECT * FROM orders WHERE user_id = ?", int64(3)).Scan(&orders)
fmt.Printf("%#v\n", orders)

// This will throw ErrMissingShardingKey error, because WHERE conditions not included sharding key
err = db.Model(&Order{}).Where("product_id", "1").Find(&orders).Error
fmt.Println(err)

// Update and Delete are similar to create and query
db.Exec("UPDATE orders SET product_id = ? WHERE user_id = ?", 2, int64(3))
err = db.Exec("DELETE FROM orders WHERE product_id = 3").Error
fmt.Println(err) // ErrMissingShardingKey

The full example is here.

🚨 NOTE: Gorm config PrepareStmt: true is not supported for now.

🚨 NOTE: Default snowflake generator in multiple nodes may result conflicted primary key, use your custom primary key generator, or regenerate a primary key when conflict occurs.

Primary Key

When you sharding tables, you need consider how the primary key generate.

Recommend options:

Use Snowflake

Built-in Snowflake primary key generator.

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKSnowflake,
}, "orders")

Use PostgreSQL Sequence

There has built-in PostgreSQL sequence primary key implementation in Gorm Sharding, you just configure PrimaryKeyGenerator: sharding.PKPGSequence to use.

You don't need create sequence manually, Gorm Sharding check and create when the PostgreSQL sequence does not exists.

This sequence name followed gorm_sharding_${table_name}_id_seq, for example orders table, the sequence name is gorm_sharding_orders_id_seq.

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKPGSequence,
}, "orders")

Use MySQL Sequence

There has built-in MySQL sequence primary key implementation in Gorm Sharding, you just configure PrimaryKeyGenerator: sharding.PKMySQLSequence to use.

You don't need create sequence manually, Gorm Sharding check and create when the MySQL sequence does not exists.

This sequence name followed gorm_sharding_${table_name}_id_seq, for example orders table, the sequence name is gorm_sharding_orders_id_seq.

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKMySQLSequence,
}, "orders")

No primary key

If your table doesn't have a primary key, or has a primary key that isn't called id, anyway, you don't want to auto-fill the id field, then you can set PrimaryKeyGenerator to PKCustom and have PrimaryKeyGeneratorFn return 0.

Combining with dbresolver

🚨 NOTE: Use dbresolver first.

dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable"
dsnRead := "host=localhost user=gorm password=gorm dbname=gorm-slave port=5432 sslmode=disable"

conn := postgres.Open(dsn)
connRead := postgres.Open(dsnRead)

db, err := gorm.Open(conn, &gorm.Config{})
dbRead, err := gorm.Open(conn, &gorm.Config{})

db.Use(dbresolver.Register(dbresolver.Config{
  Replicas: []gorm.Dialector{dbRead.Dialector},
}))

db.Use(sharding.Register(sharding.Config{
  ShardingKey:         "user_id",
  NumberOfShards:      64,
  PrimaryKeyGenerator: sharding.PKSnowflake,
}))

Sharding process

This graph show up how Gorm Sharding works.

graph TD
first("SELECT * FROM orders WHERE user_id = ? AND status = ?
args = [100, 1]")

first--->gorm(["Gorm Query"])

subgraph "Gorm"
  gorm--->gorm_query
  gorm--->gorm_exec
  gorm--->gorm_queryrow
  gorm_query["connPool.QueryContext(sql, args)"]
  gorm_exec[/"connPool.ExecContext"/]
  gorm_queryrow[/"connPool.QueryRowContext"/]
end

subgraph "database/sql"
  gorm_query-->conn(["Conn"])
  gorm_exec-->conn(["Conn"])
  gorm_queryrow-->conn(["Conn"])
  ExecContext[/"ExecContext"/]
  QueryContext[/"QueryContext"/]
  QueryRowContext[/"QueryRowContext"/]


  conn-->ExecContext
  conn-->QueryRowContext
  conn-->QueryContext
end

subgraph sharding ["Sharding"]
  QueryContext-->router-->| Format to get full SQL string |format_sql-->| Parser to AST |parse-->check_table
  router[["router(sql, args)<br>"]]
  format_sql>"sql = SELECT * FROM orders WHERE user_id = 100 AND status = 1"]

  check_table{"Check sharding rules<br>by table name"}
  check_table-->| Exist |process_ast
  check_table_1{{"Return Raw SQL"}}
  not_match_error[/"Return Error<br>SQL query must has sharding key"\]

  parse[["ast = sqlparser.Parse(sql)"]]

  check_table-.->| Not exist |check_table_1
  process_ast(("Sharding rules"))
  get_new_table_name[["Use value in WhereValue (100) for get sharding table index<br>orders + (100 % 16)<br>Sharding Table = orders_4"]]
  new_sql{{"SELECT * FROM orders_4 WHERE user_id = 100 AND status = 1"}}

  process_ast-.->| Not match ShardingKey |not_match_error
  process_ast-->| Match ShardingKey |match_sharding_key-->| Get table name |get_new_table_name-->| Replace TableName to get new SQL |new_sql
end


subgraph database [Database]
  orders_other[("orders_0, orders_1 ... orders_3")]
  orders_4[(orders_4)]
  orders_last[("orders_5 ... orders_15")]
  other_tables[(Other non-sharding tables<br>users, stocks, topics ...)]

  new_sql-->| Sharding Query | orders_4
  check_table_1-.->| None sharding Query |other_tables
end

orders_4-->result
other_tables-.->result
result[/Query results\]

License

MIT license.

Original fork from Longbridge.

sharding's People

Contributors

a631807682 avatar comeonjy avatar dependabot[bot] avatar huacnlee avatar hyperphoton avatar jinzhu avatar liangjunmo avatar ricleal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

sharding's Issues

Occur DATA RACE when use sharding in concurrency

GORM Playground Link

go-gorm/playground#567

Description

Use go-gorm/sharding in concurrency, occur DATA RACE sometimes on production environment.

The code below may occur DATA RACE:

func (s *Sharding) switchConn(db *gorm.DB) {
	// Support ignore sharding in some case, like:
	// When DoubleWrite is enabled, we need to query database schema
	// information by table name during the migration.
	if _, ok := db.Get(ShardingIgnoreStoreKey); !ok {
		s.ConnPool = &ConnPool{ConnPool: db.Statement.ConnPool, sharding: s}
		db.Statement.ConnPool = s.ConnPool
	}
}

Update:

func (s *Sharding) switchConn(db *gorm.DB) {
	// Support ignore sharding in some case, like:
	// When DoubleWrite is enabled, we need to query database schema
	// information by table name during the migration.
	if _, ok := db.Get(ShardingIgnoreStoreKey); !ok {
		s.mutex.Lock()
		if db.Statement.ConnPool != nil {
			if _, ok := db.Statement.ConnPool.(ConnPool); !ok {
				db.Statement.ConnPool = &ConnPool{ConnPool: db.Statement.ConnPool, sharding: s}
			}
		}
		s.mutex.Unlock()
	}
}

does gorm sharding support JOIN ?

Your Question

in project when i use gorm sharding and my SQL contains JOIN, like A.col1, B.col2
the query will fail. the query trys to find the main table instead of sub table

The document you expected this should be explained

Expected answer

any help ? thanks

How to backfill primary key when use this plugin?

Your Question

type User struct{
  ID int64 `gorm:"column:id;primaryKey"`
  UserId int64 `gorm:"column:user_id"`
}

shard, err := sharding.Register(sharding.Config{
  ShardingKey:         "user_id",
  NumberOfShards:      1,
  PrimaryKeyGenerator: sharding.PKSnowflake,
  ShardingAlgorithm:   getShardFn(1),
}, "users"))
if err != nil {
  return nil, err
}

if err := db.Use(shard); err != nil {
  return nil, err
}

result := db.Create(&user) // pass pointer of data to Create

user.ID             // 0

The document you expected this should be explained

Expected answer

支持配置多个数据库吗

Your Question

支持分库吗,即将分表分布在多个数据库上

The document you expected this should be explained

Expected answer

property does not exist

Cannot find NumberOfShards in sharding.Config, PrimaryKeyGenerator in field, and sharding.PKSnowflake property does not exist.
Can you provide the latest examples

MySQL transaction not work due to conn pool management.

Description

// BeginTx Implement ConnPoolBeginner.BeginTx
func (pool *ConnPool) BeginTx(ctx context.Context, opt *sql.TxOptions) (gorm.ConnPool, error) {
	if basePool, ok := pool.ConnPool.(gorm.ConnPoolBeginner); ok {
		return basePool.BeginTx(ctx, opt)
	}
	return pool, nil
}

For this method, when init with default setting, pool.ConnPool is actually an sql.DB struct, which return pool, nil directly. Transaction is not opened.

db.Statement change after use plugin

stmt.Parse will change its own value, it will cause some bugs.
https://github.com/go-gorm/gorm/blob/master/migrator/migrator.go#L48
https://github.com/go-gorm/sharding/blob/main/sharding.go#L113

func (s *Sharding) compile() error {
...
			// stmt := &gorm.Statement{DB: s.DB}
			stmt := s.DB.Statement
			if err := stmt.Parse(table); err == nil {
				s.configs[stmt.Table] = s._config
			} else {
				return err
			}
...
}

I think the code stmt := &gorm.Statement{DB: s.DB} should be used instead, but it is commented, is this necessary?

Example in README won't work: Registering multiple Config

Description

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      64,
    PrimaryKeyGenerator: sharding.PKSnowflake,
}, "orders").Register(sharding.Config{
    ShardingKey:         "user_id",
    NumberOfShards:      256,
    PrimaryKeyGenerator: sharding.PKSnowflake,
    // This case for show up give notifications, audit_logs table use same sharding rule.
}, Notification{}, AuditLog{}))

This example won't work since Register return a struct which hasn't implemented the Register method.

sharding key or id required, and use operator =

Your Question

sharding key or id required, and use operator =

sharding rules as follows

  • user_id use Snowflake id,such as 79535114761158382

  • md5(user_id)

  • get first one letter for schema name, then join wkread_users_, such as wkread_users_a

  • every database host has two schema, such as wkread_users_2,wkread_users_3in the db06 host

  • first two letters for table name, then join user_, such as tables user_30,user_31``user_32``user_33``user_34``user_35``user_36``user_37``user_38``user_39``user_3a``user_3b``user_3c``user_3d``user_3e``user_3f in the schema wkread_users_3

Sharding is as follows in my project

db05
  wkread_users_0
       user_00
       user_01
         ...
       user_0f
  wkread_users_1
       user_10
       user_11
         ...
       user_1f
db06
  wkread_users_2
       user_20
       user_21
         ...
       user_2f
  wkread_users_3
       user_30
       user_31
         ...
       user_3f
...
db12
  wkread_users_e
       user_e0
       user_e1
         ...
       user_ef
  wkread_users_f
       user_f0
       user_f1
         ...
       user_ff

database conf

default:
  master:
    Name: express
    Addr: 10.131.32.124:30001
    UserName: root
    Password: hhel0315
db05-wkread_users_0:
  master:
    Name: wkread_users_0
    Addr: 10.131.32.124:30005
    UserName: root
    Password: hhel0315
db05-wkread_users_1:
  master:
    Name: wkread_users_1
    Addr: 10.131.32.124:30005
    UserName: root
    Password: hhel0315
db06-wkread_users_2:
  master:
    Name: wkread_users_2
    Addr: 10.131.32.124:30006
    UserName: root
    Password: hhel0315
db06-wkread_users_3:
  master:
    Name: wkread_users_3
    Addr: 10.131.32.124:30006
    UserName: root
    Password: hhel0315
db07-wkread_users_4:
  master:
    Name: wkread_users_4
    Addr: 10.131.32.124:30007
    UserName: root
    Password: hhel0315
db07-wkread_users_5:
  master:
    Name: wkread_users_5
    Addr: 10.131.32.124:30007
db08-wkread_users_6:
  master:
    Name: wkread_users_6
    Addr: 10.131.32.124:30008
db08-wkread_users_7:
  master:
    Name: wkread_users_7
    Addr: 10.131.32.124:30008
db09-wkread_users_8:
  master:
    Name: wkread_users_8
    Addr: 10.131.32.124:30009
db09-wkread_users_9:
  master:
    Name: wkread_users_9
    Addr: 10.131.32.124:30009
db10-wkread_users_a:
  master:
    Name: wkread_users_a
    Addr: 10.131.32.124:30010
db10-wkread_users_b:
  master:
    Name: wkread_users_b
    Addr: 10.131.32.124:30010
db11-wkread_users_c:
  master:
    Name: wkread_users_c
    Addr: 10.131.32.124:30011
db11-wkread_users_d:
  master:
    Name: wkread_users_d
    Addr: 10.131.32.124:30011
db12-wkread_users_e:
  master:
    Name: wkread_users_e
    Addr: 10.131.32.124:30012
    UserName: root
    Password: hhel0315
db12-wkread_users_f:
  master:
    Name: wkread_users_f
    Addr: 10.131.32.124:30012

database dialector is mysql

func NewReadWriteSplits(cm map[string]ReadWriteSplitConfig) (db *gorm.DB) {
	defaultConfig := cm["default"]
	defaultMasterConfig := defaultConfig.Master

	dialector := NewDialectorWithPool(&defaultMasterConfig)

	db, err := gorm.Open(dialector, gormConfig(&defaultMasterConfig))
	if err != nil {
		log.Panicf("database connection failed. database name: %s, err: %+v", defaultMasterConfig.Name, err)
	}
	db.Set("gorm:table_options", "CHARSET=utf8mb4")
	// default
	resolver := dbresolver.Register(dbresolver.Config{
		Replicas: func(cs []Config) []gorm.Dialector {
			var ds []gorm.Dialector
			for _, c := range cs {
				ds = append(ds, NewDialectorWithPool(&c))
			}
			return ds
		}(defaultConfig.Slaves),
		TraceResolverMode: true,
	})
	// other
	for name, c := range cm {
		if name == "default" {
			break
		}
		resolver = resolver.Register(dbresolver.Config{
			Sources: func(c Config) []gorm.Dialector {
				ds := []gorm.Dialector{
					NewDialector(&c),
				}
				return ds
			}(c.Master),
			Replicas: func(cs []Config) []gorm.Dialector {
				var ds []gorm.Dialector
				for _, c := range cs {
					ds = append(ds, NewDialector(&c))
				}
				return ds
			}(c.Slaves),
			TraceResolverMode: true,
		}, name).SetMaxOpenConns(c.Master.MaxOpenConn).
			SetMaxIdleConns(c.Master.MaxIdleConn).
			SetConnMaxLifetime(c.Master.ConnMaxLifeTime).
			SetConnMaxIdleTime(c.Master.ConnMaxIdleTime)
	}

	db.Use(resolver)

	return db
}

query a user by uid code as follows

func (d *repository) GetUser(ctx context.Context, uid int64) (*model.User, error) {
	var data model.User
	resolverName := data.GetDBResolverName(uid)
	db := d.orm.WithContext(ctx).Clauses(dbresolver.Use(string(resolverName)))
	err := db.Use(sharding.Register(sharding.Config{
		ShardingKey:         "id",
		NumberOfShards:      16,
		PrimaryKeyGenerator: sharding.PKSnowflake,
		ShardingAlgorithmByPrimaryKey: func(id int64) (suffix string) {
			hashed, _ := utils.Md5(fmt.Sprintf("%d", id))
			fmt.Printf("id:%d, hashed:%s\n", id, hashed)
			return fmt.Sprintf("_%s", hashed[:2])
		},
	}, model.User{}))
	if err != nil {
		return nil, errors.Wrapf(err, "[repo.user] sharding.Register err")
	}
	err = db.First(&data, uid).Error
	if errors.Is(err, ErrNotFound) {
		return nil, ErrNotFound
	} else if err != nil {
		return nil, errors.Wrapf(err, "[repo.user] query db err")
	}
	return &data, nil
}

get an error when run

2023/02/13 20:13:11 /Users/zhengjb/go/src/gitlab.y5ops.com/jin/hertz_demo/biz/repository/user_repo.go:30 sharding key or id required, and use operator =
[0.141ms] [rows:0] [replica] SELECT * FROM `user` WHERE `user`.`id` = 79535114761158382 ORDER BY `user`.`id` LIMIT 1

The document you expected this should be explained

Can not db.Use used when db.Clauses is used ?

cannot use Clauses

Expected answer

get a user as expected. or how to do as described ?

Dose it support mysql well now?

Your Question

I'm using mysql and I‘m not sure if i should try using this library.

image
image

The document you expected this should be explained

Expected answer

Question about the requirement of go version

Your Question

In the latest release v0.6.0, the go version requirement in go.mod was upgraded from 1.20 to 1.21.
According to the release note of go 1.21, the go version in go.mod is now a strict minimum requirement, which means that in order to use this module, users need to upgrade their go version to 1.21. This may cause some concerns, since go 1.21 was just released a month ago.
My question is that could we downgrade the go version of go.mod?

The document you expected this should be explained

Expected answer

panic: interface conversion: sharding.ShardingMigrator is not migrator.BuildIndexOptionsInterface: missing method BuildIndexOptions

GORM Playground Link

569

Description

detailed error as follows

2023/02/19 21:14:12 testing mysql...
panic: interface conversion: sharding.ShardingMigrator is not migrator.BuildIndexOptionsInterface: missing method BuildIndexOptions

goroutine 1 [running]:
gorm.io/gorm/migrator.Migrator.CreateTable.func1(0x14000484380)
	/Users/zhengjb/go/src/github.com/zishiguo/playground/gorm/migrator/migrator.go:247 +0x568
gorm.io/gorm/migrator.Migrator.RunWithValue({{0x0?, 0x14000271500?, {0x105414100?, 0x1400023c000?}}}, {0x105330f80?, 0x1400023c230}, 0x140000e3540)
	/Users/zhengjb/go/src/github.com/zishiguo/playground/gorm/migrator/migrator.go:62 +0x13c
gorm.io/gorm/migrator.Migrator.CreateTable({{0x70?, 0x14000271500?, {0x105414100?, 0x1400023c000?}}}, {0x140004803c0?, 0x140004803c0?, 0x0?})
	/Users/zhengjb/go/src/github.com/zishiguo/playground/gorm/migrator/migrator.go:198 +0x114
gorm.io/gorm/migrator.Migrator.AutoMigrate({{0x0?, 0x14000270720?, {0x105414100?, 0x1400023c000?}}}, {0x1400021bad0?, 0x105311aa0?, 0x1400023c001?})
	/Users/zhengjb/go/src/github.com/zishiguo/playground/gorm/migrator/migrator.go:112 +0x244
gorm.io/sharding.ShardingMigrator.AutoMigrate({{0x105417ca8, 0x14000270690}, 0x140002368f0, {0x105413e40, 0x14000010bd0}}, {0x1400021b770?, 0x0?, 0x0?})
	/Users/zhengjb/go/pkg/mod/gorm.io/sharding@v0.5.2/dialector.go:45 +0x308
gorm.io/gorm.(*DB).AutoMigrate(0x140000017a0?, {0x1400021b770, 0x1, 0x1})
	/Users/zhengjb/go/src/github.com/zishiguo/playground/gorm/migrator.go:28 +0x44
gorm.io/playground.RunMigrations()
	/Users/zhengjb/go/src/github.com/zishiguo/playground/db.go:113 +0x194
gorm.io/playground.init.0()
	/Users/zhengjb/go/src/github.com/zishiguo/playground/db.go:37 +0x108

Process finished with the exit code 1

如何注册不同的shardingkey 分表策略

Your Question

The document you expected this should be explained

Expected answer

Hi,
目前有需求是注册不同类型的shardingkey 切片表, gorm (db.Use()) 如果多次使用相同name的插件会被返回已注册.
请问该如何实现, 谢谢

full text query macth not work

_ = db.Use(sharding.Register(sharding.Config{
		ShardingKey:         "ymd",
		NumberOfShards:      100,
		PrimaryKeyGenerator: sharding.PKCustom,
		ShardingAlgorithm: func(columnValue any) (suffix string, err error) {
			parentId := cast.ToInt(columnValue)
			suffix = fmt.Sprintf("_%v", parentId)
			return
		},
		PrimaryKeyGeneratorFn: func(tableIdx int64) int64 {
			return 0
		},
		// This case for show up give notifications, audit_logs table use same sharding rule.
	}, ChatMsg{}))

qb.Where(fmt.Sprintf("(MATCH(body) AGAINST('%s'))", req.Key))

can not fond table

Use sharding with AutoMigrate failed

Description

  1. when a dataobject use sharding, and automigrate when connect to Mysql server, it will just appear a tabel, and sharding aljust like this
ShardingAlgorithm: func(columnValue interface{}) (suffix string, err error) {
	if id, ok := columnValue.(uint); ok {
		return fmt.Sprintf("_%02d", id%shardingNum), nil
	}
	return "", fmt.Errorf("invalid column value type")
}

and just migrate table {table_name}_00

  1. when override TableName(), and just like this
func (d *dataobject) TableName() string {
	return fmt.Sprintf("table_name_%02d", d.ID%shardingNum)
}

and just migrate table {table_name}_00 and didnot creat any new table

When DoubleWrite is on, is the query also run twice?

Your Question

func (pool ConnPool) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	ftQuery, stQuery, table, err := pool.sharding.resolve(query, args...)
	if err != nil {
		return nil, err
	}

	pool.sharding.querys.Store("last_query", stQuery)

	if table != "" {
		if r, ok := pool.sharding.configs[table]; ok {
			if r.DoubleWrite {
				pool.ConnPool.ExecContext(ctx, ftQuery, args...)
			}
		}
	}

	return pool.ConnPool.QueryContext(ctx, stQuery, args...)
}

The document you expected this should be explained

Expected answer

Why restrict the primary key of sharding to int64 and must be called "ID"

Why restrict the primary key of sharding to int64 and must be called "ID"

I am using the latest version 0.5.3 of gorm sharding and have encountered two issues with primary keys.
Problem 1: When inserting, a primary key will be generated based on the suffix. My suffix is not a number, which causes the insert to fail (such as line 363 of the sharding.go source code image). The solution comes from rows 357 and 358, but my table primary key is not called "ID" but "order_id". Is there a way to skip the error of row 363.
image

Question 2: When querying, if there is an "id" field, it must be of type int64, otherwise it will cause an error in line 486. Is there a way to skip this judgment and successfully query
image

Expected answer

I want to know if there is a way to bypass the limitation of gorm sharing for primary keys that are "ID" and of type int64

sharding key or id required, and use operator =

GORM Playground Link

go-gorm/playground#1

Description

When i use preload on sharding table, the return "msg sharding key or id required, and use operator ="

type A struct {
ID string
BS []B
}

type B struct {
ID string
AID string
}

var aModel A

db.Model(&A{}).Preload("B").Find(&aModel)

MySQL Nested transaction failed due to incorrect interface impl.

Description

in gorm.io/gorm/finisher_api.go:547 Transaction, nested transaction need to check if connPool fit TxCommitter:

  • can fit: in nested transaction now, BEGIN has been called, so use SAVEPOINT instead
  • can't fit: not in nested transaction now, call BEGIN.

However, sharding plugin impl BeginTx / Commit / Rollback in same struct. So it can fit TxCommitter interface without BEGIN call. The nested transaction won't work.

ConnPool struct CANNOT impl TxBeginner and TxCommitter interface in the same time. When BeginTx is called, different struct need to be returned.

sharding plugin seems not compatable with gorm.Model well.

my code works well befor using sharding middleware:

func (r *MySQLRepo) PlanExists(id uint) (bool, error) {
    var exists bool
    err := r.db.Model(&entity.StockPlan{}).
        Select("count(*) > 0").
        Where("id = ?", id).
        Find(&exists).
        Error
    return exists, err
}

but the error occurs after registering sharding like this:

db.Use(sharding.Register(sharding.Config{
    ShardingKey:         "title",
    NumberOfShards:      4,
    PrimaryKeyGenerator: sharding.PKSnowflake,
}, "stock_plans"))

and the error is:

Error 1054: Unknown column 'stock_plans.deleted_at' in 'where clause'

是否可以支持注册不同的shardingkey 分表策略

Describe the feature

sharding.Register func will be get the same plugin name for sharding,so different sharding-key column could not register successfully when invoking db.USE()

Motivation

The same database has multiple tables with different shardingkey rules

Related Issues

#62

Is it possible to support setting plugins name to solve the problem of different shardingKey rules for multiple tables in the same database?

Does not work in MySQL

                type Order struct {
			ID        int64 `gorm:"primarykey"`
			UserID    int64
			ProductID int64
		}

		for i := 0; i < 64; i += 1 {
			table := fmt.Sprintf("orders_%02d", i)
			db.Exec(`DROP TABLE IF EXISTS ` + table)
			db.Exec(`CREATE TABLE ` + table + ` (id int PRIMARY KEY,user_id int,product_id int)`)
		}

		middleware := sharding.Register(sharding.Config{
			ShardingKey:         "user_id",
			NumberOfShards:      64,
			PrimaryKeyGenerator: sharding.PKSnowflake,
		}, "orders")
		err = db.Use(middleware)
		if err != nil {
			panic(err)
		}

		err = db.Create(&Order{UserID: 2}).Error
		if err != nil {
			fmt.Println(err)
		}

		err = db.Create(&Order{UserID: 3}).Error
		if err != nil {
			fmt.Println(err)
		}

		err = db.Create(&Order{UserID: 4}).Error
		if err != nil {
			fmt.Println(err)
		}

Create table successfully

[13.420ms] [rows:0] CREATE TABLE orders_00 (id int PRIMARY KEY,user_id int,product_id int)
...
[13.420ms] [rows:0] CREATE TABLE orders_63 (id int PRIMARY KEY,user_id int,product_id int)

However, when Gorm is used normally, the corresponding table cannot be indexed

Index to "orders"

2022/01/27 23:41:07 ./gorm.go:144 Error 1146: Table 'orders' doesn't exist
[0.567ms] [rows:0] INSERT INTO `orders` (`user_id`,`product_id`) VALUES (2,0)
Error 1146: Table 'orders' doesn't exist

Rename EnableFullTable for a better name.

EnableFullTable is means double write in to main table for keep all records have a duplication version in main table.

This feature helpful for:

  • Admin console to list all records, without sharding key query.
  • Index page to get recent created records.

Support for custom schema name in Postgres

Right now sharding is creating tables under public schema while we use custom name so it fail when there is no public already created. Please consider adding support for custom schema names in Postgres.

i test example with Mysql, i use Model to use CURD Interface, it return error

Your Question

I try to modify the example code to test MySQL sharding

But when I use model interface to access, there will be an error

I change it to Table, is OK

I think this issue like #14 ?

my env:

  • go1.17.1 darwin/amd64
    • dependency
      • gorm.io/driver/mysql v1.3.4
      • gorm.io/gorm v1.23.6
      • github.com/bwmarrin/snowflake v0.3.0 // indirect
      • github.com/longbridgeapp/sqlparser v0.3.1 // indirect
      • github.com/technoweenie/multipartstreamer v1.0.1 // indirect
      • gorm.io/sharding v0.5.1 // indirect
  • mysql:8.0.23

examples/order.go

package main

import (
	"fmt"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	"gorm.io/gorm/schema"
	"gorm.io/sharding"
)

type Order struct {
	ID        int64 `gorm:"primarykey"`
	UserID    int64
	ProductID int64
}

func main() {
	connection := fmt.Sprintf("test:test@tcp(127.0.0.1:3306)/?parseTime=true")
	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: connection,
	}), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true,
		},
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		fmt.Println("err", err)
	}

	db.Exec("USE " + "MyTable")

	for i := 0; i < 4; i += 1 {
		table := fmt.Sprintf("orders_%d", i)
		db.Exec(`DROP TABLE IF EXISTS ` + table)
		db.Table(table).AutoMigrate(&Order{})
	}
	// db.AutoMigrate(&Order{})

	middleware := sharding.Register(sharding.Config{
		ShardingKey:         "user_id",
		NumberOfShards:      4,
		PrimaryKeyGenerator: sharding.PKSnowflake,
	}, "orders")
	db.Use(middleware)

	// this record will insert to orders_02
        // !!!!!!!!!!!!!!!!!!  it returm err: Error 1146: Table 'MyTable.order' doesn't exist
	// err = db.Create(&Order{UserID: 2}).Error
	err = db.Table("orders").Create(&Order{UserID: 2}).Error
	if err != nil {
		fmt.Println("err1", err)
	}

	// this record will insert to orders_03
	err = db.Exec("INSERT INTO orders(user_id) VALUES(?)", int64(3)).Error
	if err != nil {
		fmt.Println("err2", err)
	}

	// this will throw ErrMissingShardingKey error
	err = db.Exec("INSERT INTO orders(product_id) VALUES(1)").Error
	fmt.Println("err3", err)

	// this will redirect query to orders_02
	var orders []Order
        // !!!!!!!!!!!!!!!!!!  it returm err: Error 1146: Table 'MyTable.order' doesn't exist
	// err = db.Model(&Order{}).Where("user_id", int64(3)).Find(&orders).Error
	err = db.Table("orders").Where("user_id", int64(2)).Find(&orders).Error
	if err != nil {
		fmt.Println("err4", err)
	}
	fmt.Printf("%#v\n", orders)

	// Raw SQL also supported
	db.Raw("SELECT * FROM orders WHERE user_id = ?", int64(3)).Scan(&orders)
	fmt.Printf("%#v\n", orders)

	// this will throw ErrMissingShardingKey error
        // !!!!!!!!!!!!!!!!!!  it returm err: Error 1146: Table 'MyTable.order' doesn't exist
	// err = db.Model(&Order{}).Where("product_id", "1").Find(&orders).Error
	err = db.Table("orders").Where("product_id", "1").Find(&orders).Error
	fmt.Println("err5", err)

	// Update and Delete are similar to create and query
	err = db.Exec("UPDATE orders SET product_id = ? WHERE user_id = ?", 2, int64(3)).Error
	fmt.Println("err6", err) // nil
	err = db.Exec("DELETE FROM orders WHERE product_id = 3").Error
	fmt.Println("err7", err) // ErrMissingShardingKey
}

see // !!!!!!!!!!!!!!!!!! it returm err: Error 1146: Table 'MyTable.order' doesn't exist

The document you expected this should be explained

Expected answer

i want use Model to access, not Table

sharding.Register((sharding.Config literal), (model.XXXX literal)).Register undefined (type *sharding.Sharding has no field or method

code:
db.Use(sharding.Register(sharding.Config{ ShardingKey: "user_id", NumberOfShards: 64, PrimaryKeyGenerator: sharding.PKSnowflake, }, "orders").Register(sharding.Config{ ShardingKey: "user_id", NumberOfShards: 256, PrimaryKeyGenerator: sharding.PKSnowflake, // This case for show up give notifications, audit_logs table use same sharding rule. }, Notification{}, AuditLog{}))
the second Register function show error below:
*sharding.Register((sharding.Config literal), (model.XXXX literal)).Register undefined (type sharding.Sharding has no field or method
why?
thanks.

怎么自定义路由策略

比如我想根据主键id 自定义一个路由策略 根据一定的算法把数据路由到对应的数据库或者表 要怎么实现

替换sql, 让日志中记录真实执行的sql

Describe the feature

替换原sql, gorm 追踪记录新的sql内容

Motivation

希望执行分表后生成新的sql记录到日志中,而不是原来的sql。

Related Issues

conn_pool.go

type ConnPool struct {
	// db, This is global db instance
	sharding *Sharding
	gorm.ConnPool
	currentDB *gorm.DB // 记录当前的db实例
}


func (pool ConnPool) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
// ....
        pool.sharding.querys.Store("last_query", stQuery)
        // 重置sql
	oldSqlCap := pool.currentDB.Statement.SQL.Cap()
	pool.currentDB.Statement.SQL.Reset()
	pool.currentDB.Statement.SQL.Grow(oldSqlCap + 5)
	pool.currentDB.Statement.SQL.WriteString(stQuery)
// ...
}



func (pool ConnPool) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
// ...
        pool.sharding.querys.Store("last_query", stQuery)
        // 重置sql
	oldSqlCap := pool.currentDB.Statement.SQL.Cap()
	pool.currentDB.Statement.SQL.Reset()
	pool.currentDB.Statement.SQL.Grow(oldSqlCap + 5)
	pool.currentDB.Statement.SQL.WriteString(stQuery)
// ...
}

sharding.go

func (s *Sharding) switchConn(db *gorm.DB) {
	// Support ignore sharding in some case, like:
	// When DoubleWrite is enabled, we need to query database schema
	// information by table name during the migration.
	if _, ok := db.Get(ShardingIgnoreStoreKey); !ok {
		s.ConnPool = &ConnPool{ConnPool: db.Statement.ConnPool, sharding: s, currentDB: db} // 记录当前的db实例
		db.Statement.ConnPool = s.ConnPool
	}
}

mysql 驱动没有回填snowflake id

GORM Playground Link

go-gorm/playground#1

Description

#37

Env:
go 1.16
mysql: 5.7.28
sharding: 0.5.2
gorm: 1.24.3

package main

import (
	"fmt"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/sharding"
)

type User struct {
	ID     int64 `gorm:"column:id;primaryKey"`
	UserId int64 `gorm:"column:user_id"`
}

func main() {
	dsn := "username:pwd@tcp(host:3306)/database_name?charset=utf8mb4&parseTime=true&loc=Local"
	db, _ := gorm.Open(mysql.New(mysql.Config{DSN: dsn}))

	shard := sharding.Register(sharding.Config{
		ShardingKey:         "user_id",
		NumberOfShards:      2,
		PrimaryKeyGenerator: sharding.PKSnowflake,
	}, "users")

	db.Use(shard)

	user := User{UserId: 42}
	err := db.Create(&user).Error // nil
	if err != nil {
		panic(err)
	}

	fmt.Println(user.ID) // 0
}

debug code

gorm: callbacks/create.go
Create() {
     insertID, err := result.LastInsertId() // 0
     insertOk := err == nil && insertID > 0
}

// return 0
func (res *mysqlResult) LastInsertId() (int64, error) {
	return res.insertId, nil
}

example doesn't cover soft delete cases

Your Question

     I met soft delete question.

    order.go doesn't cover soft delete cases.

The document you expected this should be explained

    example can cover soft delete cases.

Expected answer

    example can cover soft delete cases.

The sharding does not work when call gorm.Create method!

GORM Playground Link

go-gorm/playground#1

Description

Recently, I test sharding plugin and try to intergate it in my project. However, it fail the tests when I run the example in https://github.com/go-gorm/sharding/tree/main/examples/order.go.
The only difference is the database driver. I replace postgres to mysql.

enviroment:

OS: WSL1 for Win10
Go: go version go1.17.6 linux/amd64
Package:
	gorm.io/driver/mysql v1.2.3
	gorm.io/gorm v1.22.5
	gorm.io/sharding v0.4.0

The following is the error message:

2022/02/21 09:19:34 ~/main.go:67 Error 1146: Table 'gorm.orders' doesn't exist
[19.048ms] [rows:0] INSERT INTO `orders` (`user_id`,`product_id`) VALUES (2,0)
Error 1146: Table 'gorm.orders' doesn't exist

and the code:

// this record will insert to orders_02
	err = db.Create(&Order{UserID: 2}).Error
	if err != nil {
		fmt.Println(err)
	}
```go

Apparently, the sharding policy does not work. It try to insert new record in `orders` not `orders_2`.

Error 1054 (42S22): Unknown column 'user_follow.deleted_at' in 'where clause'

GORM Playground Link

568

Description

I also met this question sharding Error 1054: Unknown column 'ny_order.deleted_at' in 'where clause'

detailed error as follows

2023/02/19 20:57:18 testing mysql...
=== RUN   TestGORM

2023/02/19 20:57:18 /Users/zhengjb/go/src/github.com/zishiguo/playground/main_test.go:13 Error 1054 (42S22): Unknown column 'user_follow.deleted_at' in 'where clause'
[2.526ms] [rows:0] SELECT * FROM `user_follow` WHERE user_id = 79535114761158382 AND `user_follow`.`deleted_at` IS NULL
    main_test.go:14: Failed, got error: Error 1054 (42S22): Unknown column 'user_follow.deleted_at' in 'where clause'
--- FAIL: TestGORM (0.00s)

FAIL

Process finished with the exit code 1

go-gorm/sharding plugin would fail when combining with hints.ForceIndex in MySQL

GORM Playground Link

go-gorm/playground#726

Description

I create a logical table named message, and use two underlying sharding tables: message_01 and message_02.
When using go-gorm/sharding plugin alone, everything works well, as in test case TestSharding.
However, after I added Clauses(hints.ForceIndex("idx_content")) in the query, the sharding plugin would fail, as in test case TestShardingAndForceIndex.
The error log shows Error 1146 (42S02): Table 'gorm.message' doesn't exist.
After some digging up, I found the cause of the issue comes from this line:

expr, err := sqlparser.NewParser(strings.NewReader(query)).ParseStatement()

It seems sqlparser can't handle the FORCE INDEX syntax in SQL, causing the sharding plugin fails and falls back to original(not sharded) SQL.
A relevant issue: #91

Refactor Config API same as dbresolver

db.Use(sharding.Register(sharding.Config{
		ShardingKey: "user_id",
		ShardingAlgorithm: func(value interface{}) (suffix string, err error) {
				if uid, ok := value.(int64); ok {
						return fmt.Sprintf("_%02d", uid%4), nil
				}
				return "", errors.New("invalid user_id")
		},
		PrimaryKeyGenerate: func(tableIdx int64) interface{} {
				// use Built in keygen for generate a sequence primary key with table index
				return keygen.Next(tableIdx)
		}
}, "orders").Register(sharding.Config{
		ShardingKey: "user_id",
		ShardingAlgorithm: func(value interface{}) (suffix string, err error) {
				if uid, ok := value.(int64); ok {
						return fmt.Sprintf("_%02d", uid%4), nil
				}
				return "", errors.New("invalid user_id")
		},
		PrimaryKeyGenerate: func(tableIdx int64) interface{} {
				return uuid.UUID()
		}
		// This case for show up give notifications, activities table use same sharding rule.
}, Notification{}, Acitivity{}))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.