Привет, меня зовут Мустафа Хаддара, я разработчик программного обеспечения в Vena. Мы строим многомерную базу данных (также известную как OLAP Cube; статья в Википедии довольно хорошо раскрывает эту тему). В основном я работаю над компилятором и средой выполнения для Vena Calcs, нашего пользовательского языка программирования для определения отношений между элементами в иерархии измерений в кубе.
9:00: Пустой офис
Большинство наших разработчиков (и других сотрудников, на самом деле) приходят около 10, а это значит, что когда я обычно прихожу около 9, офис в основном пуст. Я загружаю свой ноутбук и беру чашку кофе из эспрессо-машины на кухне.
В любой момент у меня могут возникнуть проблемы, которые нужно исправить, или добавить новую функцию. Сегодня я работаю над добавлением дополнительных проверок в наши расчеты.
Пока я работаю, показаны два новых разработчика, которые только что начали работу. Они начинают настраивать свою среду разработки. Наш стек включает Java 8, MySQL, MongoDB и множество других небольших сервисов. В прошлом нам приходилось устанавливать все это по отдельности, настраивать параметры конфигурации и сравнивать номера версий, но в последнее время наша команда по инфраструктуре работает над тем, чтобы упаковать большую часть этого в коробку Vagrant. Эта настройка Vagrant в основном завершена, поэтому новые сотрудники могут протестировать ее бета-версию.
10:00: Незадокументированные персонажи
Пока я работаю над дополнительной проверкой, на нашем канале #Vena-Calcs
в Slack возникает вопрос о допустимых символах в именах и идентификаторах переменных; они спрашивают, разрешены ли символы $
в именах участников. Обычно мой стандартный ответ — ссылка на нашу документацию, но в данном случае этот раздел немного неясен.
Я почти уверен, что наша проверка не пройдет на символе $
, но я быстро перехожу к своему уже работающему локальному экземпляру нашего серверного кода, чтобы проверить это. Я переименовываю один из существующих элементов в иерархии измерений, чтобы он содержал $
в этом имени, а затем пытаюсь сохранить файл Calc. Как я и ожидал, сохранение не удалось из-за ошибки $
. Я отвечаю на вопрос в Slack и быстро обновляю нашу документацию, чтобы сделать ее более понятной.
При этом я замечаю, что в документации утверждается, что мы поддерживаем символы <
и >
, но на самом деле символ >
не проходит нашу проверку. Это странно.
11:00: Кофе и налог на угловую скобку
Я открываю наш файл грамматики, чтобы убедиться, что мы не допускаем >
символов, и действительно, кажется, что мы пропустили этот символ. Это кажется странным персонажем для запрета; тем более, что разрешено <
символов. В этом случае исправить просто: добавить >
, заново сгенерировать анализатор, и мы свободны. Теперь пришло время проверить это.
Я указываю браузеру localhost
и вхожу в свой локальный экземпляр с исправлением. Чтобы проверить это, мне понадобится член измерения с >
в имени. Как это бывает, для начала я переименовываю элемент, чтобы он имел <
, чтобы убедиться, что все работает так, как я ожидаю; символ <
является "заведомо хорошим" символом.
Я нахожу элемент Jan
в своем измерении Period
и переименовываю его в Jan<abc
.
К моему удивлению, все, что я вижу, это:
И вывод с сервера показывает, что он действительно получил запрос на переименование. Я открываю базу данных MySQL, и, конечно же, имя члена изменилось.
Это странно. Должна быть ошибка интерфейса. Давайте посмотрим на код интерфейса.
12:00: вниз по кроличьей норе JavaScript
Глядя на внешний код, не сразу видно, где ошибка, но в конце концов я понимаю, что виджет таблицы, который мы используем, уважает HTML. Конечно же:
Это проясняет ситуацию. Таким образом, мы избегаем имени члена символов и, как и ожидалось:
Оно работает. Поэтому я отправляю это изменение на GitHub и открываю запрос на извлечение.
Все это началось с тестирования >
. Тестирование вручную на нашем внешнем интерфейсе показывает, что, к счастью, оно действительно работает правильно. Я пишу автоматический тест, чтобы предотвратить регрессию, и фиксирую его вместе с моими изменениями на стороне сервера, отправляю его на наш GitHub и открываю запрос на слияние. У нас есть автоматизированный сервер сборки (Jenkins), который следит за репозиторием нашего сервера и запускает любой код, представленный в запросах на вытягивание, с помощью набора автоматических тестов.
13:00: обед
Как только мои тесты начинаются, я иду обедать с двумя новыми сотрудниками. Мы берем немного еды из ресторанного дворика в бизнес-парке через дорогу и рассказываем о своем прошлом опыте и о том, что привело нас в Вену.
14:00: демонстрация продукта
Каждые пару недель у нас есть демонстрация, где любой, кто работал над какой-то интересной новой функцией, может показать ее остальным разработчикам и остальной команде. Мне нечем похвастаться на этой неделе, но у некоторых есть.
Все разработчики, несколько специалистов по работе с клиентами и несколько консультантов собираются вокруг проектора. Во-первых, один из разработчиков в команде Infrastructure демонстрирует внутренний инструмент, который мы можем использовать для проверки состояния всех наших экземпляров AWS: какая служба на них запущена, что это за экземпляры и т. д.
Затем мы получаем демонстрацию предстоящей функции, добавляемой в наш продукт. Функция выглядит великолепно, и все в восторге от нее. Это время, когда люди, работающие над этим, могут получить отзывы от остальной команды разработчиков, и, как всегда, есть некоторые обсуждения различных аспектов этой функции.
15:00: Проблемы клиентов
После демонстрации ко мне подходит консультант с проблемой Calcs. Он внес изменение, чтобы уменьшить объем своих вычислений (и, таким образом, уменьшить количество данных, которые он обрабатывал), но затем его вычисления зависли из-за нехватки памяти, а не завершились за 8 минут.
Он показывает мне Calc, и все выглядит хорошо. Я не могу сразу сказать, почему в одной версии не хватило памяти, но версия, которая выполняет больше обработки, завершилась за 8 минут, поэтому мой следующий шаг — получить резервную копию баз данных и восстановить ее в моем локальном экземпляре, чтобы я мог проанализировать выполнение. ближе.
16:00: Недостаточно памяти
После восстановления клиентской базы я смотрю на разницу между двумя прогонами расчетов консультанта и сразу замечаю проблему. Первый запуск обнаружил только 35 тысяч пересечений (исходных точек данных) и сгенерировал 1,3 миллиона выходных данных, но более поздний запуск обнаружил более 75 тысяч исходных точек данных и сгенерировал более 3 миллионов выходных данных.
Это кажется странным, потому что консультант сказал, что единственное изменение, которое он внес, — это уменьшение объема обрабатываемых данных. Тот факт, что сокращение внезапно привело к увеличению объема загружаемых данных, кажется подозрительным.
Затем я компилирую расчеты консультанта, открываю отладчик и проверяю, что компилятор сгенерировал правильную скомпилированную форму (что он и делает). Интересно, что скомпилированная форма имела правильные привязки к данным, которые она должна извлекать из базы данных.
Таким образом, с самого начала должно было быть 75 тысяч значений. Это хорошо знать.
Теперь я думаю, что, возможно, первоначальный запуск был на самом деле тем, в котором была обнаружена какая-то ошибка, и он работал намного быстрее, чем правильная версия. Это возможно, я полагаю, но это делает особенно плохие новости для пользователя.
В любом случае, чтобы быть уверенным, я должен запустить их вычисления и выяснить, почему они вышли из памяти во второй раз. Так что я нажимаю «беги» и сижу, наблюдая, как график памяти на моем профилировщике растет, растет и растет.
Популярный тест: в чем разница между утечкой памяти и обработкой больших данных? Ответ: при обработке больших данных планируется огромное использование памяти.
В нашем случае, поскольку любое рассчитанное пересечение потенциально может вызвать другое вычисление, мы храним результаты в кэше вместо того, чтобы переходить туда и обратно в Mongo для считывания результатов, которые мы только что вычислили. И чем дольше мы обрабатываем набор вычислений, тем больше результатов мы ожидаем получить и, следовательно, тем больше памяти мы ожидаем потреблять.
Однако это не означает, что нам нужен кеш — мы всегда можем отказаться от кеша и вернуться к чтению из базы данных за счет более медленного выполнения. Это была функция, которую мы давно хотели реализовать, но так и не дошли до нее, и похоже, что она просто стала более приоритетной проблемой, поэтому я думаю, что реализую ее сейчас.
17:00: декорации, карты и пространство
Мы храним наши значения пересечения в MongoDB, и мы передавали эти большие кэши результатов, чтобы не возвращаться в Mongo. Тип этих кешей?
Map<DenseIntersection, Intersection> resultsCache;
Итак, для каждой записи в этом кеше нам нужно иметь объект DenseIntersection
и Intersection
. Давайте посмотрим на эти объекты, начиная с объекта Intersection
.
Я не собираюсь показывать вам весь класс или даже все свойства, которыми должен обладать объект Intersection
. Он содержит очень много объектов, в том числе Map<Integer, Member>
для хранения «адреса» этого перекрестка, несколько свойств String
и Id
, Set
других объектов и так далее. В общей сложности объект Intersection
содержит почти 40 ссылок на другие объекты, то есть мы имеем дело с 480 байтами чистых заголовков объектов! Самое печальное во всем этом то, что движок Calc не использует почти ни один из атрибутов: мы используем адрес и значение, а остальные свойства нас совершенно не волнуют.
Угадайте, как выглядит объект DenseIntersection
?
public class DenseIntersection {
Long[] members;
String value;
...
}
Всего два объекта. Вот и все. Это все, что нас волнует. Нас не волнует что-либо, содержащееся в объекте Intersection
, но не содержащееся в объекте DenseIntersection
.
Так зачем держать Intersection
объектов рядом?
Хороший вопрос. Свалим их. Таким образом, во всем нашем Calc Engine, где мы передаем Map<DenseIntersection, Intersection>
, мы просто используем вместо этого Set<DenseIntersection>
.
Давайте посчитаем: 480 байтов для заголовков свойств объекта Intersection
и 24 байта для заголовков свойств объекта DenseIntersection
означают, что мы ищем 500 байтов, чисто для заголовков, для одной записи в этом Map
. . Мы выделяли более половины КБ на точку данных!
Переключение на Set<DenseIntersection>
экономит нам массу памяти (графики автоматически масштабируются; сравните значения по оси Y с графиком выше):
Мы перешли от постоянного увеличения памяти к гораздо более постоянному использованию памяти. Конечно, использование нашей памяти все еще растет, но растет медленнее. Это большая победа, и я еще даже не удосужился реализовать этот запасной вариант, когда мы сбрасываем кеш. Это может подождать до завтра.
18:00: Разделение интересов
Мы устраиваем еженедельный халкнайт в офисе, где работаем над личными проектами — работа запрещена. Это совершенно необязательно, обычно у нас не бывает большой явки, но мне все равно нравится ходить.
Я планирую пойти сегодня вечером, но мне нравится ставить границу между работой и личными проектами, а вечеринка не начинается до 19:00. Я добился значительного прогресса в оптимизации памяти, поэтому я коммит свой код на ночь, отправляю его на GitHub для запуска наших автоматических тестов, а затем собираю свой компьютер и иду в кафе через улицу, чтобы какой-то Нетфликс.
19:00: Хакнайт
Сейчас я снова в офисе — большинство людей уже ушли, ночуют дома, и осталось лишь несколько разработчиков. Те из нас, кто остался на вечеринку, собираются вместе и решают, что мы хотим от нашей пиццы, а затем заказываем онлайн. Несколько недель назад для своего проекта hacknight один из наших разработчиков написал небольшое расширение для Google Chrome, которое очищает онлайн-трекер статуса заказов Dominos Pizza и загружает обновления в наш канал Slack:
Приносят пиццу, мы показываем на проекторе сегодня вечером прошлой недели, а когда все заканчивается, мы погружаемся в свои проекты. На этой неделе у одного из разработчиков не было проекта, поэтому он просмотрел несколько вопросов из Google Code Jam и разослал их по кругу. Мы все подумали, что это достойный вызов, и под влиянием момента решили пройти всю гонку, чтобы выполнить его (к сожалению, я не выиграл гонку).
В другие дни мы проводим вечера видеоигр: мы бронируем конференц-зал и получаем Super Smash Bros или Mario Kart или Halo на проектор.
Вывод
Это не показатель рабочего дня каждого инженера в Vena; Я перескакивал с внутренней проверки на исправление внешнего интерфейса, на написание документации и на беспокойство об использовании памяти (в Java! О, ирония судьбы!). Мы делаем все это и многое другое — я не занимался кодированием инфраструктуры, я не занимался контролем качества и никогда не касался кодовой базы C# для нашей надстройки Excel. Но если что-то из этого вас волнует, если вы талантливый разработчик Java, C# или JavaScript, что ж, мы нанимаем, и мы будем рады услышать от вас!
Первоначально опубликовано на сайте engineering.vena.io 4 мая 2016 г.