diff --git a/weed/filer/mysql_store/README.md b/weed/filer/mysql_store/README.md new file mode 100644 index 000000000..4ce6438da --- /dev/null +++ b/weed/filer/mysql_store/README.md @@ -0,0 +1,66 @@ +#MySQL filer mapping store + +## Schema format + + +Basically, uriPath and fid are the key elements stored in MySQL. In view of the optimization and user's usage, +adding primary key with integer type and involving createTime, updateTime, status fields should be somewhat meaningful. +Of course, you could customize the schema per your concretely circumstance freely. + +

+CREATE TABLE IF NOT EXISTS `filer_mapping` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `uriPath` char(256) NOT NULL DEFAULT "" COMMENT 'http uriPath',
+  `fid` char(36) NOT NULL DEFAULT "" COMMENT 'seaweedfs fid',
+  `createTime` int(10) NOT NULL DEFAULT 0 COMMENT 'createdTime in unix timestamp',
+  `updateTime` int(10) NOT NULL DEFAULT 0 COMMENT 'updatedTime in unix timestamp',
+  `remark` varchar(20) NOT NULL DEFAULT "" COMMENT 'reserverd field',
+  `status` tinyint(2) DEFAULT '1' COMMENT 'resource status',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `index_uriPath` (`uriPath`)
+) DEFAULT CHARSET=utf8;
+
+ + +The MySQL 's config params is not added into the weed command option as other stores(redis,cassandra). Instead, +We created a config file(json format) for them. TOML,YAML or XML also should be OK. But TOML and YAML need import thirdparty package +while XML is a little bit complex. + +The sample config file's content is below: + +

+{
+    "mysql": [
+        {
+            "User": "root",
+            "Password": "root",
+            "HostName": "127.0.0.1",
+            "Port": 3306,
+            "DataBase": "seaweedfs"
+        },
+        {
+            "User": "root",
+            "Password": "root",
+            "HostName": "127.0.0.2",
+            "Port": 3306,
+            "DataBase": "seaweedfs"
+        }
+    ],
+    "IsSharding":true,
+    "ShardingNum":1024
+}
+
+ + +The "mysql" field in above conf file is an array which include all mysql instances you prepared to store sharding data. +1. If one mysql instance is enough, just keep one instance in "mysql" field. +2. If table sharding at a specific mysql instance is needed , mark "IsSharding" field with true and specify total table +sharding numbers using "ShardingNum" field. +3. If the mysql service could be auto scaled transparently in your environment, just config one mysql instance(usually it's a frondend proxy or VIP), +and mark "IsSharding" with false value +4. If your prepare more than one mysql instances and have no plan to use table sharding for any instance(mark isSharding with false), instance sharding +will still be done implicitly + + + + diff --git a/weed/filer/mysql_store/filer_mapping.sql b/weed/filer/mysql_store/filer_mapping.sql deleted file mode 100644 index 6bbe4e880..000000000 --- a/weed/filer/mysql_store/filer_mapping.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE IF NOT EXISTS `filer_mapping` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `uriPath` char(256) NOT NULL DEFAULT "" COMMENT 'http uriPath', - `fid` char(36) NOT NULL DEFAULT "" COMMENT 'seaweedfs fid', - `createTime` int(10) NOT NULL DEFAULT 0 COMMENT 'createdTime in unix timestamp', - `updateTime` int(10) NOT NULL DEFAULT 0 COMMENT 'updatedTime in unix timestamp', - `remark` varchar(20) NOT NULL DEFAULT "" COMMENT 'reserverd field', - `status` tinyint(2) DEFAULT '1' COMMENT 'resource status', - PRIMARY KEY (`id`), - UNIQUE KEY `index_uriPath` (`uriPath`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/weed/filer/mysql_store/mysql_store.go b/weed/filer/mysql_store/mysql_store.go index 44d0d88a7..439ed22f7 100644 --- a/weed/filer/mysql_store/mysql_store.go +++ b/weed/filer/mysql_store/mysql_store.go @@ -11,11 +11,11 @@ import ( ) const ( - sqlUrl = "%s:%s@tcp(%s:%d)/%s?charset=utf8" - maxIdleConnections = 100 - maxOpenConnections = 50 - maxTableNums = 1024 - tableName = "filer_mapping" + sqlUrl = "%s:%s@tcp(%s:%d)/%s?charset=utf8" + default_maxIdleConnections = 100 + default_maxOpenConnections = 50 + default_maxTableNums = 1024 + tableName = "filer_mapping" ) var ( @@ -24,15 +24,24 @@ var ( ) type MySqlConf struct { - User string - Password string - HostName string - Port int - DataBase string + User string + Password string + HostName string + Port int + DataBase string + MaxIdleConnections int + MaxOpenConnections int +} + +type ShardingConf struct { + IsSharding bool `json:"isSharding"` + ShardingNum int `json:"shardingNum"` } type MySqlStore struct { - dbs []*sql.DB + dbs []*sql.DB + isSharding bool + shardingNum int } func getDbConnection(confs []MySqlConf) []*sql.DB { @@ -47,6 +56,19 @@ func getDbConnection(confs []MySqlConf) []*sql.DB { _db_connection = nil panic(dbErr) } + var maxIdleConnections, maxOpenConnections int + + if conf.MaxIdleConnections != 0 { + maxIdleConnections = conf.MaxIdleConnections + } else { + maxIdleConnections = default_maxIdleConnections + } + if conf.MaxOpenConnections != 0 { + maxOpenConnections = conf.MaxOpenConnections + } else { + maxOpenConnections = default_maxOpenConnections + } + _db_connection.SetMaxIdleConns(maxIdleConnections) _db_connection.SetMaxOpenConns(maxOpenConnections) _db_connections = append(_db_connections, _db_connection) @@ -55,15 +77,24 @@ func getDbConnection(confs []MySqlConf) []*sql.DB { return _db_connections } -func NewMysqlStore(confs []MySqlConf) *MySqlStore { +func NewMysqlStore(confs []MySqlConf, isSharding bool, shardingNum int) *MySqlStore { ms := &MySqlStore{ - dbs: getDbConnection(confs), + dbs: getDbConnection(confs), + isSharding: isSharding, + shardingNum: shardingNum, } for _, db := range ms.dbs { - for i := 0; i < maxTableNums; i++ { + if !isSharding { + ms.shardingNum = 1 + } else { + if ms.shardingNum == 0 { + ms.shardingNum = default_maxTableNums + } + } + for i := 0; i < ms.shardingNum; i++ { if err := ms.createTables(db, tableName, i); err != nil { - fmt.Printf("create table failed %s", err.Error()) + fmt.Printf("create table failed %v", err) } } } @@ -74,21 +105,25 @@ func NewMysqlStore(confs []MySqlConf) *MySqlStore { func (s *MySqlStore) hash(fullFileName string) (instance_offset, table_postfix int) { hash_value := crc32.ChecksumIEEE([]byte(fullFileName)) instance_offset = int(hash_value) % len(s.dbs) - table_postfix = int(hash_value) % maxTableNums + table_postfix = int(hash_value) % s.shardingNum return } func (s *MySqlStore) parseFilerMappingInfo(path string) (instanceId int, tableFullName string, err error) { instance_offset, table_postfix := s.hash(path) instanceId = instance_offset - tableFullName = fmt.Sprintf("%s_%04d", tableName, table_postfix) + if s.isSharding { + tableFullName = fmt.Sprintf("%s_%04d", tableName, table_postfix) + } else { + tableFullName = tableName + } return } func (s *MySqlStore) Get(fullFilePath string) (fid string, err error) { instance_offset, tableFullName, err := s.parseFilerMappingInfo(fullFilePath) if err != nil { - return "", err + return "", fmt.Errorf("MySqlStore Get operation can not parse file path %s: err is %v", fullFilePath, err) } fid, err = s.query(fullFilePath, s.dbs[instance_offset], tableFullName) if err == sql.ErrNoRows { @@ -103,16 +138,18 @@ func (s *MySqlStore) Put(fullFilePath string, fid string) (err error) { instance_offset, tableFullName, err := s.parseFilerMappingInfo(fullFilePath) if err != nil { - return err + return fmt.Errorf("MySqlStore Put operation can not parse file path %s: err is %v", fullFilePath, err) } - if old_fid, localErr := s.query(fullFilePath, s.dbs[instance_offset], tableFullName); localErr != nil && localErr != sql.ErrNoRows { - err = localErr - return + var old_fid string + if old_fid, err = s.query(fullFilePath, s.dbs[instance_offset], tableFullName); err != nil && err != sql.ErrNoRows { + return fmt.Errorf("MySqlStore Put operation failed when querying path %s: err is %v", fullFilePath, err) } else { if len(old_fid) == 0 { err = s.insert(fullFilePath, fid, s.dbs[instance_offset], tableFullName) + err = fmt.Errorf("MySqlStore Put operation failed when inserting path %s with fid %s : err is %v", fullFilePath, fid, err) } else { err = s.update(fullFilePath, fid, s.dbs[instance_offset], tableFullName) + err = fmt.Errorf("MySqlStore Put operation failed when updating path %s with fid %s : err is %v", fullFilePath, fid, err) } } return @@ -122,15 +159,15 @@ func (s *MySqlStore) Delete(fullFilePath string) (err error) { var fid string instance_offset, tableFullName, err := s.parseFilerMappingInfo(fullFilePath) if err != nil { - return err + return fmt.Errorf("MySqlStore Delete operation can not parse file path %s: err is %v", fullFilePath, err) } if fid, err = s.query(fullFilePath, s.dbs[instance_offset], tableFullName); err != nil { - return err + return fmt.Errorf("MySqlStore Delete operation failed when querying path %s: err is %v", fullFilePath, err) } else if fid == "" { return nil } - if err := s.delete(fullFilePath, s.dbs[instance_offset], tableFullName); err != nil { - return err + if err = s.delete(fullFilePath, s.dbs[instance_offset], tableFullName); err != nil { + return fmt.Errorf("MySqlStore Delete operation failed when deleting path %s: err is %v", fullFilePath, err) } else { return nil } @@ -143,7 +180,7 @@ func (s *MySqlStore) Close() { } var createTable = ` -CREATE TABLE IF NOT EXISTS %s_%04d ( +CREATE TABLE IF NOT EXISTS %s ( id bigint(20) NOT NULL AUTO_INCREMENT, uriPath char(256) NOT NULL DEFAULT "" COMMENT 'http uriPath', fid char(36) NOT NULL DEFAULT "" COMMENT 'seaweedfs fid', @@ -153,11 +190,18 @@ CREATE TABLE IF NOT EXISTS %s_%04d ( status tinyint(2) DEFAULT '1' COMMENT 'resource status', PRIMARY KEY (id), UNIQUE KEY index_uriPath (uriPath) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; +) DEFAULT CHARSET=utf8; ` func (s *MySqlStore) createTables(db *sql.DB, tableName string, postfix int) error { - stmt, err := db.Prepare(fmt.Sprintf(createTable, tableName, postfix)) + var realTableName string + if s.isSharding { + realTableName = fmt.Sprintf("%s_%4d", tableName, postfix) + } else { + realTableName = tableName + } + + stmt, err := db.Prepare(fmt.Sprintf(createTable, realTableName)) if err != nil { return err } diff --git a/weed/filer/mysql_store/mysql_store_test.go b/weed/filer/mysql_store/mysql_store_test.go index 2bfe26dc8..1c9765c59 100644 --- a/weed/filer/mysql_store/mysql_store_test.go +++ b/weed/filer/mysql_store/mysql_store_test.go @@ -6,36 +6,6 @@ import ( "testing" ) -/* -To improve performance when storing billion of files, you could shar -At each mysql instance, we will try to create 1024 tables if not exist, table name will be something like: -filer_mapping_0000 -filer_mapping_0001 -..... -filer_mapping_1023 -sample conf should be - ->$cat filer_conf.json -{ - "mysql": [ - { - "User": "root", - "Password": "root", - "HostName": "127.0.0.1", - "Port": 3306, - "DataBase": "seaweedfs" - }, - { - "User": "root", - "Password": "root", - "HostName": "127.0.0.2", - "Port": 3306, - "DataBase": "seaweedfs" - } - ] -} -*/ - func TestGenerateMysqlConf(t *testing.T) { var conf []MySqlConf conf = append(conf, MySqlConf{ diff --git a/weed/server/filer_server.go b/weed/server/filer_server.go index 1da0d065d..1bcbd046f 100644 --- a/weed/server/filer_server.go +++ b/weed/server/filer_server.go @@ -23,6 +23,7 @@ import ( type filerConf struct { MysqlConf []mysql_store.MySqlConf `json:"mysql"` + mysql_store.ShardingConf } func parseConfFile(confPath string) (*filerConf, error) { @@ -83,7 +84,7 @@ func NewFilerServer(r *http.ServeMux, ip string, port int, master string, dir st } if setting.MysqlConf != nil && len(setting.MysqlConf) != 0 { - mysql_store := mysql_store.NewMysqlStore(setting.MysqlConf) + mysql_store := mysql_store.NewMysqlStore(setting.MysqlConf, setting.IsSharding, setting.ShardingNum) fs.filer = flat_namespace.NewFlatNamespaceFiler(master, mysql_store) } else if cassandra_server != "" { cassandra_store, err := cassandra_store.NewCassandraStore(cassandra_keyspace, cassandra_server)