[Перевод] Как оценить размер данных: краткий гайд

14b87526ef094c57a483d1a398914095.png

Оценка размера данных — это относительно простой навык, который одновременно: а) легко никогда не освоить; б) весьма полезен после того, как вы им овладели. Он может пригодиться при проектировании систем, в отладке сложной проблемы распределенной системы и, разумеется, при обсуждении архитектурных задач на собеседовании.

Автор Уилл Ларсон*, технический директор компании Calm, в своей статье признается, что никогда не был особенно хорош в «оценке». Поэтому он решил потратить несколько часов на развитие этого навыка, что со временем вылилось в текстовые заметки на эту тему. Под катом автор делится полезными правилами для оценки требуемого дискового пространства, а затем собирает фрагмент кода на SQLite3, чтобы продемонстрировать, как можно проверить результаты вашей «оценки».

*Обращаем ваше внимание, что позиция автора не всегда может совпадать с мнением МойОфис.

Килобайты (1000 байт) и Кибибайты (1024 байта)

Первое, что может привести к путанице в оценке требуемого пространства, это определение размера килобайта: 1024 байта или 1000 байт?

Правильный ответ состоит в том, что «килобайт» (КБ) составляет 1000 байт, а «кибибайт» (КиБ) — 1024 байта (прим. ред.: согласно терминологии, принятой в стандарте IEC 80000–13:2008). Подобное различие существует и в случае с другими единицами измерения, например: мегабайт (МБ, 1000^2) и мебибайт (МиБ, 1024^2), а также гигабайт (ГБ, 1000^3) и гибибайт (ГиБ, 1024^3).

Три ключевых момента, которые вам потребуется здесь учитывать, такие:

  • понимание как килобайтов, так и кибибайтов;

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

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

Размер UTF-8, целые числа и т. д.

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

  • Символы UTF-8. Кодируются в 1–4 байта. Следует ли вам оценивать требуемое пространство в 1, 2, 3 или 4 байта, зависит от того, что вы кодируете. Например, если вы кодируете только значения ASCII, достаточно 1 байта. Самое главное — уметь объяснить свою логику. Например, сказать, что в самом консервативном случае вы используете 4 байта на символ, — вполне обосновано.

    Взяв за основу размеры UTF-8, вы можете оценить размер строки, набора символов, текста или других подобных полей. Точные накладные расходы этих полей будут сильно различаться в зависимости от конкретной технологии, используемой для хранения данных.

  • Целочисленные данные. Их размер зависит от максимального значения, которое вам требуется сохранить, однако значением с хорошей точностью является 4 байта, которое можно взять за основу. Если вы хотите быть более точным, выясните максимальный размер целого числа: затем вы можете определить количество килобайт, необходимых для его представления с помощью вычисления:

maxRepresentableIntegerInNBytes(N) = 2 ^ (N bytes * 8 bits) - 1

Например, наибольшее число, которое можно представить в 2 байтах, равно:

2 ^ (2 bytes * 8 bits) - 1 = 65535
  • Числа с плавающей запятой. Обычно хранятся в 4 байтах.

  • Логические значения. Часто представляются как 1-байтовые целые числа (например, в MySQL).

  • Перечисления. Часто представляются как 2-байтовые целые числа (например, в MySQL).

  • Datetimes. Часто представлены в 5 байтах (например,  в MySQL).

Вооружившись этими правилами, давайте попрактикуемся в оценке размера базы данных. Представим, что наша база данных включает 10 000 человек. Мы отслеживаем возраст и имя каждого человека. Средняя длина имени составляет 25 символов, а максимальный возраст — 125 лет. Сколько пространства нам потребуется?

bytesPerName = 25 characters * 4 bytes = 100 bytes
bytesPerAge = 1 byte  # because 2^(1 bytes * 8bits) = 255
bytesPerRow = 100 bytes + 1 byte
totalBytes = 101 bytes * 10,000 rows
totalKiloBytes = (101 * 10000) / 1000          # 1,100 kB
totalMegaBytes = (101 * 10000) / (1000 * 1000)   # 1.1 MB

Видим, что для хранения этих данных необходимо 1,1 МБ.

Альтернативный результат составляет 0,96 МиБ, и рассчитан он так:

(101 * 10000) / (1024 * 1024)  # 0.96 MiB

Теперь вы можете оценить размер наборов данных.

Индексы, репликация и т. д.

Существует разница между теоретической и фактической стоимостью хранения данных в базе. Возможно, вы используете инструмент на основе реплик, такой как MongoDB или Cassandra, который хранит три копии каждого фрагмента данных. Возможно, вы используете первично-вторичную систему репликации, в которой хранятся две копии каждой части данных. Влияние хранилища здесь довольно легко определить (соответственно, в 3 или 2 раза больше базовой стоимости).

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

В зависимости от конкретной используемой базы данных будут другие функции, которые также занимают место, и для понимания их влияния на размер потребуется более глубокое понимание конкретной базы данных. Лучший способ добиться этого — проверить размер непосредственно в этой базе данных.

Проверка с помощью SQLite3

Хорошая новость: проверить размер данных относительно легко. Это мы сейчас и сделаем,  используя Python и SQLite3. Начнем с воссоздания приведенной выше оценки из 10 000 строк, каждая из которых содержит имя из 25 символов и возраст.

import uuid
import random
import sqlite3

def generate_users(path, rows):
    db = sqlite3.connect(path)
    cursor = db.cursor()
    cursor.execute("drop table if exists users")
    cursor.execute("create table users (name, age)")
    for i in range(rows):
        name = str(uuid.uuid4())[:25]
        age = random.randint(0, 125)
        cursor.execute("insert into users values (?, ?)", (name, age))
    db.commit()
    db.close()

if __name__ == "__main__":
    generate_users("users.db", 10000)

Ранее мы оценили это в 0,96 МиБ, но, запустив этот скрипт, я вижу другой размер — 344 КиБ, чуть более трети ожидаемого пространства. Немного отладив наш расчет мы видим, что мы предполагали 4 байта на символ, но имена, которые мы генерируем (усеченные UUID4 ), все являются символами ascii, поэтому на самом деле требуется 1 байт на символ. Давайте переоценим значение на основе этого:

bytesPerName = 25 characters * 1 byte = 25 bytes
bytesPerAge = 1 byte
bytesPerRow = 26 bytes
totalKibiBytes = (26 * 10,000) / 1024  # 245 KIB

Что ж, это довольно близко — если учитывать некоторые накладные расходы, которые, безусловно, есть. Например, SQLite3 открыто создает столбец rowid для использования в качестве первичного ключа, который представляет собой 64-битное целое число, для представления которого требуется 4 байта. Если мы добавим эти 4 байта к нашей предыдущей оценке в 26 байт на строку, то получим предполагаемый размер 293 КиБ, что достаточно близко к нашей оценке.

***

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

  • Поместятся ли те или иные данные в памяти?

  • Поместятся ли они на одном сервере с SSD?

  • Нужно ли разбивать эти данные на множество серверов?  Если да, то на какое количество?

  • Может ли тот или иной индекс поместиться на одном сервере?

  • Если нет, то как правильно разбить индекс?

  • И т.д. и т.п.

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

© Habrahabr.ru