Хеш-таблицы
Хеш-таблица (lookup table) — это структура данных, которая обеспечивает быстрый доступ к данным, хранящимся с использованием ключа. Она позволяет уменьшить частоту обращений к базе данных или файлам.
По умолчанию хеш-таблица позволяет хранить несколько записей с одинаковым значением ключа. Если для вашего кейса это некорректно, можно запретить хранить дублирующиеся значения, тогда будет сохранено последнее записанное значение.
Все записи такой таблицы находятся в памяти во время работы графа. Поэтому на сервере должно быть достаточно памяти для хранения всех записей из используемой таблицы. Содержимое таблицы можно записать в файл и сохранить в проекте, чтобы использовать её содержимое через время в других графах.
Заметка: В редкторе содержимого используйте обращение к хеш-таблицам через конструкцию
lookupTables.<tableName>.<lookupMethod>(<arguments>);
в коде атрибутаtransform
в шагах, разрешающих пользовательские преобразования, например, DATA_GENERATOR и MAP.
Создание lookup таблицы
Чтобы создать хеш-таблицу, сначала нужно её определить в графе, а затем вставить в неё значения с помощью метода insert
. В дизайнере для создания таблицы используйте панель Outline и редактор lookup таблиц, аналогично созданию метаданных и параметров. В веб интерфейсе OneBridge таблицу можно определить на странице Проекты в редакторе содержимого файла с помощью тега <LookupTable>
и его атрибутов, описанных в таблице ниже.
Атрибуты lookup таблиц:
Атрибут | Обязательный | Описание | Возможные значения |
---|---|---|---|
name | да | Уникальное имя таблицы. | name="lt2" |
file | нет | Путь до файла с данными таблицы. Если файла не существует, то он будет создан, но папка, в которой этот файл должен лежать, должна существовать. Если путь до файла не указан, то таблица будет жить только в памяти в течении работы графа. | file="onebridge-dev/projects/ready-check/lookupExample1" |
metadata | да | Схема данных. | metadata="metadataName1" |
key | да | Ключ формата fieldname1;fieldname2;...;fieldnameN. | key="person;date" |
keyDuplicates | да | Чекбокс. Показывает, разрешается ли хранить в таблице больше одной записи с одинаковым значением ключа, по умолчанию keyDuplicates="true". | keyDuplicates="false" |
Так может выглядеть определение таблицы в файле графа:
<Graph>
<Global>
...
<LookupTable id="lookup_table1" key="num;date" metadata="meta1" name="lt1" file="lt1_file" keyDuplicates="false"/>
</Global>
Методы lookup таблиц:
Метод | Описание | Пример использования |
---|---|---|
get | Возвращает одну запись по ключу. Ключ это массив значений полей. |
|
insert | Вставляет запись. |
|
remove | Удаляет запись по ключу аналогично get. |
|
numKeys | Возвращает количество уникальных ключей в таблице, аргументов нет. |
|
clear | Очищает таблицу, аргументов нет. |
|
Правила использования lookup таблиц
Для корректной работы с таблицами такого типа, нужно соблюдать правила их использования:
-
В начале работы графа он считывает содержимое хеш-таблицы и далее работает с ним в памяти. Запись в таблицу происходит в конце фазы, в которой производится обращение к таблице.
-
Необходимо избегать использования шагов для чтения таблиц и записи в таблицу в рамках одной фазы.
На данный момент алгоритм таков, что при попытке использовать сочетание шагов для чтения и записи в лукап таблицу в одной фазе результат работы графа будет некорректен.
В одной фазе можно:
- Использовать несколько LOOKUP_TABLE_READER для чтения из таблицы А.
- Использовать несколько LOOKUP_TABLE_READER для чтения из разных таблиц А, B, C.
- Использовать один LOOKUP_TABLE_WRITER для записи в таблицу А.
- Производить чтение и запись в таблицу А, но только внутри пользовательского кода шага, с помощью методов get и insert.
В одной фазе нельзя:
- Обращаться к таблице А из шагов для чтения и записи (LOOKUP_TABLE_READER и LOOKUP_TABLE_WRITER).
- Использовать несколько LOOKUP_TABLE_WRITER для записи в таблицу А.
- Обращаться к таблице А из разных шагов с пользовательским кодом.
Пример использования отдельных шагов для чтения и записи:
<Graph>
<Global>
<Metadata id="meta">
<Record>
<Field name="foo" type="integer" />
</Record>
</Metadata>
<LookupTable id="lookup_table1" key="foo" metadata="meta" name="lt1" file="1170_lt1" />
</Global>
<Phase number="0">
<!-- This shows how to insert records into lookup tables with built-in node -->
<Node id="datagen1" type="DATA_GENERATOR" recordsNumber="4">
<attr name="generate">
<![CDATA[
let i = 0;
function generate() {
$out[0].foo = i;
i++;
return ALL;
}
]]>
</attr>
</Node>
<Node id="ltwriter" type="LOOKUP_TABLE_WRITER" lookupTableName="lt1" />
<Edge id="edge1" fromNode="datagen1:0" toNode="ltwriter:0" metadata="meta" />
</Phase>
<Phase number="1">
<!-- This shows how to read all records from lookup tables with built-in node -->
<Node id="ltreader" type="LOOKUP_TABLE_READER" lookupTableName="lt1" />
<Node id="trash2" type="TRASH" debugOutput="false" />
<Edge id="edge2" fromNode="ltreader:0" toNode="trash2:0" metadata="meta" />
</Phase>
</Graph>
Пример обращения к таблице из шага с пользовательским кодом:
<Graph>
<Global>
<Metadata id="meta">
<Record>
<Field name="foo" type="integer" />
</Record>
</Metadata>
<LookupTable id="lookup_table1" key="foo" metadata="meta" name="lt1" file="1170_lt1" />
</Global>
<Phase number="0">
<!-- This shows how to insert and retrieve records to/from lookup tables in user code -->
<Node id="datagen" type="DATA_GENERATOR" recordsNumber="4" enabled="false">
<attr name="generate">
<![CDATA[
let i = 0;
function generate() {
let got = lookupTables.lt1.get([i]);
let foo = got ? got.foo : null;
$out[0].foo = foo;
lookupTables.lt1.insert({ foo: i + 1 });
i++;
return ALL;
}
]]>
</attr>
</Node>
<Node id="trash" type="TRASH" debugOutput="true" />
<Edge id="edge" fromNode="datagen:0" toNode="trash:0" metadata="meta" />
</Phase>
</Graph>
Объединение lookup таблиц
Объединение хеш-таблиц возможно с помощью шагов MAP и NORMALIZER.
- MAP стоит использовать, если 1 запись мастер-потока объединяется с 1 записью слейв-потока.
- NORMALIZER стоит использовать, если записей для объединения на слейв-потоке больше, чем 1.
Пример объединения lookup таблиц с помощью MAP:
<Graph>
<Global>
<Metadata id="meta1">
<Record>
<Field name="n" type="integer"/>
<Field name="s" type="string"/>
</Record>
</Metadata>
<Metadata id="meta2">
<Record>
<Field name="n" type="integer"/>
<Field name="b" type="boolean"/>
</Record>
</Metadata>
<Metadata id="meta3">
<Record>
<Field name="num" type="integer"/>
<Field name="bool" type="boolean"/>
<Field name="str" type="string"/>
</Record>
</Metadata>
<LookupTable id="lookup_table1" key="n" metadata="meta1" name="lt1" file="1170_lt3_string" />
<LookupTable id="lookup_table2" key="n" metadata="meta2" name="lt2" file="1170_lt4_bool" />
<LookupTable id="lookup_table3" key="num" metadata="meta3" name="lt3" file="1170_lt5_joinmap" keyDuplicates="false"/>
</Global>
<Phase number="0">
<Node id="ltreader1" type="LOOKUP_TABLE_READER" lookupTableName="lt1" />
<Edge id="edge3" fromNode="ltreader1:0" toNode="map:0" metadata="meta1" />
</Phase>
<Phase number="1">
<Node id="map" type="MAP">
<attr name ="transform">
<![CDATA[
let i = 0;
function transform() {
let lt2 = lookupTables.lt2.get([i]);
let bool = lt2 ? lt2.b : null;
let str = lt2 ? lt2.s : null;
$out[0].num = $in[0].n;
$out[0].bool = bool;
$out[0].str = $in[0].s;
i++;
return ALL;
}
]]>
</attr>
</Node>
<Node id="trash2" type="TRASH" debugOutput="true" />
<Edge id="edge4" fromNode="map:0" toNode="trash2:0" metadata="meta3" />
</Phase>
</Graph>