Top.Mail.Ru
Модель данных | Tarantool
 
Модель данных
Модель данных

Модель данных

Модель данных

В этом разделе описывается то, как в Tarantool организовано хранение данных и какие операции с данными он поддерживает.

Если вы пробовали создать базу данных, как предлагается в упражнениях в «Руководстве для начинающих», то ваша тестовая база данных выглядит следующим образом:

../../../_images/data_model.png

Tarantool operates data in the form of tuples.

кортеж

A tuple is a group of data values in Tarantool’s memory. Think of it as a «database record» or a «row». The data values in the tuple are called fields.

Когда Tarantool выводит значение в кортеже в консоль, используется формат YAML, например: [3, 'Ace of  Base', 1993].

Internally, Tarantool stores tuples as MsgPack arrays.

field

Fields are distinct data values, contained in a tuple. They play the same role as «row columns» or «record fields» in relational databases, with a few improvements:

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

A given tuple may have any number of fields, and the fields may be of different types.

The field’s number is the identifier of the field. Numbers are counted from base 1 in Lua and other 1-based languages, or from base 0 in languages like PHP or C/C++. So, 1 or 0 can be used in some contexts to refer to the first field of a tuple.

Tarantool stores tuples in containers called spaces. In our example there’s a space called 'tester'.

спейс

In Tarantool, a space is a primary container which stores data. It is analogous to tables in relational databases. Spaces contain tuples — the Tarantool name for database records. The number of tuples in a space is unlimited.

At least one space is required to store data with Tarantool. Each space has the following attributes:

  • a unique name specified by the user,
  • a unique numeric identifier which can be specified by the user, but usually is assigned automatically by Tarantool,
  • an engine: memtx (default) – in-memory engine, fast but limited in size, or vinyl – on-disk engine for huge data sets.

To be functional, a space also needs to have a primary index. It can also have secondary indexes.

Read the full information about indexes on page Indexes.

Индекс — это совокупность значений ключей и указателей.

Как и для спейсов, индексам следует указать имена, а Tarantool определит уникальный числовой идентификатор («ID индекса»).

An index always has a type. The default index type is TREE. TREE indexes are provided by all Tarantool engines, can index unique and non-unique values, support partial key searches, comparisons and ordered results. Additionally, memtx engine supports HASH, RTREE and BITSET indexes.

Индекс может быть многокомпонентным, то есть можно объявить, что ключ индекса состоит из двух или более полей в кортеже в любом порядке. Например, для обычного TREE-индекса максимальное количество частей равно 255.

Индекс может быть уникальным, то есть можно объявить, что недопустимо дважды задавать одно значение ключа.

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

Tarantool представляет собой систему управления базой данных и сервер приложений одновременно. Поэтому разработчику часто приходится работать с двумя системами типов данных: типы языка программирования (например, Lua) и типы формата хранения данных Tarantool (MsgPack).

Скалярный / составной MsgPack-тип   Lua-тип Пример значения
скалярный nil «nil» nil
скалярный boolean «boolean» (логическое значение) true
скалярный string «string» 'A B C'
скалярный integer «number» 12345
скалярный float 64 (double) «number» 1.2345
скалярный float 64 (double) «cdata» 1.2345
скалярный binary «cdata» [!!binary 3t7e]
скалярный ext (для decimal в Tarantool) «cdata» 1.2
скалярный ext (для uuid в Tarantool) «cdata» 12a34b5c-de67-8f90-
123g-h4567ab8901
составной map (ассоциативный массив) «table» (таблица со строковыми ключами) {'a': 5, 'b': 6}
составной array (массив) «table» (таблица с целочисленными ключами) [1, 2, 3, 4, 5]
составной array (массив) tuple («cdata») (кортеж) [12345, 'A B C']

Примечание

Данные в формате MsgPack имеют переменный размер. Так, например, для наименьшего значения number потребуется только один байт, a для наибольшего потребуется девять байтов.

nil. В языке Lua nil может иметь только одно значение, также называемое nil (отображается как null в командной строке Tarantool, где значения выводятся в формате YAML по умолчанию). Значение nil можно сравнивать со значениями любых типов данных с помощью операторов == (равен) или ~= (не равен), но никакие другие операции для nil не доступны. Nil также нельзя использовать в Lua-таблицах — вместо nil в таком случае можно указать box.NULL, поскольку условие nil == box.NULL является истинным. Пример: nil.

boolean. Логический тип данных boolean принимает значения true или false. Пример: true.

integer. В Tarantool тип полей integer используется для целых чисел от −9 223 372 036 854 775 808 до 18 446 744 073 709 551 615, то есть до примерно 18 квинтиллионов. Такой тип данных соответствует типу number в Lua или integer в MsgPack. Пример: -2^63.

unsigned. The Tarantool unsigned type is for integers between 0 and 18446744073709551615. So it is a subset of integer. Example: 123456.

double. Тип поля double создан специально для соответствия типу данных DOUBLE в Tarantool/SQL. В интерфейсе Tarantool к MsgPack, который называется msgpuck.h, используется тип хранения MP_DOUBLE, а размер кодируемого значения всегда составляет 9 байтов. В Lua поля „double“ могут содержать только нецелые числа и значения типа cdata с плавающей запятой двойной точности. Примеры: 1.234, -44, 1.447e+44.
Чтобы случайно не использовать неправильные значения, используйте ffi.cast() для поиска или изменения полей типа „double“. Например, вместо space_object:insert{value} вводите ffi = require('ffi') ... space_object:insert({ffi.cast('double',value)}). Пример:

s = box.schema.space.create('s', {format = {{'d', 'double'}}})
s:create_index('ii')
s:insert({1.1})
ffi = require('ffi')
s:insert({ffi.cast('double', 1)})
s:insert({ffi.cast('double', tonumber('123'))})
s:select(1.1)
s:select({ffi.cast('double', 1)})

Арифметические операции с cdata „double“ не дадут достоверный результат, поэтому для Lua лучше использовать тип „number“. Это не относится к Tarantool/SQL, так как Tarantool/SQL выполняет неявное приведение типов.

number. В Lua number — это числа с плавающей запятой двойной точности, но в Tarantool поле типа „number“ может содержать как целые числа, так и числа с плавающей запятой. Tarantool по возможности сохраняет значения number языка Lua в виде чисел с плавающей запятой, если числовое значение содержит десятичную запятую или если оно очень велико (более 100 триллионов = 1e14). В противном случае, Tarantool сохранит такое значение в виде целого числа. Чтобы даже очень большие числа гарантированно обрабатывались как целые числа, используйте функцию tonumber64 или же приписывайте в конце суффикс LL (Long Long) или ULL (Unsigned Long Long). Вот примеры записи чисел в обычном представлении, экспоненциальном, с суффиксом ULL и с использованием функции tonumber64: -55, -2.7e+20, 100000000000000ULL, tonumber64('18446744073709551615').

decimal. Тип данных decimal в Tarantool хранится в формате MsgPack ext (Extension). Значения с типом decimal не являются числами с плавающей запятой, хотя могут содержать десятичную запятую. Они представляют собой числа с точностью до 38 знаков. Пример: значение, которое возвращает функция в модуле decimal.

string. Значения строкового типа string содержат последовательность байтов переменной длины, обычно представленную буквенно-цифровыми символами в одинарных кавычках. Как в Lua, так и в MsgPack строки рассматриваются как двоичные данные без определения набора символов строки или преобразования строки, кроме случаев сортировки. Таким образом, обычно сортировка и сравнение строк выполняются побайтово, а дополнительные правила сравнения символов не применяются. Например, числа упорядочены по их положению на числовой прямой, поэтому 2345 больше, чем 500. Строки же упорядочены сначала по кодировке первого байта, затем по кодировке второго байта и так далее, таким образом, '2345' меньше '500'. Пример: 'A, B, C'.

bin. Значения типа bin (двоичные значения) не поддерживаются непосредственно в Lua, но в Tarantool есть тип varbinary, который кодируется в тип binary из MsgPack. Пример вставки varbinary в базу данных см. в рекомендациях по разработке ffi_varbinary_insert (продвинутого уровня). Пример: "\65 \66 \67".

uuid. Since version 2.4.1. The Tarantool uuid type is stored as a MsgPack ext (Extension). Values with the uuid type are Universally unique identifiers.
Example: 64d22e4d-ac92-4a23-899a-e5934af5479.

array. An array is represented in Lua with {...} (braces). Examples: as lists of numbers representing points in a geometric figure: {10, 11}, {3, 5, 9, 10}.

table. Lua-таблицы со строковыми ключами хранятся в виде ассоциативных массивов MsgPack map. Lua-таблицы с целочисленными ключами, начиная с 1, хранятся в виде массивов MsgPack array. В Lua-таблицах нельзя использовать nil; вместо этого можно использовать box.NULL. Пример: запрос box.space.tester:select() вернет Lua-таблицу.

tuple. Кортеж (tuple) представляет собой легкую ссылку на массив типа MsgPack array, который хранится в базе данных. Это особый тип (cdata), который создан во избежание конвертации в Lua-таблицу при выборке данных. Некоторые функции могут возвращать таблицы с множеством кортежей. Примеры с кортежами см. в разделе box.tuple.

scalar. Значения в поле scalar могут быть типа boolean, или integer, или unsigned, или double, или number, или decimal, или string, или varbinary, но не array, map или tuple. Примеры: true, 1, 'xxx'.

any. Значения в поле any могут быть типа boolean, или integer, или unsigned, или double, или number, или decimal, или string, или varbinary, или array, или map, или tuple. Примеры: true, 1, 'xxx', {box.NULL, 0}.

Примеры запросов вставки с разными типами полей:

tarantool> box.space.K:insert{1,nil,true,'A B C',12345,1.2345}
---
- [1, null, true, 'A B C', 12345, 1.2345]
...
tarantool> box.space.K:insert{2,{['a']=5,['b']=6}}
---
- [2, {'a': 5, 'b': 6}]
...
tarantool> box.space.K:insert{3,{1,2,3,4,5}}
---
- [3, [1, 2, 3, 4, 5]]
...

Индексы ограничивают значения, которые Tarantool может хранить в формате MsgPack. Вот зачем, например, нужны отдельные типы полей 'unsigned' и 'integer': хотя в MsgPack оба содержат целочисленные значения, но индекс типа 'unsigned' содержит только неотрицательные целочисленные значения, а индекс типа 'integer' содержит любые целочисленные значения.

Здесь снова приводятся типы полей, описанные в Описании типов полей, а также типы индексов, где их можно использовать. По умолчанию, тип поля — 'unsigned', тип индекса — TREE. Хотя в качестве типа индексированого поля 'nil' использовать нельзя, индексы могут содержать nil как опцию, которая не используется по умолчанию. Более подробную информацию см. в разделе Описание типов индексированных полей.

Имя типа поля Тип поля
Тип индекса
'boolean' boolean TREE или HASH
'integer' (также может называться ‘int’) integer, может включать в себя беззнаковые значения unsigned TREE или HASH
'unsigned' (также может называться ‘uint’ или ‘num’, но ‘num’ объявлен устаревшим) unsigned TREE, BITSET или HASH
'double' double TREE или HASH
'number' number, может включать в себя значения типа integer или double TREE или HASH
'decimal' decimal TREE или HASH
'string' (также может называться ‘str’) string TREE, BITSET или HASH
'varbinary' varbinary TREE, HASH or BITSET (since version 2.7)
'uuid' uuid TREE или HASH
'array' array RTREE
'scalar'

может содержания значения nil, или boolean, или integer, или unsigned, или number, или decimal, или string, или varbinary

Когда поле типа scalar содержит значения различных базовых типов, то порядок ключей следующий: nil, затем boolean, затем number, затем string, затем varbinary.

TREE или HASH

Когда Tarantool сравнивает строки, по умолчанию он использует так называемую «двоичную» сортировку. Единственный фактор, который учитывается, — это числовое значение каждого байта в строке. Таким образом, если строка кодируется по ASCII или UTF-8, то 'A' < 'B' < 'a', поскольку кодировка 'A' (раньше называлась «значение ASCII») соответствует 65, 'B'— 66, а 'a' — 98. Двоичная сортировка лучше всего подходит для быстрого детерминированного простого обслуживания и поиска с помощью индексов Tarantool.

Если же необходимо такое упорядочение, как в телефонных справочниках и словарях, то вам нужна дополнительная сортировка Tarantool — unicode или unicode_ci — которые обеспечивают 'a' < 'A' < 'B' и 'a' = 'A' < 'B' соответственно.

Дополнительные виды сортировки unicode и unicode_ci обеспечивают упорядочение в соответствии с Таблицей сортировки символов Юникода по умолчанию (DUCET) и правилами, указанными в Техническом стандарте Юникода №10 – Алгоритм сортировки по Юникоду (Unicode® Technical Standard #10 Unicode Collation Algorithm (UTS #10 UCA)). Единственное отличие между двумя видами сортировки — вес:

  • сортировка unicode принимает во внимание уровни веса L1, L2 и L3 (уровень = „tertiary“, третичный),
  • сортировка unicode_ci принимает во внимание только вес L1 (уровень = „primary“, первичный), поэтому, например, „a“ = „A“ = „á“ = „Á“.

Для примера возьмем некоторые русские слова:

'ЕЛЕ'
'елейный'
'ёлка'
'еловый'
'елозить'
'Ёлочка'
'ёлочный'
'ЕЛь'
'ель'

…и покажем разницу в упорядочении и выборке по индексу:

  • с сортировкой по unicode:

    tarantool> box.space.T:create_index('I', {parts = {{field = 1, type = 'str', collation='unicode'}}})
    ...
    tarantool> box.space.T.index.I:select()
    ---
    - - ['ЕЛЕ']
      - ['елейный']
      - ['ёлка']
      - ['еловый']
      - ['елозить']
      - ['Ёлочка']
      - ['ёлочный']
      - ['ель']
      - ['ЕЛь']
    ...
    tarantool> box.space.T.index.I:select{'ЁлКа'}
    ---
    - []
    ...
    
  • с сортировкой по unicode_ci:

    tarantool> box.space.T:create_index('I', {parts = {{field = 1, type ='str', collation='unicode_ci'}}})
    ...
    tarantool> box.space.S.index.I:select()
    ---
    - - ['ЕЛЕ']
      - ['елейный']
      - ['ёлка']
      - ['еловый']
      - ['елозить']
      - ['Ёлочка']
      - ['ёлочный']
      - ['ЕЛь']
    ...
    tarantool> box.space.S.index.I:select{'ЁлКа'}
    ---
    - - ['ёлка']
    ...
    

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

Для английского языка используйте «unicode» и «unicode_ci». Для русского языка используйте «unicode» и «unicode_ci» (хотя некоторые русские, возможно, предпочтут киргизскую сортировку, в которой совпадают кириллические буквы „Е“ и „Ё“ с весом 1 уровня). Для голландского, немецкого (словарь), французского, индонезийского, ирландского, итальянского, лингальского, малайского, португальского, сесото, коса или зулу подойдут «unicode» и «unicode_ci».

Специализированные дополнительные виды сортировки: Что касается других языков, Tarantool предлагает специализированные виды сортировки для любого современного языка, на котором говорят более миллиона человек, а также для особых случаев, отличных от сортировки в словарном порядке и по примеру телефонного справочника. Чтобы увидеть полный список, выполните команду box.space._collation:select(). Названия специализированных видов сортировки составлены по форме unicode_[language code]_[strength], где language code — это стандартный код языка из 2 или 3 символов, а значение strength может быть s1 для уровня «primary» (вес уровня 1), s2 для уровня «secondary», s3 для уровня «tertiary». Tarantool использует те же коды языка, которые указаны в списке специализированных вариантов языковых настроек на страницах справочников по Ubuntu и Fedora. Схемы с объяснением отличий от упорядочения по DUCET можно найти по ссылке Common Language Data Repository.

Последовательность – это генератор упорядоченных значений целых чисел.

Как и для спейсов и индексов, для последовательностей следует указать имена, а Tarantool определит уникальный числовой идентификатор («ID последовательности»).

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

Параметры для box.schema.sequence.create()

Имя параметра Тип и значение Значение по умолчанию Примеры
start Integer. Значение генерируется, когда последовательность используется впервые 1 start=0
min Integer. Значения, ниже указанного, генерироваться не могут 1 min=-1000
max Integer. Значения, выше указанного, генерироваться не могут 9223372036854775807 max=0
cycle Логическое значение. Если значения не могут быть сгенерированы, начинать ли заново false cycle=true
cache Integer. Количество значений, которые будут храниться в кэше 0 cache=0
step Integer. Что добавить к предыдущему сгенерированному значению, когда генерируется новое значение 1 step=-1
if_not_exists (если отсутствует) Логическое значение. Если выставлено в true (истина) и существует последовательность с таким именем, то игнорировать другие опции и использовать текущие значения false if_not_exists=true

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

Для первоначального примера сгенерируем последовательность под названием „S“.

tarantool> box.schema.sequence.create('S',{min=5, start=5})
---
- step: 1
  id: 5
  min: 5
  cache: 0
  uid: 1
  max: 9223372036854775807
  cycle: false
  name: S
  start: 5
...

В результате видим, что в новой последовательность есть все значения по умолчанию, за исключением указанных min и start.

Затем получаем следующее значение с помощью функции next().

tarantool> box.sequence.S:next()
---
- 5
...

Результат точно такой же, как и начальное значение. Если мы снова вызовем next(), то получим 6 (потому что предыдущее значение плюс значение шага составит 6) и так далее.

Затем создадим новую таблицу и скажем, что ее первичный ключ можно получить из последовательности.

tarantool> s=box.schema.space.create('T')
---
...
tarantool> s:create_index('I',{sequence='S'})
---
- parts:
  - type: unsigned
    is_nullable: false
    fieldno: 1
  sequence_id: 1
  id: 0
  space_id: 520
  unique: true
  type: TREE
  sequence_fieldno: 1
  name: I
...
---
...

Затем вставим кортеж, не указывая значение первичного ключа.

tarantool> box.space.T:insert{nil,'other stuff'}
---
- [6, 'other stuff']
...

В результате имеем новый кортеж со значением 6 в первом поле. Такой способ организации данных, когда система автоматически генерирует значения для первичного ключа, иногда называется «автоинкрементным» (т.е. с автоматическим увеличением) или «по идентификатору».

Для получения подробной информации о синтаксисе и методах реализации см. справочник по box.schema.sequence.

В Tarantool’е обновления базы данных записываются в так называемые файлы журнала упреждающей записи (WAL-файлы). Это обеспечивает персистентность данных. При отключении электроэнергии или случайном завершении работы экземпляра Tarantool’а данные в оперативной памяти теряются. В такой ситуации WAL-файлы используются для восстановления данных так: Tarantool прочитывает WAL-файлы и повторно выполняет запросы (это называется «процессом восстановления»). Можно изменить временные настройки метода записи WAL-файлов или отключить его с помощью wal_mode.

Tarantool также сохраняет ряд файлов со статическими снимками данных (snapshots). Файл со снимком – это дисковая копия всех данных в базе на какой-то момент. Вместо того, чтобы зачитывать все WAL-файлы, появившиеся с момента создания базы, Tarantool в процессе восстановления может загрузить самый свежий снимок и затем зачитать только те WAL-файлы, которые были сделаны с момента сохранения снимка. После создания новых файлов, старые WAL-файлы могут быть удалены в целях экономии места на диске.

Чтобы принудительно создать файл со снимком, можно использовать запрос box.snapshot() в Tarantool’е. Чтобы включить автоматическое создание файлов со снимком, можно использовать демон создания контрольных точек Tarantool’а. Демон создания контрольных точек определяет интервалы для принудительного создания контрольных точек. Он обеспечивает синхронизацию и сохранение на диск образов движков базы данных (как memtx, так и vinyl), а также автоматически удаляет старые WAL-файлы.

Файлы со снимками можно создавать, даже если WAL-файлы отсутствуют.

Примечание

Движок memtx регулярно создает контрольные точки с интервалом, указанным в настройках демона создания контрольных точек.

Движок vinyl постоянно сохраняет состояние в контрольной точке в фоновом режиме.

Для получения более подробной информации о методе записи WAL-файлов и процессе восстановления см. раздел Внутренняя реализация.

Tarantool поддерживает следующие основные операции с данными:

  • пять операций по изменению данных (INSERT, UPDATE, UPSERT, DELETE, REPLACE) и
  • одну операцию по выборке данных (SELECT).

Все они реализованы в виде функций во вложенном модуле box.space.

Примеры:

  • INSERT: добавить новый кортеж к спейсу „tester“.

    Первое поле, field[1], будет 999 (тип MsgPack – integer, целое число).

    Второе поле, field[2], будет „Taranto“ (тип MsgPack – string, строка).

    tarantool> box.space.tester:insert{999, 'Taranto'}
    
  • UPDATE: обновить кортеж, изменяя поле field[2].

    Оператор «{999}» со значением, которое используется для поиска поля, соответствующего ключу в первичном индексе, является обязательным, поскольку в запросе update() должен быть оператор, который указывает уникальный ключ, в данном случае – field[1].

    Оператор «{{„=“, 2, „Tarantino“}}» указывает, что назначение нового значения относится к field[2].

    tarantool> box.space.tester:update({999}, {{'=', 2, 'Tarantino'}})
    
  • UPSERT: обновить или вставить кортеж, снова изменяя поле field[2].

    Синтаксис upsert() похож на синтаксис update(). Однако логика выполнения двух запросов отличается. UPSERT означает UPDATE или INSERT, в зависимости от состояния базы данных. Кроме того, выполнение UPSERT откладывается до коммита транзакции, поэтому в отличие от``update()``, upsert() не возвращает данные.

    tarantool> box.space.tester:upsert({999, 'Taranted'}, {{'=', 2, 'Tarantism'}})
    
  • REPLACE: заменить кортеж, добавляя новое поле.

    Это действие также можно выполнить с помощью запроса update(), но обычно запрос update() более сложен.

    tarantool> box.space.tester:replace{999, 'Tarantella', 'Tarantula'}
    
  • SELECT: провести выборку кортежа.

    Оператор «{999}» все еще обязателен, хотя в нем не должен упоминаться первичный ключ.

    tarantool> box.space.tester:select{999}
    
  • DELETE: удалить кортеж.

    В этом примере мы определяем поле, соответствующее ключу в первичном индексе.

    tarantool> box.space.tester:delete{999}
    

Подводя итоги по примерам:

  • Функции insert и replace принимают кортеж (где первичный ключ – это часть кортежа).
  • Функция upsert принимает кортеж (где первичный ключ – это часть кортежа), а также операции по обновлению.
  • Функция delete принимает полный ключ любого уникального индекса (первичный или вторичный).
  • Функция update принимает полный ключ любого уникального индекса (первичный или вторичный), а также операции к выполнению.
  • Функция select принимает любой ключ: первичный/вторичный, уникальный/неуникальный, полный/часть.

Для получения более подробной информации по использованию операций с данными см. справочник по box.space.

Примечание

Помимо Lua можно использовать коннекторы к Perl, PHP, Python или другому языку программирования. Клиент-серверный протокол открыт и задокументирован. См. БНФ с комментариями.

Во вложенных модулях box.space и Вложенный модуль box.index содержится информация о том, как факторы сложности могут повлиять на использование каждой функции.

Фактор сложности Эффект
Размер индекса Количество ключей в индексе равно количеству кортежей в наборе данных. В случае с TREE-индексом: с ростом количества ключей увеличивается время поиска, хотя зависимость здесь, конечно же, не линейная. В случае с HASH-индексом: с ростом количества ключей увеличивается объем оперативной памяти, но количество низкоуровневых шагов остается примерно тем же.
Тип индекса Как правило, поиск по HASH-индексу работает быстрее, чем по TREE-индексу, если в спейсе более одного кортежа.
Количество обращений к индексам

Обычно для выборки значений одного кортежа используется только один индекс. Но при обновлении значений в кортеже требуется N обращений, если в спейсе N индексов.

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

Количество обращений к кортежам Некоторые запросы, например SELECT, могут возвращать несколько кортежей. Как правило, это наименее важный фактор из всех.
Настройки WAL Важным параметром для записи в WAL является wal_mode. Если запись в WAL отключена или задана запись с задержкой, но этот фактор не так важен. Если же запись в WAL производится при каждом запросе на изменение данных, то при каждом таком запросе приходится ждать, пока отработает обращение к более медленному диску, и данный фактор становится важнее всех остальных.