cubefs/blobstore/common/codemode/codemode.go
zhangjianwei2 00fa06c716 feat(bssdk): support specify code_mode and cluster_id for access sdk put api
fixes: https://github.com/cubefs/cubefs/issues/3954

@formatter:off

Signed-off-by: zhangjianwei2 <zhangjianwei2@cmss.chinamobile.com>
2026-04-21 09:23:09 +08:00

443 lines
13 KiB
Go

// Copyright 2022 The CubeFS Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
package codemode
import (
"fmt"
)
type (
// CodeMode EC encode and decode mode
CodeMode uint8
CodeModeName string
)
// pre-defined mode
const (
CodeModeNone CodeMode = 0
EC15P12 CodeMode = 1
EC6P6 CodeMode = 2
EC16P20L2 CodeMode = 3
EC6P10L2 CodeMode = 4
EC6P3L3 CodeMode = 5
EC6P6Align0 CodeMode = 6
EC6P6Align512 CodeMode = 7
EC4P4L2 CodeMode = 8
EC12P4 CodeMode = 9
EC16P4 CodeMode = 10
EC3P3 CodeMode = 11
EC10P4 CodeMode = 12
EC6P3 CodeMode = 13
EC12P9 CodeMode = 14
EC24P8 CodeMode = 15
// Replica3 replicate mode
Replica3 CodeMode = 100
Replica3OneAZ CodeMode = 101
// for test
EC6P6L9 CodeMode = 200
EC6P8L10 CodeMode = 201
Replica4TwoAZ CodeMode = 202
)
// Note: Don't modify it unless you know very well how codemode works.
const (
// align size per shard
alignSize0B = 0 // 0B
alignSize512B = 512 // 512B
alignSize2KB = 2048 // 2KB
)
// The tactic is fixed pairing with one codemode.
// Add a new codemode if you want other features.
var constCodeModeTactic = map[CodeMode]Tactic{
// three az
EC15P12: {N: 15, M: 12, L: 0, AZCount: 3, PutQuorum: 24, GetQuorum: 0, MinShardSize: alignSize2KB},
EC6P6: {N: 6, M: 6, L: 0, AZCount: 3, PutQuorum: 11, GetQuorum: 0, MinShardSize: alignSize2KB},
EC12P9: {N: 12, M: 9, L: 0, AZCount: 3, PutQuorum: 20, GetQuorum: 0, MinShardSize: alignSize2KB},
// two az
EC16P20L2: {N: 16, M: 20, L: 2, AZCount: 2, PutQuorum: 34, GetQuorum: 0, MinShardSize: alignSize2KB},
EC6P10L2: {N: 6, M: 10, L: 2, AZCount: 2, PutQuorum: 14, GetQuorum: 0, MinShardSize: alignSize2KB},
// single az
EC12P4: {N: 12, M: 4, L: 0, AZCount: 1, PutQuorum: 15, GetQuorum: 0, MinShardSize: alignSize2KB},
EC16P4: {N: 16, M: 4, L: 0, AZCount: 1, PutQuorum: 19, GetQuorum: 0, MinShardSize: alignSize2KB},
EC3P3: {N: 3, M: 3, L: 0, AZCount: 1, PutQuorum: 5, GetQuorum: 0, MinShardSize: alignSize2KB},
EC10P4: {N: 10, M: 4, L: 0, AZCount: 1, PutQuorum: 13, GetQuorum: 0, MinShardSize: alignSize2KB},
EC6P3: {N: 6, M: 3, L: 0, AZCount: 1, PutQuorum: 8, GetQuorum: 0, MinShardSize: alignSize2KB},
EC24P8: {N: 24, M: 8, L: 0, AZCount: 1, PutQuorum: 30, GetQuorum: 0, MinShardSize: alignSize2KB},
// for env test
EC6P3L3: {N: 6, M: 3, L: 3, AZCount: 3, PutQuorum: 9, GetQuorum: 0, MinShardSize: alignSize2KB},
EC6P6Align0: {N: 6, M: 6, L: 0, AZCount: 3, PutQuorum: 11, GetQuorum: 0, MinShardSize: alignSize0B},
EC6P6Align512: {N: 6, M: 6, L: 0, AZCount: 3, PutQuorum: 11, GetQuorum: 0, MinShardSize: alignSize512B},
EC4P4L2: {N: 4, M: 4, L: 2, AZCount: 2, PutQuorum: 6, GetQuorum: 0, MinShardSize: alignSize2KB},
EC6P6L9: {N: 6, M: 6, L: 9, AZCount: 3, PutQuorum: 11, GetQuorum: 0, MinShardSize: alignSize2KB},
EC6P8L10: {N: 6, M: 8, L: 10, AZCount: 2, PutQuorum: 13, GetQuorum: 0, MinShardSize: alignSize0B},
Replica4TwoAZ: {N: 4, M: 0, L: 0, AZCount: 2, PutQuorum: 3},
// for replicate
Replica3: {N: 3, M: 0, L: 0, AZCount: 3, PutQuorum: 3},
Replica3OneAZ: {N: 3, M: 0, L: 0, AZCount: 1, PutQuorum: 3},
}
var constName2CodeMode = map[CodeModeName]CodeMode{
"EC15P12": EC15P12,
"EC6P6": EC6P6,
"EC16P20L2": EC16P20L2,
"EC6P10L2": EC6P10L2,
"EC6P3L3": EC6P3L3,
"EC6P6Align0": EC6P6Align0,
"EC6P6Align512": EC6P6Align512,
"EC4P4L2": EC4P4L2,
"EC12P4": EC12P4,
"EC16P4": EC16P4,
"EC3P3": EC3P3,
"EC10P4": EC10P4,
"EC6P3": EC6P3,
"EC12P9": EC12P9,
"EC24P8": EC24P8,
"Replica3": Replica3,
"Replica3OneAZ": Replica3OneAZ,
"EC6P6L9": EC6P6L9,
"EC6P8L10": EC6P8L10,
"Replica4TwoAZ": Replica4TwoAZ,
}
var constCodeMode2Name = map[CodeMode]CodeModeName{
EC15P12: "EC15P12",
EC6P6: "EC6P6",
EC16P20L2: "EC16P20L2",
EC6P10L2: "EC6P10L2",
EC6P3L3: "EC6P3L3",
EC6P6Align0: "EC6P6Align0",
EC6P6Align512: "EC6P6Align512",
EC4P4L2: "EC4P4L2",
EC12P4: "EC12P4",
EC16P4: "EC16P4",
EC3P3: "EC3P3",
EC10P4: "EC10P4",
EC6P3: "EC6P3",
EC12P9: "EC12P9",
EC24P8: "EC24P8",
Replica3: "Replica3",
Replica3OneAZ: "Replica3OneAZ",
EC6P6L9: "EC6P6L9",
EC6P8L10: "EC6P8L10",
Replica4TwoAZ: "Replica4TwoAZ",
}
//vol layout ep:EC6P10L2
//|----N------|--------M----------------|--L--|
//|0,1,2,3,4,5|6,7,8,9,10,11,12,13,14,15|16,17|
// global stripe:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], n=6 m=10
// two local stripes:
// local stripe1:[0,1,2, 6, 7, 8, 9,10, 16] n=8 m=1
// local stripe2:[3,4,5, 11,12,13,14,15, 17] n=8 m=1
// Tactic constant strategy of one CodeMode
type Tactic struct {
N int `json:"n"`
M int `json:"m"`
// local parity count
L int `json:"l"`
// the count of AZ, access use this for split data shards and parity shards
AZCount int `json:"az_count"`
// PutQuorum write quorum,
// MUST make sure that ec data is recoverable if one AZ was down
// We SHOULD ignore the local shards
// (N + M) / AZCount + N <= PutQuorum <= M + N
PutQuorum int `json:"put_quorum"`
// get quorum config
GetQuorum int `json:"get_quorum"`
// MinShardSize min size per shard, fill data into shards 0-N continuously,
// align with zero bytes if data size less than MinShardSize*N
//
// length of data less than MinShardSize*N, size of per shard = MinShardSize
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// | data | align zero bytes |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// | 0 | 1 | 2 | .... | N |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// length of data more than MinShardSize*N, size of per shard = ceil(len(data)/N)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// | data |padding|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// | 0 | 1 | 2 | .... | N |
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MinShardSize int `json:"min_shard_size"`
}
func init() {
// assert all codemode
for _, pair := range []struct {
Mode CodeMode
Size int
}{
{Mode: EC15P12, Size: alignSize2KB},
{Mode: EC6P6, Size: alignSize2KB},
{Mode: EC12P9, Size: alignSize2KB},
{Mode: EC16P20L2, Size: alignSize2KB},
{Mode: EC6P10L2, Size: alignSize2KB},
{Mode: EC6P3L3, Size: alignSize2KB},
{Mode: EC6P6Align0, Size: alignSize0B},
{Mode: EC6P6Align512, Size: alignSize512B},
} {
tactic := pair.Mode.Tactic()
if !tactic.IsValid() {
panic(fmt.Sprintf("Invalid codemode:%d Tactic:%+v", pair.Mode, tactic))
}
min := tactic.N + (tactic.N+tactic.M)/tactic.AZCount
max := tactic.N + tactic.M
if tactic.PutQuorum < min || tactic.PutQuorum > max {
panic(fmt.Sprintf("Invalid codemode:%d PutQuorum:%d([%d,%d])", pair.Mode,
tactic.PutQuorum, min, max))
}
if tactic.MinShardSize != pair.Size {
panic(fmt.Sprintf("Invalid codemode:%d MinShardSize:%d(%d)", pair.Mode,
tactic.MinShardSize, pair.Size))
}
}
}
// T returns pointer of Tactic, used like:
// EC6P6.T().AllLocalStripe()
func (c CodeMode) T() *Tactic {
tactic := c.Tactic()
return &tactic
}
// Tactic returns its constant tactic
func (c CodeMode) Tactic() Tactic {
if tactic, ok := constCodeModeTactic[c]; ok {
return tactic
}
panic(fmt.Sprintf("Invalid codemode:%d", c))
}
// GetShardNum returns all shards number.
func (c CodeMode) GetShardNum() int {
tactic := c.Tactic()
return tactic.L + tactic.M + tactic.N
}
// Name turn the CodeMode to CodeModeName
func (c CodeMode) Name() CodeModeName {
if name, ok := constCodeMode2Name[c]; ok {
return name
}
panic(fmt.Sprintf("codemode: %d is invalid", c))
}
// String turn the CodeMode to string
func (c CodeMode) String() string {
if name, ok := constCodeMode2Name[c]; ok {
return string(name)
}
return ""
}
// IsValid check the CodeMode is valid
func (c CodeMode) IsValid() bool {
_, ok := constCodeMode2Name[c]
return ok
}
// GetCodeMode get the code mode by name
func (cn CodeModeName) GetCodeMode() CodeMode {
if code, ok := constName2CodeMode[cn]; ok {
return code
}
panic(fmt.Sprintf("codemode: %s is invalid", cn))
}
// IsValid check the CodeMode is valid by Name
func (cn CodeModeName) IsValid() bool {
_, ok := constName2CodeMode[cn]
return ok
}
// Tactic get tactic by code mode name
func (cn CodeModeName) Tactic() Tactic {
return cn.GetCodeMode().Tactic()
}
// IsValid ec tactic valid or not
func (c *Tactic) IsValid() bool {
if c.IsReplicateMode() {
return c.N > 0 && c.AZCount > 0 && c.N%c.AZCount == 0 &&
c.PutQuorum > 0 && c.GetQuorum >= 0
}
return c.N > 0 && c.M > 0 && c.L >= 0 && c.AZCount > 0 &&
c.PutQuorum > 0 && c.GetQuorum >= 0 && c.MinShardSize >= 0 &&
c.N%c.AZCount == 0 && c.M%c.AZCount == 0 && c.L%c.AZCount == 0
}
// GetECLayoutByAZ ec layout by AZ
func (c *Tactic) GetECLayoutByAZ() (azStripes [][]int) {
azStripes = make([][]int, c.AZCount)
n, m, l := c.N/c.AZCount, c.M/c.AZCount, c.L/c.AZCount
for idx := range azStripes {
stripe := make([]int, 0, n+m+l)
for i := 0; i < n; i++ {
stripe = append(stripe, idx*n+i)
}
for i := 0; i < m; i++ {
stripe = append(stripe, c.N+idx*m+i)
}
for i := 0; i < l; i++ {
stripe = append(stripe, c.N+c.M+idx*l+i)
}
azStripes[idx] = stripe
}
return azStripes
}
// GlobalStripe returns initial stripe return name.GetCodeMode().Tactic()
func (c *Tactic) GlobalStripe() (indexes []int, n, m int) {
indexes = make([]int, c.N+c.M)
for i := 0; i < c.N+c.M; i++ {
indexes[i] = i
}
return indexes, c.N, c.M
}
// AllLocalStripe returns all local stripes
func (c *Tactic) AllLocalStripe() (stripes [][]int, n, m int) {
if c.L == 0 {
return
}
n, m, l := c.N/c.AZCount, c.M/c.AZCount, c.L/c.AZCount
return c.GetECLayoutByAZ(), n + m, l
}
// LocalStripe get local stripe by index
func (c *Tactic) LocalStripe(index int) (localStripe []int, n, m int) {
if c.L == 0 {
return nil, 0, 0
}
n, m, l := c.N/c.AZCount, c.M/c.AZCount, c.L/c.AZCount
var azIdx int
if index < c.N {
azIdx = index / n
} else if index < c.N+c.M {
azIdx = (index - c.N) / m
} else if index < c.N+c.M+c.L {
azIdx = (index - c.N - c.M) / l
} else {
return nil, 0, 0
}
return c.LocalStripeInAZ(azIdx)
}
// LocalStripeInAZ get local stripe in az index
func (c *Tactic) LocalStripeInAZ(azIndex int) (localStripe []int, n, m int) {
if c.L == 0 {
return nil, 0, 0
}
n, m, l := c.N/c.AZCount, c.M/c.AZCount, c.L/c.AZCount
azStripes := c.GetECLayoutByAZ()
if azIndex < 0 || azIndex >= len(azStripes) {
return nil, 0, 0
}
return azStripes[azIndex][:], n + m, l
}
// IsReplicateMode return current mode tactic is replicate or not
func (c *Tactic) IsReplicateMode() bool {
return c.M == 0 && c.L == 0
}
// GetECCodeModes get all available ec CodeModes
func GetECCodeModes() []CodeMode {
modes := make([]CodeMode, 0)
for _, mode := range GetAllCodeModes() {
if !mode.T().IsReplicateMode() {
modes = append(modes, mode)
}
}
return modes
}
// GetAllCodeModes get all the available CodeModes
func GetAllCodeModes() []CodeMode {
modes := make([]CodeMode, 0, len(constCodeModeTactic))
for mode := range constCodeModeTactic {
modes = append(modes, mode)
}
return modes
}
const extendStart CodeMode = (256 - 16)
// ExtendCodeMode use-defined codemode.
// Notice, you MUST well know the codemode,
// and CANNOT change it when it was setted.
type ExtendCodeMode struct {
CodeMode CodeMode `json:"code_mode"`
Name CodeModeName `json:"name"`
Tactic
}
// Extend adds extend codemodes.
func Extend(modes ...ExtendCodeMode) {
for _, mode := range modes {
code := mode.CodeMode
tactic := mode.Tactic
if code < extendStart {
panic(fmt.Sprintf("codemode:%d not in extend [%d-255]", code, extendStart))
}
if !tactic.IsValid() {
panic(fmt.Sprintf("codemode:%d invalid:%+v", code, tactic))
}
if tactic.PutQuorum < tactic.N {
panic(fmt.Sprintf("codemode:%d too small put quorum", code))
}
if oldtactic, exist := constCodeModeTactic[code]; exist {
if oldtactic != tactic {
panic(fmt.Sprintf("codemode:%d code conflicted", code))
}
if oldname := constCodeMode2Name[code]; oldname != mode.Name {
panic(fmt.Sprintf("codemode:%d name mismatch", code))
}
continue
}
if _, exist := constName2CodeMode[mode.Name]; exist {
panic(fmt.Sprintf("codemode:%d name conflicted %s", code, mode.Name))
}
constCodeModeTactic[code] = tactic
constCodeMode2Name[code] = mode.Name
constName2CodeMode[mode.Name] = code
}
}