Оценка размера данных — это относительно простой навык, который одновременно: а) легко никогда не освоить; б) весьма полезен после того, как вы им овладели. Он может пригодиться при проектировании систем, в отладке сложной проблемы распределенной системы и, разумеется, при обсуждении архитектурных задач на собеседовании.
Автор Уилл Ларсон*, технический директор компании 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?
Нужно ли разбивать эти данные на множество серверов? Если да, то на какое количество?
Может ли тот или иной индекс поместиться на одном сервере?
Если нет, то как правильно разбить индекс?
И т.д. и т.п.
Я углубился в оценку размера данных не так давно, и все еще продолжаю удивляться тому, насколько эффективно этот подход может сузить для вас круг возможных решений в различных задачах.