пятница, 18 октября 2019 г.

Proxmox VE: Replication Job failed

        Нас долгое время задалбывали рандомные варнинги приходящие от ProxmoxVE - Replication Job failed.  Какой-то дополнительной информации - что именно пошло не так и где proxmox не предоставляет. Вообще в этой системе с документацией туго,  и пул реквест им не пришлешь. Зато есть платная поддержка. 
               Эта хрень приходила в почту несколько месяцев,  админы все время грозились что-то сделать, но как-то у них не очень получалось. Мне как начальнику делать нехер, поэтому я закатав рукава полез внутрь proxmox. Внутри там с одной стороны все довольно красиво написано, API, плагины, код не плохого качества. С другой стороны - это все на перле. Никогда не думал что придется вспоминать перл, но как говорится никогда не говори никогда. 
          Оказалось что варнинги выбрасывались заданиями по репликации виртуальных машин с одного сервера на другой. Если быть более точным - реплицировались не сами виртуалки, а снэпшоты сделанные с виртуалки. Причем обычно все отрабатывало штатно, а в некоторых случаях (примерно 10%) это заканчивалось ничем. С чем это было связано - непонятно.
            Покопавшись в коде выяснил что "репликация" производится путем выполнения команд через shell, с каким-то таймаутом.  Если команды не укладывается в заданный таймаут - процесс завершается ошибкой. Никаких настроек типа "таймаут такой-то" в конфигах ProxmoxVE нет. Есть только магические чиселки, прямо в перловом коде. Чиселок много, они разным способом перетирают друг друга, комментарии в коде - это тоже для слабоков.     
            В общем если вас преследует аналогичная проблема, то в случае ZFS менять таймаут нужно в PVE/Storage/ZFS/PoolPlugin.pm, строка 185:
Если вы используете другой сторадж - логично будет посмотреть в других модулях директории PVE/Storage. Место установки  перловых модулей зависит от системы, у меня они лежат в /usr/share/perl5/

понедельник, 23 сентября 2019 г.

AppOptics и другие продукты SolarWinds

        Не в первый раз встречаюсь с продуктами SolarWinds, и каждый раз они оказываются редкостным дермищем которое стоит конских денег. Сначала я познакомился с мониторингом баз данных, в частности MS SQL. Это решение стоит нам около 5k USD в год, это при том что у нас довольно мало серверов баз данных и по сути мы мониторим только прод. Но оно выглядит как дедушка нагиоса или отец заббикса. В общем по сравнению с тем же PMM от перконы(бесплатного между прочим!!) - это просто диназавр из начала двухтясячных. 
         Другой пример - AppOptics. Это такой клон NewRelic, только херовенький. У него все нормально с юхабилити/внешним видом, но он нифига не работает. Ты его ставишь а данные не появляются. Вот уже неделю как разговариваю с саппорттом по поводу этой проблемы и пока результат никакой. Но при всем этом решение также стоит немалых денег. 
          В общем на мой взгляд - SolarWinds это хороший пример дерьмокомпании, которой я всячески желаю обанкротиться.   

вторник, 17 сентября 2019 г.

Code review

      Про code review много чего сказано, написано. Но все как-то кусочками. И не совсем тем что написано я был согласен. А тут наткнулся на документ где (с моей точки зрения) все правильно написано, и довольно полно.  https://google.github.io/eng-practices/review/reviewer/ Самое ценное в этом документе на мой взгляд то что он охватывает не только и не столько техническую часть, сколько организационную и то что касается человеческого общения. Про это вообще мало кто пишет. Многие вообще считают что диктатура при code review - это круто. И не понимают что code review - это в первую очередь менторство, а не поле для самоутверждения.  

воскресенье, 1 сентября 2019 г.

Из Golang в Assembler

       Я в последнее время все меньше пишу про Golang. Наверное потому что пишу в основном про то что меня интересует, а в мире Golang в последнее время меня не так много интересовало.  Недавно был на Golang meetup - из трех докладов интересным в какой-то степени был один, да и тот был интересен в основном отдельными техническими  деталями. Такого чтобы - ах как клево, давно не было. 
       На выходных отсматривал видео с GopherCon 2019. Из того что посмотрел очень понравился доклад Michael McLoughlin про то как они местами переписывают с Golang на Assembler. Ребята на мой взгляд пропагандируют очень здравый подход к оптимизации.  
            Посмотрев этот доклад я с удивлением обнаружил отсылку к докладу с GopherCon Russia. Вообще очень не часто можно встретить в докладах ведущих конференций ссылку на материалы российских конференций. Но в данном случае можно сказать что доклад Marko Kevac о bitmap индексах этого более чем заслужил.  
Для тех кому лень смотреть просто оставлю ссылку на https://github.com/mmcloughlin/avo 

вторник, 13 августа 2019 г.

HTTP smuggling

         Иногда диву даешься как люди способны находить дырки даже в реализациях  относительно простых и очень хорошо изученных протоколах вроде HTTP.  Текстовый протокол, вроде все четко и понятно. Но нет, и тут есть просто куча проблем. Как говорится было гладко на бумаге да забыли про овраги. 
                 Что делать если в запросе два одинаковых заголовка Content-length ? Что если присутствуют взаимоисключающие заголовки типа Content-length и Transfer-encoding ? Кому из них верить ? А что если между названием заголовка и ":" добавить пробел ? Эту и многие другие ситуации разные реализации обрабатывают по разному. В результате до сих пор даже в самых казалось бы известных решениях по защите веб приложений находятся дыры(не буду раскрывать имен раньше времени).  

пятница, 2 августа 2019 г.

Arista EOS (Arista Extensible Operating System)

          Иногда разбираешь как работает та или иная технология - и в голове только одна мысль - "что за дерьмо, и они это еще людям продают!"(луч позора в сторону Microsoft). А иногда читаешь  и думаешь - "блин, вот это ребята молодцы, вот это отлично придумали!". И это про компанию Arista. Мне решительно нравится все что делают эти ребята! 
         На мой взгляд эти парни совершили революцию в на рынке сетевого железа. Взяли x86 процессоры, взяли обычный линукс (fedora), написали обвязку вокруг этого - и сделали самые быстрые в мире свитчи/маршрутизаторы! Обставили всех этих бородатых дядек из контролируют рынок и распродают свои свитчи/маршрутизаторы как горячие пирожки. Казалось бы у Cisco есть все - специализированная OS, специально написанная для подобных вещей, экспертиза в создании специализированных чипов для обработки сетевого траффика. И тут оказывается что балалайка на обычных интеловских процессорах и не модифицированном линукс ядре может обставить их по скорости! Короче я стал фанатом Arista. Ну и плюс ко всему эти ребята очень любят Golang, у них даже в EOS (Arista Extensible Operating System) есть поддержка Golang из коробки.   
            Ну и конечно им на роду поддерживать всякие OpenConfig, OpenFlow и другие элементы SDN. В общем если ребята не продадутcя Cisco то у них есть все шансы захватить мир.

среда, 31 июля 2019 г.

GetHeapCodeAndMetadataStatistics()

  После месяца ожидания, мой третий пул реквест был принят в node.js - https://github.com/nodejs/node/pull/27978 Теперь в node.js появится новый метод который позволит посмотреть сколько памяти нода выделяет под хранения кода и метаданных.

пятница, 19 июля 2019 г.

Microsoft SLB (Software Load Balancing)

      Кроме довольно убого NLB про которого я писал в прошлый раз, Microsoft сейчас предлагает SLB - Software Load Balancer. На мой взгляд этот балансер сделан гораздо более продумано. Но в данном случае название(Software) не отображает принципов работы этого балансировщика. В основе работы SLB лежат сетевые технологии, хотя некоторое количество серверного ПО конечно используется. 
Если кратко то SLB использует  BGP для балансировки запросов между несколькими MUX-ами (ECMP - Equal Cost Multi Path). Далее каждый MUX инкапсулирует клиентский IP пакетик в UDP пакетик (VXLAN) и отправляет их на железный сервер на котором запущена нужная виртуалка(виртуалки с нужным DIP - destinition IP). Далее железный сервер открывает пакет,  в оригинальном IP пакете заменяет Destinition IP на DIP и передает его на сетевой интерфейс виртуалки.   Виртуалка обрабатывает IP пакет, отвечает на него. И хост машина снова перехватывает IP пакет,  и заменяет source IP на VIP (IP балансера). Таким образом клиент не знает истинного IP сервера, и следующий IP пакет тоже прийдет на балансер (VIP). 
        Еще из интересного - Microsoft научила MUX-ы пирится со свичами по BGP протоколу, так что они сами себя анонсируют. В общем из минусов на вскидку вижу только оверхед на VXLAN инкапсуляцию и расходы на NAT. Но от этого никуда не денешься.   

четверг, 18 июля 2019 г.

Microsoft NLB

      Я тут внезапно осознал еще одну особенность которая меня выбешивает в продуктах компании Microsoft. Они всегда используют общие термины для обозначения своих продуктов. К примеру- как называется MS SQL ? В документации Microsoft он всегда называется просто SQL Server. Как будто других SQL серверов вовсе нет. Поэтому когда говоришь о MS SQL всегда приходится уточнять - ты говоришь именно о Microsoft SQL Server, а не в общем обо всех серверах. Этим они убивают двух зайцев, во первых они  "приватизируют" общие термины, во вторых они повышают упоминаемость своего брэнда. Ты как идиот вынужден всегда повторять название компании. С именованием балансировщика они придерживались такой же схемы. Я как идиот требовал от админов название софта который используется для балансировки - а они как попугаи повторяли - NLB, NLB. Я раза с третьего понял что NLB - это не какой-то абстрактный сетевой балансер, а прям вот такая "технология" microsoft. 
         На мой взгляд NLB это такой образец софта который круто выглядит с точки зрения маркетинга и презентации - а с точки зрения сетевых технологий и того как он реализуется - это просто жесть. Собственно принцип работы NLB состоит в том что все IP пакеты идущие от клиента к серверам дублируются на L2  уровне. Как будто вместо свитчей у нас тупые хабы. Для тех кто не так стар поясню что hub - это примитивная версия свитча, которая каждый пакет поступающий на любой из его портов рассылает на все другие порты. Современный свитчи знают к какому порту у них подключен какой mac адрес и посылают пакет именно туда. Таким образом каждый подключенный к свитчу сервер может пользоваться физическим носителем (оптика/витая пара) так как будто других серверов нет. В общем microsoft придумала технологию которая превращает свитч обратно в тупой повторитель. Для этого они придумали три способа которые собственно называются режимами работы NLB:
  • Unicast - в этом режиме они просто генерируют новый mac адрес и присваивают его сразу всем сетевым интерфейсам участвующим в балансировке. Похеру что люди писавшие стандарты прилагали столько усилий чтобы сделать mac адреса уникальными. В такой ситуации свитч понимает что мак адрес соответсвующий виртуальному IP балансера привязан к нескольким портам и при поступлении пакета отправляет его на все порты.  
  • Multicast - это уже более стандартизированная штука. Есть пул mac адресов начинающихся с 03-BF (возможно есть и другие префиксы, я оригинал стандарта не читал)
  • Multicast + IGMP  - это более продвинутая версия предыдущего варианта, которая как вы можете догадаться использует Internet Group Management Protocol. Если в кратце - хосты сами подписываются на широковещательные сообщения по определенному мак адресу. 
Итак, NLB добился того что все пакеты дублируются на все интерфейсы одним из перечисленных выше способов. После этого у вас есть возможность отфильтровать какую-то часть из этих пакетов на уровне сетевого стэка, а какую-то оставить на обработку другим хостам.  Примерно тоже самое но на английском описано вот в этой статье: The NLB Deployment Reference – All you need to know to implement and deploy Microsoft Network Load Balancing К сожалению нормальной документации по NLB у Microsoft нет. Есть только статьи описывающие структуру меню и дискламеры. 

среда, 17 июля 2019 г.

Плёс

         В последнее время я в основном все пишу на профессиональные или около профессиональные темы. Думаю самое время смахнуть пыль с рубрики "Путешествия". Так сложилось что в последнее время путешествовать получается все больше по России. Но и в России можно найти очень красивые места. 
                 Сегодня речь пойдет о маленьком (примерно полторы тысячи жителей) городке в Ивановской области, который называется Плёс. 
Этот городок покорил меня своеобразным шармом провинциального приволжского городка. Думаю мой родной Ульяновск/Симбирск когда-то был примерно таким же. Но к сожалению сейчас Ульяновск уже совсем другой, а вот Плёс все тот же. 
Не смотря на то что Плёс это довольно древний город, сейчас он известен широкому кругу людей не своей историей уходящей в века, а тем что в 19 веке там жил знаменитый художник Исаак Левитан. Кстати жил он там совсем не долго, всего 3 лета. Уже в то время Плёс был "дачным" местом.   Сейчас все стало еще хуже - практически все дома на берегу выкуплены москвичами под "дачи". Люди которые бывали в Плесе лет 20 назад говорят что сейчас это совсем другой, туристический город. Хотя я сам находясь там этого не чувствовал. 
Я наслаждался этим сочетанием волжских просторов и деревенских пейзажей,
В общем Плёс сейчас занимает одно из первых мест в моем личном рейтинге городов Подмосковья. Всем рекомендую хоть разок туда съездить.

Prox mox

          Давеча решил разобраться с prox mox - это вроде open source, должно быть довольно понятно.  В общем хер там был - prox mox оказался только формально open source, а на самом деле по духу это microsoft.  Формально ребята выложили исходники в репозитории - https://git.proxmox.com/,  но кроме самих исходников больше ничего и нету. Есть документация из разряда - маркетинговый булшит, есть документация типа - "руководство начинающего пользователя".  И на этом все. Ребята решили - раз уж мы зарабатываем на консалтинге и саппорте - надо предоставить минимум информации.  В общем никакого описания внутренней архитектуры, ни каких-то открытых интерфейсов ожидать не приходится.  Установка ТОЛЬКО через ISO образы (нет даже элементарных deb/rpm пакетов). Внутри себя он собирает довольно много метрик, и кладет их в RRDTool. Какого-то нормального интерфейса для экспорта этих метрик в Prometheus не предусмотрено. Короче печаль и разочарование. Open source который мы заслужили. 
             Из хорошего - я нашел довольно годную презентацию  от Stefan HajnocziKVM Architecture Overview. После прочтения этой презентации и смежных статей "магии" в моей голове относительно того как работает виртуализация и KVM стало меньше, а понимания больше. 

пятница, 12 июля 2019 г.

Сказка про MS SQL и высокую доступность

         Подумал я грешным делом а не научится ли мне жить на два датацентра, как это делают взрослые компании. Думаю - MS SQL это же классный сервер, столько бабла за него отдаем. Entrerprise редакция обязана уметь master-master репликацию. Начал читать и тут постигло меня разочарование страшное. Выяснилось что умеет MS SQL в области высокой доступности приблизительно ничего. Ну то есть на уровне MySQL. Это был спойлер, а теперь расскажу по порядку. 
           Первое что предлагает Microsoft - это высокая доступность по высоким ценам, типичное enterprise решение - покупаешь SAN(СХД или хранилка по нашему), подключаешь ее к двум серверам. На этот шаренный диск ставишь MS SQL Server и получаешь решение которое называется Windows Failover Cluster или SQL Server Failover Cluster Instances (FCIs). По сути база и лог транзакций хранится на шаренном диске, только один SQL Server может принимать/обрабатывать транзакции, второй стоит рядом. Эта архитектура не гарантирует ни высокую доступность ни распределенность. Единственное для чего он годится - это установка обновлений. Ставишь обновления на пассивную ноду, переключаешься - смотришь, если все ок - ставишь обновления на вторую ноду.
           Следующее что нам предлагает Microsoft - это Availability Groups(AG). У этой штуки есть еще коммерческое название - Always On Availability Groups. Microsoft не была бы Microsoft если бы не придумала для банальной репликации "продающее" название.  Да, к сожалению вы  не ослышались - весь этот хваленый Always On это банальная master->slave репликация с кучей ограничений.  Дело в том что Always On требует наличия Windows Server Failover Clustering(WSFC),  который в свою очередь опирается на Active Directory и требует чтобы все сервера входящие в WSFC находились в одном домене. Отсюда вытекает интересное следствие -  Availability Group также должна находится в одном домене, соответсвенно использовать Availability Group для репликации между серверами находящимися в разных датацентрах становится практически невозможно (только если вы не хотите растянуть один домен на два датацентра, а вы скорее всего не хотите этого делать). Еще Availability Group не поддерживает больше 8 реплик (даже в Enterprise редакции). Ну и естественно Availability Group не поддерживает master-master репликацию. 
             Так что же умеет Availability Group ? Умеет перенаправлять read-only траффик на реплику. Причем для того чтобы это работало само приложение должно указать при подключении флаг - ApplicationIntent=ReadOnly. То есть коннекты должны быть разделены на read only и read-write на уровне приложения, а это значит что неподготовленное приложение вы не сможете просто так отмасштабировать на чтение. Потребуется рефакторинг. Отдельно стоит рассказать как происходит разделение траффика на read/write. Если вы подключаетесь к Availability Group Listener то вы подключитесь к primary replica (SQL Server master) и SQL Server отвечает за то чтобы знать топологию AG и если в нем имеется нода в состоянии synchronized настроенная для приема read-only запросов, то SQL Server отправляет клиенту параметры для прямого подключения к read-only серверу. Такой редирект на уровне MS SQL протокола поддерживают далеко не все клиенты. 
        Еще AG умеет реплицироваться синхронно и асинхронно. Умеет auto failover, но только если: 
  • Используется синхронный режим репликации
  • Реплика на которую мы планируем failover находится в состоянии synchronized
  • Windows Server Failover Clustering(WSFC) имеет кворум.
  • Autofailover настроен должным образом

В общем у меня не получилось придумать сценария при котором автоматическое переключение могло сработать как надо. WSFC по сути отвечает за синхронизацию на уровне серверов, и если на уровне серверов связи нет(нет кворума) - то автопереключение работать не будет. То есть при физическом отказе сервера сказки не случится. А зачем тогда весь этот цирк с конями если он работает только во время плановых учений ?
          При использовании асинхронного режима оно умеет только в ручную переключать master на одну из реплик с частичной потерей данных, причем про частичную потерю данных говорится при каждом упоминании.  Чтобы все сразу воодушевились - как здорово просрать данные. Давай немедленно использовать асинхронную репликацию. Причем как при ее использовании данные не просрать не сказано. К примеру тот же MySQL лет 10 как умееет failover без потери данных при асинхронной репликации.
           Отдельно стоит рассказать о взаимоотношениях между WSFC и AG. Как я уже сказал AG опирается в своей работе на WSFC. WSFC в свою очередь отвечает за мониторинг состояния SQL Server запущенного на локальной машине и за поддержание связности с другими серверами входящими в WSFC кластер. Мониторинг состояния SQL сервера сделан в стиле Microsoft - через shared memory. Есть процесс RHS.exe который шарит память с SQL Server, они там по очереди timestamp обновляют. Не обновил timestamp - значит умер. Идея здравая, но зачем это через shared memory делать ?? 
          Следующий интересный момент - как происходит failover. Для подключения к Availability Group используется так называемый Availability Group Listener. Listener состоит из Network Name Resource про который я уже писал и порта. Когда происходит failover все коннекты обрубаются, и клиенты начинают переподключаться. Сколько-то retry делается внутри  SQL клиента, но также рекомендуется выполнять переподключения в приложении. И в общем после какого-то количества повторов клиенты подключаются к новому серверу.
          На этом казалось бы уже длинная сказка про отсутсвие вменяемого HA setup-а должна подойти к концу. Но надо рассказать про еще одного героя - Distributed Availability Group. DAG появился как костыль, позволяющий строить cross-site setup (инсталяции работающие более чем в одном датацентре).  DAG позволяет объединить 2 или более AG в единую Availability Group. По сути позволяет реплицировать лог транзакций из primary replica одной AG в primary replica другой AG, а та уже в свою очередь реплицирует его на свои slave-ы. То есть DAG позволяет построить многоранговую сеть репликации и обойти ограничение на 8 slave-ов. Автоматически реплицироваться она не умеет, все это придется делать руками. 
       В общем как-то так. Никакой master-master репликации там близко нет.

Network Name Resource

          За что я не люблю Microsoft - так это за отсутствие нормальной документации. Ну то есть формально какая-то документация всегда есть - но это маркетинговая документация. Цель этой документации - не объяснить как работает та или иная технология, а напичкать тебя маркетинговыми названиями и аббревиатурами. Сделать так чтобы ты запутался в бесконечных уровнях абстракции. Сделать каждый уровень абстракции еще более абстрактным чем предыдущий.  Что в купе с отсутствием информации о реальной стороне вещей порождает карго-культ технологий Microsoft. В итоге понимание того как реально работает та или иная технология, на какие протоколы она опирается, какие алгоритмы использует приходится черпать из того раздела документации где описываются известные проблемы.  
         Возьмем как пример - Network Name Resource.  Я профильтровал через свой мозг десятки страниц маркетингового булшита прежде чем найти вот эту страницу - https://techcommunity.microsoft.com/t5/Failover-Clustering/DNS-Registration-with-the-Network-Name-Resource/ba-p/371482  Прочитав ее я понял что Network Name - это не имя сети, как можно было заключить из дословного перевода , а по сути service discovery сделанный через DNS. Ты создаешь Network Name Resource (DNS имя), привязываешь к нему ресурсы на которые оно должно показывать либо статические IP. Если ты привязал к доменному имени ресурсы(сервера) то их текущие IP адреса будут определены через DHCP. Как только эти сервера появляются в сети - DNS сервер начинает отдавать IP адреса этих серверов при запросе на resolve этого доменного имени.  
            Нет, это конечно похвально что в Microsoft изобрели service discovery еще до того как это стало main stream-ом, но блин - это мертворожденное решение. Все знают про DNS cache, а значит этот service discovery заведомо обречен на то чтобы лажать. Всегда будет какая-то задержка. Клиент не может каждый раз резолвить DNS имя при его использовании. 

понедельник, 1 июля 2019 г.

What makes a leader? by Daniel Goleman

       Иногда со мной так бывает, прочитаешь какую-нибуть книгу/статью и понимаешь что - "блин, я это знал". Но знал интуитивно,  теперь знаю что большие дядьки пишущие книги про менеджмент думают приблизительно также и что у того что мне подсказывала интуиция есть научная основа. 
         Не смотря на избитый заголовок, What makes a leader? на удивление годная статья. Если в кратце - хороших лидеров отличает вещь которую сейчас принято называть эмоциональным интеллектом. В нем выделяют 5 компонентов.
  • self-awareness(самоосознание,самоанализ) - способность понимать свои сильные и слабые стороны, трезво оценивать свои силы.
  • self-regulation (саморегуляция) - способность управлять своими эмоциями, способность нивелировать влияние своих слабых сторон.
  • motivation - это слово не нуждается в переводе, оно давно затерто до дыр. Но вопреки устоявшемуся мнению - мотивация это способность оставаться мотивированным без привлечения внешних мотиваторов (деньги, престиж, должность). То есть этим суперскилом обладают только те люди для которых мотиватором является сам факт достижения результата, люди которые кайфуют от фразы - "я сделал это, я нереально крут!"
  • empathy - это способность понимать эмоции других людей. Это не про сопереживание/жалость к другим людям, а про способность понимать эмоции людей и принимать в расчет этот фактор наряду с другими факторами.
  • social skills - это способность быстро налаживать взаимоотношения с другими людьми, способность к нетворкингу,   и способность использовать эти отношения для решения своих задач. 
        social skills - это наверное та суперспособность которой я лишен начисто. Молчаливым подтверждением тому является этот "блог начинающего социопата" или "блог который никто не читает" как я его назвал. 

вторник, 25 июня 2019 г.

Похоронил GoRelic

       Сегодня похоронил один из своих open source проектов - GoRelic. Когда я его начал NewRelic был стандартом де факто для мониторинга, а никакой поддержки для приложений на Golang у них не было. Когда NewRelic анонсировал открытие API для плагинописателей - я быстренько налабал этот проект.  Но как показал опыт писать плагины для закрытых систем (одной из которых без сомнения является NewRelic) это бесперспективное занятие. 
        Их API тогда был убогим и не покрывал и 10% функциональности доступной их собственным плагинам. А когда он стал нормальным - появился официальный Golang агент.  Сегодня я выложил последнюю, 7-ю по счету версию. Больше обновлений не будет.          

пятница, 21 июня 2019 г.

Секретное поле в PerformanceNodeTiming

     Пока разбирался с PerformanceNodeTiming обнаружил там недокументированное поле environment, содержащее время когда закончилась инициализация структуры environment (относительно времени старта процесса). В очередной раз наконтрибьютил в ноду - https://github.com/nodejs/node/pull/28280  Этот pull request прошел в мастер довольно быстро. Мой предыдущий pull request c новым методом в модуле v8 до сих пор ждет мерджа.

вторник, 18 июня 2019 г.

PerformanceTiming API

      PerformanceTimingAPI - довольно занятное API, появившееся еще в версии 8.5 но почему-то обделенное вниманием js программистов. К примеру это API позволяет относительно легко (без погружения в C++ код) замониторить GC, ну и много чего еще.  Возможно причина тому - не очень хорошая документация, да и сам API нельзя назвать интуитивно понятным. Я считаю себя в общем-то не глупым парнем, но я разобрался в нем не с первого раза. Поэтому я и решил потратить время на написание этой статьи.
В основе интерфейса PerformanceTimingAPI (или perf_hooks как оно называется в исходникахлежит иерархия классов основанная на PerformanceEntry. PerformanceEntry - это нечто что ты можешь измерить. У PerformanceEntry есть поле entryType которое собственно показывает что же ты измерил. Для того чтобы получать PerformanceEntry тебе нужно с помощью PerformanceObserver подписаться на них. 
       Если у PerformanceEntry поле entryType="mark" то мы имеем дело с отметиной на timeline.  startTime = время когда произошло это событие, duration = 0, name = имя события. Единственный способ получить mark - самим создать его с помощью фукнции performance.mark("Start of something")
       Единственный сценарий использования performance mark - создать два performance.mark и измерить время между ними используя performance.measure(measureName, startMark, endMark). Этот вызов возвращает нам еще один PerformanceEntry с типом measure. В общем такая красивая и бестолковая обертка вокруг timestamp. 
    Далее следует PerformanceEntry с типом function. Этот тип PerformanceEntry можно получить если обернуть некоторую функцию в мониторинг (через performance.timerify(fn)). В этом случае каждый раз когда функция вызовется будет создан новый PerformanceEntry с типом function описывающий время выполнения функции. Функция timrify использует кастомные атрибуты Object.defineProperty() что негативно сказывается на скорости работы с объектом.
        PerformanceEntry с типом gc - описывает запуск сборщика мусора. В этом случае поле kind будет указывать на тип сборки- major, minor, incremental и тд. На самом деле PerformanceEntry  с типом gc это отдельный класс в C++ коде - GCPerformanceEntry. По каким-то причинам разработчики решили не экспортировать его в мир JS. 
        PerforamanceEntry с типом http2 предназначены для мониторинга http2 сессий и потоков. Если name = HTTP2Stream то этот PerformanceEntry описывает создание нового http2 stream. Если name = HTTP2Session - то соответсвенно сессии.
      Напоследок расскажу о самом сомнительном на мой взгляд архитектурном решении - PerformanceNodeTiming. Он наследуется от PerformanceEntry но у своего родителя он использует только  два поля - entityType = node и duration. Все остальные поля его собственные - они описывают процесс инициализации Node.js: сколько времени потратили на инициализацию процесса ноды(nodeStart), сколько потратили на инициализацию v8 (v8Start), сколько потратили на инициализацию environment-а(поле так и называется environment и оно почему-то не документировано),  сколько времени потратили на bootstrap (bootstrapComplete), сколько времени потребовалось для запуска event loop (loopStart). 
        В отличии от всех остальных PerformanceEntry, работа с PerformanceNodeTiming ведется  не через PerformanceObserver, а через статическое поле performance.nodeTiming. Если ты подпишешься на PerformanceObserver с типом "node" то ты не получишь ничего.  Второй момент - мне не совсем понятно как мониторить инициализацию воркеров. Ведь у них как минимум свой environment и свой eventLoop который также будет инициализироваться. А если я два воркера запускаю одновременно ? В общем это лишний раз доказывает что singleton это антипаттерн проектирования. 

вторник, 11 июня 2019 г.

libuv internals и как эта библиотека интегрирована в Node.js

       С libuv сложилась такая парадоксальная ситуация - про нее все одновременно знают и не знают. Все знают что это нечто используемое Node.js для работы с сокетами и файловой системой и нечто обладающее магическим event loop. Ну а дальше показания расходятся. Причем даже у "экспертов" выступающих на конференциях. 
        В документации libuv есть довольно красивая и но малоинформативная диаграмма:

На мой взгляд функциональность предоставляемая libuv очень просто и понятно описана вот тут https://github.com/danbev/learning-libuv libuv можно использовать для того чтобы:
  • Работать с сетью (tcp, upd, pipe и тд)
  • Асинхронно работать с файловой системой
  • Асинхронно работать с DNS
  • Работать с нитями
  • Обрабатывать сигналы
  • Работать с таймерами

Это довольно большой список, но на самом деле интерфейс libuv основывается всего на трех концепциях: event loop(uv_loop_t), handle (uv_handle_t), request(uv_request_t):
  • event loop - это то, чем собственно занят поток выполнения. бегаем по кругу, дергаем callback-и. Детальнее опишу ниже.
  • handle  представляет собой некий ресурс. Это может быть обертка вокруг ресурса операционной системы (сокет, файловый дескриптор) или просто нечто вроде таймера умеющее дергать callback   
  • request - это некий запрос. Он может использовать хендлер (запрос на запись в файл) или может не использовать хендлеры.
Также внутри libuv сидит thread pool но я бы рассматривал его как деталь реализации а не часть интерфейса. Thread pool используется для превращения синхронных действий в асинхронные и в настоящее время используется только при работе с файловой системой и DNS. Ну еще мы сами момжем попросить libuv выполнить какую-то работу асинхронно в thread pool а не в основном потоке исполнения.
        Так вот, что же из себя представляет event loop ? На мой взгляд его проще всего описать вот такой диаграммой: 
Все прямоугольнички нарисованные на этой диаграмме детально описаны вот тут - https://github.com/libuv/libuv/blob/v1.x/docs/src/design.rst#the-io-loop
Я же хочу остановится на том как это все интегрировано в Node.js. Практически вся работа с libuv сосредоточена в evn.cc. Начинается все с void Environment::InitializeLibuv() - тут мы инициализируем таймеры, check handles и тд. 
        Теперь собственно посмотрим что из прямоугольничков оригинального event loop'а используется в Node.js:
  • Первое это таймеры. При инициализации Node.js создает один libuv таймер который запускает все JS таймеры. Время срабатывания таймера - минимальное среди всех установленных JS таймеров
  • Idle handlers в мир JavaScript никак не транслируются. Они используются внутри для управления логикой которую libuv использует для рассчета времени которое она должна провести заблокированной в poll вызове
  • Prepare handles - также в мир JavaScript не транслируются. Там стоит системный хендлер который служит для измерения времени которое мы фактически проведем заблокированными в poll вызове
  • Check handles - здесь Node.js также вешает один хэндлер который выполняет все callback-и установленные через setImmediate()
  • Close handlers - также не транслируются в JS. Здесь libuv запускает on close callback-и закрываемых ресурсов (хендлеров). 
В общем как-то так. Вообще по моему скроменому мнению - libuv одна из самых понятно написанных библиотек. Тут довольно интересно организорвано наследование структур - через макросы. Все поля структуры объявляются внутри макроса. Соответсвенно в дочерней структуре сначала идет макрос объявляющий родительские поля, а потом макрос объявляющий собственные поля:







пятница, 7 июня 2019 г.

V8 internals - отслеживание ссылок на объекты в куче V8

       То что спецификацию (как минимум первые версии) для языка JavaScript писали не очень умные люди с лихвой компенсируется мозгами тех кто пишет V8.  Чем больше разбираюсь в проекте - тем больше восхищаюсь этими людьми. И так - как же GC V8 узнает о ссылках на объекты в куче из C++ кода ? 
         Вся работа с объектами размещенными в куче построена на так называемых хендлерах. Так как GC может перемещать объекты размещенные в куче во время сборки мусора прямая работа с этими объектами (по указателю) запрещена. Все обращения только через хендлеры. Этих хендлеров есть несколько типов, все они объявлены в v8.h
  • Local - это хендлер время жизни которого ограничено временем жизни области видимости в которой он объявлен.  Как только мы выходим из области видимости - стэк отматывается и хендлер становится недействительным.
  • Persistent - это хендлер время жизни которого не контролируется областью видимости. Живет до тех пока явно не вызовешь Reset()
  • Global - тоже самое что и Persistent но c поддержкой move семантики
  • Eternal - это хендлер используемый для "вечных" ссылок. То есть ссылок живущих до тех пор пока жив Isolate. Они немного более удобны с точки зрения GC чем Persistent 
  • TracedGlobal - тоже самое что и Global но с подержкой трассировки GC. Для чего он может использоваться честно до конца не разобрался
Вся магия по управлению Local хэндлерами через область видимости реализуюется в классе HandleScope и Isolate.  Инстанс HandleScope обычно создается до создания первого хендлера. Все созданные после этого Local хэндлеры создаются в этой области видимости. HandleScope могут быть вложенными. При выходе за границы области видимости вызывается деструктор HandleScope  в котором происходит вызов HandleScope::CloseScope() который подчищает Local хэндлеры созданные в этой области видимости.  
        Тут собственно и начинается самое интересное. Выясняется что внутри Isolate (экземпляр виртуальной машины V8 со своей кучей и всем остальным) есть список буферов в которых собственно размещаются указатели на объекты размещенные в куче. val_ который хранится в Local хендлере это указатель на этот адрес, размещенный в буфере Isolate. Каждый раз когда мы создаем новый хендлер HandleScope::CreateHandle() выделает в буфере класса Isolate новый указатель который указывает на кучу. А возвращает указатель на этот указатель. Собственно когда нужно собрать мусор, GC пробегает по этому списку буферов заполнгенному указателями на кучу и помечает объекты как используемые. При перемещении объектов GC также меняет указатели если кто-то ссылается на этот объект. В общем получается весьма эффективная система.

четверг, 6 июня 2019 г.

V8 internals - хранение объектов и массивов. Инициализация глобального контекста

       Итак, я продолжаю свои упражнения в археологии. Для начала меня заинтересовал вопрос - вот есть у меня виртуальная машина, а как в ней появляются все эти прекрасные "встроенные функции" ? Собственно в этой статье это довольно подробно объясняется - https://v8.dev/blog/custom-startup-snapshots  Если в кратце то  в папочке tools есть утилита js2c.py которая конвертирует javascript код из папочки lib в бинарный массив который можно напрямую загрузить в V8 Heap.  Таким образом инициализация контекста (объект global) очень сильно ускоряется.
        Что касается хранения объектов в памяти V8 (memory layout) - по этой теме есть много статей, и даже доклад от Fedor Indutnyhttps://www.youtube.com/watch?v=tLyIs_0cUyc но на этот раз меня его доклад не впечатлил. Он прошелся по верхушкам и прорекламировал llnode - node.js плагин для lldb. Большое спасибо Федору за инструмент, но тема memory layout в его докладе была раскрыта на 10% не больше. Лучше всего это объяснено в этой статье - https://v8.dev/blog/fast-properties В ней акцент сделан на хранении свойств, а в следующей статье https://v8.dev/blog/elements-kinds более подробно объяснено хранение элементов массива.   Вообще в статьях очень много полезной информации по оптимизации - как писать код так чтобы V8 его быстрее исполнял. Ведь благодаря создателям JavaScript - одну и ту же вещь там можно написать 20 способами. Вообще мне кажется что люди проектировавшие JavaScript изначально - прямо-таки ненавидили разработчиков компиляторов/интерпретаторов и сделали все чтобы затруднить разработку компиляторов/интерпретаторов для JavaScript. 
В общем обе статьи я бы рекомендовал прочитать каждому JS разработчику стремящемуся писать оптимальный код. Для тех кто не в ладах с английским языком перескажу в кратце:
  • Если вы хотите чтобы доступ к свойствам объекта происходил быстро - явно определяйте все его свойства при создании объекта. Определяйте их в одном и том же порядке(в конструкторе). Не добавляйте новые свойства по ходу использования объекта. Не используйте Object.defineProperty() если у вас нет для этого большой необходимости. 
  • Избегайте полиморфизма. То есть передайвайте в функцию только аргументы одного типа. 
  • Не храните элементы разных типов в одном массиве. Явно приводите их к одному виду.
  • Избегайте разряженных массивов (массивов с пропусками). Сплошные массивы работают намного быстрее
  • Не пытайтесь обращаться к несуществующим элементам массива (требует обращения к prototype)

среда, 5 июня 2019 г.

V8 internals/Кишки V8

       Продолжаю разбираться как внутри устроен V8. Из интересного - доклад про Ignition - интерпретатор JS который V8 использует при первом исполнении JS кода:

При многократном выполнении код компилируется оптимизирующим компилятором TurboFan (с ним пока не разбирался).
    Начал копать memory layout по исходникам. Начал с src/objects.h - там общая иерархия объектов описана.  Если в кратце то V8 имеет следующие типы объектов(OBJECT_TYPE_LIST):

  • Smi - immediate small integer 
  • LayoutDescriptor
  • HeapObject - superclass for everything allocated in the heap
  • Primitive
  • Number
  • Numeric
В V8 используется хитрая схема тэгирования указателей (tagging scheme) -  так как все что они читают/пишут из памяти выровненно по границе машинного слова (4/8 байт) то младшие биты указателя всегда должны быть равны нулю. Они используют их для тэгирования значения хранящегося в области памяти. Если младший бит ноль - то это вовсе не указатель а Smi -целочисленное значение хранящиеся в верхних 31 бите указателя. Если младший бит равен 1 - то это указатель на HeapObject. Второй с конца бит используется для тэгирования WeakObject. Если второй бит равен 1 то это указатель на WeakHeapObject. 
Реализация лежит в include/v8-internal.h
 В добавок ко всему этому если архитектура 64-битная то Node.js умеет сжимать указатели (также как и Java Runtime).  
       Из источников информации отдельно хочется отметить https://github.com/danbev/learning-v8 Автор провел просто фантастическую работу по разбору кишков V8 с отладчиком в руках. И не поленился все это задокументировать. Вообще сама по себе идея документировать то что ты исследуешь в github очень крутая. У этого же автора еще штук пять подобных репозиториев где он разбирает libuv, node.js и прочее. 
      Еще для понимания очень полезным оказался V8’s Object Model Using Well-Defined C++  

понедельник, 3 июня 2019 г.

Этот день мы приближали как могли...

    Этот пост я пишу из поезда Ульяновск-Москва. Мало кто знает но я как минимум раньше очень часто ездил на поездах. Практически каждые выходные катал из Москвы в Ульяновск и обратно. Это продолжалось долгие семь лет (с перерывом на полтора года Вьетнама). И все это время я думал - когда же в поезде появится интернет ??? Так вот в купейных вагонах ТКС он наконец-то появился. Бесплатный. Я практически уверен что на просмотр фильма его не хватит - но для серфинга более чем достаточно. Я не знаю каких усилий потребовала реализация подобного проекта в условиях бюрократии РЖД - но я думаю не малых. В общем респект тем кто это сделал. А то что это еще и бесплатно - это в двойне приятно!

четверг, 30 мая 2019 г.

Внутренности Node.js

            Не могу не поделится классным одним докладом по внутренностям Node.js:
Судя по акценту парень говорит на русском лучше чем на английском. Слайды можно посмотреть http://jsconfbp.indutny.com/#/ 
Не смотря что доклад 4-х летней давности, основные моменты он раскрывает. Все ObjectWrap, AsyncWrap, TCPConnectWrap до сих пор используются в коде ноды. Также раскрывается то как инициализируются встроенные модули написанные на плюсах. 
Ну и напоследок не могу не похвастаться - мой первый пул реквест в node.js попал в мастер - https://github.com/nodejs/node/pull/27933 
Второй на рассмотрении https://github.com/nodejs/node/pull/27978 

среда, 29 мая 2019 г.

Мониторинг Node.js

      В последнее время озадачился мониторингом Node.js приложений. И понял что там все довольно печально. Начал контрибьютить в несколько довольно известных npm модулей дабы исправить эту ситуацию. Пока дела обстоят с переменным успехом:

  • Удалось добавить статистики в node-gcstats https://github.com/dainis/node-gcstats/pull/38#issuecomment-496421764 Научил  этот NPM модуль возвращать number_of_native_contexts() / number_of_detached_contexts().  В нормальном приложении number_of_detached_contexts() должно быть равно 0. Если в вашем приложении это не так - у вас большие проблемы с утечкой памяти.
  • Не удалось пропихнуть пул реквест в Node.js Prometheus клиент - к сожалению не могу убедить автора что breaking changes это плохо и лучше их избегать - https://github.com/siimon/prom-client/pull/260 В общем если кто-то читает этот блог - плюсаните пожалуйста pull реквест, может это сподвигнет автора принять его.

вторник, 28 мая 2019 г.

Node.js

          Последние лет 5 я был очень погружен в бэкэнд и инфраструктуру и все что происходило с фронтенд технологиями обходило меня стороной. В первую очередь Node.js. Сейчас у меня есть чуть больше чем дохрена свободного времени и я решил разобраться что же это такое. 
          Если совсем просто - взяли библиотеку под названием V8 которая может выполнять JS код. Прикрутили к ней другую библиотеку - libuv,  добавили немного обвязки по вкусу:
Если сравнить с  runtime других языков программирования - мягко говоря не густо. Но на мой взгляд в этой простоте - единственное достоинство ноды. Ну и еще npm - огонь. Все остальное я бы записал в недостатки. 
         Главный из них - однопоточность ноды. Нода однопоточна потому что V8 создавался как движок для браузера, а в браузере весь JS исполняется в одном потоке. И на клиенте это всех в принципе устраивает.  Но для server-side это на мой взгляд это большой недостаток. Львиная часть всех статей про производительность ноды повторяет одну фразу - не занимайте event loop.  Не выполняйте никаких долгих операций, не загружайте большие JSON. Только представьте, Node.js которая создана для  JS не может кушать большие куски JSON. JS не может осилить JSON, Карл! В общем это второе пришествие корпоративной многозадачности для тех кто не успел по программировать для Windows 3.11
         Второй большой недостаток ноды - это JavaScript. Да, вы не ослышались. JavaScript не плохо подходит для написание небольших кусков кода для клиента, где сборщик мусора по большому счету не нужен. Но когда это все крутится на backend - сборка мусора и ее скорость становится довольно критичной. В V8 реализовали очень приличный сборщик мусора, конкурентный mark and sweep, с поддержкой поколений и отдельной кучей для больших объектов - https://v8.dev/blog/trash-talk Но если на объект остаются ссылки - то даже самый лучше сборщик мусора не соберет этот объект. 
            Структура языка JavaScript провоцирует создание утечек памяти. Куча колбеков и анонимных функций. Каждый объект замыкания - это ссылка на все переменные находящиеся в области видимости. Замыканий много, переменный находящиеся в области видимости далеко не всегда прописываются в коде явным образом. Поэтому отследить образование нежелательных ссылок на объекты очень трудно.

четверг, 23 мая 2019 г.

Базовое знание OS и сетевых протоколов

        Практически на всех своих собеседованиях я задаю простой вопрос: Представь я открываю браузер и ввожу в адресную строку www.ozon.travel и нажимаю enter. Что при этом происходит?     
      Этот вопрос с одной стороны простой, с другой стороны - он позволяет быстро понять насколько человек знает сразу две области - работу операционных систем и работу сетевых протоколов. Для разных позиций я требую разную глубину понимания этого процесса. 
         Для ручного тестировщика достаточно просто знания что есть dns, есть http, есть tcp и ip и что они вместе работают как-то так. 
       Для разработчика/тестировщика-автоматизатора обязательно знание того как просиходит резолвинг имен, как происходит установление tcp коннекта и 100% понимание того как работает HTTP. 
       Для админа добавляется знание о том что есть системные вызовы, есть процессы, основные настройки влияющие на прохождение этого flow, базовые понятия о маршрутизации. Какие возможны сценарии закрытия TCP соединения, TCP slow start, TCP congestion control, и тд.
       Долгое время я считал что какого-то одного источника, который все это сразу сможет объяснить - не существует. Но не давно наткнулся на лекцию своего бывшего коллеги Владимира Иванова, которую тот давал еще в Яндексе - https://events.yandex.ru/lib/talks/2336/
Всем кто не смотрел - всячески рекомендую.  Он очень простыми и понятными словами доносит весьма сложные для понимания вещи - начиная от работы системных вызовов и заканчивая работой протоколов маршрутизации.  
     

пятница, 8 февраля 2019 г.

Selenoid/Moon

      Раньше у людей отвечающих за QA инфраструктуру было любимое развлечение - по приходу в новую компанию руками разворачивать инфраструктуру для Selenium автотестов. Сделать это хорошо довольно сложно а времени на поддержку инфраструктурных проектов вечно не хватает. Поэтому веселье обычно растягивалось минимум на пол года. Сейчас похоже лавочку прикрыли.  С приходом кубера в широкие массы развернуть Selenoid можно не запачкав ручки - все за вас сделает Moon. По крайней мере обещает что сделает :-)

вторник, 5 февраля 2019 г.

CLR via C# by Jeffrey Richter

       Наконец-то дочитал книгу CLR via C# by Jeffrey Richter. Скажу честно - дочитал через силу. Прям заставлял себя дочитывать.  В книге очень много воды и сомнительных рекомендаций по оптимизации.  Временами автор даже сваливается в какие-то внутри мелкософтовские разборки(как например при обсуждении EAP - event based asynchronous pattern).  
      В общем из плюсов - очень много информации по .NET CLR в одном месте, объяснены некоторые интересные аспекты работы .NET CLR. Из минусов - очень много воды, объяснения очевиднейших вещей, пересказывания мануалов. В общем временами читается как инструкция к стиральной машинке. 
        Если говорить о том мнении которое я составил о C# после прочтения этой книги: C# и его runtime писались с оглядкой на Java, и они всегда старались сделать немного лучше чем сделано в Java.  Иногда это получалось, но в некоторых случаях Java выигрывает за  счет открытости платформы и того что более качественные идеи рождаются и реализуются в community. 
     Параллельное программирование на .NET никогда не будет таким же эффективным как в Golang или другими языками где идея легковесных потоков была заложена с основания языка. Причина этого не в технологиях - я уверен что в microsoft достаточно компетенции написать scheduler легковесных потоков  а в legacy - то что раньше было возможно запускать потоки операционной системы с разными контекстами (правами, приоритетами и тд). И каждый раз при переключении на другой таск ThreadPool обязан все эти огромные структуры данных копировать. Добавьте к этому поддержку иерархической системы тасков которая требует большого количества памяти и хранение не перехваченных exceptions. 
       В общем отказаться от поддержки всего этого они не могут, а без этого просто взять и вкрутить планировщик легковесных потоков в существующий runtime будет очень трудно/не возможно.

среда, 30 января 2019 г.

Thanos

       Сегодня открыл для себя Thanos - инструмент для масштабирования Prometheus через federation (не знаю русскоязычного аналога этого глагола).  В свое время когда мы строили мониторинг Lazada на Prometheus нам очень не хватало инструмента подобного Thanos. Prometheus превосходный инструмент, но он не поддерживает HA из коробки, да и масштабируется только через ручное шардирование нагрузки на несколько истансов. По сути все работало превосходно пока все метрики помещались на один сервер и его мощности хватало и на запись собираемых метрик и на обработку входящих звапросов. Дальше начинались танцы с бубнами. 
         Сейчас судя по всему это уже позади.  Если говорить по простому: Thanos это инструмент который позволяет поставить рядом два Prometheus сервера и построить высоко доступный (HA) сервер мониторинга.  Также он позволяет масштабировать Prometheus через федерирование.  Дело осталось за малым - чтобы это все работало так же красиво как об этом пишут его создатели. 

вторник, 15 января 2019 г.

Как работает ApplePay

     Не так давно мне пришлось вплотную столкнуться с ApplePay, хотя вообще я с платежками давно уже не работаю. Попытался понять как работает ApplePay и с удивлением обнаружил что  нормального технического описания нет. Есть куча маркетингового булшита и отдельных догадок.  Единственное что я нашел - https://medium.freecodecamp.org/how-apple-pay-works-under-the-hood-8c3978238324  Это тоже не официальная документация, но выглядит вполне правдоподобно.
       Если в двух словах - когда вы добавляете карточку в ApplePay, Apple валидирует данные и отправляет к МПС(Visa/MasterCard) запрос на генерацию секретного токена(DAN - device account number), который по сути представляет "фейковый" номер кредитной карточки. Этот фейковый номер запоминается в устройстве. Во время транзакции iphone "прикидывается" этой фейковой карточкой и NFC терминал передает этот номер карты в МПС. МПС подменяет фейковый номер карточки реальным номером карточки добавленной ранее в ApplePay. Дальше транзакция проходит как обычно.