{
    "version": "https:\/\/jsonfeed.org\/version\/1",
    "title": "Блог об аналитике, визуализации данных, data science и BI, заметки с тегом: troubleshooting",
    "home_page_url": "http:\/\/test.leftjoin.ru\/tags\/troubleshooting\/",
    "feed_url": "http:\/\/test.leftjoin.ru\/tags\/troubleshooting\/json\/",
    "icon": "http:\/\/test.leftjoin.ru\/user\/userpic@2x.jpg",
    "author": {
        "name": "Николай Валиотти",
        "url": "http:\/\/test.leftjoin.ru\/",
        "avatar": "http:\/\/test.leftjoin.ru\/user\/userpic@2x.jpg"
    },
    "items": [
        {
            "id": "149",
            "url": "http:\/\/test.leftjoin.ru\/all\/problema-roistat-kak-gotovogo-resheniya-dlya-analitiki\/",
            "title": "Проблема Roistat как готового решения для аналитики",
            "content_html": "<p><img src=http:\/\/test.leftjoin.ru\/pictures\/unnamed-1.png  border=“0” width=100% height=100%><\/p>\n<p>Нередко компании, которые обращаются к нам за системой сквозной аналитики, уже используют какие-то инструменты для работы с данными. Иногда это «коробочные» решения. Сразу отказываться от них в пользу кастома не обязательно — на старте их можно интегрировать в новую систему, чтобы сэкономить время и деньги на разработку. Но в этом случае надо быть готовым к тому, что готовые решения не гарантируют точность данных на том же уровне, что специально построенная под вас система аналитики. Именно с этой проблемой мы столкнулись, когда начали строить аналитику для стартапа Refocus: до нас они использовали Roistat, но ошибки в его работе вынудили нас отказаться от него раньше, чем мы планировали.<\/p>\n<p>Способов построить сквозную аналитику, не обращаясь к готовым решениям, немало: можно собрать in-house отдел аналитики или найти специалистов на аутсорсе.<\/p>\n<p>При этом нужно учитывать, что работа целой команды аналитики может стоить порядка 5-7 тысяч евро или 500-700 тысяч рублей в месяц. И это не предел — все зависит от размеров компании-заказчика, состояния аналитики до начала работ и других факторов.<\/p>\n<p>На начальном этапе для многих компаний, у которых объем данных небольшой, такие расходы неоправданы. Из-за этого они часто предпочитают «коробочные» продукты — например, тот же <b>Roistat.<\/b> Это самое популярное решение на рынке, которое предлагает премиум-тариф с максимумом функционала и полной поддержкой от 1500 евро \/ 150 тысяч рублей в месяц. Базовые тарифы у них и того дешевле. У этого инструмента больше 200 встроенных интеграций, и не только с популярными Google Analytics или Яндекс Директ, но и с целым списком нишевых кабинетов и систем, о которых вы даже не слышали.<\/p>\n<p>После настройки интеграций с вашими источниками Roistat позволит создать дашборды за пару кликов, а сверху предложит автоматизацию маркетинга и лидогенерации. Конкуренты Roistat вроде <b>OWOX BI<\/b> или <b>Alytics<\/b> также предоставляют базовые возможности для аналитики и визуализации. В общем, недорогих решений под любые потребности много.<\/p>\n<p>Хотя “коробка” гораздо лучше бесконечных таблиц и поначалу хорошо оптимизирует работу с данными, она далеко не идеальна. С точки зрения эксперта все минусы универсальных решений видны как на ладони:<\/p>\n<ul>\n<li>не заглянешь под капот, если что-то идет не так;<\/li>\n<li>их система интеграций может не включать нужных источников;<\/li>\n<li>интеграции могут устаревать, создавая ошибки в выгрузке данных.<\/li>\n<\/ul>\n<p>Кроме того, у вендоров таких готовых решений почти никогда нет ресурсов и возможностей, чтобы подогнать его под специфические требования каждого клиента. Более того, чаще такие “кастомные” требования многократно превосходят стоимость самой коробки (в этом суть готового решения). Если что-то не так на бэке у юзера, если меняется воронка в CRM, если нужно настроить кастомные интеграции — велики шансы, что за это придется доплатить немаленькие деньги, если вендор вообще согласится реализовать эти “специфичные требования”.<\/p>\n<p>На одном из наших проектов нам довелось поработать именно с такой ситуацией: Roistat не смог обеспечить точность данных и интеграций со всеми нужными источниками, и с этим пришлось разбираться самой компании — и нам.<\/p>\n<h2>Как Refocus работал с аналитикой<\/h2>\n<p>Один из наших заказчиков,  который строил аналитику на самых ранних этапах — EdTech-стартап Refocus. Неудивительно, что когда мы пришли создавать для них кастомную систему, они уже работали с Roistat.<\/p>\n<p>Почему Refocus выбрали именно его? Во-первых, он доступен на русском языке, и это было удобно для команды, в которой много русскоязычных сотрудников. Во-вторых, им нужна была именно система сквозной аналитики, а не CRM с функцией автоматического построения графиков, поэтому альтернативы вроде HubSpot не подходили. В-третьих, все базовые потребности компании он удовлетворял за относительно небольшую сумму. Ну и в-четвертых — многие сотрудники из команды Refocus уже имели опыт работы с Roistat, так что для них это был удобный и знакомый инструмент.<\/p>\n<p>Roistat тянул данные из двух основных рекламных кабинетов — <b>Google и Facebook<\/b>*. В нем трекались все базовые метрики — бюджеты, показы, клики, конверсия — и отражались на автоматически созданных внутри программы дашбордах.<\/p>\n<p><img src=http:\/\/test.leftjoin.ru\/pictures\/unnamed-(2).png border=“0” width=100% height=100%><\/p>\n<p>К сожалению, скрины дашбордов из Roistat у нас не сохранились, но мы нашли для вас пример в интернете — выглядели они примерно так.<\/p>\n<p>Там даже присутствовал свой пользовательский идентификатор — когда мы вводили общий айди студентов, мы думали попробовать использовать его для мэтчинга персональных данных. На практике оказалось, что сопоставление по нему проходит очень неточно, и эту идею оставили.<\/p>\n<p>Стартап рос, число требований и людей увеличилось, поэтому нас позвали  строить для ребят кастомную аналитику и более подробные дашборды, и подключились мы.<\/p>\n<h2>Как обнаружилась проблема<\/h2>\n<p>Мы провели аудит существующей системы, получили доступы к нужным ресурсам и начали выстраивать инфраструктуру и готовить первый дашборд — маркетинговый, самый актуальный для компании на тот момент.<\/p>\n<p>Откуда мы тянули данные для этого дашборда? Конечно, из Roistat, ведь именно там уже лежали готовые маркетинговые показатели. Настройка хранилища и выгрузка данных из источников, которые Roistat не трекал, еще не была закончена, а смотреть на классные, качественные дашборды хотелось уже сейчас.<\/p>\n<p>Например, так выглядел код для выгрузки данных о затратах на рекламу через API:<\/p>\n<pre class=\"e2-text-code\"><code>def get_costs(channel, channel_level, date_from, date_to, source):\r\n         \r\n     data = '{&quot;dimensions&quot;: [&quot;marker_level_2&quot;, &quot;marker_level_3&quot;, &quot;marker_level_4&quot;],\\\r\n     &quot;metrics&quot;:[&quot;visitsCost&quot;, &quot;impressions&quot;, &quot;visits&quot;],\\\r\n     &quot;period&quot;:{&quot;from&quot;:&quot;' + date_from + '&quot;,&quot;to&quot;:&quot;' + date_to + '&quot;},\\\r\n     &quot;filters&quot;:[{&quot;field&quot;:&quot;' + channel_level + '&quot;, &quot;operation&quot;:&quot;=&quot;, &quot;value&quot;:&quot;' + channel + '&quot;}],\\\r\n     &quot;interval&quot;: &quot;1d&quot;}'\r\n     columns=['dimensions.marker_level_2.value',\r\n             'dimensions.marker_level_2.title',\r\n             'dimensions.marker_level_3.value',\r\n             'dimensions.marker_level_3.title',\r\n             'dimensions.marker_level_4.value',\r\n             'dimensions.marker_level_4.title',\r\n             'dateFrom'\r\n             ]\r\n     headers = {'content-type': 'application\/json'}\r\n\r\n     response = requests.request(&quot;POST&quot;, url, data=data, headers=headers)\r\n     df = response.json()['data']\r\n\r\n     l = []\r\n     for i in range(len(df)):\r\n         if df[i]['items'] != []:\r\n             l.append(df[i])\r\n     if len(l) &gt; 0:\r\n         res1 = pd.json_normalize(l, 'items', ['dateFrom'])[columns]\r\n         res2 = pd.DataFrame()\r\n         q = pd.json_normalize(l, ['items', 'metrics'])\r\n         for x in [&quot;visitsCost&quot;, &quot;impressions&quot;, &quot;visits&quot;]:\r\n             res2[x] = q.query('metric_name == @x')['value'].values\r\n         res = pd.concat([res1, res2], axis=1)\r\n         res['source'] = source\r\n         return res\r\n     else:\r\n         return pd.DataFrame()<\/code><\/pre><p>Подробнее о <tt>marker_levels<\/tt> можно прочитать <a href=\"https:\/\/help-en.roistat.com\/API\/methods\/analytics\/#sample-report\">здесь<\/a> в документации API Roistat.<\/p>\n<p>Когда Refocus посмотрели на первую версию дашборда, они заметили, что цифры не сходятся с их реальными бюджетами, кликами, конверсией в рекламных кабинетах.<\/p>\n<p>Мы начали искать ошибку с нашей стороны, но убедились, что наш код работает исправно и данные в нашем дашборде один в один совпадают с цифрами в Roistat. Никаких сбоев между источником и дашбордом не происходило.<\/p>\n<p>Дело в том, что пока компания смотрела только на визуализации Roistat, расхождения в данных не бросались в глаза. А наш дашборд содержал несколько новых графиков, более подробных, чем мог предложить Roistat. Когда Refocus стали сравнивать детали по разным срезам, на которые Roistat не позволял посмотреть, расхождения стали очевидны.<\/p>\n<p><img src=http:\/\/test.leftjoin.ru\/pictures\/unnamed-(1).png border=“0” width=100% height=100%><\/p>\n<p>У нас дашборд был более гибкий — можно было отфильтровать данные по разным важным срезам, более тонко настроить гранулярность. И благодаря этому ошибки в метриках стали заметны.<\/p>\n<p>Об этом мы и сообщили коллегам, продемонстрировав совпадение. Как раз в этот момент сыграл роль недостаток закрытой системы — посмотреть внутрь Roistat и найти, где именно происходит ошибка, не могли ни мы, ни бэкенд-специалисты Refocus.<\/p>\n<p>Разумеется, кастомная система уже предполагала отключение этого источника в будущем — зачем платный посредник, если мы все равно будем грузить данные напрямую и из рекламных кабинетов, и из кучи других источников?<\/p>\n<p>После обнаружения ошибки стало ясно, что затягивать с этим не стоит, если мы хотим предоставить клиенту чистые данные.<\/p>\n<p>Полностью отказаться от Roistat сразу не получилось, поэтому мы добавили на дашборд фильтр <tt>“match with Roistat”.<\/tt><\/p>\n<p>На нем мы отмечали количество рекламных кампаний, данные по которым расходятся с показателями на нашем дашборде и на Roistat’овском.<\/p>\n<h2>Как мы наконец заменили Roistat на кастомную систему и уточнили данные о воронках<\/h2>\n<p>Мы сразу написали несколько скриптов для выгрузки тех же самых данных из GA4 и Facebook Ads* через API.<br \/>\nНапример, так данные о рекламных показах, затратах и кликах  за день в разрезе {campaign, adset, adname} выгружались из кабинета GA4:<\/p>\n<pre class=\"e2-text-code\"><code>def get_google_costs(date_start, customer_id):\r\n\r\n    request = RunReportRequest(\r\n        property = f&quot;properties\/{property_id}&quot;,\r\n        dimensions = [Dimension(name = &quot;date&quot;),\r\n                     Dimension(name = &quot;sessionGoogleAdsCustomerId&quot;),\r\n                     Dimension(name = &quot;sessionGoogleAdsCampaignId&quot;),\r\n                     Dimension(name = &quot;sessionGoogleAdsCampaignName&quot;),\r\n                     Dimension(name = &quot;sessionGoogleAdsAdGroupId&quot;),\r\n                     Dimension(name = &quot;sessionGoogleAdsAdGroupName&quot;),\r\n                     Dimension(name = &quot;sessionGoogleAdsCreativeId&quot;)],\r\n        metrics = [Metric(name=&quot;advertiserAdCost&quot;),\r\n                   Metric(name=&quot;advertiserAdImpressions&quot;),\r\n                   Metric(name='advertiserAdClicks')],\r\n        date_ranges = [DateRange(start_date = date_start, end_date = date_start)],\r\n        dimension_filter = FilterExpression(\r\n            filter = Filter(\r\n                field_name = &quot;sessionGoogleAdsCustomerId&quot;,\r\n                string_filter = Filter.StringFilter(value = customer_id),\r\n            )\r\n        ),\r\n    )\r\n    response = client.run_report(request)\r\n\r\n    date = map(lambda x: x.dimension_values[0].value, response.rows)\r\n    customer_id = map(lambda x: int(x.dimension_values[1].value), response.rows)\r\n    campname_id = map(lambda x: int(x.dimension_values[2].value), response.rows)\r\n    campname = map(lambda x: x.dimension_values[3].value, response.rows)\r\n    groupname_id = map(lambda x: int(x.dimension_values[4].value), response.rows)\r\n    groupname = map(lambda x: x.dimension_values[5].value, response.rows)\r\n    adnam_id = map(lambda x: int(x.dimension_values[6].value), response.rows)\r\n    visitsCost = map(lambda x: float(x.metric_values[0].value), response.rows)\r\n    impressions = map(lambda x: int(x.metric_values[1].value), response.rows)\r\n    visits = map(lambda x: int(x.metric_values[2].value), response.rows)\r\n\r\n    df = pd.DataFrame({'region': customer_id, 'date': date, 'campname': campname, 'groupname': groupname, 'campname_id': campname_id, 'groupname_id': groupname_id, 'adnam_id': adnam_id, 'visitsCost': visitsCost, 'impressions': impressions, 'visits': visits})\r\n    return df\r\n\r\ndf = pd.DataFrame()<\/code><\/pre><p>После этого обнаружилось еще одно несовпадение: метрики в самом кабинете были чуть меньше, чем у нас на дашбордах. Отклонение не было критичным, но клиент снова не был уверен в точности наших данных.<\/p>\n<p>Оказалось, что в GA4 по умолчанию включается настройка <b>Thresholding applied.<\/b> Ее цель — предотвратить возможную идентификацию отдельных пользователей по данным о возрасте, гендере и интересах. В результате ее применения терялись данные о кампаниях с небольшим (меньше 50) количеством кликов. Чтобы этого избежать, нужно было или не трекать эти показатели, или не использовать в дашбордах метрики, касающиеся пользователей напрямую. Roistat тоже не выгружал эти “пограничные” кампании через свою дефолтную интеграцию с GA4, поэтому для Refocus расхождение стало неожиданностью. Зато при прямой выгрузке через API этого ограничения не было, и ускользнувшие поначалу кампании считались, так что данные были более полными без дополнительных ухищрений.<\/p>\n<p>С добавлением все новых и новых источников, конечно, пришлось еще и мэтчить данные рекламных кабинетов о затратах, кликах и показах с записями на вебинары и их посещением, со сделками в CRM — но Roistat этого вовсе не делал, и поэтому не мог дать полной картины о воронках.<\/p>\n<p>После введения прямой выгрузки из рекламных кабинетов данные наконец-то стали полностью совпадать с реальностью, и Roistat отключили, заменив другими источниками. Теперь Refocus могли не платить дважды и получить куда более точный анализ.<\/p>\n<h2>Выводы<\/h2>\n<p>Кастомная сквозная аналитика — конечно, не панацея, и есть много причин и возможностей обойтись без услуг агентств, которые ее строят. Тем не менее, даже самое популярное на рынке коробочное решение, очевидно, не всегда корректно работает. Обрубать его сразу не обязательно, и ничего не мешает сочетать Roistat и похожие продукты с кастомом. В конце концов, иногда удобно просто достроить интеграции, которые не поддерживает Roistat, и держать дашборды в нем. Но в этом случае важно понимать, что данные там могут быть неполными или некорректными, а сообщение с бэком компании — ненадежным. В результате обнаружить или исправить ошибки может быть сложнее, чем организовать прямую выгрузку данных в хранилище — и здесь кастомная система отлично справится.<\/p>\n<p><i>*Facebook принадлежит компании Meta, которая признана экстремистской организацией в России.<\/i><\/p>\n",
            "date_published": "2024-10-02T15:25:45+03:00",
            "date_modified": "2024-10-02T14:55:37+03:00",
            "image": "http:\/\/test.leftjoin.ru\/pictures\/unnamed-(2).png",
            "_date_published_rfc2822": "Wed, 02 Oct 2024 15:25:45 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "149",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "http:\/\/test.leftjoin.ru\/pictures\/unnamed-(2).png",
                    "http:\/\/test.leftjoin.ru\/pictures\/unnamed-(1).png",
                    "http:\/\/test.leftjoin.ru\/pictures\/unnamed-1.png"
                ]
            }
        },
        {
            "id": "34",
            "url": "http:\/\/test.leftjoin.ru\/all\/clickhouse-as-a-consumer-to-amazon-msk\/",
            "title": "Clickhouse в качестве consumer для Amazon MSK",
            "content_html": "<p class=\"note\"><b>Disclaimer<\/b>: заметка носит технический характер, поэтому может быть интересна меньшему числу лиц с бизнес-бэкграундом.<\/p>\n<p>В этом блоге еще ни разу не затрагивалась тема <a href=\"https:\/\/clickhouse.yandex\/\">Clickhouse<\/a>, одной из самых быстрых баз данных от компании Яндекс. Краткая справка без деталей: Clickhouse — <a href=\"https:\/\/habr.com\/ru\/company\/otus\/blog\/463665\/\">наиболее эффективно написанная СУБД<\/a> колоночного типа с точки зрения программного кода, информация о СУБД довольно <a href=\"https:\/\/clickhouse.tech\/docs\/ru\/\">подробно описана в документации<\/a> и во множестве видео на Youtube (<a href=\"https:\/\/www.youtube.com\/watch?v=bSyQahMVZ7w\">раз<\/a>, <a href=\"https:\/\/www.youtube.com\/watch?v=Ac2C2G2g8Cg\">два<\/a>, <a href=\"https:\/\/www.youtube.com\/watch?v=ltg8vstuHUU\">три<\/a>).<\/p>\n<p>В своей практике последние четыре года я использовал Clickhouse в качестве аналитика и эксперта по построению аналитической отчетности. В основном для решения задачи визуализации отчетности \/ отчетов с параметрами \/ дашбордов использовался <a href=\"all\/redash-polnocennaya-on-demand-analitika\/\">Redash<\/a> как наиболее удобный интерфейс для доступа к данным Clickhouse.<br \/>\nОднако совсем недавно в Looker, <a href=\"all\/looker-overview\/\">о котором я рассказывал ранее<\/a>, появилась возможность подключить Clickhouse в качестве источника данных. Следует заметить, что подключение к Clickhouse в Tableau существует уже довольно давно.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"http:\/\/test.leftjoin.ru\/pictures\/setup-clickhouse-new-connection-624@2x.png\" width=\"635\" height=\"761\" alt=\"\" \/>\n<\/div>\n<p>Архитектура аналитического сервиса, в основе которого лежит Clickhouse, в основном облачная. И в рассматриваемой задаче было именно так. Предположим, у вас существует выделенный instance EC2 в Amazon (на который вы установили Clickhouse) и отдельный Kafka-кластер (решение <a href=\"https:\/\/aws.amazon.com\/ru\/msk\/\">Amazon MSK<\/a>).<\/p>\n<p><b>Задача<\/b>: подключить Clickhouse в качестве <i>consumer<\/i> для получения информации с брокеров вашего кластера Kafka. На самом деле, в документации на сайте Amazon MSK довольно подробно описано <a href=\"https:\/\/docs.aws.amazon.com\/msk\/latest\/developerguide\/create-client-machine.html\">как именно подключаться к кластеру Kafka<\/a>, не буду дублировать эту информацию. В моем случае гайд помог: топики создавались продюсером с машины, на которой установлен Clickhouse и с неё читались консюмером.<\/p>\n<p>Но возникла проблема: при <a href=\"https:\/\/clickhouse.tech\/docs\/ru\/operations\/table_engines\/kafka\/\">подключении Clickhouse к Kafka в качестве консюмера<\/a>, происходила следующая ошибка:<\/p>\n<pre class=\"e2-text-code\"><code>020.02.02 18:01:56.209132 [ 46 ] {e7124cd5-2144-4b1d-bd49-8a410cdbd607} &lt;Error&gt; executeQuery: std::exception. Code: 1001, type: cppkafka::HandleException, e.what() = Local: Timed out, version = 20.1.2.4 (official build) (from 127.0.0.1:46586) (in query: SELECT * FROM events), Stack trace (when copying this message, always include the lines below):<\/code><\/pre><p>Продолжительное время я искал информацию в документации Clickhouse о том, что может вызывать эту ошибку, но не смог ничего найти. Следующей мыслью стала проверка работы локального брокера Kafka с той же машины. Установил клиент Kafka, подключил Clickhouse, отправил данные в топик и Clickhouse с легкостью их прочитал, т. е. консюмер Clickhouse работает с локальным брокером, а значит и вовсе работает.<\/p>\n<p>Поговорив со всеми своими знакомыми экспертами в области инфраструктуры и Clickhouse (<a href=\"https:\/\/rebrainme.com\/devops\/\">Вася<\/a>, Макс, привет!), с ходу мы не смогли определить в чем проблема. Проверили firewall, настройки сети, все было открыто. Подтверждалось также тем, что с локальной машины можно было отправить в топик удаленного брокера Kafka сообщения командой <i>bin\/kafka-console-producer.sh<\/i> и прочитать оттуда же <i>bin\/kafka-console-consumer.sh<\/i>.<\/p>\n<p>Затем мне пришла в голову мысль обратиться к главному гуру и разработчику Clickhouse — Алексею Миловидову. Алексей с радостью постарался ответить на возникшие вопросы и предложил ряд гипотез, которые мы проверили (трассировку сетевых подключений и т. п.), однако и после более низкоуровневого аудита проблему локализовать не удалось. Тогда Алексей посоветовал обратиться к Михаилу Филимонову из компании <a href=\"https:\/\/www.altinity.com\/\">Altinity<\/a>. Михаил оказался очень отзывчивым экспертом и одну за другой предлагал гипотезы для проверки (параллельно подсказывая как именно будет лучше их проверить).<\/p>\n<p>В итоге в ходе совместных усилий мы обнаружили, что проблема возникает у библиотеки <i>librdkafka<\/i>, так как другой пакет <i>kafkacat<\/i>, который использует эту же библиотеку отваливается от подключения к брокеру с той же проблемой (<i>Local: timed out<\/i>).<\/p>\n<p>После изучения подключения через <i>bin\/kafka-console-consumer.sh<\/i> и параметров подключения, Михаил посоветовал добавить такую строку в \/<i>etc\/clickhouse-server\/config.xml<\/i>:<\/p>\n<pre class=\"e2-text-code\"><code>&lt;kafka&gt;&lt;security_protocol&gt;ssl&lt;\/security_protocol&gt;&lt;\/kafka&gt;<\/code><\/pre><p>И, о чудо! Clickhouse подключился к кластеру и вытянул необходимые данные с брокера.<\/p>\n<p>Надеюсь, этот рецепт и мой опыт позволит вам сэкономить время и силы на изучение похожей проблемы :)<\/p>\n",
            "date_published": "2020-02-04T11:55:18+03:00",
            "date_modified": "2020-05-10T17:39:24+03:00",
            "image": "http:\/\/test.leftjoin.ru\/pictures\/setup-clickhouse-new-connection-624@2x.png",
            "_date_published_rfc2822": "Tue, 04 Feb 2020 11:55:18 +0300",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "34",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "http:\/\/test.leftjoin.ru\/pictures\/setup-clickhouse-new-connection-624@2x.png"
                ]
            }
        }
    ],
    "_e2_version": 3365,
    "_e2_ua_string": "E2 (v3365; Aegea)"
}