Skip to content

my-mail-ru/perl-MR-Tarantool-Box-Record

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ActiveRecord для коробки Tarantool/Octopus

MR::Tarantool::Box::Record - объектная обертка над клиентом тарантула/октопуса MR::Tarantool::Box::XS. Каждый класс соответствует неймспейсу коробки, каждый объект - одному туплу.

Описание класса

Класс тупла объявляется аналогично любому маусовскому классу, за иcключением того, что вместо класса Mouse используется MR::Tarantool::Box::Record. Это приводит к тому, что:

  • класс автоматически становится наследником класса MR::Tarantool::Box::Record::Object;
  • к его метаклассу применяется роль MR::Tarantool::Box::Record::Trait::Class, для хранения структуры полей и опций коробки;
  • импортируются дополнительные sugar-функции: iproto, namespace, shard_by, has_field, has_field_object, has_index, has_index_part.
package Sample::Record;

use MR::Tarantool::Box::Record;

# Собственно класс здесь

no MR::Tarantool::Box::Record;
__PACKAGE__->meta->make_immutable();

1;

Sugar-функции

iproto

Используется для того, чтобы указать, какой инcтанс клиента iproto-xs использовать для подключения к кластеру. Принимает либо объект MR::IProto::XS, либо параметры для его конструктора (либо имя параметра в onlineconf, если используется MR::OnlineConf::Generic::Trait::Class::TarantoolBoxRecord):

iproto($iproto);

iproto masters => ['1.2.3.4:1234'];

iproto '/my/video/video-shard-box';

namespace

Задает номер неймспейса:

namespace 22;

shard_by

Определят функцию выбора шарда для новой записи:

shard_by { $_[0]->user->video->video_shard_num() };

has_field

Аналогична has и создает соответствующий атрибут, но помимо этого еще применяет к его метаклассу роль MR::Tarantool::Box::Record::Trait::Attribute::Field.

Понимает следующие дополнительные опции:

  • number - номер поля в тупла, автоматически вычисляется на основании порядка объявления полей в модуле;
  • format - односимвольный формат хранимых в поле данных (по умолчанию: &), при отсутствии прочих опций допускается сокращенная форма записи;
  • primary_key - является ли поле первичным ключем;
  • index - имя индекса, если поле является индексным;
  • uniq - уникальный ли индекс (по умолчанию - уникальный);
  • shard_by - функция (или имя метода), используемая для вычисления шарда на основании данных этого поля;
  • index_number - номер индекса, автоматически вычисляется на основании порядка объявления индексов;
  • selector - имя метода-селектора, который нужно создать для соответствующего индекса;
  • mutators - список методов-модификаторов, которые нужно создать для поля для выполнения спец-операций, допустимо: inc, dec, add, and, or, xor, set_bit, clear_bit;
  • serialize и deserialize - функции для упаковки/распаковки данных, вызываются при каждом чтении/записи (без lazy), поэтому использовать очень осторожно во избежание ухудшения производительности;
  • sequence - является ли поле автоинкрементным;
  • sequence_id - номер сиквенса, по умолчанию совпадает с номером неймспейса;
  • sequence_iproto - кластер iproto, используемый для хранения неймспейса с сиквенсами;
  • sequence_namespace - номер неймспейса с сиквенсами;
  • size - длина поля в байтах, для числовых полей вычисляется автоматически (используется только для прогнозирования объема хранилища);
  • min_size - минимальная длина строкового поля в байтах (используется только для прогнозирования объема хранилища);
  • max_size - максимальная длина строкового поля в байтах (используется только для прогнозирования объема хранилища).

Порядок, в котором перечисляются поля в классе важен и должен строго соответствовать порядку полей в тупле.

has_field id => (
    format   => 'L',
    selector => 'select_by_id',
    index    => 'primary_id',
    sequence    => 1,
    primary_key => 1,
);

has_field first_name => '&';
has_field last_name => '&';

has_field shard_num => (
    format  => 'S',
    default => sub { My::ShardBoxBase->get_shard_for_new_user($_[0]->id) },
);

has_field base => (
    format      => 'L',
    serialize   => sub { $_ ? unpack('N', Socket::inet_aton($_)) : 0 },
    deserialize => sub { $_ ? Socket::inet_ntoa(pack('N', $_)) : undef },
);

has_field owner_id => (
    format    => 'l',
    index     => 'owner',
    uniq      => 0,
    shard_by  => sub { 'all' },
);

has_field_object

Позволяет связать поле с атрибутом-объектом, например id пользователя с объектом пользователя. Фактически обертка над has и понимает следующие дополнительные параметры:

  • field - имя поля, с которым связывается объект;
  • key - имя метода-акцессора объекта, по которому можно получить ключ, по которому осуществляется связь;
  • shard_by - функция (или имя метода объекта), по которой можно определить в каком шарде храниться запись;
  • to_object - функция (или имя метода), позволяющая преобразовать значение поля в объект;
  • selectors - хэш имя_индекса => имя_метода селекторов, позволяющих делать запросы с указанием объектов вместо значений полей.
has_field owner_id => (
    format    => 'l',
    index     => 'owner',
    uniq      => 0,
    shard_by  => sub { 'all' },
);

has_field_object owner => (
    is        => 'ro',
    isa       => 'UGC::User',
    field     => 'owner_id',
    key       => 'ID',
    shard_by  => 'VideoShardNum',
    selectors => { owner => 'select_by_owner' },
    to_object => sub { UGC::User->SelectByID($_) },
);

has_index

Применяется для создания индексов (как правило, мультиколоночных, так как одноколоночные проще описывать прямо в has_field). Понимает следующие опции:

  • number - номер индекса, автоматически вычисляется на основании порядка объявления индексов;
  • fields - список участвующих в индексе полей;
  • uniq - уникальный ли индекс (по умолчанию: уникальный);
  • primary_key - индекс является первичным ключем;
  • default - использовать ли индекс по умолчанию;
  • selector - имя метода-селектора, который нужно создать для индекса;
  • shard_by - функция (или имя метода), используемая для вычисления шарда на основании данных полей индекса;
  • default_limit - лимит по умолчанию для запросов по неуникальному индексу.

В сокращенной форме допускается указывать только имя индекса и список полей, либо даже только имя или только список полей.

Порядок в котором перечисляются индексы (в том числе неявно в has_field) важен и должен совпадать с порядком индексов в конфиге тарантула.

# сокращенные формы:
has_index birthday => ['birthday_month', 'birthday_day']; # уникальный индекс с именем "birthday" по полям "birthday_month" и "birthday_day"
has_index 'email';                                        # уникальный индекс с именем "email" по полю "email"
has_index ['user_id', 'item_id'];                         # уникальный индекс с именем "user_id,item_id" по полям "user_id" и "item_id"

# неуникальный индекс (полная форма записи):
has_index birthay => (
    fields => ['birthday_month', 'birthday_day'],
    uniq   => 0,
);

has_index_part

Применяется для создания возможности делать выборки по части мультиколоночного индекса.  Допустимы параметры:

  • index - имя мультиколоночного индекса;
  • count - количество используемых полей мультиколоночного индекса (по умолчанию: 1);
  • uniq - уникальный ли индекс (по умолчанию: не уникальный);
  • selector - имя метода-селектора, который нужно создать для индекса;
  • shard_by - функция (или имя метода), используемая для вычисления шарда на основании данных полей индекса;
  • default_limit - лимит по умолчанию для запросов по неуникальному индексу.
has_index user_sort => ['user_id', 'sort_index'];

has_index_part user => 'user_sort'; # Индекс только по полю user_id из индекса user_sort
# эквивалентно
has_index_part user => (
    index => 'user_sort',
    count => 1,
);

Sequence

Зачастую возникает необходимость сделать поле автоинкрементным. Для этого можно завести (можно даже в стороночке) тарантул/октопус с неймспейсом, в котором будут храниться текущие значения всех сиквенсов. При этом удобно, чтобы для автоинкрементных первичных ключей номер сиквенса (значение первичного ключа в этом неймсейсе) совпадал с номером неймспейса. Все прочие сиквенсы лучше нумеровать откуда-нибудь от миллиона. Для автоматического использования этой возможности достаточно указать при объявлении поля параметр sequence => 1. Но, разумеется, если настройки кластера iproto и номер неймспейса для коробочки не указаны глобально, то придется указать и их. Чтобы задать эти настройки глобально можно (но не рекомендуется) сделать примерно следующее:

my $meta = MR::Tarantool::Box::Record->meta;
$meta->sequence_iproto($sequence_iproto);
$meta->sequence_namespace(5);

Вместо этого рекомендуется отнаследовать от MR::Tarantool::Box::Record свой класс как будет рассказано ниже.

Перед первым применением сиквенс необходимо инициализировать примерно следующим образом:

Sample::Record->meta->initialize_sequence('id'); # id - имя поля, для которого инициализируем sequence

Использование дочерних классов MR::IProto::XS и MR::Tarantool::Box::XS

Иногда бывает необходимо использоваться наследников этих классов, например чтобы считать определенную статистику. Сделать это можно (но не рекомендуется) примерно так:

my $meta = MR::Tarantool::Box::Record->meta;
$meta->iproto_class('My::IProto::XS');
$meta->box_class('My::Tarantool::Box::XS');

Вместо этого рекомендуется отнаследовать от MR::Tarantool::Box::Record свой класс как будет рассказано ниже.

Использование onlineconf

Для удобного использования онлайнконфа для конфигурирования клиента тарантула/октопуса есть два класса: MR::OnlineConf::Generic::IProtoXS и MR::OnlineConf::Generic::Trait::Class::TarantoolBoxRecord. Оба находятся в пакете perl-MR-OnlineConf-Generic.

Первый содержит в себе функцию get_iproto_config, которая позволяет считать из онлайнконфа конфигурацию кластера. Понимает конфигурацию с любой из следующих структур дерева:

  • без шардинга:
    • варинат 1:
      • path - список мастеров через запятую
      • path/replica - список реплик через запятую
    • вариант 2:
      • path/master - список мастеров через запятую
      • path/replica - список реплик через запятую
  • с шардингом:
    • варинат 1:
      • path/max-shard - количество шардов
      • path/i - список мастеров шарда i через запятую
      • path/i/replica - список реплик для шарда i через запятую
    • вариант 2:
      • path/max-shard - количество шардов
      • path/i/master - список мастеров шарда i через запятую
      • path/i/replica - список реплик для шарда i через запятую

Второй класс является трэйтом для метакласса ActiveRecord и позволяет использовать при вызове sugar-функции iproto имя параметра в онлайнконфе. Как подключать - описано чуть ниже.

Наследование от MR::Tarantool::Box::Record

Может быть удобно для определения глобальных настроек. Ниже пример, который позволяет:

  • использовать своих наследников от MR::IProto::XS и MR::Tarantool::Box::XS;
  • установить параметры неймспейса для сиквенсов;
  • использовать обертку для онлайнконф.
package My::Tarantool::Box::Record;

use Mouse::Exporter;
use MR::Tarantool::Box::Record ();
use MR::OnlineConf::Generic::IProtoXS;
use My::IProto::XS;
use My::Tarantool::Box::XS;

Mouse::Exporter->setup_import_methods(
    also => 'MR::Tarantool::Box::Record',
);

my $sequence_iproto = My::IProto::XS->new(%{MR::OnlineConf::Generic::IProtoXS::get_iproto_config('/my/system/sequence-box')});

sub init_meta {
    my ($class, %args) = @_;
    MR::Tarantool::Box::Record->init_meta(%args);
    Mouse::Util::MetaRole::apply_metaroles(
        for => $args{for_class},
        class_metaroles => {
            class => ['MR::OnlineConf::Generic::Trait::Class::TarantoolBoxRecord'],
        },
    );
    my $meta = $args{for_class}->meta;
    $meta->iproto_class('My::IProto::XS');
    $meta->box_class('My::Tarantool::Box::XS');
    $meta->sequence_iproto($sequence_iproto);
    $meta->sequence_namespace(5);
    return $meta;
}

1;

Хелперы для конфигурирования коробки

Для того, чтобы упростить написание конфигурации неймспейса и автоматизировать подсчет необходимой под него памяти, в метаклассе существует две вспомогательные функции: print_storage_config и print_approximate_storage_size.

print_storage_config

Печатает в STDOUT конфиг неймспейса. Можно скопипастить as is.

$ perl -MMR::mPOPlib -MMy::Video::Item -lwe 'My::Video::Item->meta->print_storage_config()'
# My::Video::Item
object_space[83].enabled = 1
object_space[83].index[0].type = "HASH"
object_space[83].index[0].unique = 1
object_space[83].index[0].key_field[0].fieldno = 0
object_space[83].index[0].key_field[0].type = "NUM64"
object_space[83].index[1].type = "TREE"
object_space[83].index[1].unique = 0
object_space[83].index[1].key_field[0].fieldno = 1
object_space[83].index[1].key_field[0].type = "NUM"
object_space[83].index[2].type = "TREE"
object_space[83].index[2].unique = 0
object_space[83].index[2].key_field[0].fieldno = 2
object_space[83].index[2].key_field[0].type = "NUM"
object_space[83].index[3].type = "TREE"
object_space[83].index[3].unique = 1
object_space[83].index[3].key_field[0].fieldno = 3
object_space[83].index[3].key_field[0].type = "NUM"
object_space[83].index[3].key_field[1].fieldno = 5
object_space[83].index[3].key_field[1].type = "NUM16"

print_approximate_storage_size

Печатает в STDOUT примерный размер хранилища. Учитывает размер памяти под хранение туплов, их фрагментацию, индексы и их заполнение. В качестве параметра принимает прогнозируемое количество записей. Если среди полей встречаются строки, то может понадобиться указать для них min_size, max_size либо size. Печатает две чиселки: минимальный размер при размере строк min_size и максимальный - для строк длиной max_size. Если максимальный размер не определен, значит для какого-то из строковых полей не указан max_size.

$ perl -MMR::mPOPlib -MMy::Video::Item -lwe 'My::Video::Item->meta->print_approximate_storage_size(500_000_000)'
98.84 Gb .. 247.85 Gb

Применение класса

Специальные атрибуты

В базовом классе объявлены следующие атрибуты:

  • shard_num - номер шарда, из которого была получена запись;
  • replica - была ли запись получена из реплики;
  • readonly - запрещены ли над записью операции модификации (если справедливо replica, то будет справедливо и readonly);
  • exists - запись получена из коробки.

Стандартные методы

select

Базовый класс MR::Tarantool::Box::Record содержит метод select, который позволяет сделать запрос к кластеру тарантула/октопуса и получить одну или несколько записей параллельно. Применяется примерно так:

$class->select($field => $keys, %opts);

# Получение одной записи по полю id:
my $record = Sample::Record->select(id => 10);

# Несколько записей по полю id:
my $list = Sample::Record->select(id => [10, 20, 30]);

# Если применяется шардинг, то:
my $record = Sample::Record->select(id => 10, shard_num => 7);
# Для выборки из нескольких шардов сразу чуть сложнее (1 и 7 - номера шардов):
my $list = Sample::Record->select(id => { 1 => [10, 20, 30], 7 => [12, 14, 18] });

# сразу же инициализировать атрибут user имеющимися значениями:
my $list = Sample::Record->select(id => [10, 20, 30], objects => { user => { 10 => $u1, 20 => $2, 30 => $3 } });
# или проще, если есть коллекция, умеющая прикидываться хэшом:
my $list = Sample::Record->select(id => [10, 20, 30], objects => { user => $users });

Помимо этого, если указать для индексного поля атрибут selector, то будет создан соответствующий метод и им можно будет пользоваться аналогично методу select, но не указывая имя ключа:

has_field id => (
    format   => 'l',
    selector => 'select_by_id',
    index    => 'primary_id',
    primary_key => 1,
);

my $record = Sample::Record->select_by_id(10);
my $list = Sample::Record->select_by_id([10, 20, 30]);

Метод понимает следующие дополнительные опции:

  • noraise_unavailable - не сбрасывать исключение, если не удалось получить данные из какого-либо шарда (по умолчанию 0);
  • by_object - вместо значений полей были переданы соответствующие объекты (по умолчанию 0);
  • objects - хэш уже готовых объектов для has_field_object (используется для оптимизации, см. ниже);
  • shard_num - номер шарда;
  • limit - лимит для запросов по неуникальным индексам (обязателен, если не сконфигурен default_limit соответствующего индекса);
  • а также передает все прочие параметры в запрос MR::Tarantool::Box::XS->bulk().

В тех случаях, когда необходимо заселектить по индексу, для полей которого есть соответствующие has_field_object, всегда следует предпочитать селектор, аргументами которого являются объекты (такие селекторы можно объявить в has_field_object либо использовать обычный селектор с опцией by_object => 1). Это позволяет автоматически, после выполнения селекта, проставить значения для соответствующих атрибутов, а также может использоваться при выполнении определения необходимого шарда при помощи метода из атрибута индекса shard_by. В случае, если уже есть готовые объекты для полей не входящих в индекс, но которые хочется прокинуть либо в результат селекта либо в shard_by, можно воспользоваться опцией objects. objects может содержать хэш, ключами в котором являются имена атрибутов, объявленных при помощи has_field_object, а значениями хэши (либо коллекции, которые оверлоадят %{}), ключи которых - значения полей, а значения - соответствующие им объекты.

insert

Метод insert позволяет записать все поля текущего объекта в коробку. Вызывается без параметров.

my $record = Sample::Record->new(
    id => 10,
    email => '[email protected]',
);
$record->insert();

update

Метод update записывает в коробку все изменения, произведенные над объектом в порядке произведения изменений. Фактически, когда для объекта вызывается мутатор (в том числе акцессор записи), выполненное действие ставится в очередь, и при вызове метода update вся эта очередь скидывается как список операторов операции update.

my $record = Sample::Record->select_by_id(10);
$record->name('Василий');
$record->inc_counter();
$record->update(); # Вот тут выполнится update с операциями: [ [ name => set => 'Василий' ], [ counter => num_add => 1 ] ]

Помимо стандартного модифицирующего акцессора, можно, при помощи атрибута поля mutators, создать дополнительные мутаторы, при помощи которых можно выполнять разные атомарные операции, например inc и dec. Но тут надо иметь в виду, что:

  • значение атрибута в перле хотя и изменяется сразу после вызова мутатора, но при этом может не совпадать с результатом обновления поля в коробке;
  • значение в коробке обновится только после вызова метода update.

delete

Удаляет запись из коробки по первичному ключу. Не принимает параметров.

About

ActiveRecord for Octopus/Box (Tarantool) tuple

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages