Хеш-таблицы

Хеш-таблица (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Возвращает одну запись по ключу. Ключ это массив значений полей.
//из таблицы lt1 будет прочитана запись со значением ключа = i
let got = lookupTables.lt1.get([i]); 
insertВставляет запись.
//в таблицу lt1 будет добавлена запись
lookupTables.lt1.insert({ foo: i + 1 }); 
removeУдаляет запись по ключу аналогично get.
//из таблицы lt1 будут удалены записи с ключом i
lookupTables.lt1.remove([i]); 
numKeysВозвращает количество уникальных ключей в таблице, аргументов нет.
//переменной num будет присвоено значение, отражающее количество уникальных ключей в таблице lt1
function generate() {
    let num = lookupTables.lt1.numKeys(); 
    $out[0].foo = num;

    return ALL;
}
clearОчищает таблицу, аргументов нет.
//таблица lt1 будет очищена от значений (её структура останется такой, как была задана при создании)
lookupTables.lt1.clear(); 

Правила использования lookup таблиц

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

  1. В начале работы графа он считывает содержимое хеш-таблицы и далее работает с ним в памяти. Запись в таблицу происходит в конце фазы, в которой производится обращение к таблице.

  2. Необходимо избегать использования шагов для чтения таблиц и записи в таблицу в рамках одной фазы.

На данный момент алгоритм таков, что при попытке использовать сочетание шагов для чтения и записи в лукап таблицу в одной фазе результат работы графа будет некорректен.

В одной фазе можно:

  • Использовать несколько 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>