Привет, меня зовут Мустафа Хаддара, я разработчик программного обеспечения в 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 г.