VShard - горизонтальное масштабирование в Tarantool

Владислав Шпилевой

План доклада

  1. Что это?
  2. Shard
  3. VShard
  4. Использование
  5. Ребалансировка
  6. Прокси
  7. Auto read failover
  8. Write failover
  9. Мониторинг

Масштабирование

Горизонтальное

Вертикальное

Репликация

Шардинг

ABCD

ABCD

ABCD

ABCD

A

C

D

B

Диапазоны

Хеши

Tarantool Shard

function shard_function(primary_key)
    return guava(crc32(primary_key), shard_count)
end

Первый шардинг в Tarantool

Request

old hash

new hash

Нет локальности

Медленный решардинг

Нестабильные чтения

SELECT

Tarantool Shard

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 → разные хеши → разные узлы

Tarantool VShard

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

... но если очень хочется, то можно

Bucket Pin

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()

Replicaset Lock

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

?

Auto read failover

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)

Auto read failover

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

Write failover

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
storage\_meta = ~30\ byte\ /\ bucket\\ router\_meta = ~16\ byte\ /\ bucket

99%

99.99999%

Итоги

... локальность данных,

... гранулярный автоматический решардинг,

... auto read failover

... гибкое управление доступностью: рефы, пины, локи, веса

Планы

  • Балансировка нагрузки на роутерах
  • Lock-free перенос бакета
  • Атомарная авто-реконфигурация