Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Владислав Шпилевой
Горизонтальное
Вертикальное
Репликация
Шардинг
ABCD
ABCD
ABCD
ABCD
A
C
D
B
Диапазоны
Хеши
function shard_function(primary_key)
return guava(crc32(primary_key), shard_count)
end
Первый шардинг в Tarantool
Request
old hash
new hash
Нет локальности
Медленный решардинг
Нестабильные чтения
SELECT
format = {{'id', 'unsigned'},
{'email', 'string'}}
box.schema.create_space('customer', {format = format})
format = {{'id', 'unsigned'},
{'customer_id', 'unsigned'},
{'balance', 'number'}}
box.schema.create_space('account', {format = format})
user {100, 'foo@mail.ru'}
account {1, 100, 123}
100 != 1 → разные хеши → разные узлы
Buckets
Replicaset
Data → Bucket → Storage
User defined map
Internal map
format = {{'id', 'unsigned'},
{'email', 'string'},
{'bucket_id', 'unsigned'}}
box.schema.create_space('customer', {format = format})
format = {{'id', 'unsigned'},
{'customer_id', 'unsigned'},
{'balance', 'number'},
{'bucket_id', 'unsigned'}}
box.schema.create_space('account', {format = format})
Локальность по bucket_id
Связка данных
user { 100, 'bar@mail.ru', 5 }
account {1, 100, 123, 5 }
5
Что именно это дает?
format = {{'id', 'unsigned'},
{'email', 'string'}}
box.schema.create_space('customer', {format = format})
format = {{'id', 'unsigned'},
{'customer_id', 'unsigned'},
{'balance', 'number'}}
box.schema.create_space('account', {format = format})
Связка данных
customer {1, 'bar@mail.ru'}
account {1, 1, 100}
account {2, 1, 200}
account {3, 1, 300}
SELECT * FROM accounts WHERE customer_id = 1;
3 запроса
customer {1, 'bar@mail.ru', 5}
account {1, 1, 100, 5}
account {2, 1, 200, 5}
account {3, 1, 300, 5}
1 запрос
...
...
5
Обычный хеш-шардинг
VShard
function create_user(email)
local customer_id = next_id()
local bucket_id = crc32(customer_id)
box.space.customer:insert(customer_id, email, bucket_id)
end
function add_account(customer_id)
local id = next_id()
local bucket_id = crc32(customer_id)
box.space.account:insert(id, customer_id, 0, bucket_id)
end
Как масштабировать?
vshard.storage.cfg(new_config)
Failover
Rebalancer
Zookeeper
new_cfg.sharding[new_uuid] = {
uuid = new_uuid,
replicas = {
{
name = new_replica1,
uri = 'example.com:113',
uuid = replica1_uuid,
master = true,
},
{
name = new_replica2,
uri = 'example.com:114',
uuid = replica2_uuid,
}
}
}
vshard.storage.cfg(new_cfg)
#
= Const
Как это сделать?
Routes
Routes
100
200
#
= 1000
#
= 2000
new_cfg.sharding[uuid1].weight = 100
new_cfg.sharding[uuid2].weight = 200
vshard.storage.cfg(new_cfg)
Data
new_cfg.sharding[uuid1].weight = 0
vshard.storage.cfg(new_cfg)
= 0
0
Состояние 1
Состояние 2
Атомарный переход
Это плохой способ
Это хороший способ
Не нужны выделенные состояния кластера - есть только состояния бакетов
Ошибка
Ошибка
Ошибка
Плавный переход
Read
Write
Сеть
Read
Write
Read
Как не блокировать запись?
Write
Что с уже имеющимися запросами?
vshard.storage.bucket_refro(i)
-- Do some read-only things.
vshard.storage.bucket_unrefro(i)
vshard.storage.bucket_refrw(i)
-- Do some read-write things.
vshard.storage.bucket_unrefrw(i)
Подсчет числа запросов
Reading ...
Reading ...
Reading ...
ref_ro = 3
Сборщик мусора
Writing ...
Writing ...
Writing ...
ref_rw = 3
Ребалансировщик
ref_ro = 2
ref_ro = 1
... но если очень хочется, то можно
box.begin()
vshard.storage.bucket_pin(10)
vshard.storage.bucket_pin(11)
box.commit()
--
-- Do anything with these buckets.
--
box.begin()
vshard.storage.bucket_unpin(10)
vshard.storage.bucket_unpin(11)
box.commit()
new_cfg.sharding[uuid].lock = true
vshard.storage.cfg(new_cfg)
?
bucket_id → storage
vshard.router
r = vshard.router
accounts = r.call(bucket_id,
'get_accounts',
{customer_id})
function get_accounts(customer_id)
local customer =
box.space.accounts.index.customer
return customer:select({customer_id})
end
Routing
?
Write
Read
Зона 1
Зона 2
Зона ...
Зона K - 1
Зона K
Зона N
Зона 1 | Зона 2 | ... | Зона K | |
Зона 1 | w(1, 1) | w(1, 2) | w(1, i) | w(1, K) |
Зона 2 | w(2, 1) | w(2, 2) | w(2, i) | w(2, K) |
... | w(j, 1) | w(j, 2) | w(j, i) | w(j, K) |
Зона K | w(K, 1) | w(K, 2) | w(K, i) | w(K, K) |
new_cfg.weights = {
[1] = {
[1] = 0,
[2] = 100,
[3] = 200,
},
[2] = {
[1] = 300,
[2] = 0,
[3] = 400,
},
[3] = {
[1] = 500,
[2] = 600,
[3] = 0,
}
}
new_cfg.zone = 1
vshard.router.cfg(new_cfg)
Зона 1
Зона 2
Зона 3
100
200
300
400
500
600
replicas = new_cfg.sharding[uud].replicas
replicas[old_master_uuid].master = false
replicas[new_master_uuid].master = true
vshard.storage.cfg(new_cfg)
Read
Write
Be Master
Write
Write
Sync
Read
Write
vshard.storage.info()
---
- replicasets:
<replicaset_2>:
uuid: <replicaset_2>
master:
uri: storage@127.0.0.1:3303
<replicaset_1>:
uuid: <replicaset_1>
master: missing
bucket:
receiving: 0
active: 0
total: 0
garbage: 0
pinned: 0
sending: 0
status: 2
replication:
status: slave
alerts:
- ['MISSING_MASTER', 'Master is not configured for '
'replicaset <replicaset_1>']
vshard.storage.buckets_info()
vshard.router.info()
---
- replicasets:
<replicaset_2>:
replica: &0
status: available
uri: storage@127.0.0.1:3303
uuid: 1e02ae8a-afc0-4e91-ba34-843a356b8ed7
bucket:
available_rw: 500
uuid: <replicaset_2>
master: *0
<replicaset_1>:
replica: &1
status: available
uri: storage@127.0.0.1:3301
uuid: 8a274925-a26d-47fc-9e1b-af88ce939412
bucket:
available_rw: 400
uuid: <replicaset_1>
master: *1
bucket:
unreachable: 0
available_ro: 800
unknown: 200
available_rw: 700
status: 1
alerts:
- ['UNKNOWN_BUCKETS', '200 buckets are not discovered']
#
?
Метаданные
vs
Доступность на запись при решардинге
Kb
Mb
Тысячи
Сотни тысяч
function bucket_id(tuple)
...
end
99%
99.99999%
... локальность данных,
... гранулярный автоматический решардинг,
... auto read failover
... гибкое управление доступностью: рефы, пины, локи, веса
By Vladislav Shpilevoy
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.