Русский
Русский
English
Статистика
Реклама

Высокая производительность

Оцените шансы хакнуть криптообменник и получить книжку с кабанчиком в подарок

12.01.2021 10:14:24 | Автор: admin
Разрабатываете софт под высокую нагрузку? Готовитесь к техническому интервью по System Design? Считаете, что криптообменники зажрались и пора бы их уже потрясти? Интересует, что такое надежность, производительность и масштабируемость, и какие метрики нужно использовать для их оценки? Хотите разобраться, какие типы баз данных использовать и для каких задач? Что такое Map Reduce? Что такое потоки событий и при чем тут Apache Kafka?

Тогда вам стоит почитать книгу с кабанчиком Мартина Клеппмана и оценить, какие ваши шансы хакнуть криптообменник с помощью теста (лучший получит от меня книгу в подарок с доставкой в любое место, куда доставляет Озон).

Итак, в 2014 году из криптообменника Poloniex были украдены 12,3% BTC. Хакер нашел уязвимость в коде, реализующем снятие денег со счета...



Код был реализован так, что при попытке снять BTC выполнялись:

  1. валидация запроса,
  2. проверка баланса на достаточность средств,
  3. если средств достаточно, то баланс уменьшался на требуемую сумму,
  4. и т.д. (см. сообщение на рисунке ниже).

Оказалось, что если зарегистрировать одновременно несколько корректных запросов на снятие средств с одного счета, то они будут обработаны одновременно и успешно пройдут проверку достаточности средств на шаге 2, даже если общая запрашиваемая сумма будет превосходить размер имеющихся средств. Конкретики у меня нет, но код мог быть реализован примерно следующим образом:

BEGIN TRANSACTION-- проверяем баланс счета списанияSELECT balance INTO $t_balance FROM accounts WHERE account_id=$account_id;-- если сумма на счете больше или равна сумме списания,-- то уменьшается баланс и выполняются другие действияIF $t_balance-$sum>=0 THENUPDATE accounts SET balance=$t_balance-$sum WHERE account_id=$account_id;-- выполнялись дальнейшие действия по снятию средств в той же транзакции-- ...ENDIF;COMMIT;

Если в БД используется уровень изоляции SNAPSHOT_ISOLATION или слабее, то возможна следующая ситуация. Например, баланс вашего счета 10 BTC, вы создаете две транзакции со списаниями по 10 BTC, которые одновременно прочитают положительное значение баланса и будут авторизованы криптообменником. В результате баланс счета станет -10 BTC, а вы сможете списать с вашего счета больше средств, чем у вас было.

Источник

Прочитав книгу, начинаешь понимать, что конкурентный доступ и транзакции это не просто теоретические вопросы для гиков. Подобные проблемы вызывали немалые денежные потери, приводили к расследованиям финансовых аудиторов и становились причинои порчи пользовательских данных. В книге приводится масса интересных ссылок и примеров. Раньше я думал, что не надо заморачиваться нюансами конкурентного доступа и всякими аномалиями. Просто нужно использовать ACID-базу данных, и все само собой образуется. Однако, как оказалось, за примерами проблем, которые могут в таком случае возникнуть, далеко ходить не надо. Подобные вопросы возникают, если нужно предотвратить двойное бронирование конференц-зала; в многопользовательской игре обеспечить, чтобы два пользователя не поставили фигуру на одно и тоже поле; в криптообменнике обеспечить невозможность двойного списания средств и так далее. Камон! Моя команда в ЛАНИТ как раз сейчас реализует функциональность записи на прием в госорганизацию.

Очень часто можно услышать мнение, что если вы не Google и не Amazon, то не надо ничего усложнять просто используйте реляционную БД, и все будет нормально. С другой стороны, довольно часто встречаются ситуации, когда команды применяют какое-то модное решение просто потому, что о нем последнее время много говорят. Я лично на собеседованиях кандидатов часто слышу странные обоснования, например: Мы решили перейти на Apache Kafka, т.к. его все используют, и это, похоже, уже стандарт. Обе эти крайности могут привести как минимум к неэффективным решениям, а, возможно, и к проблемам.

Действительно, технологии постоянно меняются. Сейчас мы уже хотим хостить наши приложения в облаках, использовать микросервисную архитектуру, разрабатывать с использованием непрерывной поставки и прочее. А это все очень серьезно меняет предпосылки, исходя из которых разрабатывались и использовались те или иные технологии.

В главе 9 автор, например, обращает внимание на то, что при использовании двухфазной фиксации критически важным звеном является координатор транзакций. Если координатор транзакций окажется недоступен, то это может повлечь значительную недоступность системы. Часто координатор является частью сервера приложений (например, WildFly с Narayana), поэтому если вы переносите сервер приложений с координатором в облако, то журналы координатора становятся критически важной частью системы, и эти узлы больше нельзя считать узлами без состояния (stateless). Это существенным образом влияет на схему развертывания. Таких тонкостей масса, и если не понимать, что там у технологий под капотом, то можно серьезно опростоволоситься.

Книга полна интересных исторических справок. Оказывается, термин согласованность (consistency) в аббревиатуре ACID на самом деле был добавлен авторами терминов для красоты, и согласованность вообще не считалась чем-то существенным.

Атомарность, изоляция и сохраняемость своиства базы данных, в то время как согласованность в смысле ACID своиство приложения. Оно может полагаться на своиства атомарности и изоляции базы данных, чтобы обеспечить согласованность, но не на одну только базу.

Для меня эта книга стала настольной. Я не могу ее охватить за один присест и уже перечитываю третий раз с упором на разные темы. Обычно я делаю выжимки из книг, когда их читаю, однако в данном случае это просто бессмысленно. Поэтому я решил, что будет полезно сделать тесты, чтобы я сам (так как все забывается!) и мои коллеги могли попрактиковаться и оценить свои знания.

Информации так много, что я решил сократить тест до пары десятков вопросов и ограничил его несколькими темами: надежность, масштабируемость, легкость развития (глава 1), модели данных (глава 2), подсистемы хранения и извлечения данных (глава 3), кодирование и эволюция (глава 4), транзакции (глава 7).

Оцените ваши знания в области проектирования высоконагруженных систем по версии книги с кабанчиком тут (лучший получит от меня книгу в подарок с доставкой в любое место, куда доставляет Озон, ответы принимаются в течение 1 недели после публикации поста).

Было жутко интересно и полезно придумывать вопросы по книге. Понимание материала улучшается значительно. Мне лично всегда не хватает практических задач или тестов после прочтения книги, просто так прочитать не достаточно. Если у вас есть интересные вопросы по затронутым здесь темам, напишите в комментариях. Было бы здорово поломать голову.

Если вам хочется присоединиться к высокотехнологичной команде, которая работает по схеме DevOps/непрерывная поставка с использованием современных технологий и микросервисной архитектуры, то пишите мне в личку, сюда или сюда.
Подробнее..

Как я создал лучший colorpicker

18.01.2021 18:09:31 | Автор: admin

Эта статья посвящена тому, как я решил написать свой выбор цвета для сайта, потому что не устраивала кривая работа и тяжеловесность аналогов. Мой код не содержит ни одного плагина или библиотеки, поэтому он максимально простой и производительный.

Начнём с самого начала. Создаём базовую HTML-разметку:

<!doctype html><html lang="en"><head>    <meta charset="UTF-8">    <meta name="viewport"          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">    <meta http-equiv="X-UA-Compatible" content="ie=edge">    <title>Document</title></head><body>    <div id="app">        <header>            <h1 id="title">Color Picker</h1>        </header>    </div>    <div id="colors-block"></div></body></html>

Далее надо вывести все цвета. Я решил выводить их на PHP, чтобы пользователь после открытия страницы уже видел палитру, а не ждал, пока это отрендерится у него на клиенте. Выводить решил в шестнадцатеричном варианте. Для этого делаем 6 вложенных циклов for и выводим блоки 1x1 px.

Код
<?php$color = '';        for($i=0;$i<15;$i++) {            $a = $i;            switch($i) {                case 10:                    $a = 'A';                    break;                case 11:                    $a = 'B';                    break;                case 12:                    $a = 'C';                    break;                case 13:                    $a = 'D';                    break;                case 14:                    $a = 'E';                    break;                case 15:                    $a = 'F';                    break;            }            for($j=0;$j<15;$j++) {                $b = $j;                switch($j) {                    case 10:                        $b = 'A';                        break;                    case 11:                        $b = 'B';                        break;                    case 12:                        $b = 'C';                        break;                    case 13:                        $b = 'D';                        break;                    case 14:                        $b = 'E';                        break;                    case 15:                        $b = 'F';                        break;                }                for($k=0;$k<15;$k++) {                    $c = $k;                    switch($k) {                        case 10:                            $c = 'A';                            break;                        case 11:                            $c = 'B';                            break;                        case 12:                            $c = 'C';                            break;                        case 13:                            $c = 'D';                            break;                        case 14:                            $c = 'E';                            break;                        case 15:                            $c = 'F';                            break;                    }                    for($l=0;$l<15;$l++) {                        $d = $l;                        switch($l) {                            case 10:                                $d = 'A';                                break;                            case 11:                                $d = 'B';                                break;                            case 12:                                $d = 'C';                                break;                            case 13:                                $d = 'D';                                break;                            case 14:                                $d = 'E';                                break;                            case 15:                                $d = 'F';                                break;                        }                        for($m=0;$m<15;$m++) {                            $e = $m;                            switch($m) {                                case 10:                                    $e = 'A';                                    break;                                case 11:                                    $e = 'B';                                    break;                                case 12:                                    $e = 'C';                                    break;                                case 13:                                    $e = 'D';                                    break;                                case 14:                                    $e = 'E';                                    break;                                case 15:                                    $e = 'F';                                    break;                            }                            for($n=0;$n<15;$n++) {                                $f = $n;                                switch($n) {                                    case 10:                                        $f = 'A';                                        break;                                    case 11:                                        $f = 'B';                                        break;                                    case 12:                                        $f = 'C';                                        break;                                    case 13:                                        $f = 'D';                                        break;                                    case 14:                                        $f = 'E';                                        break;                                    case 15:                                        $f = 'F';                                        break;                                }                                $color = "#{$a}{$b}{$c}{$d}{$e}{$f}";                                                                echo '<div class="color__item" style="width:1px; height:1px;background:'.$color.';"></div>';                                $color = '';                                                            }                        }                    }                }            }        }

Если i>=10 то заменяем на символ из шестнадцатеричной кодировки.

В итоге через пару минут ожидания я решил выключить пылесос, который почему-то лежал включенный. Но оказалось, что это не он, а кулер моего ноутбука. Я понял, что, видимо, вывожу сразу слишком много цветов, поэтому решил выводить только первые 10 000 цветов для тестирования. Получилась такая красота:

Неплохо, но попадать по цветам, слишком неудобно. Поэтому увеличим размеры "пикселей"

Отлично! Но есть одна проблема: здесь только синий цвет, но хотелось бы получить побольше. К сожалению, мой слабенький ноут не может отобразить всю палитру сразу, несмотря на то, что я не использую даже сторонних плагинов. Поэтому я решил выводить различные палитры для основных цветов в зависимости от GET-параметра. Для этого создаем меню:

<header>  <h1 id="title">Color Picker</h1>  <nav>    <ul>      <li><a href="?x=blue">Blue colors</a></li>      <li><a href="?x=green">Green colors</a></li>      <li><a href="?x=red">Red colors</a></li>    </ul>  </nav></header>

А в самом PHP и придумал хитрый способ чтобы переписывать нужны было как можно меньше. Теперь цвета выводятся вот так:

<?php$x = $_GET['x'];if ($x == 'red') {$color = "#{$e}{$f}{$c}{$d}{$a}{$b}";} elseif ($x == 'green') {$color = "#{$a}{$b}{$e}{$f}{$c}{$d}";} else {$color = "#{$a}{$b}{$c}{$d}{$e}{$f}";}$q++;if ($q <=10000) {  echo '<div class="color__item" style="width:2px; height:2px;background:'.$color.';"></div>';  $color = '';}

Что получилось:

Остальные палитры доработаю чуть позже или можете доработать их сами!)

Теперь нам нужно непосредственно сделать выбор цвета и сохранение его в элемент формы. В HTML добавляем такой код:

<h3>Selected Color:</h3><div id="selected-color"></div><input type="hidden" id="color" name="color" value="rgb(255,255,255)">

А вот так выглядит JS:

document.querySelectorAll('.color__item').forEach((item, i, arr) => item.onclick = (e) => {document.querySelector('#selected-color').style.background = e.target.style.background; document.querySelector('#color').value = e.target.style.background});

Отлично! Теперь на все цвета вешается обработчик клика и выбранный цвет записывается в наш input[type='hidden'], который можно будет передать с остальной формой. Также мы показываем выбранный цвет пользователю

Безопасность

Не забудем и о безопасности. Так как название палитры передается как GET-параметр и обрабатывается нашим PHP, то есть вероятность XSS-инъекции! Чтобы защититься от инъекции, я позаимствовал код из этой статьи: http://personeltest.ru/aways/habr.com/ru/post/470193/ Автор показал очень простой и эффективный способ защиты, так что не будем городить костыли и воспользуемся им. Код вставлять сюда не буду, т.к. он идентичен коду в той статье, поэтому не буду дублироваться.

Теперь при открытии страницы появляется ошибка: <b>Call to undefined function mysql_real_escape_string()</b>. Я сначала хотел просто удалить эту строку, но подумал, что если кто-то из вас будет пользоваться моим кодом, то наверняка у вас будет работа с базой данных, поэтому проблему нужно решить.

Я вспомнил, что mysql в PHP устарел, и надо использовать mysqli. Поэтому соответствующим образом исправил вызов функции.

Умная IDE подсказывает, что не хватает обязательного параметра. Поэтому вторым аргументом я просто добавил true. Я нередко видел, как вторым аргументом передают булево значение. Например, в json_decode()

Однако, это не сработало, появился warning, говорящий, что аргумент должен быть строкой. Пришлось обернуть true в кавычки, и это, конечно же, помогло.

$v = mysqli_real_escape_string( $v, 'true' );

Однако, теперь ругается на первый аргумент, говоря, что аргумент должен быть объектом mysqli(), поэтому вместо "$v" я передал "new mysqli()". Он опять начал ругаться на что-то, но у меня плохо с английским, и я не понял суть. Поэтому решил забить и просто закомментировать строку. Скорее всего это баг из-за того, что PHP8 вышел лишь недавно, и многое работает криво.

Зато от XSS мы теперь защищены!

Запоминание цветов

Наша палитра отлично работает, но хотелось бы иметь возможность "запомнить цвет" - чтобы пользователь в случае закрытия страницы смог быстро восстановить цвет, который он выбирал. Для этого добавляем следующий код:

<div id="save-color-block"><button id="save-color__button">Запомнить мой цвет</button></div>

Как и говорил, у меня плохо с английский языком, и я не знаю как переводится "запоминать". Поэтому, написал по-русски (приношу извинения перед англоязычными пользователями моего кода (I am sorry)).

Теперь будем сохранять данные на сервере по клику на кнопку. Для этого пользователь должен будет ввести любой ключ по которому можно будет в будущем извлечь его цвет. Для ввода ключа воспользуемся функцией prompt(), т.к. самописные модальные окна - зло, и они пользователей бесят. Затем, для отправки на сервер будем использовать fetch - никаких jQuery, axios и т.п. Ведь наша задача сделать код простым и производительным, который поддерживать и использовать будет приятно.

Код получился вот таким:

document.querySelector('#save-color__button').onclick = () => {  const color = document.querySelector('#color').value;  const key = prompt('Write your key:');  const formData = new FormData();  formData.append('key', key);  formData.append('color', color);  fetch('save-color.php', {    method: 'POST',    body: formData  }).then((response) => {    if (response.status === 200) {      alert('success!');    } else {      alert('error!');    }  })

Теперь нам нужно создать файл "save-color.php". Код его будет крайне простым

<?php$key = $_POST['key'];$value = $_POST['color'];file_put_contents($key.'.txt', $value);

Вот и всё! Теперь мы можем просто вбить в адресную строку mydomain.ru/{key}.txt и увидеть цвет, который мы выбирали!

Заключение

Вот так, буквально за пару часов мы реализовали свой функционал выбора цвета. Да, он ещё несовершенный, но каждый может править код под себя и использовать в своё удовольствие! Если эта статья не уйдёт в минус, то дальше я покажу, как очень просто можно найти все ключи ассоциативного массива с помощью простейшего перебора строк. А на этом всё. Пишите качественный и производительный код.

Ну и напоследок

/sarcasm

Подробнее..

Мониторинг многопоточных приложений Node.JS

28.12.2020 10:22:36 | Автор: admin
В этой статье мы разберем особенности мониторинга многопоточного Node.JS приложения на примере нашего коллектора для сервиса мониторинга и анализа логов серверов PostgreSQL.


Для масштабирования коллектора мы используем многопроцессный подход, с одним управляющим и несколькими рабочими процессами, межпроцессное взаимодействие происходит только между рабочим и управляющим процессом.
Рабочие процессы выполняют одинаковые задачи сбор, обработка и запись логов с серверов PostgreSQL. При этом сбор и запись это по сути IO-задачи, в которых nodejs очень хороша. А вот обработка и парсинг планов запросов это довольно CPU-емкая задача, блокирующая event-loop. Поэтому такие задачи лучше выносить в отдельный воркер или пул воркеров, передавая им данные на обработку посредством обмена IPC-сообщениями.

Раньше, для задачи обработки и парсинга планов запросов мы использовали именно такой подход. Но у него есть недостаток большие объемы передаваемых данных по IPC могут привести к значительному увеличению затрат на сериализацию в JSON и обратно.

Например при передаче по IPC буфера, в которой содержится строка 'test' происходит передача строки:
'{"type":"Buffer","data":[116,101,115,116]}'

При большом количестве передаваемых данных накладные расходы могут стать такими:


Решением для нас стало использование worker_threads, появившихся в Node.JS 10.5.0, работающих в рамках одного процесса и позволяющих использовать новые методы коммуникации между потоками.
Архитектура изменилась:

А вместе с ней и подходы к мониторингу. Например, использование CPU внутри worker_thread традиционным способом измерить не получится.
Т.е. раньше, для каждого процесса-воркера, мы измеряли CPU-usage с помощью process.cpuUsage() и process.hrtime() примерно так:
let startCpuUsage = process.cpuUsage();let startTime = process.hrtime();let n = 1000;while (n--) Math.sin(n);let {user, system} = process.cpuUsage(startCpuUsage); // время в микросекундахlet time = process.hrtime(startTime); // наносекундыlet cpuUsage = 100 * 1000 * (user + system) / (time[0] * 1e9 + time[1]);

Но для процесса с worker_threads вызов process.cpuUsage() выдает процессорное время для всего процесса в целом, суммируя все его потоки. И такой же результат мы получим, если вызовем process.cpuUsage() изнутри worker_thread.
Почему так происходит?
Дело в то что process.cpuUsage() использует вызов uv_getrusage, а тот в ОС Linux выполняет системный вызов getrusage с параметром RUSAGE_SELF, т.е. возвращает статистику для вызывающего процесса как сумму по всем его потокам, при этом не важно из какого потока мы делаем этот вызов во всех потоках он будет возвращать одинаковые значения.

Как же получить CPU-usage для worker_threads и почему в Node.JS нет встроенных методов для профилирования CPU worker_threrads?
Здесь есть ответ разработчика worker_threads.
Предложено два варианта либо с помощью системного вызова gettid() получить tid для worker_thread и просматривать данные в /proc/${tid}, либо использовать getrusage() с параметром RUSAGE_THREAD, позволяющим получать статистику только для вызывающего потока.
Кстати, таким же образом можно получать метрики использования CPU и для основного потока процесса, без учета всех дополнительных потоков и worker_threads.

Итак, разобравшись с этим вопросом, мы стали искать готовые модули для мониторинга worker_threads, и не нашли Поэтому сделали свой , заодно добавив в него выдачу всех остальных метрик для мониторинга Node.JS приложения. Серверные метрики мы уже получаем с помощью своей системы сбора метрик .

Мониторинг CPU


Для анализа использования CPU мы берем метрики от воркеров и worker_threads, а также метрики общей загруженности CPU и в разрезе ядер:

  • Для воркеров в целом:

  • Для основных потоков воркеров:

  • Для worker_threads (по первым из пула, но полезным будет и суммарный в разрезе воркера):

  • Общая загрузка CPU на сервере:



С метриками CPU разобрались, а что насчет профайлинга worker_threads?
Проверим, запустив этот небольшой тест с параметром node --prof
Код теста
const { Worker, isMainThread} = require('worker_threads');const crypto = require('crypto');function mainTest() {  let cnt = 10000;  while (cnt--) crypto.randomBytes(cnt);}function threadTest() {  let cnt = 10000;  while (cnt--) crypto.randomBytes(cnt);}if (isMainThread) {  let worker = new Worker(__filename);  setInterval(mainTest, 1000);} else {  setInterval(threadTest, 1000);}


В результате получили два isolate-* файла, для основного потока и для worker_thread.
Далее, с помощью node --prof-process <isolate_file> можем посмотреть нужный профайл.
Кстати, с опцией --no-logfile-per-isolate вместо нескольких isolate* файлов будет один v8.log с суммарным результатом по всем потокам, включая основной.

И еще используя опцию node --inspect или послав сигнал SIGUSR1 работающему процессу с целью снять CPU профайл в Chrome DevTools, мы увидим данные только по основному потоку.

Использование памяти


Также как и для CPU, снимая профайл в Chrome DevTools мы получим Heap snapshot только основного потока.
К счастью, с версии node 12.17.0 появилась возможность получить heap snapshot прямо из кода worker_threads с помощью вызова worker.getHeapSnapshot(), а с версии 11.13.0 также для основного потока вызовом v8.getHeapSnapshot()

Попробуем
const { Worker, isMainThread } = require('worker_threads');const v8 = require('v8');const fs = require('fs');if (isMainThread) {  let worker = new Worker(__filename);  let mainArray = [];  function mainTest() {    let cnt = 100;    while (cnt--) mainArray.push(`main-msg-${cnt}`);  }  process.on('SIGUSR2', () => {    v8.getHeapSnapshot().pipe(fs.createWriteStream(`process_${process.pid}.heapsnapshot`));    worker.getHeapSnapshot().then((heapsnapshot) => {      heapsnapshot.pipe(fs.createWriteStream(`process_${process.pid}_wt_${worker.threadId}.heapsnapshot`));    })  });  setInterval(mainTest, 1000);} else {  let threadArray = [];  function threadTest() {    let cnt = 100;    while (cnt--) threadArray.push(`thread-msg-${cnt}`);  }  setInterval(threadTest, 1000);}


Послав сигнал SIGUSR2 процессу, мы получим два heapsnapshot, которые затем можно проанализировать в Chrome DevTools:

  • Основной процесс:

  • worker_thread:



Какие метрики памяти интересны для анализа?
Мы используем те, что выдает process.memoryUsage() rss, heapTotal, heapUsed, external.
И также v8.getHeapSpaceStatistics(), с его помощью можно получить данные по сегментам Heap new_space, old_space, code_space, large_object_space.
rss всегда выдается для всего процесса, а остальные метрики в рамках вызывающего контекста.

  • Суммарный по воркерам:

  • По воркеру:

  • По worker_threads:



Сборка мусора


Т.к. в каждом worker_thread запускается свой инстанс Node.JS с v8/libuv, то и GC у каждого тоже свой и мониторить их надо по отдельности.
Для мониторинга GC нам нужно получать данные об общей продолжительности сборки мусора, а также количество запусков и время выполнения каждого цикла.
Уже довольно давно, с версии 8.5.0, в Node.JS появился механизм PerformanceObserver, позволяющий кроме всего прочего получить всю необходимую информацию по циклам GC.

Например так
const { PerformanceObserver, constants } = require('perf_hooks');let stats = {};let gcObserver = new PerformanceObserver((list) => {  list    .getEntries()    .map(({kind, duration}) => {      stats['gc.time'] += duration;      switch (kind) {        case constants.NODE_PERFORMANCE_GC_MINOR:          stats['gc.Scavenge.count']++;          stats['gc.Scavenge.time'] += duration;          break;        case constants.NODE_PERFORMANCE_GC_MAJOR:          stats['gc.MarkSweepCompact.count']++;          stats['gc.MarkSweepCompact.time'] += duration;          break;        case constants.NODE_PERFORMANCE_GC_INCREMENTAL:          stats['gc.IncrementalMarking.count']++;          stats['gc.IncrementalMarking.time'] += duration;          break;        case constants.NODE_PERFORMANCE_GC_WEAKCB:          stats['gc.ProcessWeakCallbacks.count']++;          stats['gc.ProcessWeakCallbacks.time'] += duration;          break;      }    })});function resetStats() {  Object.assign(stats, {    'gc.time': 0,    'gc.Scavenge.count': 0,    'gc.Scavenge.time': 0,    'gc.MarkSweepCompact.count': 0,    'gc.MarkSweepCompact.time': 0,    'gc.IncrementalMarking.count': 0,    'gc.IncrementalMarking.time': 0,    'gc.ProcessWeakCallbacks.count': 0,    'gc.ProcessWeakCallbacks.time': 0,  });}resetStats();gcObserver.observe({entryTypes: ['gc'], buffered: true});function triggerScavenge() {  let arr = [];  for (let i = 0; i < 5000; i++) {    arr.push({});  }  setTimeout(triggerScavenge, 50);}let ds = [];function triggerMarkCompact() {  for (let i = 0; i < 10000; i++) {    ds.push(new ArrayBuffer(1024));  }  if (ds.length > 40000) {    ds = [];  }  setTimeout(triggerMarkCompact, 200);}triggerScavenge();triggerMarkCompact();setInterval(() => {  console.log(stats);  resetStats();}, 5000);

Результат:
{  'gc.time': 158.716144,  'gc.Scavenge.count': 11,  'gc.Scavenge.time': 135.690545,  'gc.MarkSweepCompact.count': 2,  'gc.MarkSweepCompact.time': 22.96941,  'gc.IncrementalMarking.count': 2,  'gc.IncrementalMarking.time': 0.056189,  'gc.ProcessWeakCallbacks.count': 0,  'gc.ProcessWeakCallbacks.time': 0}


Этот метод работает как в основном потоке так и в worker_threads, для нашего коллектора мы получаем графики с метриками за секунду:
  • По воркерам

  • По worker_threads

  • Общее время GC в разрезе воркеров

  • Общее время GC в разрезе worker_threads


Event-loop latency


Для мониторинга задержек event-loop удобно использовать появившийся в версии 11.10.0 monitorEventLoopDelay тут можно получить не только среднее и предельные значения, но и различные перцентили.
Мы используем max, min, mean, и percentile(99):
  • Суммарный по всем воркерам

  • Суммарный по worker_threads

  • По воркеру

  • По worker_thread


Мониторинг пула worker_threads


Системные показатели работы пула уже приведены выше, а здесь поговорим о метриках производительности многопоточного приложения.
При старте каждый воркер коллектора запускает пул с одним worker_thread, который обрабатывает очередь поступающих планов запросов.
Дополнительные worker_thread запускаются при увеличении размера очереди и при нахождении задач в очереди дольше определенного времени. Также они автоматически завершаются после периода неактивности.
Код обработки очереди задач
  const SPAWN_LAG = 2000;  this._queue = [];  assignTask(msg) {    if (this.mainExplainer.ready === true) {      this.mainExplainer.ready = false;      this.mainExplainer.sendMessage(msg);    } else if (this._idleExplainers.length > 0) {      let explainer = this._idleExplainers.pop();      clearTimeout(explainer.timeoutIdle);      explainer.sendMessage(msg);    } else {      this._checkAndStartNew(msg);    }  }  _checkAndStartNew(msg) {    let ts = Date.now();    let q = this._queue;    if (msg && process[hasFreeRAM]) q.push({msg, ts});    if (this._canCreateExplainer && q.length > this._workersCnt() && q[0].ts + SPAWN_LAG < ts) {      this._createExplainer();    }  }  explainer.on('explainDone', (msg) => {    explainer.pulse();  });  explainer.pulse = () => {    if (this._queue.length > explainer.id) {      explainer.sendMessage(this._queue.shift().msg);    } else if (this._isMain(explainer)) {      explainer.ready = true;    } else {      this._idleExplainers.push(explainer);      explainer.unpool();    }  };


Важными метриками пула worker_thread являются размер очереди и количество работающих потоков:


Кроме этого мы мониторим скорость и производительность worker_thread и воркеров в целом:
  • Скорость обработки планов запросов:

  • Производительность воркеров по количеству задач:

  • Производительность воркеров по объему данных:

  • Производительность worker_thread по количеству задач:

  • Производительность worker_thread по объему данных:


Заключение


Мы рассмотрели особенности мониторинга многопоточного приложения Node.JS.
Для комплексного анализа работы приложения необходимо отслеживать массу показателей метрики по серверу в целом, использование приложением системных ресурсов, метрики среды выполнения, а также различные показатели самого приложения. В общем всего, что включает в себя APM.
Подробнее..

Новогодние бенчмарки компьютеров Эльбрус

29.12.2020 02:22:15 | Автор: admin

Новогодние бенчмарки компьютеров Эльбрус


LuaBenchmarks.png


Продолжение статьи Большое тестирование процессоров различных архитектур. В этот раз я решил измерить производительность конкретных сред/языков программирования (C#, Java, JavaScript, Python, Lua) на компьютерах с процессорами Эльбрус и сравнить их с компьютерами (даже телефонами) на процессорах архитектурой ARM и X86-64.


Языки программирования:


  • C#
  • PHP
  • JavaScript (Browser, не NodeJS)
  • Java
  • Python
  • Lua

Список тестов



Но сперва приведу результаты нативных бенчмарков на языке C, а также результаты других популярных бенчмарков.


Тестовые стенды и их процессоры


А пока можете опробовать JavaScript версию бенчмарка: http://laseroid.azurewebsites.net/js-bench/


Стенды на процессорах x86 (i386) х86-64 (amd64):



Компилируемые бенчмарки на C/C++


Таблица с результатами с прошлой статьи


Результаты нативных бенчмарков

Тут выходит так: компьютеры на Эльбрусах имеют сопоставимую производительность с Intel Core i7 2600, если бы он работал на частоте 1200 1300 МГц (Для Эльбрус-8С), кроме теста MP MFLOPS, который хорошо распараллеливается компилятором LCC и для Эльбрус 8СВ выдаёт 325 ГФлопс, где Core i7 2600 выдавал
85 ГФлопс (Это с SSE, без AVX).


Задержки кеша. Тест TLB от Линуса Торвальдса


Cpu Elbrus 2C+ Elbrus 4C Elbrus 8C Elbrus 8CB Elbrus R1000 Allwinner A64 Amd A6 3650
Freq (MHz) 500 750 1300 1550 1000 1152 2600
Cores 2 4 8 8 4 4 4
4k 14.02ns 4.04ns 2.51ns 1.94ns 45.01ns 3.48ns 1.16ns
4k cycles 7.0 3.0 3.0 3.0 5.0 4.0 3.0
8k 14.02ns 4.04ns 2.51ns 1.94ns 5.01ns 3.48ns 1.16ns
8k cycles 7.0 3.0 3.0 3.0 5.0 4.0 3.0
16k 14.02ns 4.04ns 2.51ns 1.94ns 5.01ns 3.48ns 1.16ns
16k cycles 7.0 3.0 3.0 3.0 5.0 4.0 3.0
32k 14.03ns 4.04ns 2.51ns 1.94ns 5.16ns 3.58ns 1.16ns
32k cycles 7.0 3.0 3.0 3.0 5.2 4.1 3.0
64k 14.04ns 4.06ns 2.51ns 1.94ns 23.45ns 6.83ns 1.16ns
64k cycles 7.0 3.0 3.0 3.0 23.4 7.9 3.0
128k 18.04ns 14.83ns 9.19ns 7.10ns 23.75ns 7.28ns 4.00ns
128k cycles 9.0 11.1 11.0 11.0 23.7 8.4 10.4
256k 24.97ns 14.82ns 9.19ns 7.10ns 23.98ns 7.69ns 4.00ns
256k cycles 12.5 11.1 11.0 11.0 24.0 48.9 10.4
512k 22.26ns 14.82ns 9.28ns 7.13ns 24.12ns 8.04ns 4.00ns
512k cycles 11.1 11.1 11.1 11.1 24.1 9.3 10.4
1M 67.80ns 14.83ns 27.57ns 21.31ns 24.07ns 34.36n 4.03ns
1M cycles 33.9 11.1 33.1 33.0 24.1 39.6 10.5
2M 106.21ns 22.49ns 27.56ns 21.31ns 46.62ns 37.05n 12.14ns
2M cycles 53.1 16.9 33.1 33.0 46.6 42.7 31.6
4M 107.51ns 120.65ns 27.56ns 21.31ns 119.53ns 37.36n 12.06ns
4M cycles 53.8 90.5 33.1 33.0 119.5 43.0 31.3
8M 107.92ns 121.18ns 27.57ns 21.31ns 141.08ns 37.37n 12.21ns
8M cycles 54.0 90.9 33.1 33.0 141.1 43.0 31.7
16M 107.86ns 121.27ns 47.72ns 31.54ns 143.90ns 37.57n 12.01ns
16M cycles 53.9 90.9 57.3 48.9 143.9 43.3 31.2
32M 107.91ns 119.22ns 111.71ns 117.28ns 144.34ns 37.09n 12.02ns
32M cycles 54.0 89.4 134.1 181.8 144.3 42.7 31.3
64M 107.91ns 119.48ns 149.90ns 117.39ns 144.36ns 37.07n 11.98ns
64M cycles 54.0 89.6 179.9 182.0 144.4 42.7 31.2
128M 107.91ns 121.75ns 169.79ns 117.51ns 144.42ns 37.57n 12.02ns
128M cycles 54.0 91.3 203.7 182.1 144.4 43.3 31.3
256M 107.97ns 122.11ns 174.90ns 117.58ns 144.34ns 37.77n 12.21ns
256M cycles 54.0 91.6 209.9 182.3 144.3 43.5 31.7

Задержки кеша на Эльбруса 8СВ таковы:


  • L1: 3 такта с 1,94 нс
  • L2: 11 тактов с 7,1 нс
  • L3: 3 такта с 21,31 нс
  • ОЗУ: 90-180 тактов с 117 нс

Характеристики кеша для Эльбрус 8С можно посмотреть здесь: Архитектура Эльбрус 8С


Исходный код: Test TLB


Тесты памяти STREAM


Array size = 10000000 (elements), Offset = 0 (elements)

Memory per array = 76.3 MiB (= 0.1 GiB).

Total memory required = 228.9 MiB (= 0.2 GiB).

CPU Frequency Threads Memory Type Copy (MB/s) Scale (MB/s) Add (MB/s) Triad (MB/s)
Elbrus 4C 750 4 DDR3-1600 9 436.30 9 559.70 10 368.50 10 464.80
Elbrus 8C 1300 8 DDR3-1600 11 406.70 11 351.70 12 207.50 12 355.10
Elbrus 8CB 1550 8 DDR4-2400 23 181.80 22 965.20 25 423.90 25 710.20
Allwinner A64 1152 4 LPDDR3-800 2 419.90 2 421.30 2 112.70 2 110.10
AMD A6-3650 2600 4 DDR3-1333 6 563.60 6 587.90 7 202.80 7 088.00

Исходный код: STREAM


Geekbench 4/5 (В режиме RTC: x86 -> e2k трансляция)


Geekbench 5


CPU Frequency Threads Single Thread Multi Thread
Эльбрус 8С 1300 8 142 941
Эльбрус 8СВ 1550 8 159 1100
Intel Core i7 2600 3440 8 720 2845
Amd A6 3650 2600 4 345 1200

Geekbench 4


CPU Frequency Threads Single Thread Multi Thread
Эльбрус 8С 1300 8 873 3398
Эльбрус 8СВ 1550 8 983 4042
Intel Pentium 4 2800 1 795 766
Intel Core i7 2600 3440 8 3702 12063
Qualcomm 625 2000 8 852 2829

Crystal Mark 2004 (В режиме RTC: x86 -> e2k трансляция)


CPU Threads Frequency ALU FPU MEM R (Mb/s) MEM W (Mb/s) Anounced
486 DX4 1 75 119 77 9 11 1993
P1 (P54C) 1 200 484 420 80 65 1994
P1 MMX (P55C) 1 233 675 686 112 75 1997
P2 1 400 1219 1260 222 150 1998
Transmeta Crusoe TM5800 1 1000 2347 1689 405 223 2000
P3 (Coopermine) 1 1000 3440 3730 355 170 2000
P4 (Willamete) 1 1600 3496 4110 1385 662 2001
Celeron (Willamete) 1 1800 3934 4594 1457 657 2001
Athlon XP (Palomino) 1 1400 4450 6220 430 520 2001
P4 (Northwood) 1 2400 5661 6747 1765 754 2002
P4 (Prescott) 1 2800 5908 6929 3744 851 2004
Athlon 64 (Venice) 1 1800 6699 7446 1778 906 2005
Celeron 530 (Conroe-L) 1 1733 7806 9117 3075 1226 2006
P4 (Prescott) 2 3000 9719 10233 3373 1578 2004
Atom D525 4 1800 10505 7605 3407 1300 2010
Athlon 64 X2 (Brisbane) 2 2300 16713 19066 3973 2728 2007
Core i3-6100 2 3700 17232 10484 5553 9594 2015
Pentium T3200 (Merom) 2 2000 20702 18063 4150 1598 2008
Atom x5-Z8350 4 1440 21894 18018 4799 2048 2016
Core i3-M330 4 2133 25595 26627 6807 4257 2010
Core 2 Duo 2 3160 28105 18196 6850 2845 2008
Atom Z3795 4 1600 40231 34963 12060 5797 2016
AMD A6-3650 4 2600 46978 35315 9711 3870 2011
Core 2 Quad 4 2833 47974 31391 9710 5493 2008
Core i3-4130 4 3400 54296 39163 19450 9269 2013
AMD Phenom II X4 965 (Agena) 4 3400 59098 56272 11162 5973 2009
Core i7-2600 8 3400 95369 71648 19547 9600 2011
Core i7-9900K 16 3600 270445 238256 44618 17900 2018
Elbrus-8C RTC-x86 8 1300 65817 29977 49800 7945 2016
Elbrus-8CB RTC-x86 8 1500 77481 37972 62100 13940 2018
Elbrus-1C+ RTC-x86 1 1000 6862 2735 6230 1800 2015

Процессор Эльбрус-8С 1.3 ГГц на уровне AMD Phenom II X4 965 3.4 ГГц 4 ядра. 8СВ на 20% быстрее.


Бенчмарки сред/языков программирования


А теперь переходим к бенчмаркам языков программирования (C#, Java, JavaScript, Python, Lua).


Исходный код здесь: https://github.com/EntityFX/EntityFX-Bench
Исходный код для прощлых бенчмарков можете найти тут: https://github.com/EntityFX/anybench


Микро бенчмарки


В цикле с большим числоv итераций проводим некоторые операции и замеряем время выполнения данного куска кода. Ниже буду приводить примеры на языке Python.


Arithmetics


Замеряет скорость арифметики: в цикле выполняет различные математические операции с замером времени выполнения.


Пример кода на Python:


    @staticmethod    def _doArithmetics(i : int) -> float:        return math.floor(i / 10) * math.floor(i / 100) * math.floor(i / 100) * math.floor(i / 100) * 1.11) + math.floor(i / 100) * math.floor(i / 1000) * math.floor(i / 1000) * 2.22 - i * math.floor(i / 10000) * 3.33 + i * 5.33

Math


Замеряет скорость математических функций (Cos, Sin, Tan, Log, Power, Sqrt):


    @staticmethod    def _doMath(i : int, li : float) -> float:        rev = 1.0 / (i + 1.0)        return (math.fabs(i) * math.acos(rev) * math.asin(rev) * math.atan(rev) + math.floor(li) + math.exp(rev) * math.cos(i) * math.sin(i) * math.pi) + math.sqrt(i)

Loops


Замеряет скорость работы холостых циклов. Кстати, некоторые компиляторы и рантаймы могут оптимизировать этот код.


Conditions


Замеряет скорость работы условий.


        d = 0        i = 0; c = -1        while i < self._iterrations:             c = ((-1 if c == (-4) else c))            if (i == (-1)):                 d = 3            elif (i == (-2)):                 d = 2            elif (i == (-3)):                 d = 1            d = (d + 1)            i += 1; c -= 1        return d

Array speed (Memory, Random Memory)


Замеряет скорость чтения из массива в переменную (последовательно или со случайными индексами)


Большой кусок кода на Python
    def _measureArrayRead(self, size) :        block_size = 16        i = [0] * block_size        array0_ = list(map(lambda x: random.randint(-2147483647, 2147483647), range(0, size)))        end = len(array0_) - 1        k0 = math.floor(size / 1024)        k1 = 1 if k0 == 0 else k0        iter_internal = math.floor(self._iterrations / k1)        iter_internal = 1 if iter_internal == 0 else iter_internal        idx = 0        while idx < end:             i[0] = (array0_[idx])            i[1] = (array0_[idx + 1])            i[2] = (array0_[idx + 2])            i[3] = (array0_[idx + 3])            i[4] = (array0_[idx + 4])            i[5] = (array0_[idx + 5])            i[6] = (array0_[idx + 6])            i[7] = (array0_[idx + 7])            i[8] = (array0_[idx + 8])            i[9] = (array0_[idx + 9])            i[0xA] = (array0_[idx + 0xA])            i[0xB] = (array0_[idx + 0xB])            i[0xC] = (array0_[idx + 0xC])            i[0xD] = (array0_[idx + 0xD])            i[0xE] = (array0_[idx + 0xE])            i[0xF] = (array0_[idx + 0xF])            idx += block_size        start = time.time()        it = 0        while it < iter_internal:             idx = 0            while idx < end:                 i[0] = (array0_[idx])                i[1] = (array0_[idx + 1])                i[2] = (array0_[idx + 2])                i[3] = (array0_[idx + 3])                i[4] = (array0_[idx + 4])                i[5] = (array0_[idx + 5])                i[6] = (array0_[idx + 6])                i[7] = (array0_[idx + 7])                i[8] = (array0_[idx + 8])                i[9] = (array0_[idx + 9])                i[0xA] = (array0_[idx + 0xA])                i[0xB] = (array0_[idx + 0xB])                i[0xC] = (array0_[idx + 0xC])                i[0xD] = (array0_[idx + 0xD])                i[0xE] = (array0_[idx + 0xE])                i[0xF] = (array0_[idx + 0xF])                idx += block_size            it += 1        elapsed = time.time() - start        return (iter_internal * len(array0_) * 4 / elapsed / 1024 / 1024, i)

String manipulation


Скорость работы со строковыми функциями (replace, upper, lower)


    @staticmethod    def _doStringManipilation(str0_ : str) -> str:        return ("/".join(str0_.split(' ')).replace("/", "_").upper() + "AAA").lower().replace("aaa", ".")

Hash algorithms


Алгоритмы SHA1 и SHA256 над байтами строк.


    @staticmethod    def _doHash(i : int, prepared_bytes):        hashlib.sha1()        sha1_hash = hashlib.sha1(prepared_bytes[i % 3]).digest()        sha256_hash = hashlib.sha256(prepared_bytes[(i + 1) % 3]).digest()        return sha1_hash + sha256_hash

Комплексные бенчмарки


Выполнил реализацию популярных бенчмарков Dhrystone, Whetstone, LINPACK, Scimark 2 на всех 5 языках программирования (конечно же использовал существующие исходники, но адаптировал под мои тесты).


Dhrystone


Dhrystone синтетический тест, который был написан Reinhold P. Weicker в 1984 году.
Данный тест не использует операции с плавающей запятой, а версия 2.1 написана так, чтобы исключить возможность сильных оптимизаций при компиляции.
Бенчмарк выдаёт результаты в VAX Dhrystones в секунду, где 1 VAX DMIPS = Dhrystones в секунду делить на 1757.

Whetstone


Whetstone синтетический тест, который был написан Harold Curnow в 1972 году на языке Fortran.
Позже был переписан на языке C Roy Longbottom. Данный тест выдаёт результаты в MWIPS,
также промежуточные результаты в MOPS (Миллионов операций в секунду) и MFLOPS (Миллионы вещественных операций с плавающей запятой в секунду).
Данный тест производит различные подсчёты: производительность целочисленных и операций с плавающей запятой,
производительность операций с массивами, с условным оператором, производительность тригонометрических функций и функций возведения в степень, логарифмов и извлечения корня.

LINPACK


LINPACK тест, который был написан Jack Dongarra на языке Fortran в 70х годах, позже переписан на язык C.
Тест считает системы линейных уравнений, делает различные операции над двумерными (матрицами) и одномерными (векторами).
Используется реализация Linpack 2000x2000.

Scimark 2


SciMark 2 набор тестов на языке C измеряющий производительность кода встречающегося в научных и профессиональных приложениях. Содержит в себе 5 вычислительных тестов: FFT (быстрое преобразование Фурье), Gauss-Seidel relaxation (Метод Гаусса Зейделя для решения СЛАУ), Sparse matrix-multiply (Умножение разреженных матриц), Monte Carlo integration (Интегрирование методом Монте-Карло), и LU factorization (LU-разложение).

Переходим к результатам.


Результаты


Бенчмарки Java


Результаты нативных бенчмарков

Результаты Java на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков 3.4 ГГц:


  • Эльбрус 1С+ в 11 раз медленнее на 1 поток
  • Эльбрус 4С в 10 раз медленнее на 1 поток
  • Эльбрус 8С в 5,5 раз медленнее на 1 поток
  • Эльбрус 8СВ в 4,5 раз медленнее на 1 поток
  • Эльбрус 1С+ в 18 раз медленнее на всех потоках
  • Эльбрус 4С в 12,5 раз медленнее на всех потоках
  • Эльбрус 8С в 3 раз медленнее на всех потоках
  • Эльбрус 8СВ в 2,5 раз медленнее на всех потоках

Результаты Java на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков, но на одинаковых частотах:


  • Эльбрус 1С+ в 3,5 раз медленнее на 1 поток Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 2,5 раз медленнее на 1 поток Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 2 раз медленнее на 1 поток Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 2 раз медленнее на 1 поток Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус 1С+ в 5 раз медленнее на всех потоках Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 2,75 раз медленнее на всех потоках Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 1,15 раз медленнее на всех потоках Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,15 раз медленнее на всех потоках Core i7 2600 на частоте 1,55 ГГц

Для Java делали очень серьёзные оптимизации, поэтому отставание в 2 раза на одинаковых частотах не является плохим результатом.


Про то, как оптимизировали Java можно почитать тут: Java на Эльбрусе


Посмотреть здесь:



Бенчмарки C# (.Net Framework, .Net Core, Mono)


Результаты бенчмарков на C#

DotnetBenchmarks.png


Так как сред исполнения несколько (.Net Framework, .Net Core, Mono), то я старался сравнивать одинаковые среды исполнения, т. е. Mono на e2k c Mono на x86.


Результаты C# (Mono) на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков 3.4 ГГц:


  • Эльбрус 1С+ в 15,5 раз медленнее на 1 поток
  • Эльбрус 4С в 19 раз медленнее на 1 поток
  • Эльбрус 8С в 10,5 раз медленнее на 1 поток
  • Эльбрус 8СВ в 8 раз медленнее на 1 поток
  • Эльбрус 1С+ в 24 раз медленнее на всех потоках
  • Эльбрус 4С в 12,5 раз медленнее на всех потоках
  • Эльбрус 8С в 4,5 раз медленнее на всех потоках
  • Эльбрус 8СВ в 4 раз медленнее на всех потоках

Результаты C# (Mono) на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков, но на одинаковых частотах:


  • Эльбрус 1С+ в 4,5 раз медленнее на 1 поток Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 4,2 раз медленнее на 1 поток Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 3 раз медленнее на 1 поток Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 3 раза медленнее на 1 поток Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус 1С+ в 7 раз медленнее на всех потоках Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 3 раза медленнее на всех потоках Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 1,5 раз медленнее на всех потоках Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,2 раз медленнее на всех потоках Core i7 2600 на частоте 1,55 ГГц

Результаты C# (NetCore) в режиме RTC на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков 3.4 ГГц:


  • Эльбрус 8С в 3,5 раз медленнее на 1 поток Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 3 раз медленнее на 1 поток Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус 8С в 2 раз медленнее на всех потоках Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,5 раз медленнее на всех потоках Core i7 2600 на частоте 1,55 ГГц

Результаты C# (NetCore) в режиме RTC на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков, но на одинаковых частотах:


  • Эльбрус 8С в 1,3 раз медленнее на 1 поток Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,2 раз медленнее на 1 поток Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус 8С в 1,25 раза быстрее на всех потоках Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,25 раза быстрее на всех потоках Core i7 2600 на частоте 1,55 ГГц

Mono сам по себе является достаточно медленной средой выполнения по сравнению с Net Fremework, а особенно с NetCore (до 3х раз). Что достаточно допустимо. Здесь я не знаю, делали ли оптимизации как это было сделано с Java.


Выходит NetCore в режиме RTC на Эльбрусах работает до 4х раз быстрее чем Mono. Будем ждать нативного NetCore для e2k.


Бенчмарки JavaScript (Браузерные)


JavaScript версия бенчмарка: http://laseroid.azurewebsites.net/js-bench/


Результаты бенчмарков на JavaScript

Результаты JavaScript на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков 3.4 ГГц:


  • Эльбрус 1С+ в 16 раз медленнее
  • Эльбрус 4С в 12,5 раз медленнее
  • Эльбрус 8С в 6,5 раз медленнее
  • Эльбрус 8СВ в 5 раз медленнее

Результаты JavaScript на Эльбрусах в сравнении с Core i7 2600 4 ядра, 8 потоков, но на одинаковых частотах:


  • Эльбрус 1С+ в 5 раз медленнее Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 2,75 раза медленнее Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 2,5 раза медленнее Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 2,25 раз медленнее Core i7 2600 на частоте 1,55 ГГц

Другие популярные JavaScript бенчмарки


Octane


Firefox (версии разные)


Cpu Result (баллы)
Intel Core i7 2600 23321
AMD A6-3650 11741
Intel Pentium 4 2800 3387
Elbrus 8C (rtc x86 32bit) 2815
Elbrus 8C 2102
Elbrus 1C+ 739

Kraken Benchmark


Firefox (версии разные)


Cpu Result (ms)
Elbrus 8C 10493.4
Elbrus 8CB RTX x86 9567.5
Elbrus 8CB 8714.2
Intel Pentium 4 2800 9486.6
AMD A6-3650 3052.5
Intel Core i7 2600 (3.4 GHz) 1456.8

Sunspider


Firefox (версии разные)


Cpu Result (ms)
Elbrus 8C 3059.8
Elbrus 8CB 2394.6
Intel Pentium 4 2800 1295.5
AMD A6-3650 485.6
Intel Core i7 2600 (3.4 GHz) 242.9

Результаты слабоваты. Причина: низкие тактовые частоты и недостаточная оптимизация. Но это гораздо лучше, чем было раньше. Также браузер FX52 уже старый, а будет новая, надеюсь, там уже допилили JavaScript.


Бенчмарки PHP


Результаты бенчмарков на PHP

Результаты PHP на Эльбрусах в сравнении с Core i7 2600 3.4 ГГц:


  • Эльбрус 2С+ в 15,5 раз медленнее
  • Эльбрус 1С+ в 8 раз медленнее
  • Эльбрус 4С в 4,5 раза медленнее
  • Эльбрус 8С в 3 раза медленнее
  • Эльбрус 8СВ в 2,5 раза медленнее
  • Эльбрус R1000 в 12,5 раз медленнее

Результаты PHP на Эльбрусах в сравнении с Core i7 2600, но на одинаковых частотах:


  • Эльбрус 2С+ в 2,3 раз медленнее Core i7 2600 на частоте 0,5 ГГц
  • Эльбрус 1С+ в 2,3 раз медленнее Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С = Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 1,1 раза медленнее Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,1 раза медленнее Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус R1000 в 3,75 раз медленнее Core i7 2600 на частоте 1 ГГц

PHP показывает почти равную скорость на одинаковых частотах с Intel процессорами. Причина проста: здесь МЦСТ делали оптимизацию. Очень удивительно для интерпретируемого языка. Кстати, хочу обратить внимание на то что PHP 7.4 стал быстрее версии PHP 5.6 в 1,5 раза, поэтому я запускал бенчмарки на 2х версиях на Core i7 2600.


Другие популярные PHP бенчмарки


PHP Simple Benchmark


Test Elbrus 8C Elbrus 8CB Pentium 4 2800 AMD A6-3650 Core i7-2600 Allwinner A64
Frequency 1300 1550 2800 2600 3400 1152
CPU Threads 8 8 1 4 8 (4) 4
Version 7.0.33 7.0.33 7.2.24 7.4.3 7.0.33 5.6.20 7.0.33
01_math (kOp/s) 58.15 69.72 104.19 295.97 308.94 131.73 44.33
02_string_concat (MOp/s) 3.56 3.92 4.00 13.15 5.52 0.56 3.07
03_1_string_number_concat (kOp/s) 418.29 472.77 631.10 1510.00 1680.00 1600.00 332.99
03_2_string_number_format (kOp/s) 506.39 573.89 724.44 1690.00 1810.00 1620.00 432.88
04_string_simple_functions (kOp/s) 77.06 91.50 198.03 332.67 39.12 57.60 59.48
05_string_multibyte (kOp/s) 2.48 2.90 -.-- 57.53 11.01 12.77 2.50
06_string_manipulation (kOp/s) 22.10 26.91 78.96 127.08 14.11 23.96 35.73
07_regex (kOp/s) 48.24 54.60 128.41 233.76 334.99 62.43 47.64
08_1_hashing (kOp/s) 113.58 132.62 180.46 306.24 345.52 270.31 71.44
08_2_crypt (Op/s) 361.21 403.62 571.99 813.60 460.00 454.15 238.00
09_json_encode (kOp/s) -.-- -.-- 88.33 233.62 313.52 191.66 48.67
10_json_decode (kOp/s) -.-- -.-- 68.02 143.01 211.62 94.15 33.57
11_serialize (kOp/s) 73.67 81.57 130.16 307.52 435.66 263.06 62.20
12_unserialize (kOp/s) 63.89 69.02 79.33 301.98 348.62 258.75 46.21
13_array_fill (MOp/s) 2.08 2.50 5.30 9.69 14.07 5.35 1.97
14_array_range (kOp/s) 50.36 57.54 31.68 61.01 1140.00 30.35 25.25
14_array_unset (MOp/s) 2.08 2.48 7.17 14.05 14.45 7.32 2.16
15_loops (MOp/s) 13.57 16.21 38.75 150.46 78.92 42.54 12.64
16_loop_ifelse (MOps/s) 4.74 5.64 13.41 28.34 19.04 18.72 4.48
17_loop_ternary (MOp/s) 3.18 3.79 7.29 12.10 11.40 11.85 2.90
18_1_loop_defined_access (MOp/s) 3.28 3.90 9.03 18.90 18.29 15.35 3.18
18_2_loop_undefined_access (MOp/s) 0.60 0.66 1.13 2.60 2.40 2.10 0.49
19_type_functions (MOp/s) 250.57 293.21 806.37 1560.00 1180.00 971.77 193.89
20_type_conversion (MOp/s) 382.32 458.44 812.72 1570.00 1530.00 1510.00 298.61
21_0_loop_exception_none (MOp/s) 7.45 8.91 19.67 56.57 26.35 15.67 6.97
21_1_loop_exception_try (MOp/s) 6.48 7.74 19.11 52.18 23.61 18.99 6.39
21_2_loop_exception_catch (kOp/s) 184.22 216.00 573.09 1380.00 1240.00 498.60 147.28
22_loop_null_op (MOp/s) 3.25 3.74 8.39 16.03 17.62 -.-- 3.08
23_loop_spaceship_op (MOp/s) 4.30 5.12 8.50 17.98 20.39 -.-- 3.96
24_xmlrpc_encode (Op/) -.-- -.-- -.-- -.-- 17.6 -.-- -.--
25_xmlrpc_decode (Op/) -.-- -.-- -.-- -.-- 9.16 -.-- -.--
26_1_class_public_properties (MOp/s) 3.32 4.08 10.51 26.70 19.57 9.42 3.22
26_2_class_getter_setter (MOp/s) 1.31 1.51 4.66 9.41 5.52 4.13 0.97
26_3_class_magic_methods (MOp/s) 0.52 0.59 1.35 3.77 3.21 1.89 0.41
Total (MOp/s) 1.23 1.43 2.60 5.33 2.48 2.02 0.98
Time (sec) 488.324 419.895 231.485 113.087 252.376 261.652 609.787

Бенчмарки Python


Результаты бенчмарков на Python

Так как под Windows не удалось запустить многопоточный Python (я не Питонист, да и времени сделать универсальную многопоточность на всех ОС не было, принимаю патчи), приведу относительно Amd A6 3650, который на 40% медленнее Core i7.


Результаты Python на Эльбрусах в сравнении с Core i7 2600 3.4 ГГц:


  • Эльбрус 2С+ в 30 раз медленнее на 1 поток
  • Эльбрус 1С+ в 12,5 раз медленнее на 1 поток
  • Эльбрус 4С в 15,5 раз медленнее на 1 поток
  • Эльбрус 8С в 9 раз медленнее на 1 поток
  • Эльбрус 8СВ в 7,8 раз медленнее на 1 поток
  • Эльбрус 2С+ в 58 раз медленнее на всех потоках
  • Эльбрус 1С+ в 25 раз медленнее на всех потоках
  • Эльбрус 4С в 13,5 раз медленнее на всех потоках
  • Эльбрус 8С в 4,2 раза медленнее на всех потоках
  • Эльбрус 8СВ в 3,8 раза медленнее на всех потоках

Результаты Python на Эльбрусах в сравнении с Core i7 2600, но на одинаковых частотах:


  • Эльбрус 2С+ в 4,5 раз медленнее на 1 поток Core i7 2600 на частоте 0,5 ГГц
  • Эльбрус 1С+ в 3,6 раз медленнее на 1 поток Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 3,5 раз медленнее на 1 поток Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 3,5 раз медленнее на 1 поток Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 3,5 раз медленнее на 1 поток Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус 2С+ в 8,5 раз медленнее на всех потоках Core i7 2600 на частоте 0,5 ГГц
  • Эльбрус 1С+ в 7,4 раз медленнее на всех потоках Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 3 раз медленнее на всех потоках Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 1,5 раза медленнее на всех потоках Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 1,35 раза медленнее на всех потоках Core i7 2600 на частоте 1,55 ГГц

Во первых, для Python пока не делалось оптимизаций, которые повысили бы производительность. Во вторых, интерпретируемые языки плохо подходят к VLIW-архитектуре Эльбруса. Но МЦСТ советуют использовать CPython для производительности (Python -> C, а затем компилируем с помощью LCC). Про PyPy c Jit пока ничего не известно, но если потребуется, то такая среда выполнения будет реализована, а это сильно повысит производительность.


Также было замечено, что в некоторых случаях Python в режиме RTC (двоичная трансляция x86-64 в e2k) показывает большую производительность: в арифметике и математике. Значит есть ещё потенциал для оптимизации Python, возможно даже в 2 раза.


Бенчмарки Lua


Результаты бенчмарков на Lua

Результаты Lua на Эльбрусах в сравнении с Core i7 2600 3.4 ГГц:


  • Эльбрус 2С+ в 16 раз медленнее
  • Эльбрус 1С+ в 6 раз медленнее
  • Эльбрус 4С в 10 раз медленнее
  • Эльбрус 8С в 6 раз медленнее
  • Эльбрус 8СВ в 5 раз медленнее
  • Эльбрус R1000 в 9 раз медленнее

Результаты Lua на Эльбрусах в сравнении с Core i7 2600, но на одинаковых частотах:


  • Эльбрус 2С+ в 2,4 раза медленнее Core i7 2600 на частоте 0,5 ГГц
  • Эльбрус 1С+ в 1,75 раза медленнее Core i7 2600 на частоте 1 ГГц
  • Эльбрус 4С в 2 раза медленнее Core i7 2600 на частоте 0,8 ГГц
  • Эльбрус 8С в 2,3 раза медленнее Core i7 2600 на частоте 1,3 ГГц
  • Эльбрус 8СВ в 2,2 раза медленнее Core i7 2600 на частоте 1,55 ГГц
  • Эльбрус R1000 в 2,6 раз медленнее Core i7 2600 на частоте 1 ГГц

У Lua отставание всего в 2 раза на равных частотах, это достаточно тепримо для VLIW-архитектуры.


Выводы


Во сколько раз Core i7 2600 быстрее Эльбрусов:


Сводная таблица: во сколько раз Core i7 2600 быстрее Эльбрусов


Во сколько раз Core i7 2600 быстрее Эльбрусов, если бы он работал на частоте Эльбрусов:


Сводная таблица: во сколько раз Core i7 2600 быстрее Эльбрусов на одинаковой частоте


Как мы знаем, Эльбрус имеет VLIW архитектуру, у которой повышение производительности достигается путём оптимизации компилируемого кода (Эльбрус имеет явный параллелизм). Также у Эльбруса нет предсказателя переходов и переупорядочивания инструкций (снова всё явно задаётся компилятором).


Следует:


  • Компилируемые программы на C/C++ (возможно, другие) будут иметь хорошую производительность. Это достигается патчами участков кода, где нужно оптимизировать производительность и умным компилятором LCC (eLbrus C Compiler).
  • Языки с JIT-трансляцией (Java, JavaScript, C# Mono) будут иметь среднюю производительность. Здесь оптимизируют саму среду исполнения. Возможно, также потребуется оптимизировать сами программы.
  • Интерпретируемые языки (PHP, Python, Lua) будут иметь низкую производительность. Но оптимизация среды выполнения позволит поднять до среднего уровня.

Другие способы:


  • Доработка компилятора LCC.
  • Архитектурно-специфические доработки в самой ОС.
  • Улучшать архитектуру Эльбрус:
    • Поднимать частоту
    • Добавить предсказатель и т.д.

Какие языки ещё хотелсоь бы потестировать:


  • Golang (Ждём выпуска)
  • Ruby
  • Perl

P.S. Поздравляем команду МЦСТ с Новым Годом. Желаем удачи в разработке следующих поколений процессоров. Ждём массового появления устройств на процессорах с архитектурой E2K!


Другая интересная информация

"Что такое Эльбрус?


Эльбрус это полувековая история развития отечественной вычислительной технологии.
Это уникальная российская архитектура микропроцессора.
Это группа компаний объединенных одной идеей.
И наконец, это просто современный микропроцессор."
elbrus.ru


Web:


Официальный сайт: http://elbrus.ru


Официальная вики: http://wiki.community.elbrus.ru


Официальный форум: http://forum.community.elbrus.ru


Персональный сайт Максима Горшенина: http://imaxai.ru


Отличная вики на сайте ALT Linux Team: https://www.altlinux.org/Эльбрус


Telegram:


https://t.me/imaxairu основной канал с новостями из мира микропроцессоров Эльбрус из первых рук


https://t.me/e2k_chat на текущий момент основной чат по микропроцессорам Эльбрус, в котором можно пообщаться с разработчиками (организован сотрудниками компании Промобит, хорошо известной по продуктам BITBLAZE)


https://t.me/joinchat/FUktBxkwG8FKlhdzQ7DegQ чат для поболтать на разные темы любителями (и не очень) микропроцессоров Эльбрус с целью незасорения основного чата ненужной информацией, одним словом флудильня фан-клуба :)


Instagram:


Максим Горшенин и Эльбрусы: https://instagram.com/imaxai


Youtube:


Официальный канал группы компаний Elbrus: https://www.youtube.com/c/ElbrusTV


Частный канал Максима Горшенина: https://www.youtube.com/c/MaximGorshenin


Частный канал Михаила Захарова с эмз "Звезда": https://www.youtube.com/channel/UC3mtwuC2ugAngyO9tY2mqRw


Канал фанатов Е2К Elbrus PC Test (https://www.youtube.com/channel/UC4zlCBy0eFLkE-BxgqQK8FA):



Серия видеороликов по Эльбрусам от Дмитрия Бачило:



Серия видеороликов по играм на Эльбрусах от Дмитрия Пугачева:



Старенький, но не устаревающий ликбез по Эльбрусам Константина Трушкина на HighLoad++ 2014: https://youtu.be/ZTxOSGBCRec


Актуальными моделями микропроцессоров Эльбрус являются:



О существующих и будующих моделях микропроцессоров можно прочитать в вики Модели процессоров Эльбрус


Альта и оф. вики Характеристики процессоров Эльбрус


Результаты тестирования Результаты тестирования Эльбрус энтузиастами дают примерную оценку производительности. И это не предел, т.к. работы над совершенствованием оптимизирующего компилятора ведутся постоянно.


Попробовать ПК с процессором Эльбрус вживую можно в Яндекс.Музее в Москве.


Полезно прочитать:


Руководство по эффективному программированию на платформе Эльбрус http://mcst.ru/elbrus_prog Нейман-заде М. И., Королёв С. Д. 2020 [ PDF ] [ HTML ]


Микропроцессоры и вычислительные комплексы семейства Эльбрус (http://mcst.ru/files/511cea/886487/1a8f40/000000/book_elbrus.pdf) Ким А. К., Перекатов В. И., Ермаков С. Г. СПб.: Питер, 2013 272 с. ( PDF )


Операционные системы для архитектуры E2K:


АЛЬТ 8 СП (http://altsp.su/produkty/o-produktakh/) | Рабочая станция (https://www.basealt.ru/products/alt-workstation/) | Сервер (https://www.basealt.ru/products/alt-server/) | Образование (https://www.basealt.ru/products/alt-education/)


Astra Linux SE "Ленинград" (http://astralinux.ru/)


Эльбрус Линукс (http://mcst.ru/programmnoe-obespechenie-elbrus)


Попробовать ОС Эльбрус Линукс для x86_64:
https://yadi.sk/d/spqkqOAncT-aKQ
Документация:
https://yadi.sk/d/2shOcqIrmZQLLA


Интересное:


Вопрос:
Зачем VLIW, давай другую?


Ответ:


  1. http://www.mcst.ru/e2k_arch.shtml
  2. https://t.me/e2k_chat/89326

Лекция Бориса Бабаяна "История развития архитектуры вычислительных машин":
Часть 1 https://youtu.be/Swk27K9m_SA
Часть 2 https://youtu.be/QFD0NboTwTU

Подробнее..

Перевод Мы обработали миллион веб-страниц, чтобы выяснить, что замедляет работу Интернета

30.12.2020 18:06:06 | Автор: admin
Мы обработали 1 миллион самых популярных страниц в Интернете, отслеживая все мыслимые показатели производительности, регистрируя каждую ошибку и отмечая каждый запрошенный URL. Насколько нам известно, это дало первый набор данных, который связывает воедино данные производительности, ошибок и использования конкретной библиотеки в сети. В этой статье мы анализируем то, что данные рассказывают о создании высокопроизводительных веб-сайтов.



Можете ли вы сделать анализ лучше нашего? Мы опубликовали набор данных на Kaggle, так что вы можете сами погрызть гранит этих цифр.



Зачем рендерить миллион веб-страниц?


В наши дни распространено мнение, что Интернет работает в какой-то степени медленнее и глючнее, чем 15 лет назад. Постоянно растущее количества кода на JavaScript, фреймворки, веб-шрифты и полифиллы просто съели все преимущества быстрых компьютеров, сети и протоколов. По крайней мере такие приводят аргументы. Мы хотели проверить, правда ли это, и попытаться выяснить, какие общие причины приводят к медленным и неработающим сайтам в 2020 году.

На высоком уровне план был прост: скрипт для веб-браузера, отображение корневой страницы 1 миллиона самых популярных доменов и логирование всех мыслимых показателей: время отрисовки, количество запросов, перерисовка, ошибки JavaScript, используемые библиотеки и т. д. Со всеми этими данными мы можем задавать вопросы о корреляции причин. Какие факторы больше всего способствуют замедлению времени рендеринга? Какие библиотеки соотносятся с большими затратами времени на время до включения интерактивности? Какие ошибки встречаются чаще всего, и что их вызывает?

Сбор данных сводился к написанию небольшого количества кода, использованию Puppeteer для создания сценария Chrome, запуску 200 экземпляров EC2, рендерингу миллиона веб-страниц за выходные и молитвам о том, что вы действительно поняли, как работает ценообразование AWS.

Числа в целом



Протокол используемый для корневого HTML-документа

HTTP 2 сейчас более распространён, чем HTTP 1.1, тогда как HTTP 3 всё ещё редко встречается. Примечание: мы приравниваем QUIC к HTTP 3, даже если Chrome иногда сообщает об HTTP 2 + QUIC. Такой подход используется для корневого документа, для связанных ресурсов версии протоколов немного другие.


Протокол используемый для связанных ресурсов

В смысле связанных ресурсов HTTP 3 примерно в 100 раз более распространён. Как такое возможно? Это возможно потому, что все сайты ссылаются на одно и то же:


Самые популярные связанные URL-адреса

Есть несколько скриптов, на которые ссылается большая часть веб-сайтов. Значит, мы можем ожидать, что эти ресурсы будут в кэше, верно? Уже нет: начиная с Chrome 86 ресурсы, запрашиваемые с разных доменов, не будут использовать общий кэш. Firefox планирует реализовать то же самое. Safari уже много лет разбивает свой кэш таким образом.

Что замедляет работу Интернета: прогнозирование времени до интерактивности


Учитывая этот набор данных веб-страниц и их метрики времени загрузки, было бы неплохо узнать что-нибудь о том, что именно делает веб-страницы медленными. Мы исследуем метрику DOM-интерактивности, то есть время, необходимое для того, чтобы документ стал интерактивным для пользователя. Самое простое, что мы могли бы сделать, посмотреть на корреляцию каждого показателя с DOM-интерактивностью.


Корреляция метрик с DOM-интерактивностью

По сути, положительно коррелирует с DOM-интерактивностью каждый показатель, за исключением переменной 01, указывающей на протокол HTTP 2 или выше. Многие из этих показателей также положительно коррелируют друг с другом. Чтобы учесть отдельные факторы, способствующие быстрому взаимодействию, нам нужен более изощрённый подход.

Некоторые из показателей это время в миллисекундах. Чтобы понять, где браузеры проводят своё время, можно взглянуть на диаграмму ящик с усами.


График временных показателей. Оранжевая линия медиана, границы ящика процентили 25 и 75.

Один из способов получить информацию об отдельных факторах, влияющих на длительное время взаимодействия, это провести линейную регрессию, в которой мы прогнозируем DOM-интерактивность на основе других показателей. Это означает, что мы назначаем вес каждой метрике и моделируем время DOM-интерактивности страницы как взвешенную сумму других метрик плюс некоторая константа. Алгоритм оптимизации устанавливает веса таким образом, чтобы минимизировать ошибку прогноза для всего набора данных. Размер весов, найденных с помощью регрессии, свидетельствует о том, насколько каждый показатель влияет на скорость страницы.

Мы исключим временные метрики из регрессии. Если на установление соединения мы потратим полсекунды, это добавит полсекунды к DOM-интерактивности, но этот факт не особенно интересен. Метрики времени это, по сути, последствия. Мы хотим узнать, что приводит к ним.


Коэффициенты регрессии для показателей, прогнозирование DOM-интерактивности

Числа в скобках это полученные алгоритмом оптимизации коэффициенты регрессии. Вы можете интерпретировать их как миллисекунды. Хотя к точным числам следует относиться скептически (смотрите примечание ниже), интересно увидеть отведённый каждому признаку масштаб. Например, модель прогнозирует замедление на 354 мс для каждого перенаправления, необходимого для доставки основного документа. Всякий раз, когда основной HTML-документ доставляется через HTTP 2 или HTTP более поздней версии, модель прогнозирует сокращение времени до DOM-интерактивности на 477 мс. Для каждого инициированного документом запроса она прогнозирует дополнительные 16 мс.

Интерпретируя коэффициенты регрессии, мы должны помнить, что работаем в упрощённой модели реальности. На самом деле время до интерактивности не определяется взвешенной суммой этих входных показателей. Очевидно, что существуют причины, которые модель не может обнаружить. Очевидно также, что путающие переменные это проблема. Например, если загрузка основного документа с помощью HTTP 2 коррелирует с загрузкой других запросов по HTTP 2, тогда модель включит это преимущество в весовые коэффициенты main_doc_is_http2_or_greater [основной документ загружен по HTTP 2 или выше], даже если ускорение происходит от запросов, отличающихся от основного документа. Мы должны проявлять осторожность при отображении того, что сообщает модель, на выводы о реальной ситуации.

Как на DOM-интерактивность влияет версия HTTP?


Вот забавный график DOM-интерактивности, разделённый по версии протокола HTTP, используемого для доставки корневой страницы.


График DOM-интерактивности, разделённый по версии используемого для доставки корневой HTML-страницы HTTP. Оранжевая линия это медиана, границы прямоугольника процентили 25 и 75. Проценты в скобках это доля запросов, сделанных с помощью протокола.

Небольшое количество сайтов по-прежнему доставляются по HTTP 0.9 и 1.0. И эти сайты бывают быстрыми. Кажется, мы запутались: протоколы стали быстрее, программисты с радостью пользуются этим ускорением, доставляя в браузер больше данных.

Это для версии протокола, используемая для доставки корневой HTML-страницы. Что, если мы посмотрим на влияние протокола на ресурсы, связанные с этим документом? Если мы сделаем регрессию количества запросов по версии протокола, то получим следующее.


Коэффициенты регрессии для количества запросов по версии протокола, прогнозирование DOM-интерактивности

Если бы мы поверили в это, то пришли бы к выводу, что перенос запрошенных ресурсов с HTTP 1.1 на 2 дает ускорение в 1,8 раза, а переход с HTTP 2 на 3 вызывает замедление в 0,6 раза. Действительно ли HTTP 3 медленнее? Нет; более вероятное объяснение: HTTP 3 редко встречается, а несколько ресурсов, которые отправляются через HTTP 3 (например, Google Analytics), имеют более чем среднее влияние на DOM-интерактивность.

Как на DOM-интерактивность влияет тип контента?


Давайте спрогнозируем время до интерактивности по количеству переданных байтов, разделённых по типу этих данных.


Коэффициенты регрессии для килобайтов, переданных инициатором запроса, с прогнозированием DOM-интерактивности

Вот аналогичная регрессия, на этот раз смотрим на количество запросов по типу инициатора запроса.


Коэффициенты регрессии для количества запросов от инициатора запроса, прогнозироваине DOM-интерактивности

Здесь запросы разделяются по тому, что инициировало их. Ясно, что не все запросы одинаковы. Запросы, инициируемые элементом ссылки (например, CSS, иконками) и запросы, инициируемые CSS (например, шрифты или ещё какой-то CSS), а также скрипты и фреймы значительно замедляют работу. Выполнение запросов через XHR и Fetch API предсказывает время DOM-интерактивности быстрее базового (вероятно, потому, что эти запросы почти всегда асинхронны). CSS и скрипты часто загружаются с блокировкой рендеринга, поэтому неудивительно, что они связаны с увеличением времени до DOM-интерактивности. Видео сравнительно дёшево в этом смысле.

Выводы


Мы не обнаружили здесь каких-либо новых приёмов оптимизации, но анализ даёт представление о масштабе воздействия, которое можно ожидать от различных оптимизаций. Следующие утверждения хорошо подкреплены эмпирически:

  • Делайте как можно меньше запросов. Количество запросов имеет большее значение, чем количество переданных килобайт.
  • Делайте необходимые запросы через HTTP 2 или выше, если это возможно.
  • По возможности старайтесь избегать блокирующих рендеринг запросов, а также, где возможно, предпочитайте асинхронную загрузку.

Библиотеки


Чтобы выяснить, какие библиотеки используются на странице, мы применили следующий подход: на каждом сайте мы отмечали глобальные переменные (то есть свойства объекта окна). Впоследствии каждая глобальная переменная с более чем шестью тысячами появлений была связана (где это возможно) с библиотекой JavaScript. Это кропотливая работа, но, поскольку в наборе данных также есть запрошенные URL-адреса для каждой страницы, можно было посмотреть на перекрытие между вхождениями переменных и запросами URL-адресов, чего часто было достаточно, чтобы определить, какая библиотека будет устанавливает каждую глобальную переменную. Глобальные переменные, которые нельзя было уверенно связать с одной библиотекой, игнорировались. Эта методология в некоторой степени приводит к недооценке: библиотеки JS не обязаны оставлять что-либо в глобальном пространстве имен. Метод также не фильтрует шум, когда разные библиотеки устанавливают одно и то же свойство, и этот факт был упущен при маркировке.

Какие библиотеки JavaScript используются сегодня чаще всего? Если судить по темам конференций и постам в блогах, вы можете предложить, что это React, Vue и Angular. В этом рейтинге они далеко не лучшие.

10 лучших библиотек согласно частоте использования



Просмотреть полный список

Да, наверху старый добрый jQuery. JQuery был выпущен в 2006 году, то есть 14 лет назад по человеческим меркам, но намного раньше по меркам JavaScript. Судя по версиям Angular, это, вероятно, сотни версий назад. 2006 год был другим временем. Наиболее часто используемый браузер назывался Internet Explorer 6, крупнейшей социальной сетью была MySpace, а закруглённые углы на веб-страницах стали такой революцией, что люди назвали это веб 2.0. Основным вариантом применения JQuery было применение для кроссбраузерной совместимости, которая в 2020 году стала совсем другой. Тем не менее 14 лет спустя, половина веб-страниц в нашей выборке загружала jQuery.

Как ни странно, 2,2 % веб-сайтов выдают ошибку по той причине, что JQuery не загружен.

Судя по этой десятке, наши браузеры в основном выполняют аналитику, рекламу и код для совместимости со старыми браузерами. Каким-то образом 8 % веб-сайтов определяют полифил setImmediate / clearImmediate для функции, которая пока не реализована ни одним браузером.

Прогнозирование времени до DOM-интерактивности при использовании библиотеки


Мы снова запустим линейную регрессию, прогнозируя DOM-интерактивность на основании наличия библиотек. Входные данными для регрессии это вектор X с X.length == количество библиотек, где X[i] == 1,0, если библиотека i присутствует, X[i] == 0,0, если нет. Конечно, мы знаем, что DOM-интерактивность на самом деле не определяется наличием или отсутствием определённых библиотек. Однако моделирование каждой библиотеки как имеющей дополнительный вклад в медлительность и регрессия по сотням тысяч примеров по-прежнему оставляют интересные результаты.

Лучшие и худшие библиотеки в смысле времени до интерактивности, по коэффициентам регрессии



Просмотреть полный список библиотек по коэффициентам регрессии, прогнозирующим DOM-интерактивность

Отрицательные коэффициенты здесь означают, что модель предсказывает меньшее время до интерактивности при наличии этих библиотек, чем при их отсутствии. Конечно, это не означает, что добавление этих библиотек сделает ваш сайт быстрее; это означает только то, что сайты с этими библиотеками оказываются быстрее установленного моделью базового уровня скорости. Результаты здесь могут быть как социологическими, так и техническими. Например, библиотеки для отложенной загрузки приводят к прогнозу малого времени до интерактивности. Прогноз может быть большим, потому что страницы с этими библиотеками созданы программистами, утратившими время на оптимизацию ради быстрой загрузки страниц, поскольку скорость прямое следствие ленивой загрузки. Мы не можем детально разобраться в причинах с помощью текущей схемы.

Лучшие и худшие библиотеки по времени загрузки, по коэффициентам регрессии


Мы можем повторить описанные выше действия, но на этот раз с прогнозом времени загрузки. Onloadtime это время, необходимое для запуска события загрузки окна, то есть время, необходимое для загрузки всех ресурсов на странице. Линейная регрессия делается так же, как и раньше.


Полный список библиотек по коэффициентам регрессии, прогнозирующим время загрузки

Лучшие и худшие библиотеки в смысле используемого размера кучи JS, по коэффициентам регрессии


Здесь прогноз это размер используемой JavaScript кучи в мегабайтах.


Полный список библиотек по коэффициентам регрессии, прогнозирующим размер кучи JS

В комментах любят писать, что корреляция не равняется причинно-следственной связи, и, действительно, мы не можем напрямую установить причинность с помощью этих моделей. Следует проявлять большую осторожность при интерпретации коэффициентов, особенно потому, что может быть задействовано множество путающих факторов. Тем не менее моделей вполне достаточно, чтобы заставить вас хмыкнуть. Тот факт, что модель связывает загрузку на 982 мс медленнее с наличием jQuery, а также факт, что половина сайтов загружает этот скрипт, должны вызывать некоторую обеспокоенность. Если вы оптимизируете свой собственный сайт, то перекрёстные ссылки на его список зависимостей с рангами и коэффициентами здесь должны дать вам приличный индикатор, удаление зависимостей которого может принести вам наибольшую отдачу за ваш доллар.

image



Подробнее..

Kafka Streams непростая жизнь в production

14.01.2021 16:09:15 | Автор: admin

Привет, Хабр! Вокруг меня сформировался позитивный информационный фон на тему обработки событий через Kafka Streams. Этот инструмент привлекает множеством видео-докладов и статей на Хабре, подробной документацией, понятным API и красивой архитектурой. Некоторые мои знакомые и коллеги разрабатывают с его помощью свои системы. Но что происходит с в реальной жизни, когда эти системы уходят в production?

В этой статье я опущу введение в Kafka Streams, предполагая, что читатель уже знаком с ней, и расскажу о нашем опыте жизни с этой библиотекой на примере достаточно нагруженной системы.

Коротко о проекте

Внутренней командой вместе с партнерами мы работаем над биржой Ad Exchange, которая помогает перепродавать рекламный трафик. Специфику подобных инструментов мы уже когда-то описывали в статье на Хабре. По мере роста числа партнеров среди SSP и DSP, нагрузка на сервера биржи растет. А для повышения ценности самой биржи мы должны собирать с этого трафика развернутую аналитику. Тут-то мы и попытались применить Kafka Streams.

Внедрить новый инструмент у себя на продакшене - это всегда некоторый риск. Мы его осознавали, но смирились с этим, поскольку в целом Kafka Streams концептуально должен был хорошо подойти для расчета агрегатов. И хотя первое впечатление было отличным, далее я расскажу о проблемах, к которым это привело.

Статья не претендует на объективность и описывает только лишь наш опыт и проблемы, с которыми мы столкнулись, работая с этой технологией. Надеюсь, кому-то она поможет избежать ошибок при использовании Kafka Streams. А может быть даст повод посмотреть по сторонам.

Агрегаты

В нашу систему приходят десятки и сотни тысяч сообщений в секунду. Первое, что нам необходимо было сделать, - агрегации по различным ключам. Например, идёт поток событий "Показ рекламы". Для него надо считать на лету общую стоимость и количество, группируя по различным ключам: стране, партнёру, рекламной площадке и т.д.

Вроде бы стандартный кейс для Kafka Streams: используем функции groupBy и aggregate и получаем нужный результат. Всё работает именно так, причем с отличной скоростью за счёт внутреннего кэша: несколько последовательных изменений по одному и тому же ключу сначала выполняются в кэше и только в определённые моменты отправляются в changelog-топик. Далее Kafka в фоне удаляет устаревшие дублирующиеся ключи через механизм log compaction. Что здесь может пойти не так?

Репартиционирование

Если ваш ключ группировки отличается от ключа, под которым изначально пришло событие, то Kafka Streams создаёт специальный repartition-топик, отправляет в него событие уже под новым ключом, а потом считывает его оттуда и только после этого проводит агрегацию и отправку в changelog-топик. В нашем примере вполне может быть, что событие "Показ рекламы" пришло с ключом в виде UUID. Почему нет? Если вам надо сделать группировку, например по трём другим ключам, то это будет три разных repartition-топика. На каждый топик будет одно дополнительное чтение и одна дополнительная запись в Kafka. Чувствуете, к чему я веду?

Предположим, на входе у вас 100 тысяч показов рекламы в секунду. В нашем примере вы создадите дополнительную нагрузку на брокер сообщений в размере +600 тысяч сообщений в секунду (300 на запись и 300 на чтение). И ведь не только на брокер. Для таких объёмов надо добавлять дополнительные сервера с сервисами Kafka Streams. Можете посчитать, во сколько тысяч долларов обойдется такое решение с учётом цен на железо.

Для читателей, не очень хорошо знакомых с механизмом репартиционирования, я поясню один момент. Это не баг или недоработка Kafka Streams. С учётом её идеологии и архитектуры это единственное возможное поведение - его нельзя просто взять и отключить. Когда у сообщения меняется ключ, этот новый ключ надо равномерно "размазать" по кластеру так, чтобы каждый инстанс Kafka Streams имел собственный набор ключей. Для этого и служат дополнительные запись/чтение в repartition-топик. При этом если инстанс А записал в топик сообщение, то не факт, что он же его и прочитает. Это может сделать инстанс Б, В и т.д. в зависимости от того, кто какую партицию читает. В итоге каждый ключ группировки у вас будет более-менее равномерно распределён по серверам кластера (если вы его хэшируете, конечно).

Запросы к агрегатам

Данные пишут для того, чтобы с ними потом можно было работать. Например делать к ним запросы. И здесь наши возможности оказались серьезно ограничены. Исчерпывающий список того, как мы можем запрашивать данные у Kafka Streams, есть здесь или здесь для оконных агрегатов. Если используется стандартная реализация state-store на основе RocksDB (а скорее всего это так), фактически данные можно получать только по ключам.

Предположу, что первое, что вам хочется сделать с полученными данными, - это фильтрация, сортировка и пагинация. Но здесь таких возможностей нет. Максимум, что вы можете, - это считать всё, что есть, используя метод all(), а потом вручную делать нужные операции. Вам повезет, если ключей не слишком много и данные уместятся в оперативной памяти. Нам не повезло. Пришлось писать дополнительный модуль, который по ночам выгружал данные из RocksDB и отправлял в Postgres.

Ещё надо помнить, что на каждом отдельно взятом сервисе находится только часть ключей. Что если вам необходимо отдавать ваши агрегаты, скажем, по HTTP? Вот кто-то сделал запрос к одному из сервисов Kafka Streams: мол, дай мне такие-то данные. Сервис смотрит у себя локально - данных нет. Или есть, но какая-то часть. Другая часть может быть на другом сервере. Или на всех. Что делать? Документация Kafka Streams говорит, что это наши проблемы.

Стабильность Kafka Streams

У нас бывают проблемы с хостинг провайдером. Иногда выходит из строя оборудование, иногда человеческий фактор, иногда и то, и другое. Если по какой-то причине теряется связь с Kafka, то Kafka Streams переводит все свои потоки в состояние DEAD и ждёт, когда мы проснёмся и сделаем ей рестарт. При этом рядом стоят соседние сервисы, которые работают с той же Kafka через Spring и @KafkaListener. Они восстанавливаются сами, как ни в чём не бывало.

Kafka Streams может умереть и по другой причине: не пойманные исключения в вашем коде. Предположим, вы делаете какой-то внешний вызов внутри потоков Kafka Streams. Пусть это будет обращение к базе данных. Когда база данных падает, у вас два варианта. Первый - вы ловите эту ошибку и продолжаете работать дальше. Но если база будет лежать долго, а работа с ней - критична, вы будете считывать большое количество сообщений из топиков и некорректно их обрабатывать. Второй вариант кажется лучше - совсем не ловить исключение и дать Kafka Streams умереть. Дальше как обычно: ночной подъём, рестарт всех серверов с Kafka Streams. "Из коробки" у вас нет варианта подождать, пока база восстановится, и продолжить работу.

Нам пришлось дописать в каждый сервис Kafka Streams дополнительный модуль, который работает как watchdog: поднимает Kafka Streams, если видит, что она умерла.

Кстати, если вы работаете с Kafka Streams через Spring, то не забудьте переопределить стандартный StreamsBuilderFactoryBean, указав в нём свой CleanupConfig. Иначе будете неприятно удивлены тем, что при каждом рестарте будет удаляться вся локальная база RocksDB. Напомню, что это приведёт к тому, что при каждом рестарте все сервера начнут активно считывать данные из changelog-топика. Поверьте, вам это не нужно.

KStream-KStream Join

Здесь можно было бы обойтись одной фразой: никогда это не используйте. Джоин двух потоков создаёт десятки топиков в Kafka и огромную нагрузку на всю систему. Просто не делайте этого. Ну или хотя бы проверьте все под нагрузкой, прежде чем ставить в production.

Вообще Kafka Streams любит создавать топики под различные свои нужды. Если вы не изучили под лупой документацию и не протестировали, как это работает, то это может стать для вас и ваших DevOps неприятным сюрпризом. Чем больше топиков, тем сложнее их администрировать.

Масштабируемость

Любой человек, знакомый с Kafka, скажет, что масштабируемость - одна из главных её преимуществ: "В чём проблема? Добавляем партиций и радуемся". Всё так. Но не для Kafka Streams.

Если вы используете джоины, то все ваши топики должны быть ко-партиционированы (co-partitioning), что, в числе прочего, означает, что у них должно быть одинаковое количество партиций. Так в чём же проблема?

Представим, что вы сделали несколько топологий: с агрегатами, джоинами, всё как положено. Поставили в production, оно работает, вы радуетесь. Дальше бизнес пошел в гору, нагрузка начала расти, вы добавили серверов и хотите увеличить количество партиций, чтобы загрузить эти новые сервера. Но Kafka Streams наплодил десятки топиков. И у всех надо поднимать количество партиций, иначе следующий рестарт ваших сервисов может стать последним. Если Kafka Streams увидит, что топики не ко-партиционированы, она выдаст ошибку и просто не запустится.

На вопрос, что с этим делать, у меня сегодня ответа нет. Вероятно, можно потушить все рабочие инстансы Kafka Streams, потом поднять число партиций на всех причастных топиках, затем поднять Kafka Streams обратно и молиться. А может быть последовать совету отсюда: Matthias J. Sax пишет, что это нужно делать, создавая новый топик с новым количеством партиций и подключать к нему Kafka Streams с новым application.id. Там же есть совет, что если вы знаете заранее, что нагрузка будет большая, то лучше сделать партиций с запасом.

Заключение

Если у вас в системе нет и не планируется высоких нагрузок, то со всеми этими проблемами вы скорее всего не столкнётесь. Всё будет работать отлично. В противном случае стоит серьёзно задуматься, нужно ли вам это, особенно если планируете использовать следующий уровень абстракции - KSQL.

Автор статьи: Андрей Буров, Максилект.

P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на наши страницы в VK, FB, Instagram или Telegram-канал, чтобы узнавать обо всех наших публикациях и других новостях компании Maxilect.

Подробнее..

Что под капотом у BI? Детальный разбор технологии In-Memory OLAP

29.12.2020 16:19:06 | Автор: admin
Привет, Хабр! Меня зовут Иван Вахмянин, и сегодня я хочу рассказать о том, что находится под капотом у современной BI-системы, от чего зависит ее производительность (и как можно её ненароком убить), и какие технические оптимизации позволяют технологии In-Memory OLAP выигрывать по скорости у других подходов.




Вообще, современные BI-платформы это очень умный софт, который незаметно для пользователя делает множество оптимизаций, и чаще всего не требует каких-то особых ухищрений для настройки производительности. Но когда нагрузка становится действительно серьезной, BI-системой начинают пользоваться сотни пользователей, и решение обрабатывает сотни миллионов и миллиарды строк данных, иногда что-то идет не так.

С точки зрения бизнеса это бывает очень грустно. Какой-то пользователь создает дашборд, и всё падает. При этом увеличение объема памяти и количества процессоров не даёт почти никакого эффекта. Предотвратить или быстро решить такую проблему гораздо проще, если хотя бы в общих чертах представляешь, как система работает под капотом.

Когда мы только начинали работать в сфере BI 5 лет назад, в основу продукта Visiology легла open-source библиотека Pentaho Mondrian. Но достаточно быстро мы столкнулись с проблемами по части производительности и начали самостоятельно разрабатывать In-Memory OLAP движок под названием ViQube (об этом можно почитать в другой нашей статье Как разработать BI-платформу наш трудный, но интересный опыт). Собственно, в процессе этой разработки мы и накопили опыт, которым сейчас хотим поделиться.

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


На первый взгляд, все BI-платформы выглядят одинаково: у вас есть источники информации, у вас есть инструменты загрузки, анализа и визуализации данных, а на выходе пользователь получает разнообразные отчеты от печатных форм до дашбордов, в том числе на мобильных, на видеостенах, на любых устройствах. В своей основе все BI-инструменты используют модель данных на основе OLAP (On-Line Analytical Processing, многомерное представление данных), но техническая реализация OLAP движка (который непосредственно занимается вычислениями) может быть реализован по-разному, и от этого очень сильно зависит производительность и масштабируемость системы.



MOLAP

Технология OLAP возникла ещё в 80-х годах. В то время процессоры были намного медленнее, да и память была в дефиците, поэтому чтобы аналитик мог реально работать с данными в онлайн-режиме, придумали такую вещь как MOLAP (Multidimensional OLAP). Идея подхода в том, что для всего многомерного куба после загрузки данных производится предрасчет: на узлах иерархий предварительно рассчитываются агрегации, чтобы под любой более или менее типовой запрос пользователя можно было получить результат запроса без необходимости пересчитывать все строки. Да, при любом изменении данных нужно долго пересчитывать куб, а объем рассчитанного куба может быть в разы больше исходного датасета, но в то время других вариантов не было. MOLAP до сих пор существует и используется, например, в SQL Server Analysis Services, но на практике его используют все реже и реже.

ROLAP

Позже появилась реляционный OLAP, или ROLAP. Отличие от MOLAP заключается в том, что не происходит никакого предварительного расчёта агрегаций, а вычисления происходят на СУБД из бэкэнда BI-платформы. В этом случае пользователь работает с удобными инструментами, например, с конструктором дашбордов, а под капотом ROLAP-движок преобразует его запросы на лету в SQL, и они просто выполняются на какой-то СУБД.



Подобный подход характерен, например, для таких open-source систем, как Pentaho или Metabase или проприетарного SAP Business Objects, Oracle OBIEE.

У ROLAP есть целый ряд недостатков. Во-первых, если, не использовать на бэкенде специальные аналитические СУБД, такие как ClickHouse или Vertica, все будет работать ооочень медленно (дальше будет понятно, почему). Во-вторых, даже при использовании аналитической СУБД, при работе с ROLAP очень неэффективно используется кэш, потому что СУБД и BI-платформа работают отдельно друг от друга. В-третьих, поскольку не все аналитические задачи можно завернуть в SQL-запрос, ограничивается аналитическая функциональность. Но зато, на сегодняшний день ROLAP это единственный способ работы с реально большими объемами данных, которые не помещаются в память.

In-Memory OLAP

Если речь идет о работе с данными объемом до терабайта, как правило, используется схема In-Memory. Данные постоянно находятся в памяти, и за расчеты отвечает специальный движок. В системах Qlik это QIX, Power BI использует SQL Server Tabular Engine, который раньше был продуктом xVelocity, но Microsoft купил эту компанию, и теперь движок является частью MS SQL Server. У нас в Visiology движок In-Memory OLAP называется ViQube.

In-Memory OLAP привлекает простотой установки и работы, подобные движки изначально интегрированы в BI-платформы и укомплектованы удобными визуальными интерфейсами настройки, загрузки данных, управления правами и т.п. За счет размещения данных в памяти и специальных оптимизаций (про них ниже) многократно растет скорость обработки, расширяются возможности для аналитиков.



При этом у подхода In-Memory есть и свои недостатки. И главный из них это предел емкости памяти. Если объем данных измеряется в терабайтах, вам нужно либо строить дорогой кластер, либо склоняться к ROLAP. Кроме этого, при таком подходе не всегда удается минимизировать задержку отображения обновлений, потому что для этого данные приходится перегружать из источника в память.

Гибридный OLAP

Основной схемой работы для большинства промышленных BI становится гибридная схема с одновременным использованием и In-Memory OLAP, и реляционного OLAP-движка. Горячие данные хранятся в In-Memory, холодные данные, которые не влезли в заданный объем, в СУБД. Такое решение в QlikView, например, называется Direct Discovery, в Power BI Direct Query. В Visiology тоже поддерживаются интеграции с несколькими СУБД, в том числе с ClickHouse.



Кстати, выбор СУБД для гибридного режима также критически важен. Если мы будем работать с PostgreSQL, в котором лежит 5 терабайт данных, аналитические запросы будут исполняться крайне медленно. И если у вас не SAP HANA, придется вручную распределять данные на холодные и горячие. Как следствие, не все аналитические функции будут доступны на полном объёме данных. Но если памяти не хватает, увы, с таким положением дел приходится мириться.

Откуда растут плюсы In-memory OLAP?



Для скорости работы In-Memory OLAP есть как очевидные, так и скрытые причины. Тот факт, что работа движка происходит в памяти, а она намного быстрее, чем жесткий диск (спасибо, кэп) это только 1/10 правды. Но давайте подумаем, как работают реляционные СУБД, например, тот же PostgreSQL. Ведь он в какой-то мере тоже является In-Memory. И вообще, любая современная СУБД активно использует как блочный кэш в памяти, так и внутренний.



Когда обычной дисковой СУБД, такой как PostgreSQL, нужно считать данные с жёсткого диска, она обращается к накопителю и считывает какую-то страницу. Эта страница помещается в блочный кэш (в Linux он располагается в свободном пространстве памяти). Допустим, у нас есть 128 гигабайт памяти, и 20 из них мы занимаем софтом. Всё остальное может использоваться под блочный кэш. И если СУБД нужно будет считать с этой страницы ещё что-нибудь, она возьмет эту информацию из памяти. И от того, насколько эффективно используется кэш, зависит производительность. Если для анализа используется, скажем, 30-40 гигабайт данных, мы можем расширить емкость оперативной памяти на сервере и уже после первого чтения СУБД все данные окажутся In-Memory, а обращения к диску на чтения будут происходить лишь эпизодически.



Кроме этого, у умных СУБД, в том числе у Postgres, имеется механизм cache-aware управления. Они могут выбирать, что складывать в кэш, а что нет, какие данные надо заново прочитать с диска.


Источник: www.enterprisedb.com/blog/autoprewarm-new-functionality-pgprewarm

На графике выше влияние прогрева кэша на производительность PostgreSQL. Жёлтым показана производительность в зависимости от времени, и наглядно видно, что по мере работы пользователей СУБД считывает данные, постепенно раскладывает всё в In-Memory и достигает предела своей производительности. Если же использовать prewarm и дать Postgres команду поднять все данные в память сразу, максимальная производительность достигается сразу.

Также стоит учитывать, что мы говорим об аналитической нагрузке. Она очень сильно отличается от транзакционных задач, когда в базу нужно внести запись о покупке в интернет-магазине или считать 10 строк с историей заказов. На графике ниже показан типовой аналитический запрос из теста TPC-H. Этот тест состоит из нескольких десятков реальных аналитических запросов и широко используется для нагрузочного тестирования.


Источник: www.tpc.org/information

В SQL-запросе из теста TPC-H можно найти много чего интересного. Во-первых, запрос идет не на поля, а на агрегации. Во-вторых, здесь присутствуют линейные арифметические операции. В-третьих, здесь часто встречаются фильтры по полям с малой кардинальностью: регионы и федеральные округа, в которых работает компания, типы клиента активный, неактивный и так далее. В-четвертых, часто используются фильтры по полю с датой. Если мы изучаем динамику выручки, то нас интересуют такие периоды как год или квартал.

На входе подобного запроса всегда очень большое количество строк миллионы или даже миллиарды. Поэтому СУБД вынуждена делать серию полных сканирований. С другой стороны, на выходе получается небольшое количество строк, ограниченное количеством точек, которые можно отобразить на графике чаще всего десятки или сотни значений. Зная эти особенности аналитических запросов, можно провести оптимизацию и получить прирост производительности.

In-Memory OLAP: конкретные примеры оптимизации для BI


Учитывая особенности аналитических запросов, о которых мы уже говорили ранее, для движка BI возможен целый ряд оптимизаций, причем как технических, так и эвристических. Давайте рассмотрим их подробнее.

1. Колоночное хранение данных

Это первый шаг, дающий заметный эффект. Для обычных транзакционных запросов хранение в строках подходит оптимально. Но для аналитики необходимо получать данные по столбцам. Казалось бы, какая разница, ведь все это уже In-Memory? Но на практике кроме памяти, которая быстрее дисков, у нас также есть кэш процессора, как правило, трехуровневый. Он работает намного быстрее, чем память, доступ к которой занимает примерно 200 тактов процессора.



Процессор может считать из памяти только целую страницу, которая сразу попадает в кэш того или иного уровня. И если следующее нужное значение попало в эту же страницу, мы потратим 10-40 тактов, а если нет в 10 раз больше. При колоночном режиме хранения при запросе мы получаем страницу памяти, в которой, скорее всего, будет лежать все данные из одной колонки. Поэтому процессору во много раз реже нужно будет обращаться к основной памяти.


Источник: arstechnica.com/gadgets/2002/07/caching/2

2. Сжатие

В этой оптимизации есть свои особенности. Для дисковых СУБД это обязательная оптимизация. Здесь мы выигрываем за счет того, что реже ходим в медленное хранилище, но также проигрываем, потому что данные надо распаковать, а это вычислительно ёмкая операция. Для дисковых СУБД получается очень выгодно, для In-Memory все не так очевидно, потому что читать из памяти обычно быстрее, чем заниматься распаковкой.


Источник: www.percona.com/blog/2016/03/09/evaluating-database-compression-methods

Самый быстрый из алгоритмов сжатия по скорости распаковки LZ4. Он в среднем уменьшает размер всего в 2 раза, но зато очень быстро распаковывает, со скоростью порядка 500 мегабайт в секунду на ядро процессора. В бенчмарке на графике LZ4 вообще показал результат 3 гигабайта данных в секунду. Такая скорость дает очень хороший выигрыш для дисковых СУБД, скорость чтения для которых те же 500 мегабайт в секунду. Но для памяти скорость передачи данных составляет десятки гигабайт в секунду, получить преимущество за счёт LZ4 оказывается сложно.

Однако не стоит забывать, что мы чаще всего работаем с данными низкой кардинальности. Например, в строке с названиями торговой точки очень часто будут повторяться одни и те же значения или их ID: Москва, Москва, Москва, Москва


Источник: Guassian and speckle noise removal from ultrasound images using bivariate shrinkage by dual tree complex wavelet transform (Professor G R Sinha, 2015)

Для подобных данных есть ещё один алгоритм, который называется Run-length encoding. Он работает очень просто: строки типа ААААВВВВВСС он сжимает в виде 4A5B2C. Это прекрасный подход для данных с низкой кардинальностью, он помогает экономить память и оптимальнее использовать кэш процессора.

3. Векторные инструкции

Чтобы сложить 2 числа, мы кладём в один регистр процессора одно число, а в другой регистр другое. Для ускорения этого процесса существуют векторные регистры и векторные операции (SIMD Single Instruction Multiple Data). Они позволяют за одну операцию сложить сразу N чисел. Это уже очень зрелая технология, которая появилась в процессорах еще в 1993 году. Она поддерживалась еще в Pentium MMX (166 МГц у меня такой был, до сих пор помню, как на него термопасту намазывал).


Источник: www.codetd.com/en/article/9633503

В Pentium MMX векторных регистров было 8, и они были рассчитаны только на целочисленную арифметику. На текущий момент практически во всех процессорах есть 128-битные регистры SSE и наборы инструкций. Регистры AVX уже 256-битные, а в серверах есть даже AVX 512. Они работают с числами с плавающей запятой, и их можно использовать для оптимизации аналитической нагрузки.


Источник: technews.tw/2020/07/16/linus-torvalds-avx-512-critique

При работе с AVX 512 мы можем обрабатывать в одном регистре 16 чисел со стандартной точностью. Теоретически тут можно получить хороший выигрыш, но для этого необходимо преодолеть ряд технических сложностей. Чтобы при компиляции оптимизация работала, нужно предусмотреть ее при написании кода. Для этого используются интринсики особый набор функций, которые в явном виде указывают компилятору, какие инструкции процессора нужно использовать.

Вот пример, как для расчёта скалярного произведения используется AVX-инструкции 256-битных регистров.



На нашей практике использование именно AVX даёт от 1 до 10% прирост производительности, в зависимости от задач. Когда вы используете современную BI-систему, в зависимости от конкретного процессора, система будет применять разный код. Производительность от этого увеличивается, но не драматически, а в лишь в небольших пределах.

4. Поздняя материализация

Материализация это процесс формирования результата, ответа на запрос. Например, простой SQL-запрос SELECT C1, С2, D1, D2 из 2 таблиц, получит 2 поля из одной и 2 поля из другой таблицы. Далее мы соединяем их по ключу С1=D1.



В случае ранней материализации мы получаем 4 колонки и работаем с колоночной базой. То есть мы сначала берём С1 и С2, соединяем их в таблицу. Потом делаем то же самое с D1, D2 и после этого выполняем Join, то есть формируем строки из этих 2 таблиц, для которых истинно условие С1=D1.

В случае аналитической нагрузки на выходе всегда мало данных, потому что фильтры отсекают достаточно много значений. Когда выполняется JOIN, создаётся очень большая таблица, которая не нужна. Ведь на последней операции будет отброшено много данных.

При поздней материализации мы сначала разбираемся, что и в какой колонке нам нужно. Сначала выполняется операция JOIN С1 = D1, и мы выбираем нужные нам значения только из правой таблицы. Это можно сделать за один проход. А после этого можем взять из таблицы С только те поля, строки которых после JOIN остались. В итоге не нужно создавать большую промежуточную таблицу.

Конечно, такая оптимизация не сработает на любой нагрузке. Исследования Vertica, например, показывают, как многопрофильная СУБД позволяет выбирать различные стратегии материализации, в зависимости от задач. Но именно для BI характерна нагрузка, связанная с поздней материализацией, поэтому ее использование предпочтительно.

5. Эвристические оптимизации

In-Memory OLAP чаще всего является неотъемлемой частью BI-платформы, а BI-платформа прекрасно знает, какие данные в нее загружаются, как пользователь с ними работает. Конечно, это неочевидные вещи, они происходят под капотом BI-системы и не всегда даже видны, но позволяют получить хороший прирост производительности.

Во-первых, часто применяется автоматическая нормализация или денормализация. Данные с низкой кардинальностью иногда бывает выгодно нормализовывать, а иногда наоборот. Чаще всего BI-платформы стараются максимально денормализовать таблицы. Такой подход позволяет максимально избегать достаточно тяжелых операций JOIN. Пользователь может даже не видеть этого: если мы загрузили в систему 2 таблицы и связали их по ключу, система может сразу превратить их в одну таблицу.

В некоторых случаях, наоборот, происходит автоматическая нормализация. Если поле очень жирное, например, с текстовым комментарием, держать его по всей колонке будет неоптимально. Вместо этого такое поле можно автоматически вывести в справочник. Вы будете видеть в таблице и строку, и столбец, а на самом деле данные будут находиться в отдельной таблице, а в памяти будет находиться только ID. Такие методики используются довольно часто.

6. Сортировка по календарю

Поскольку мы работаем с данными, которые практически всегда растянуты во времени, стоит учитывать интерес пользователей а он чаще всего сконцентрирован на ближайших данных. Поэтому оказывается выгодно сортировать данные по календарному измерению. Действительно, если пользователь работает с текущим годом, зачем ему данные с начала времён?
Однако, если вы храните дату в виде строкового столбца, BI-система не сможет оптимизировать данные. Но если сортировка удается, иногда получается свести большой датасет к очень компактному, просто выкинув из него все года, кроме текущего.

7. Разделяемый кэш

Пользователи BI часто работают с одними и теми же данными. Поэтому хорошую эффективность показывает разделяемый кэш. Если пользователь А запустил дашборд и выполнил свой запрос, то пользователь В на том же дашборде сможет получить результат быстрее, потому что необходимые данные будут уже в кэше.

Разделяемый кэш под запросы позволяет кэшировать данные внутри запросов. Например, один пользователь запросил аналитику и отфильтровал её по отделу А, а другой хочет по отделу В. В этом случае оптимизации не получится, потому что фильтрация происходит на раннем этапе. Но в некоторых случаях и для подобных ситуаций удается оптимизировать запрос.

8. Обратный индекс

Многие BI-системы выстраивают обратный индекс для строковых полей. Он позволяет ускорить поиск с операторами по строкам. И хотя BI-системы и OLAP движок In-Memory это не замена полнотекстовому поиску, подобные оптимизации встречаются достаточно часто.

Каков эффект?


Все перечисленные меры помогают сделать BI-систему, основанную на In-Memory OLAP, более устойчивой и производительной, не прибегая к гибридным схемам работы с подключением реляционных БД. Рост производительности сильно зависит от используемых задач, но в процессе работы над ViQube мы убедились в том, что оптимизации лучше всего закладывать на этапе исходного кода и изначально проектировать систему с учетом особенностей аналитических запросов.

Кроме этого, на своем опыте мы нашли ряд кейсов, которые позволяют легко убить производительность In-Memory OLAP. Можно считать этот набор вредных советов пасхалочкой для тех, кто дочитал до конца :)

5 способов убить производительность In-Memory OLAP


BI-система, работающая на базе In-Memory OLAP, уже по определению имеет высокую производительность однако она не будет неубиваемой! Ниже список из 5 вредных советов по In-Memory, выстраданных на своей шкуре в процессе разработки движка ViQube и его использования в реальных проектах.

1. Сложные вложенные выражения


Прекрасными убийцами производительности In-Memory систем являются запросы, в которых плохо работает векторизация. Любые вычисления с использованием нелинейных функций, которые задействуют много полей, приводят к радикальному снижению скорости работы системы.

Очень часто в своей практике я сталкивался с тем, что аналитик пишет какое-то выражение на Qlik Expressions или на DAX в Power BI, которое хорошо работает, когда в базе 10 тысяч строк. Но по мере роста масштабов производительность начинает деградировать невероятными темпами.

Обычно это происходит потому, что формула запроса сложна и не даёт движку использовать преимущества колоночного хранения векторных инструкций. Вместо этого системе приходится находить все поля по строкам, перебирать их одну за другой. В этом случае производительность падает до уровня СУБД со строчным хранением. Конец оптимизации.

2. Вложенные запросы


Вложенные запросы крайне важная в аналитике вещь. Очень часто результатом ответа на первый запрос оказывается таблица, и по этой таблице мы производим дополнительный анализ. Например, в DAX подобный подход легко реализуется с помощью так называемых контекстов, и поэтому он весьма популярен среди аналитиков. Возможность создавать вложенные запросы есть и в других движках.

Однако пользоваться такими запросами нужно аккуратно. Ведь если первый запрос возвращает большой промежуточный результат (например, десятки миллионов строк), движок будет вынужден выполнить раннюю материализацию и создать огромную промежуточную таблицу.

Кстати, такой запрос может вообще не пройти в принципе, если движок упрётся в предельную емкость доступной памяти. А может привести к очень сильному замедлению работы BI-системы. Поэтому при создании вложенных запросов нужно оценивать размеры промежуточного результата.

3. Частое обновление данных


Частое обновление данных не так страшно, если вы используете специальные инструменты для работы в режиме Real-time. Для чего? Почему BI-платформа In-Memory OLAP не очень любят Real-time и для Real-time предлагают, по сути, отдельные инструменты. В Power BI, например, Streaming Dataset. Зачем это сделано? Почему просто не дописывать и не считать заново?

Каждый раз, когда мы что-то меняем в исходных данных, происходит серьёзная инвалидация кэша. То есть и общий кэш, и разделяемый кэш (свой для каждого пользователя), кэш под запрос всё это приходится выкинуть и ждать, пока оно рассчитается заново. Даже добавление одной новой строки приводит к инвалидации, особенно если она реализована не слишком умно. К тому же BI-платформы чаще всего инвалидируют кэш с запасом, чтобы исключить показ пользователю неправильных данных.

Частая инвалидация ведёт к деградации производительности BI-системы, и это особенно заметно, если одновременно BI-система обрабатывает тысячи запросов. Как правило, большинство пользователей работают с готовыми дашбордами, для которых кэш основной источник оптимизации. Когда мы проводили нагрузочное тестирование для профиля пользователя, который просто работает с дашбордом, 90-95% запросов в принципе не доходили до движка и обслуживались из кэша. Именно поэтому частая инвалидация ведет к падению производительности в 10 и более раз.

4. Маленький запас свободной памяти


Иногда кажется, что для работы системы неважно, сколько имеется свободной оперативной памяти, лишь бы хватало общей емкости. Но на самом деле свободная память используется для буферного кэша. Можно сказать, что для In-Memory движков это не так критично, потому что они не так часто работают с жёстким диском. Но в те моменты, когда он поднимает данные, использует snapshot, сохраняет или загружает что-то, наличие буферного кэша оказывается очень даже важным фактором. К тому же, помимо In-Memory движка в любой BI есть и другие части системы, например, компоненты ОС. И если память кончится, они начнут резко тормозить, потому что не смогут использовать буферный кэш.

Но и это еще не все. Нехватка свободной памяти ведет к рискам просадки производительности в сложных запросах. Когда происходит создание больших объемов промежуточных данных, они тоже могут вытеснить буферный кэш, не позволяя использовать его. Что ещё хуже, такие запросы могут достичь предела доступной памяти, и вся система упадёт в swap. Это будет полный коллапс с точки зрения производительности.

5. Строковые поля с высокой кардинальностью


Последний вредный совет загружать строковые поля с высокой кардинальностью. Добавляя к датасету комментарии к заказам, сообщения из чата или что-то подобное, можно сильно просадить производительность. То, что хорошо подходит для полнотекстового поиска, плохо работает для движков In-Memory OLAP. Такие данные не дают использовать RLE, векторные инструкции. Здесь мы снова падаем в выполнение строковых операций, производительность для которых намного меньше, чем для арифметических. BI-система в принципе не всегда может создать индекс на такие строковые поля со всеми вытекающими последствиями.

Да пребудет с вами производительность


Как я уже говорил, In-Memory OLAP это продвинутая и умная технология, которая, чаще всего, просто работает и позволяет не задумываться о том, что внутри у BI-платформы. Но, исключения из правил случаются, и, я надеюсь, что эта статья поможет быть к ним готовым.

Всех с наступающим, и отличной производительности всем вашим сервисам в Новом Году! :)
Подробнее..

Выбор хэш-функции в задаче шардирования данных

28.12.2020 02:07:52 | Автор: admin

Введение

Мы в Miro работаем над процессом шардирования баз Postgres и используем разные подходы в зависимости от бизнес-требований. Недавно перед нами встала задача шардирования новых баз, в ходе неё мы выбрали новый для нас подход к шардированию, основанный на согласованном хешировании (consistent hashing).

В ходе реализации этого подхода один из центральных вопросов заключался в том, какую реализацию не-криптографической хэш-функции нам лучше выбрать и использовать. В статье я опишу критерии и алгоритм сравнения, который мы выработали и использовали на практике для поиска наилучшей реализации.

Oб архитектурном подходе

Есть много продуктов (mongo, redis, и т.д.), использующих согласованное хеширование для шардинга, и наша реализация будет сильно похожа на них.

Пусть, на входе у нас есть множество сущностей с выбранными ключами шардирования, строкового типа. Для этих ключей с помощью хэш-функции мы получим хэш-код определенной длины, для которого через операцию деления по модулю определим необходимый слот. Кол-во слотов и соответствие сущностей слотам фиксировано. Также необходимо хранить соответствие диапазонов слотов и шардов, что не является сложной задачей, и для места хранения вполне подойдет конфигурационный файл.

Плюсами данного подхода являются:

  • равномерное распределение сущностей по шардам;

  • определение соответствия сущностей и шардов без дополнительного хранилища с минимум ресурсо-затрат;

  • возможность добавления новых шардов в кластер.

Из минусов:

  • неэффективность некоторых операций поиска, в которых необходимо делать запросы на все шарды;

  • достаточно сложный процесс решардинга.

Требования

Центральным местом решения является выбор java-реализации хэш-функции.

Функция принимает на вход ключ - объект строки, размером до 256 символов, и выдает хэш-код - беззнаковое целое число, размером до 4 байт. На самом деле мы будем сравнивать реализации которые генерируют хэш-коды размером 2 и 4 байта.

Критерии сравнения

Рассмотрим четыре распространенных критерия сравнения реализаций хэш-функций:

  1. Скорость, функция должна работать быстро, на любых входных данных;

  2. Вид распределения результатов. Очень важно, чтобы функция на выходе генерировала хэши, которые соответствуют равномерному распределению;

  3. Устойчивость к коллизиям (первого и второго рода);

  4. Соответствие лавинному эффекту. Отражает зависимость всех выходных битов от каждого входного бита, на любых входных данных.

Для нашей задачи нам будут важны только первые два критерия: первый - поскольку операция расчета хэша будет очень частой; и второй - поскольку крайне важно, чтобы данные распределялись по шардам равномерно.

Отсутствие возможности атаки на характеристики функции делает для нас неважным третий критерий.

В случае несоответствия четвертому критерию мы можем получить только единичные выбросы из равномерного распределения, которые нас не сильно волнуют.

Реализации

Мы будем рассматривать самые популярные java-реализации не-криптографических хэш-функций:

  1. DJB2 (32-бита);

  2. SDBM (32-бита);

  3. LoseLose (32-бита);

  4. FNV-1 / FNV-1a (32-бита);

  5. CRC16 (16-бит) ;

  6. Murmur2/Murmur3 (32-бита).

Тестирование

Входные данные

В качестве входных данных мы будем использовать следующие наборы данных

  1. Набор реальных данных, составленный из 216,553 английских слов;

  2. Набор синтетических данных, составленный из рандомно сгенерированных символов в кодировке UTF-8.

В обоих тестовых наборах мы будем иметь группы строк с определенными длинами (кол-во символов) - "2", "4", "8", "16", "32", "64", "128", "256".

Метрики

Для сравнения различных критериев мы будем использовать следующие метрики:

  1. Для первого критерия, скорости - ops/ms (кол-во операций в миллисекунду работы);

  2. Для второго критерия - факт удовлетворения критерию согласия Пирсона для равномерного распределения. Для этого нам придется ввести гипотезу о виде распределения результатов и проверить ее. Впрочем такая метрика будет бинарной, и для того чтобы визуально оценить насколько распределение хэш-кодов каждой из имплементаций близко к равномерному распределению, мы воспользуемся построением гистограмм относительных частот для каждой серии тестов.

Инструменты

Оценка скорости работы

Для оценки скорости работы мы воспользуемся нагрузочными тестами и библиотекой JMH. Общая схема тестовой итерации выглядит следующим образом:

Слова из каждого тестового набора мы сгруппируем по длине, при максимальном значении в 256 символов. Затем в каждой итерации будем подавать на вход хэш-функции слова из каждой группы, с одинаковой вероятностью.

Для бэнчмарков мы будем использовать следующие настройки

  • Кол-во warmup-итераций - 50;

  • Кол-во measurement-итераций - 100;

  • Режим - throughput

  • Добавим ограничение по памяти -Xms1G, -Xmx8G

  • Для оценки расхода памяти добавим GCProfiler

Полный код тестов можно посмотреть здесь.

Оценка распределения результатов

Для проверки соответствия выходных значений функции нашим ожиданиям проверим гипотезу о том, что выборка результатов при уровне значимости =0,05, распределена по равномерному закону. Для проверки мы будем использовать критерий согласия Пирсона.

Алгоритм для проверки гипотезы следующий:

  1. Разобьем выборку на частичные интервалы, число которых найдем по формуле Стерджеса, а их длину найдем по правилу равноинтервальной группировки;

  2. Для каждого интервала подсчитаем его характеристики - среднее значение, частоты, относительные частоты;

  3. Подсчитаем выборочное среднее \overline{x_{b}} , среднеквадратическое отклонение \sigma_{b} = \sqrt{D_{b}} и теоретические частоты

    \hat{n_{i}}=np_{i},

    где n число элементов в выборке, а p_{i} вероятность попадания случайной величины в частичные интервалы, в нашем случае она равна -

    p_{i} = \frac{x_{length}}{b - a},

    где x_{length} - одинаковая длина интервалов, a параметры a и b - a = \overline{x_{b}} - \sqrt{3\sigma_{b}} , b = \overline{x_{b}} + \sqrt{3\sigma_{b}} ;

  4. Можем приступить к расчёту критерия согласия, по формуле

    \chi_{набл}^2 = \sum\frac{n_{i}-\hat{n_{i}}}{\hat{n_{i}}},

    где n_{i} - эмпирические частоты, полученные из выборки, \hat{n_{i}} - теоретические частоты, найденные по формулам выше;

  5. Определяем по таблице критических точек распределения \chi_{кр}^2(\alpha, k) , по заданному уровню значимости и числу степеней свободы k ;

  6. Если \chi_{набл}^2<\chi_{кр}^2 , то принимаем гипотезу, если же данное условие не выполняется отвергаем.

Код для расчёта критерия согласия и вероятностных характеристик выборок здесь.

Общая схема тестовой итерации похожа на схему в предыдущем разделе и выглядит следующим образом:

Слова из каждого тестового набора мы сгруппируем по длине, при максимальном значении символов в 256. Затем создадим входные тестовые выборки разных размеров в диапазоне 16384, 8192, 4096, 2048, 1024, в выборки поместим слова из каждой группы, с одинаковой вероятностью.

Все элементы каждой из групп подадим на вход хэш-функции и получим выходные выборки, состоящие из целочисленных хэш-кодов. После чего по алгоритму выше рассчитаем для них критерий согласия и определим, удовлетворяет ли он гипотезе о равномерном распределении.

Полный код тестов можно посмотреть здесь.

Результаты

Оценка скорости работы

Рассмотрим скорость работы (количество операций в миллисекунду) для различных имплементаций в зависимости от длины входных строк.

В диапазоне от двух до восьми символов:

ДиаграммаДиаграмма

Видно, что в этом диапазоне практически все алгоритмы работают с одинаковой скоростью, незначительно опережает всех loseLose, а очевидными аутсайдерами выглядят только crc16 и sdbm.

В диапазоне от 16 до 256 символов:

ДиаграммаДиаграмма

Функция murmur2 явный фаворит, ей немного уступает murmur; crc16 и sdbm остались в аутсайдерах и на этой выборке.

Оценка распределения результатов

Рассмотрим таблицу результатов соответствия критерию Пирсона

Видно, что имплементации crc16, murmur2, murmur3 удовлетворяют критерию Пирсона о равномерном распределении практически на всех выборках.

Рассмотрим гистограммы относительных частот, в разрезе разных выборок.

На гистограммах ниже, для loseLose, Djb2, Sdbm, не прошедших тест, видно, что распределение далеко от равномерного и больше похоже на геометрическое:

ДиаграммаДиаграммаДиаграммаДиаграммаДиаграммаДиаграмма

Для проваливших тест Fnv1 и Fnv1a ситуация похожа, распределения отдалённо напоминают нормальное:

.

ДиаграммаДиаграммаДиаграммаДиаграмма

Смотрим на тройку победителей:

ДиаграммаДиаграммаДиаграммаДиаграммаДиаграммаДиаграмма

За исключением некоторых всплесков, crc16, murmur2, murmur3 удовлетворяют критерию Пирсона, что согласуется с характеристиками их гистограмм относительных частот.

Выводы

Рассмотрим выбор наиболее подходящей реализации, которую мы оцениваем по двум выбранным критериям: скорость работы и удовлетворение гипотезы о равномерном распределении.

Скорость работы. Функции murmur2/murmur3 имеют лучшее время работы для входных строк длиной больше 8 символов.

Удовлетворение гипотезы о равномерном распределении. Можем выделить три функции, для которых гипотеза принимается для большинства наборов данных: crc16, murmur2/murmur3. Графики распределения гистограмм относительных частот подтверждают вид равномерного распределения для функций crc16, murmur2/murmur3.

Таким образом, исходя из двух критериев, лучшим выбором являются реализации murmur2/murmur3.

Подробнее..

Перевод Мы отрендерили миллион страниц, чтобы понять, из-за чего тормозит веб

30.12.2020 12:20:04 | Автор: admin
Мы отрендерили 1 миллион самых популярных страниц веба, фиксируя все мыслимые метрики производительности, записывая все ошибки и замечая все запрошенные URL. Похоже, таким образом мы создали первый в мире набор данных, связывающий производительность, ошибки и использование библиотек в сети. В этой статье мы проанализируем, что наши данные могут сообщить о создании высокопроизводительных веб-сайтов.


  • Посещён 1 миллион страниц
  • Записано по 65 метрик каждой страницы
  • Запрошен 21 миллион URL
  • Зафиксировано 383 тысячи ошибок
  • Сохранено 88 миллионов глобальных переменных

Можно ли превзойти наш анализ? Мы опубликовали наш набор данных на Kaggle, поэтому вы можете обработать данные самостоятельно.

Зачем рендерить миллион веб-страниц?


Сегодня распространено мнение о том, что веб почему-то стал более медленным и забагованным, чем 15 лет назад. Из-за постоянно растущей кучи JavaScript, фреймворков, веб-шрифтов и полифилов, мы съели все преимущества, которые даёт нам увеличение возможностей компьютеров, сетей и протоколов. По крайней мере, так утверждает молва. Мы хотели проверить, правда ли это на самом деле, а также найти общие факторы, которые становятся причиной торможения и поломок сайтов в 2020 году.

Общий план был простым: написать скрипт для веб-браузера, заставить его рендерить корневую страницу миллиона самых популярных доменов и зафиксировать все мыслимые метрики: время рендеринга, количество запросов, перерисовку, ошибки JavaScript, используемые библиотеки и т.п. Имея на руках все эти данные, мы могли бы начать задаваться вопросами о том, как один фактор корреллирует с другим. Какие факторы сильнее всего влияют на замедление рендеринга? Какие библиотеки увеличивают время до момента возможности взаимодействия со страницей (time-to-interactive)? Какие ошибки встречаются наиболее часто, и что их вызывает?

Для получения данных достаточно было написать немного кода, позволяющего Puppeteer управлять по скрипту браузером Chrome, запустить 200 инстансов EC2, отрендерить миллион веб-страниц за пару выходных дней и молиться о том, что мы действительно правильно поняли ценообразование AWS.

Общие значения



Протокол, используемый для корневого HTML-документа

HTTP 2 сейчас более распространён, чем HTTP 1.1, однако HTTP 3 по-прежнему встречается редко. (Примечание: мы считаем сайты, использующие протокол QUIC как использующие HTTP 3, даже если Chrome иногда говорит, что это HTTP 2 + QUIC.) Это данные для корневого документа, для связанных ресурсов значения выглядят немного иначе.


Протокол, используемый для связанных ресурсов

Для связанных ресурсов HTTP 3 используется почти в 100 раз чаще. Как такое может быть? Дело в том, что все сайты ссылаются на одно и то же:


Самые популярные URL ссылок

Есть несколько скриптов, связанных с большой частью веб-сайтов. И это ведь означает, что эти ресурсы будут в кэше, правильно? Увы, больше это не так: с момента выпуска Chrome 86 ресурсы, запрашиваемые с разных доменов, не имеют общего кэша. Firefox планирует реализовать такой же подход. Safari разделяет свой кэш уже многие годы.

Из-за чего тормозит веб: прогнозируем time-to-interactive


Имея такой набор данных веб-страниц и их метрики времени загрузки, было бы здорово узнать о том, из-за чего же тормозят веб-страницы. Мы изучим метрику dominteractive, это время до того момента, как документ становится интерактивным для пользователя. Проще всего рассмотреть корреляцию каждой метрики с dominteractive.


Корреляции метрик с dominteractive

По сути, каждая метрика позитивно коррелирует с dominteractive, за исключением переменной 01, обозначающей использование HTTP2 или более старшей версии. Многие из этих метрик также положительно коррелируют друг с другом. Нам нужен более сложный подход, чтобы выйти на отдельные факторы, влияющие на высокий показатель time-to-interactive.

Некоторые из метрик это тайминги, измеряемые в миллисекундах. Мы можем взглянуть на их диаграмму размаха (ящик с усами), чтобы понять, на что браузеры тратят своё время.


Диаграмма размаха метрик таймингов. Оранжевая линия это медиана, ящик ограничивает с 25-го по 75-й перцентиль.

Один из способов определения отдельных факторов, влияющих на высокий показатель time-to-interactive выполнение линейной регрессии, при которой мы прогнозируем dominteractive на основании других метрик. Это означает, что мы назначаем каждой метрике вес и моделируем время dominteractive страницы как взвешенную сумму других метрик, плюс некая константа. Алгоритм оптимизации расставляет веса так, чтобы минимизировать погрешность прогноза для всего набора данных. Величина весов, определённая регрессией, сообщит нам что-то о том, как каждая метрика влияет на медленность страницы.

Из регрессии мы исключим метрики таймингов. Если мы потратим 500 мс на установку соединения, то это добавит 500 мс к dominteractive, но это не особо интересный для нас вывод. По сути, метрики таймингов являются результатами. Мы хотим узнать, что становится их причиной.


Коэффициенты регрессии для метрик, прогнозирующей dominteractive

Числа в скобках это коэффициенты регрессии, выведенные алгоритмом оптимизации. Можно интерпретировать их как величины в миллисекундах. Хотя к точным значениям нужно относиться скептически (см. примечание ниже), интересно увидеть порядок, назначенный каждому аспекту. Например, модель прогнозирует замедление в 354 мс для каждого перенаправления (редиректа), необходимого для доставки основного документа. Когда основной HTML-документ передаётся по HTTP2 или более высокой версии, модель прогнозирует снижение time-to-interactive на 477 мс. Для каждого запроса, причиной которого стал документ, она прогнозирует добавление 16 мс.

В процессе интерпретации коэффициентов регрессии нужно помнить о том, что мы работаем с упрощённой моделью реальности. На самом деле time-to-interactive не определяется взвешенной суммой этих входящих метрик. Очевидно, что есть причинные факторы, которые модель выявить не может. Бесспорной проблемой являются искажающие факторы. Например, если загрузка основного документа по HTTP2 коррелирует с загрузкой других запросов по HTTP2, то модель встроит это преимущество в веса main_doc_is_http2_or_greater, даже если ускорение вызвано запросами не к основному документу. Нам нужно быть аккуратными, сопоставляя показания модели с выводами о реальном положении дел.

Как на dominteractive влияет версия протокола HTTP?


Вот любопытный график dominteractive, разделённый по версиям протокола HTTP, используемым для доставки корневой HTML-страницы.


Диаграмма размаха dominteractive, разделённая по версиям протокола HTTP первого запроса. Оранжевая линия медиана, ящик ограничивает с 25-го по 75-й перцентиль. Проценты в скобках доля запросов, выполненная по этому протоколу.

Существует крошечная доля сайтов, до сих пор доставляемых по HTTP 0.9 и 1.0. И эти сайты оказываются быстрыми. Похоже, мы не можем отвязаться от того факта, что протоколы стали быстрее, а поэтому довольные программисты пользуются этим ускорением, доставляя в браузер больше данных.

Это показатели для версии протокола, используемой для доставки корневой HTML-страницы. А если мы рассмотрим влияние протокола, используемого для ресурсов, на которые ссылается этот документ? Если провести регрессию для количества запросов по версии протокола, то получится следующее.


Коэффициенты прогнозирующей dominteractive регрессии для количества запросов по версии протокола

Если бы мы поверили этим показателям, то пришли бы к выводу, что перемещение запрашиваемых ресурсов при переходе с HTTP 1.1 на 2 ускоряется 1,8 раза, а при переходе с HTTP 2 на 3 замедляется в 0,6 раза. Действительно ли протокол HTTP 3 более медленный? Нет: наиболее вероятное объяснение заключается в том, что HTTP 3 встречается реже, а немногочисленные ресурсы, передаваемые по HTTP 3 (например, Google Analytics), больше среднего влияют на dominteractive.

Как на dominteractive влияет тип контента?


Давайте спрогнозируем time-to-interactive в зависимости от количества переданных данных при разделении по типам передаваемых данных.


Коэффициенты прогнозирующей dominteractive регрессии для килобайтов, переданных инициатором запроса

Вот похожая регрессия, на этот раз здесь рассматривается количество запросов по типу инициатора запроса.


Коэффициенты прогнозирующей dominteractive регрессии для количества запросов, переданных инициатором запроса

Здесь запросы разделены по инициаторам запросов. Очевидно, что не все запросы равны. Запросы, вызванные элементом компоновки (например, CSS или файлов favicon), и запросы, вызванные CSS (например, шрифтов и других CSS), а также скриптами и iframe значительно замедляют работу. Выполнение запросов по XHR и fetch прогнозируемо выполняются быстрее, чем базовое время dominteractive (вероятно, потому, что эти запросы почти всегда асинхронны). CSS и скрипты часто загружаются так, что препятствуют рендерингу, поэтому неудивительно, что они связаны с замедлением time-to-interactive. Видео относительно малозатратно.

Выводы


Мы не нашли никаких новых трюков для оптимизации, но анализ даёт нам понимание масштаба влияния, которого следует ожидать от различных оптимизаций. Похоже, чёткое эмпирическое подтверждение имеют следующие утверждения:

  • Делайте как можно меньше запросов. Количество запросов важнее, чем количество запрошенных килобайтов.
  • По возможности делайте запросы по HTTP2 или более старшей версии протокола.
  • Стремитесь по возможности избегать запросов, мешающих рендерингу, старайтесь пользоваться асинхронной загрузкой.

Библиотеки


Чтобы разобраться, какие библиотеки используются на странице, мы воспользовались следующим подходом: на каждом сайте мы фиксировали глобальные переменные (например, свойства объекта окна). Далее каждая глобальная переменная, встречавшаяся более шести тысяч раз связывалась (когда это было возможно) с библиотекой JavaScript. Это очень кропотливая работа, но поскольку в наборе данных также содержались запрашиваемые URL для каждой страницы, мы могли изучить пересечение встречаемых переменных и запросов URL, и этого часто было достаточно, чтобы определить, какая библиотека задаёт каждую из глобальных переменных. Глобальные переменные, которые нельзя было с уверенностью связать с какой-то одной библиотекой, игнорировались. Такая методология в определённой мере обеспечивает неполный учёт: библиотеки JS не обязаны оставлять что-то в глобальном пространстве имён. Кроме того, она обладает некоторым шумом, когда разные библиотеки задают одно и то же свойство, и этот факт при привязке библиотек не учитывался.

Какие библиотеки JavaScript используются сегодня наиболее часто? Если следить за темами конференций и постов, было бы вполне логично предположить, что это React, Vue и Angular. Однако в нашем рейтинге они совершенно далеки от вершины.

10 самых используемых библиотек



Просмотреть полный список библиотек по уровню использования

Да, на вершине находится старый добрый jQuery. Первая версия JQuery появилась в 2006 году, то есть 14 человеческих лет назад, но в годах JavaScript это гораздо больше. Если измерять в версиях Angular, то это произошло, вероятно, сотни версий назад. 2006 год был совершенно иным временем. Самым популярным браузером был Internet Explorer 6, крупнейшей социальной сетью MySpace, а скруглённые углы на веб-страницах были такой революцией, что люди называли их Веб 2.0. Основная задача JQuery обеспечение кроссбраузерной совместимости, которая в 2020 году совершенно отличается от ситуации 2006 года. Тем не менее, 14 лет спустя аж половина веб-страниц из нашей выборки загружала jQuery.

Забавно, что 2,2% веб-сайтов выбрасывали ошибку, потому что JQuery не загружался.

Судя по этой десятке лучших, наши браузеры в основном выполняют аналитику, рекламу и код для совместимости со старыми браузерами. Почему-то 8% веб-сайтов определяют полифил setImmediate/clearImmediate для функции, реализация которой даже не планируется ни в одном из браузеров.

Прогнозирование time-to-interactive по использованию библиотек


Мы снова запустим линейную регрессию, прогнозирующую dominteractive по наличию библиотек. Входящими данными регрессии будет вектор X, где X.length == количество библиотек, где X[i] == 1.0, если библиотека i присутствует, X[i] == 0.0, если её нет. Разумеется, мы знаем, что на самом деле dominteractive не определяется наличием или отсутствием конкретных библиотек. Однако моделирование каждой библиотеки как имеющей вклад в замедление и выполнение регрессии для сотен тысяч примеров всё равно позволяет нам обнаружить интересные находки.

Наилучшие и наихудшие для time-to-interactive библиотеки по коэффициентам регрессии



Посмотреть полный список библиотек по коэффициентам регрессии, прогнозирующей dominteractive

Отрицательные коэффициенты означают, что модель прогнозирует меньшее time-to-interactive при наличии этих библиотек, чем при их отсутствии. Разумеется, это не означает, что добавление этих библиотек ускорит ваш сайт, это только значит, что сайты с этими библиотеками оказываются быстрее, чем некая базовая линия, установленная моделью. Результаты могут быть столь же социологическими, сколь и техническими. Например библиотеки для ленивой загрузки по прогнозу обеспечивают снижение time-to-interactive. Возможно, это просто вызвано тем, что страницы с этими библиотеками созданы программистами, потратившими время на оптимизацию с целью быстрой загрузки страниц, поскольку ленивая загрузка приводит непосредственно к этому. Мы не можем отвязать подобные факторы от данной структуры.

Наилучшие и наихудшие для времени onload библиотеки по коэффициентам регрессии


Мы можем повторить описанный выше процесс, но на этот раз для прогнозирования onloadtime. Onloadtime это время, которое требуется для срабатывания события окна load, то есть время, требуемое для загрузки всех ресурсов страницы. Проведём линейную регрессию точно таким же образом, как и ранее.


Посмотреть полный список библиотек по коэффициентам регрессии, прогнозирующей onloadtime

Наилучшие и наихудшие библиотеки для jsheapusedsize по коэффициентам регрессии


Это прогноз для размера в мегабайтах кучи, используемой JavaScript.


Посмотреть полный список библиотек по коэффициентам регрессии, прогнозирующей jsheapusedsize

Комментаторы в Интернете с любят высокомерно говорить, что корреляция не равна причинно-следственной связи, и мы в самом деле не можем напрямую вывести из этой модели причинности. При интерпретировании коэффициентов следует быть очень аккуратными, частично это вызвано тем, что может участвовать множество искажающих факторов. Однако этого определённо достаточно для того, чтобы задуматься. Тот факт, что модель связывает замедление time-to-interactive на 982 мс с наличием jQuery, и что половина сайтов загружает этот скрипт, должен навести нас на определённые мысли. Если вы оптимизируете свой сайт, то сверка его списка зависимостей с представленными здесь рейтингами и коэффициентами определённо обеспечит вам приличный показатель того, устранение какой зависимости даст вам наибольший рост производительности.

Так же вам может показаться любопытным наш глубокий анализ о встречаемых при обходе ошибках. Прочитайте нашу статью об ошибках JavaScript в естественных условиях, где мы анализируем найденные ошибки и рассуждаем о том, как они могут нам помочь разрабатывать веб-технологии будущего, менее подверженные ошибкам.



На правах рекламы


VDS для проектов и задач любых масштабов это про наши эпичные серверы! Новейшие технологии и оборудование, качественный сервис. Поспешите заказать!

Подробнее..

Анонс, предзаказ и бесплатные уроки видеокурса по Apache Kafka

24.12.2020 18:19:54 | Автор: admin


Открываем предзаказ продвинутого курса по Apache Kafka.


Видеокурс о том, как настроить и оптимизировать Apache Kafka брокер сообщений для микросервисов. Вы последовательно узнаете, откуда взялась технология, как настраивать распределенный отказоустойчивый кластер, как отслеживать метрики, работать с балансировкой.


Курс подойдет опытным инженерам.


Программа


Базовая часть (будет доступна бесплатно в личном кабинете Слёрма)


Введение


  • Немного истории: почему лог и стриминг важны, и занимают значительное место в бизнес процессах компаний;
  • Fun fact: почему "кафка"? А не почему, просто хорошо звучит.

Базовые основы технологии


  • Сравнения с подобными технологиями;
  • Базовые примитивы: брокеры, топики, партиции, оффсеты, retention.

Установка, запись и чтение создание и конфигурация топиков


  • Первая запись и первое чтение;
  • Базовые основы Apache Zookeeper.

Продвинутая часть


Тема 1: Работа с распределенным кластером


  • Конфигурация кластера,
  • Отказоустойчивость,
  • Контроллер,
  • Репликация между кластерами в разных ЦОД,
  • Примеры архитектуры.

Тема 2: Клиентские библиотеки


  • Producer,
  • Consumer,
  • Как (не) потерять данные: консистентность против доступности.

Тема 3: Мониторинг


  • Ключевые метрики и алерты,
  • SLI & SLO.

Тема 4: Анализ производительности


  • Почему Кафка такая быстрая?
  • Бенчмаркинг.

Тема 5: Поддержка работоспособности кластера и траблшутинг


  • Балансировка нагрузки: partition assignment & repartitioning,
  • Обновление версии кластера и клиентов,
  • Чтение логов,
  • Продвинутый функционал траблшутинга,
  • Примеры сбоев из жизни.

Тема 6: Развертывание кластера в проде


  • Рекомендуемая конфигурация и архитектура,
  • Практики и примеры из жизни.

Авторы курса:


  • Анатолий Солдатов, Lead Engineer в Avito;
  • Александр Миронов, Infrastructure Engineer в Stripe.

Релиз курса в апреле 2021 года. Сейчас собираем обратную связь по программе, готовим материалы и уже записываем бесплатную базовую часть курса. Оставить заявку на весь курс или бесплатные вводные уроки можно здесь.

Подробнее..

Гибриды побеждают или холивары дорого

11.01.2021 02:05:19 | Автор: admin

Мотивом для написания данной статьи послужил тот факт, что на habr.com участилось появление материалов маркетингового характера про Apache Kafka. А также тот факт, что из статей складывается впечатление что пишут их немного далекие от реального использования люди это конечно же только впечателение, но почему-то в большинстве своем статьи обязательно содержат сравнение Apache Kafka с RabbitMQ, причем не в пользу последнего. Что самое интересное читая подобные статьи управленцы без технического бэкграунда начинают тратить деньги на внутренние исследования, чтобы ведущие разработчики и технические директора выбрали одно из решений. Так как я очень жадный/домовитый, а также так как я сторонник тезиса "В споре НЕ рождается истина" предлагаю вам ознакомится с другим подходом почти без сравнения разных брокеров.


Без сравнения никуда


Вообще, по правильному, я должен был сделать статью в формате Kafka+RabbitMQ+Nats+ActiveMQ+Mosquito+etc, но мне кажется, что для Вас дорогие читатели это будет перебор, хотя обычно в моих архитектурных решениях присутствуют все вышеуказанные сервисы (и не только). И это я еще не рассказываю про AzureServiceBus/AmazonServiceBus которые также участвуют в "гибридах" при крупных программах проектов. Поэтому пока остановимся на связке Kafka+RabbitMQ и далее вы поймете почему: по аналогии можно подключить любой сервис с его протоколом. Потому что:


сравнивая Apache Kafka и RabbitMQ вы сравниваете 2 (два) бренда, а точнее 2 коммерческие компании Confluent и vmWare, и немножко Apache Software Foundation (но это не компания)

то есть формально при сравнении мы должны сравнивать бизнес-модели компаний которые являются основными драйверами развития наших сегодняшних подоопытных. Так как Хабр все таки не портал экономических исследований, поэтому мы для начала должны вспомнить не бренды, а те описания которые стоят за этими брендами (то как сами себя называют наши сегодняшние участники).


  • RabbitMQ мультипротокольный и расширяемый брокер сообщений
  • Apache Kafka платформа для распределенной потоковой передачи событий
  • Confluent Platform платформа потоковой передачи событий с возможностью создания высокопроизводительных конвейеров обработки данных для целей аналитики и интеграции в бизнес-сценариях

Я не зря третьим пунктом выделяю наработки компании Confluent те кто собирается использовать Apache Kafka в продуктиве должны хотя бы видеть какую функциональность дополнительно добавляет Confluent к Apache Kafka. А это SchemeRegistry, RestProxy, kSQL и еще несколько интересных штук, о одной из которых мы поговорим ниже, она называется Kafka-Connect.


Но вернемся к сравнению внимательный читатель видит, что RabbitMQ сам себя называет брокером сообщений выделяя свою главную фишку "мультипротокольность", а товарищи из экосистемы Kafka почему-то называют себя аж платформой (завышенное самомнение оно такое).


Итак чтобы было совсем понятно, куда я веду.


  • ключевая особенность RabbitMQ мультипротокольность и расширяемость. (основной язык якобы Erlang)
  • ключевая особенность экосистемы Kafka потоковая передача с обработкой (основной язык якобы Scala/Java)

Отсюда и возникают минусы каждого из решений


  • для RabbitMQ мы не сможем построить нормального решения для потоковой обработки. Точнее сможем, но НЕ штатно.
  • а для Kafka мы не сможем сделать мультипротокольность, точнее сможем но НЕ штатно.

Сократ не говорил, что в споре рождается истина


Еще одна новость: действительно если почитать источник, то Сократ вообще-то в итоге пришел к тому, что нужно обеспечить диалог, а если по научному то истина рождается в научном споре, который формально представляет собой процесс публикация со ссылкой на источники -> а затем научная критика оппонентов -> истина


А значит перейдем к ссылкам для начала их будет три. Когда 14 лет назад я совместно с коллегами начинал использовать брокеры сообщений в качестве основы для построения своих интеграционных решений, мы сразу обратили внимание, что фактически с точки зрения "клиента" (конечного приложения), под разные задачи подходят разные протоколы интеграции.


  • ODBC
  • AMQP
  • MSMQ
  • XMPP
  • IP over Avian Carriers

так как тогда наша задача была интегрировать всякое (python, C#, java) и 1С был придуман проект One-S-Connectors (https://code.google.com/archive/p/one-c-connectors/source/default/source). Сейчас он имеет сугубо академический интерес (так как в 1С мире моя персона достаточно известна и на Хабре много 1С специалистов из сообщества "воинствующих 1С-ников" эта ссылка специально для них).
Однако уже тогда (в 2006 году) стало понятно, что по большому счету конечному разработчику придется менять/выбирать протокол под бизнес-задачу. А инфраструктурщикам придется обеспечить максимально широкий спектр интеграционных протоколов. От ODBC до Kafka/NATs/ModBus.


Но вернемся к дню сегодняшнему когда я начал использовать в проектах уровня ГИС (госсударственные информационные системы) различные транспорта данных внезапно выяснилось, что универсальные адаптеры это не только концепт воинствующих 1С-ников, но и соседей. Поэтому многие идеи при внедрении черпались из еще двух интересных проектов



маленькое примечание для менеджеров про Kombu как то так получилось, что имплементация протокола Apache Kafka до сих пор открыта https://github.com/celery/kombu/issues/301 и почему-то перешла в разряд "Дайте денег", поэтому для Python проектов приходится использовать дополнительно https://github.com/confluentinc/confluent-kafka-python

Когда вы дочитаете до этого момента предполагаю, что вы зададите вопрос про остальные языки: Java, GoLang, RUST, etc. Но во первых я не зря выше указал что по серьезному в наш обсуждаемый сегодня гибрид нужно добавить историю про NATs и ActiveMQ и внезапно JMS поэтому просьба дочитать до конца: Java будет, а во вторых мы переходим к еще трем полезным ссылкам



Прокоментируем их? Дело в том, что как бы вы не хотели, а для полноценного использования "в длинную" вам придется подписаться на историю релизов как сервера RabbitMQ и самое главное на те самые расширения (лежат в каталоге /deps) которые постоянно добавляются в ядро RabbitMQ, так и на портал компании Confluent где она публикует приложения полезные для конечного бизнеса использующего Apache Kafka в продуктиве.


подход к расширяемости за счет активируемых расширений также используется в экосистеме PostgreSQL тот который CREATE EXTENSION hypopg, так что подход реализованный компанией Pivotal/vmWare далеко не новый в нашем чудесном мире архитектуры программного обеспечения

Дополнительно на чудесном рынке облачных услуг в формате "Серьезная штука как сервис" есть еще один игрок это компания 84Codes https://github.com/84codes. Когда в рамках проектов внедрения нет нормальных инженеров по инфраструктуре именно 84Codes спасает пилотные проекты, потому как у них можно легко арендовать бесплатные/сильнодешевые контура CloudAMQP и CloudKarafka.


Я как бы обещал, что не буду ничего говорить про деньги, однако придется отразить 2 ключевых момента:


  • компания vmWare зарабатывает известно на чем, поэтому RabbitMQ ей развивается как часть своей платформы то есть они инвестируют в открытый проект не особо занимаясь его монетизацией. Возврат их инвестиций происходит в других местах, ну и также за счет контрибьторов на GitHub.
  • а вот компания Confuent собирается монетизировать свою платформу через Enterprise лицензию в которую включает те самые коннекторы Enterprise-Kafka-Connect, а также GUI для управления платформой.

Когда-то давно существовал https://github.com/jcustenborder/kafka-connect-rabbitmq, примечателен тот факт что товарищ Джереми его скрыл, оставив только свои наработки для Java разработчиков в виде Maven Archetype https://github.com/jcustenborder/kafka-connect-archtype еще раз обращаю Ваше внимание, что компания Confluent будет и дальше пытаться монетизировать свою деятельность, так что переводить всю интеграцию только на Kafka я бы на вашем месте поостерегся.

Поэтому когда вам топят за Kafka учитывайте, что вы либо изучаете Java, либо платите за Enterprise лицензию. А когда вам топят за RabbitMQ учитывайте, что либо вы изучаете системное администрирование (Erlang накладывает особенности системного администрирования), либо покупаете сервис у провайдеров типа 84Codes. Кодить на Erlang никогда не придется там это не нужно, если только вы не контрибьюторы OpenStack.


Поставил и забыл уже не работает


Приближаемся к дальнейшему пониманию. Данный раздел уже будет полезен инфраструктурщикам, хотя и разработчикам важно знать, что в эпоху когда семимильными шагами развивается имплементация ITILv4, для того чтобы перейти от текста и менеджерских хитростей про риски и деньги к реальности нам придется осознать 3 тезиса


  • использование только одного протокола интеграции приводит к появлению ProtocolLock и как следствие к VendorLock я же не зря выше написал, что за каждым открытым продуктом, стоит какой-то ключевой комплект вендоров как они себя поведут: мы не знаем.
  • в мире ИТ больше нет серьезных продуктов, которые бы представляли собой монолитную службу все приложения давно стали композитными.
  • все нормальные вендоры сокращают свои релизные циклы по ключевым продуктам нормальной практикой стало выпускать редакции раз в 3 месяца TDD, BDD, CICD, ScallableAgile и DevOps (DocOps, DevSecOps) эти инженерные практики и методики управления не просто так развиваются. Всем очень хочется сокращать себестоимость и TimeToMarket.

Абзац выше важен, как финальный аккорд, прежде чем мы перейдем к Docker-Compose. А именно к нему я вел чтобы и разработчики и инфраструктурщики понимали что такое гибридная инфраструктура в режиме мультипротокольности (с) нужно сделать так, чтобы каждый мог поэкспериментировать с предлагаемым контуром. Как я уже указал выше первично подобное применительно к Kafka+RabbitMQ было подсмотрено именно у коллег из 84Codes (хорошие ребята всем советую https://www.84codes.com/).


Чтобы вы смогли поэкспериментировать сами


Итак подходим к примерам, так как обоснования и вводных уже хватит. Предположим вы уже поняли, что вам также нужна мультипротокольность, однако мы же помним, что все рекламные материалы про Apache Kafka нам рассказывают что это единственное решение с реализацией exactly-ones доставки сообщений от отправителя получателю. Собственно на самом деле нам и нужен гибрид, чтобы сделать из связки ТочкаОбмена->Очередь журнал Kafka (это тот который Topic) чтобы возникла сущность под называнием Offsets у нашей очереди событий.


exactly-ones

проверка на внимательность читающего exactly-ones это шутка в формате "Хотя бы один раз из 1С", а имеется в виду концепт Exactly once строго однократная доставка сообщений получателю, без необходимости повторной отправки от отправителя.


Предлагаю попробовать. Концепт для проверки Вашими руками будет состоять из:


  • Zookeper
  • KafkaBroker
  • RabbitMQ
  • KafkaConnect

и трех приложений приложений


  • отправитель на Python по протоколу AMQP 0.9
  • получатель на С# по протоколу AMQP 1.0
  • получатель на C# по протоколу Kafka

Еще интересное замечание: когда вы смотрите на всякие обучающие видео по Apache Kafka авторы часто (но не всегда) старательно пишут примеры на Java, это они делают скорее всего для того, чтобы скрыть от вас особенности использования librdkafka C++ библиотеки на основе которой сделаны многие не-джава адаптеры,. Я же наоборот предлагаю вам начинать исследование интеграции с Kafka именно с неё, чтобы четко оценивать риски "куда вы ввязываетесь": очень примечательно что там работает фактически один разработчик, формально в одиночку https://github.com/edenhill/librdkafka/pulse/monthly, а допустим wmWare старается поддерживать свою линейку клиентов под своим брендом https://github.com/rabbitmq

ну и самое главное и тяжелое:


контур содержит открытый форк старого RabbitMQ-Kafka-Sinc-Connector того самого который товарищи из Confluent в своё время скрыли с Github.


Докер контура для экспериментов


Для показательного эксперимента мы сделаем 2 композитных приложения инфраструктурное-трансформационное и непосредственно бизнес-приложения.


Развертываем RabbitMQ и Kafka


контур инфраструктуры который нам понадобится запускается достаточно просто


docker-compose -f dockers/infra.yml up -d

Если вам интересно что же там внутри, нашего композитного приложения, то в конце статьи дается ссылка на полный комплект исходников, наиболее интересен в нем Kafka-UI и непосредственно RabbitMQ-Sinc, все остальное обычно и штатно для всех известных примеров по Kafka или RabbitMQ


    image: provectuslabs/kafka-ui:latest    ports:      - 8080:8080    depends_on:      - kafka-broker      - zookeeper    environment:      KAFKA_CLUSTERS_0_NAME: local      KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: broker:29092      KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181      KAFKA_CLUSTERS_0_JMXPORT: 9101

Но самое главное кроется в репозитории Java


    <parent>        <groupId>com.github.jcustenborder.kafka.connect</groupId>        <artifactId>kafka-connect-parent</artifactId>        <version>1.0.0</version>    </parent>

Если подробно изучить pom.xml то выяснится, что существует заглавный проект для всех конекторов к Кафка https://github.com/jcustenborder/kafka-connect-parent, в котором используется Java-Kafka-Adapter


И непосредственно синхронизацией c RMQ занимается штатный Java клиент https://www.rabbitmq.com/java-client.html


            <groupId>com.rabbitmq</groupId>            <artifactId>amqp-client</artifactId>            <version>${rabbitmq.version}</version>

Таким образом по правильному, чтобы получились повторить тот же эксперимент что и у меня, необходимо выполнить:


  • собрать из исходников java синхронизатор -1-build-connect-jar.bat
  • собрать контейнер с синхрозатором 00-build-connect-image.sh

и уже потом запустить полный инфраструктурный контур


  • стартуем полный инфраструктурный контур 01-start-infra.sh

обратите внимание так как Docker использует разное поведение при работе с PWD для Windows и Linux приходится делать дубликаты скриптов. В остальных случаях под обоими операционными системами используется интерпретатор sh

В итоге вы получите следующий комплект сервисов



На картинке можно увидеть как подключаются конфигурационные файлы к RabbitMQ и какая топология сетевых портов у нас будет участвовать в эксперименте:


Назначение портов:


  • 9092 будет использоваться для Kafka протокола
  • 8080 используется для отображения красивой картинки состояния Apache Kafka UI
  • 5672 будет использоваться для протокола AMQP 0.9 и он же будет работать и как AMQP 1.0
  • 15672 используется для красивой картинки управления RabbitMQ
  • 28082 отладочный порт для управления через curl трансформатором протоколов

В этот момент нужно остановится и прокомментировать особенность развертывания RabbitMQ в Docker:


  • хорошей практикой является версионирование включенных плагинов расширений enabled-rmq-plugins

[    rabbitmq_management,     rabbitmq_amqp1_0,     rabbitmq_mqtt,     rabbitmq_federation,     rabbitmq_federation_management,    rabbitmq_shovel,    rabbitmq_shovel_management,    rabbitmq_prometheus].

  • а также в крупных проектах когда нужно передать разработчику преднастроенную топологию точек обмена и очередей, можно и нужно добавлять это в виде конфигурационного файла rmq_definitions.json

     "bindings":[        {           "source":"orders-send",           "vhost":"/",           "destination":"orders-amqp-10-consumer",           "destination_type":"queue",           "routing_key":"",           "arguments":{

Запускаем наши приложения


Остается только запустить наши приложения эмулирующие подключения


docker-compose -f dockers/infra.yml restart protocol-connect-syncdocker-compose -f applications.yml builddocker-compose -f applications.yml up

Топология наших тестовых приложений достаточно простая



Исходный код также максимально упрощён:


  • отправляется как-будто бы заказ Васи с периодичностью в 2 секунды

        producer = conn.Producer(serializer='json')        producer.publish({'client': 'Вася', 'count': 10, 'good': 'АйФончик'},                      exchange=order_exchange,                      declare=[kafka_queue, amqp10_queue])        time.sleep(2)

RUN python -m pip install \    kombu \    librabbitmq

причем используется для этого максимально производительная библиотека на Си для AMQP 0.9 librabbitmq наследуется именно от неё https://github.com/alanxz/rabbitmq-c


  • создан подписчик который уже по протоколу AMQP 1.0 смотрит в свою очередь и получает события, соответственно очередь очищается и больше мы заказов Васи не получим. В этом потоке нам это и не нужно.

            Attach recvAttach = new Attach()            {                Source = new Source()                {                    Address = "orders-amqp-10-consumer",                    Durable = 1,                },

            ReceiverLink receiver =                 new ReceiverLink(session,"netcore_amqp_10_consumer", recvAttach, null);            Console.WriteLine("Receiver connected to broker.");            while (true) {                Message message = receiver.Receive();                if (message == null)                {                    Console.WriteLine("Client exiting.");                    break;                }                Console.WriteLine("Received "                   + System.Text.Encoding.UTF8.GetString((byte[])message.Body)

Причем в качестве драйвера выбран


  <ItemGroup>    <PackageReference Include="AMQPNetLite.Core" Version="2.4.1" />  </ItemGroup>

именно его https://github.com/Azure/amqpnetlite Microsoft использует для маркетинга своей реализации сервисной шины. Собственно именно AMQP 1.0 как протокол они и рекламируют https://docs.microsoft.com/ru-ru/azure/service-bus-messaging/service-bus-amqp-overview


Ну и финально


  • создан подписчик по протоколу Kafka который при каждом старте перечитывает с нуля журнал отправленных заказов Васи. Тот самый Exactly once.

                AutoOffsetReset = AutoOffsetReset.Earliest

                c.Subscribe("orders-from-amqp");

                    while (true)                    {                        try                        {                            var cr = c.Consume(cts.Token);

Выглядит наш контур в итоге следующим образом:


  • 5 инфраструктурных контейнеров


  • 3 контейнера с приложениями


  • готовый журнал транзакций заказов который можно посмотреть через Kafka-Ui


  • и готовый контур связей для RabbitMQ


А где же Java ?


Не волнуйтесь при таком гибридном подходе, без неё никуда, для того чтобы всё вышеуказанное заработало пришлось сделать форк и актуализировать версии Kafka-Connect-Base


[submodule "dockers/rabbitmq-kafka-sink"]    path = dockers/rabbitmq-kafka-sink    url = https://github.com/aliczin/kafka-connect-rabbitmq

Но самое интересное не это, самое интересное что в этом самом Kafka-Connect нет по сути никакой магии только код трансформации.


По сути нам предлагают:


  • создать наследника абстрактной задачи Источника

public class RabbitMQSourceTask extends SourceTask {

  • выполнить подписку на очередь сообщений

        this.channel.basicConsume(queue, this.consumer);        log.info("Setting channel.basicQos({}, {});", this.config.prefetchCount, this.config.prefetchGlobal);        this.channel.basicQos(this.config.prefetchCount, this.config.prefetchGlobal);

  • трасформировать полученные сообщения в абстрактные записи причем с буфером.

  @Override  public List<SourceRecord> poll() throws InterruptedException {    List<SourceRecord> batch = new ArrayList<>(4096);    while (!this.records.drain(batch)) {

Отдельно можно выделить чудесный трансформатор сообщений из AMQP 0.9 в Кафка. У несведующего в Java глаз может задергаться. У автора чувствуется многолетный опыт работы в J2EE.


  private static final Logger log = LoggerFactory.getLogger(MessageConverter.class);  static final String FIELD_ENVELOPE_DELIVERYTAG = "deliveryTag";  static final String FIELD_ENVELOPE_ISREDELIVER = "isRedeliver";  static final String FIELD_ENVELOPE_EXCHANGE = "exchange";  static final String FIELD_ENVELOPE_ROUTINGKEY = "routingKey";  static final Schema SCHEMA_ENVELOPE = SchemaBuilder.struct()      .name("com.github.jcustenborder.kafka.connect.rabbitmq.Envelope")      .doc("Encapsulates a group of parameters used for AMQP's Basic methods. See " +          "`Envelope <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html>`_")      .field(FIELD_ENVELOPE_DELIVERYTAG, SchemaBuilder.int64().doc("The delivery tag included in this parameter envelope. See `Envelope.getDeliveryTag() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#getDeliveryTag-->`_").build())      .field(FIELD_ENVELOPE_ISREDELIVER, SchemaBuilder.bool().doc("The redelivery flag included in this parameter envelope. See `Envelope.isRedeliver() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#isRedeliver-->`_").build())      .field(FIELD_ENVELOPE_EXCHANGE, SchemaBuilder.string().optional().doc("The name of the exchange included in this parameter envelope. See `Envelope.getExchange() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#getExchange-->`_"))      .field(FIELD_ENVELOPE_ROUTINGKEY, SchemaBuilder.string().optional().doc("The routing key included in this parameter envelope. See `Envelope.getRoutingKey() <https://www.rabbitmq.com/releases/rabbitmq-java-client/current-javadoc/com/rabbitmq/client/Envelope.html#getRoutingKey-->`_").build())      .build();

Но Не будем критиковать, мы же в самом начале договорились что наша главная задача добиться конечного результата удобным на сегодня способом. А итоги у нас получаются следующие.


Итоги


Все что здесь продемонстрировано естественно лежит на Github.


В репозитории https://github.com/aliczin/hybrid-eventing. Лицензия выставленна простая до невозможности Creative Commons Attribution 4.0 International.


Полезно использовать в обучающих целях для команд разработки и инфраструктуры и поиграться с DevOps и поиграться с мультипротокольными приложениями. Ничего особо экстравагантного в данном концепте конечно нет, ключевое тут я как я написал в самом начале мы делаем избыточное количество интеграционных протоколов, добавляя транформаторов между потоками интеграции.


Схема коммуникации в итоге для "разработчика интеграционных потоков" (с) выглядит следующим образом для источника и брокеров


orderEventsApp->Amqp09: send orderAmqp09->Amqp10: fanout\n copy eventAmqp09->KafkaQ: fanout\n copy eventKafkaQ->KafkaConnect: consume\n on messageKafkaConnect->KafkaConnect: transform\n messageKafkaConnect->Kafka: publish to topic


а для приемников все упрощается


Amqp10->orderEventSubApp: subcribe\n for eventorderJournalApp->Kafka: read kafka journal


Приемники берут нужные им данные только по нужному им протоколу

Ключевые посылы


Ключевые моменты которые я хотел расскрыть данной статьей


  • стройте эксперименты и продуктивы с Apache Kafka не со штатным Java клиентом, а librdkafka и базирующихся на ней адаптерах это позволит вам отладить сценарии разных версий протоколов KafkaAPI. Java вам пригодится в другом месте.


  • не ввязывайтесь с священные войны, что лучше RabbitMQ/Kafka/Nats/ActiveMQ просто развертывайте сервисы и публикуйте протоколы и пробуйте свои бизнес-сценарии.


  • начните уже внедрять продуктивный Docker, или хотя бы пилотные и разработческие контура.


  • реальный ИТ ландшафт почти всегда будет мультипротокольным



Примечание для понимающих


чтобы гибриды развивались дальше:


  • Mosquito очень удобен как встраиваемый брокер на уровне контролера SCADA для преобразования из ModBus/OPC-UA. Хотя как вы уже поняли из статьи интересны реализации "мостов из протокола в протокол" пример https://github.com/mainflux/mainflux


  • ActiveMQ удобен для Java разработчиков, потому что у них есть боязнь Erlang, но как мы выше уже сказали мост RabbitMQ AMQP 1.0 -> ActiveMQ легко организуется средствами RabbitMQ, кстати также как и JMS.


  • NATs интересен как часть OpenFaaS платформы, при внедрении "своего маленького" Amazon Lambda с преферансом. И опять же подход будет всё тот же мультипротокольные мосты с трансформацией: https://github.com/nats-io/nats-kafka если Вам не страшно посмотрите эксперименты с OpenFaaS веселых 1С-ников 2.5 часа примеров https://youtu.be/8sF-oGGVa9M



Надеюсь мой архитектурный подход Вам придется по душе и вы перестанете тратить деньги заказчика (инвестора/свои если вы стартапщик: Маша это замечание специально для тебя) на бессмысленные обсуждения что же выбрать в качестве брокера/платформы, и начнете наконец-то делать функциональность, которая будет использовать тот протокол, который удобен прямо сейчас. С возможностью переключения в случае "если чё"


Функциональность: Мультипротокольный адаптер    Как разработчик я хочу иметь абстракцию Produser/Consumer    С возможность изменения протокола интеграции    Чтобы под каждую задачу выбирать разные протоколы     и единый интерфейс вызова для обеспечения независимости от вендора предоставляющего транспортСценарий: vmWare реализует протокол Stream средствами RabbitMQ     Когда vmWare закончит свой плагин для потоков    Тогда я активирую новый протокол     И быстро воткну его в приложение    И так как у меня есть продуктивный кластер RabbitMQ    И мне нужно будет просто поменять канал для отдельных бизнес сценариевСценарий: Завтра придут 1С-ники со своим ActiveMQ из Шины для 1С    Когда мне нужно быстро включить очереди 1С в общий контур    И чтобы на Питоне использовать старые наработки с Kafka API    Тогда я добавляю трансформацию ActivemeMQ2Kafka    и живу по старому а события ходят уже и из 1Сetc

А чтобы вы не думали, что данный подход это нечто уникальное вот Вам еще интересная ссылка: https://github.com/fclairamb/ftpserver/pull/34 это когда нужен FTP сервер, а хочется S3.


Ну и в качестве финального момента обратите внимание: есть и риски данного подхода: но они я думаю Вам и так понятны.


  • Придется оркестрировать такой комплект сервисов и вручную это почти невозможно. Придется использовать DevOps штуки типа k8s, OpenShift, etc но если вы уже решились на интеграцию в режимах слабой связаности приложений в режиме онлайн, у вас что-то на эту тему уже скорее всего есть.
  • Трансформаторы между протоколами приходится дорабатывать ничего готового открытого и PRODUCTION-READY на данный момент найти почти невозможно.

Финальное примечение для любителей писать ТЗ по ГОСТу


так как Хабр читают любители цифровой трансформации (чтобы кто не понимал под этим словом) советую в техническое задание добавлять не упоминание конкретных реализации серверов, а что-то примерно следующее:


комплект программ для интеграции должен реализовывать коммуникацию конечных приложений по открытым протоколам HTTP, AMQP 0.9, AMQP 1.0, Apache Kafka не ниже версии 23, MQTT, WebSockets, <ЛюбойДругойХотьSOAPХотяЭтоЖуть> с возможность преобразования между протоколами дополнительными средствами администрирования

Надеюсь моя публикация после долгого перерыва Вам будет полезна в ваших интеграционных проектах. Предполагаю что будет вопрос про 1С и тут у меня совет только один. Используйте Google по ключевым словам 1С+RabbitMQ или 1С+Kafka или 1С+OpenFaas и RabbitMQ и Kafka "в 1С" давно и непринужденно используются. Потому что 1С это не только язык, но и несколько сообществ где уже давно сделаны все возможные адаптеры и платные и бесплатные. Собственно как и в Java/C#/Python/C++/Rust/etc.


Данная статья написана с применением расширения https://shd101wyy.github.io/markdown-preview-enhanced для Visual Studio Code за что автору летят дополнительные лучи добра.


Ну и в качестве финального момента хотел бы заметить, что выбор Cunfluent Inc в качестве платформы разработки Kafka-Connect экосистемы JDK выглядит все таки странно. Не удивлюсь если их конкуренты сделают такое же, но на GoLang, NodeJS (что-нибудь типа Kafka-Beats-Hub)



Красивые картинки в формате GraphViz я делаю при помощи хитрого проекта Docker2GraphViz помогает поддерживать актуальный контур и техническую документацию в формате Markdown


set CURPATH=%~dp0set DOCKER_DIR=%CURPATH%\dockersdocker run --rm -it --name dcv -v %DOCKER_DIR%\:/input pmsipilot/docker-compose-viz render -m image --force --output-file=infra-topology.png infra.ymldocker run --rm -it --name dcv -v %CURPATH%\:/input pmsipilot/docker-compose-viz render -m image --force --output-file=apps-topology.png applications.ymlcopy /b/v/y dockers\infra-topology.png content\assets\infra-topology.pngcopy /b/v/y apps-topology.png content\assets\apps-topology.png
Подробнее..

Профилирование в облаке и не только

05.01.2021 12:12:21 | Автор: admin

Приложение, оптимально использующее вычислительные ресурсы, это всегда хорошо и приятно. А если такое приложение работает в облаке, то ещё и выгодно. Порой очень выгодно. Просто потому, что в один квант оплаченного облачного вычислительного ресурса влезает, например, больше показанных в браузере котиков вместе с рекламой или платёжных транзакций за подписки на тех же котиков. И если с профилированием Go приложений всё более или менее понятно, то для приложений, написанных на C или C++, всё гораздо интереснее.


Так как большинство проблем с производительностью материализуются, как правило, в продакшене, то нас будут интересовать те инструменты, которые не требуют инструментализации кода и, следовательно, остановки и перезапуска рабочих процессов. Кроме того, я не буду упоминать профилировщики, которые анализируют работу кода на уровне микроархитектуры процессора типа vTune. Во-первых, на эту тему статей и так хватает. Во-вторых, я ошибочно полагаю, что вопросы микроархитектуры больше актуальны для разработчиков middleware типа серверов баз данных или библиотек, которые настолько круты, что Хабр не читают. Итак.


До недавнего времени одним из таких инструментов были Google perftools. Они включают два профилировщика: для CPU и для оперативной памяти. Начнём с первого.


Чтобы ваша программа стала профилируемой, её необходимо слинковать вместе с профилировщиком. Можно статически, можно динамически или же вообще подгрузить при помощи LD_PRELOAD. Его размер составляет около 60k, и он более никак не нагружает систему, когда не используется. Так что всегда линковаться с ним на боевых серверах будет не слишком накладно. Активируется он установкой переменной окружения CPUPROFILE:


$ CPUPROFILE=/tmp/envoy.prof envoy --concurrency 1 -c /path/to/config.yaml

В этом случае статистика начинает собираться с момента запуска исполняемого файла. Если дополнительно установить CPUPROFILESIGNAL=12, то активация будет включаться и выключаться по пользовательскому сигналу 12 (SIGUSR2).


Принцип работы CPU-профилировщика заключается в том, что он периодически (период тоже можно изменить) делает снимок текущих стеков процесса и сохраняет их файл, который можно проанализировать утилитой pprof. Например, чтобы быстро оценить, какие функции больше других или, точнее сказать, чаще других нагружают процессор, достаточно ввести


$ pprof -text /tmp/envoy.profFile: envoy-staticType: cpuShowing nodes accounting for 17.74s, 86.75% of 20.45s totalDropped 900 nodes (cum <= 0.10s)      flat  flat%   sum%        cum   cum%     5.19s 25.38% 25.38%     17.02s 83.23%  deflate_fast     4.04s 19.76% 45.13%      4.04s 19.76%  longest_match     3.92s 19.17% 64.30%      3.92s 19.17%  compress_block     3.04s 14.87% 79.17%      3.04s 14.87%  slide_hash     0.62s  3.03% 82.20%      0.63s  3.08%  crc32_z     0.18s  0.88% 83.08%      0.18s  0.88%  writev     0.12s  0.59% 83.67%      0.12s  0.59%  readv     0.11s  0.54% 84.21%      0.11s  0.54%  close     0.08s  0.39% 84.60%      0.17s  0.83%  std::__uniq_ptr_impl::_M_ptr     0.06s  0.29% 84.89%      0.16s  0.78%  std::get     0.06s  0.29% 85.18%      0.18s  0.88%  std::unique_ptr::get     0.04s   0.2% 85.38%     18.54s 90.66%  http_parser_execute     0.03s  0.15% 85.53%      0.33s  1.61%  Envoy::Router::Filter::decodeHeaders     0.03s  0.15% 85.67%      0.26s  1.27%  absl::container_internal::raw_hash_set::find     0.02s 0.098% 85.77%     17.16s 83.91%  Envoy::Http::Http1::ClientConnectionImpl::onBody     0.01s 0.049% 85.82%      0.12s  0.59%  Envoy::Event::DispatcherImpl::clearDeferredDeleteList     0.01s 0.049% 85.87%     20.03s 97.95%  Envoy::Event::FileEventImpl::mergeInjectedEventsAndRunCb     0.01s 0.049% 85.92%     17.06s 83.42%  Envoy::Extensions::Compression::Gzip::Compressor::ZlibCompressorImpl::process     0.01s 0.049% 85.97%      0.13s  0.64%  Envoy::Http::ConnectionManagerImpl::ActiveStream::encodeData     0.01s 0.049% 86.01%      0.11s  0.54%  Envoy::Http::HeaderString::HeaderString     0.01s 0.049% 86.06%      0.32s  1.56%  Envoy::Http::Http1::ClientConnectionImpl::onHeadersComplete     0.01s 0.049% 86.11%     17.18s 84.01%  Envoy::Http::Http1::ConnectionImpl::dispatchBufferedBody     0.01s 0.049% 86.16%     19.14s 93.59%  Envoy::Network::ConnectionImpl::onReadReady     0.01s 0.049% 86.21%      0.24s  1.17%  Envoy::Network::ConnectionImpl::onWriteReady     0.01s 0.049% 86.26%      0.20s  0.98%  Envoy::Network::IoSocketHandleImpl::read     0.01s 0.049% 86.31%      0.21s  1.03%  Envoy::Network::RawBufferSocket::doRead...

Для более детального анализа можно воспользоваться довольно удобным web-интерфейсом, который предоставляет тот же pprof:


$ pprof -http=localhost:8080 /tmp/envoy.prof

Теперь по адресу http://localhost:8080 можно взглянуть на красивый граф вызовов функции (callgraph)


callgraph


и на flamegraph


flamegraph


С анализом же расхода памяти всё несколько сложнее, покуда профилировщик кучи является органической частью tcmalloc (thread caching malloc) альтернативного менеджера памяти от gperftools минимизирующего количество системных вызовов за счет предварительного выделения памяти сверх необходимого. То есть, чтобы иметь возможность профилировать память на боевой системе, необходимо отказаться от стандартных реализаций malloc() и new и всегда линковаться с tcmalloc. И хоть tcmalloc считается быстрее и эффективнее особенно в многопоточных программах, не все готовы его использовать хотя бы из-за немного большего расхода памяти. Тем не менее профилировать память с ним так же просто как и CPU:


$ HEAPPROFILE=/tmp/envoyhprof envoy --concurrency 1 -c /path/to/config.yamlDumping heap profile to /tmp/envoyhprof.0001.heap (1024 MB allocated cumulatively, 6 MB currently in use)Dumping heap profile to /tmp/envoyhprof.0002.heap (2048 MB allocated cumulatively, 6 MB currently in use)Dumping heap profile to /tmp/envoyhprof.0003.heap (3072 MB allocated cumulatively, 6 MB currently in use)Dumping heap profile to /tmp/envoyhprof.0004.heap (4096 MB allocated cumulatively, 6 MB currently in use)Dumping heap profile to /tmp/envoyhprof.0005.heap (5120 MB allocated cumulatively, 6 MB currently in use)^CDumping heap profile to /tmp/envoyhprof.0006.heap (Exiting, 5 MB in use)

Теперь, чтобы узнать какие функции больше всего выделяли память на куче, надо снова воспользоваться pprof:


$ pprof -text -sample_index=alloc_space /tmp/envoyhprof.0005.heapFile: envoy-staticType: alloc_spaceShowing nodes accounting for 1558.99MB, 98.71% of 1579.32MB totalDropped 1117 nodes (cum <= 7.90MB)      flat  flat%   sum%        cum   cum% 1043.23MB 66.06% 66.06%  1043.23MB 66.06%  zcalloc  240.73MB 15.24% 81.30%   240.73MB 15.24%  Envoy::Zlib::Base::Base  150.71MB  9.54% 90.84%   406.68MB 25.75%  std::make_unique   79.15MB  5.01% 95.85%    79.15MB  5.01%  std::allocator_traits::allocate   18.68MB  1.18% 97.04%    26.62MB  1.69%  Envoy::Http::ConnectionManagerImpl::newStream   15.18MB  0.96% 98.00%    15.18MB  0.96%  Envoy::InlineStorage::operator new    8.39MB  0.53% 98.53%     8.39MB  0.53%  std::__cxx11::basic_string::basic_string    1.98MB  0.13% 98.65%    31.99MB  2.03%  Envoy::Http::Http1::allocateConnPool(Envoy::Event::Dispatcher&, Envoy::Random::RandomGenerator&, std::shared_ptr, Envoy::Upstream::ResourcePriority, std::shared_ptr const&, std::shared_ptr const&, Envoy::Upstream::ClusterConnectivityState&)::$_1::operator()    0.93MB 0.059% 98.71%    38.29MB  2.42%  Envoy::Server::ConnectionHandlerImpl::ActiveTcpListener::newConnection         0     0% 98.71%    59.54MB  3.77%  Envoy::Buffer::WatermarkBufferFactory::create         0     0% 98.71%    73.55MB  4.66%  Envoy::ConnectionPool::ConnPoolImplBase::newStream...

Как следует из таблицы, больше всего памяти выделяла zcalloc() из zlib, что совсем не удивительно, поскольку во время профилирования приложение сжимало прокачиваемый через себя трафик.


Впрочем, это не самая интересная метрика. Обычно для нас важнее функции, которые используют больше всего памяти в настоящий момент:


$ pprof -text -sample_index=inuse_space /tmp/envoyhprof.0005.heapFile: envoy-staticType: inuse_spaceShowing nodes accounting for 6199.96kB, 96.84% of 6402.51kB totalDropped 1016 nodes (cum <= 32.01kB)      flat  flat%   sum%        cum   cum% 2237.76kB 34.95% 34.95%  3232.51kB 50.49%  std::make_unique 2041.45kB 31.89% 66.84%  2041.45kB 31.89%  std::allocator_traits::allocate  375.03kB  5.86% 72.69%   753.26kB 11.77%  google::protobuf::DescriptorPool::Tables::AllocateString[abi:cxx11]  267.78kB  4.18% 76.88%   267.78kB  4.18%  std::__cxx11::basic_string::_M_mutate  201.61kB  3.15% 80.03%   201.61kB  3.15%  zalloc_with_calloc  160.43kB  2.51% 82.53%   160.43kB  2.51%  google::protobuf::Arena::CreateMessageInternal (inline)  146.74kB  2.29% 84.82%   146.74kB  2.29%  std::__cxx11::basic_string::_M_construct  141.38kB  2.21% 87.03%   157.38kB  2.46%  google::protobuf::DescriptorPool::Tables::AllocateMessage  139.88kB  2.18% 89.22%   139.88kB  2.18%  google::protobuf::DescriptorPool::Tables::AllocateEmptyString[abi:cxx11]   88.04kB  1.38% 90.59%   113.12kB  1.77%  google::protobuf::DescriptorPool::Tables::AllocateFileTables   72.52kB  1.13% 91.72%    72.90kB  1.14%  ares_init_options      71kB  1.11% 92.83%       71kB  1.11%  _GLOBAL__sub_I_eh_alloc.cc   69.81kB  1.09% 93.92%    69.81kB  1.09%  zcalloc   51.16kB   0.8% 94.72%    51.16kB   0.8%  google::protobuf::Arena::CreateArray (inline)   46.81kB  0.73% 95.45%    60.41kB  0.94%  google::protobuf::Arena::CreateInternal (inline)   37.23kB  0.58% 96.03%    37.23kB  0.58%  re2::PODArray::PODArray   33.53kB  0.52% 96.56%    33.53kB  0.52%  std::__cxx11::basic_string::_M_assign   11.50kB  0.18% 96.74%  2213.77kB 34.58%  Envoy::ConstSingleton::get    6.06kB 0.095% 96.83%    38.85kB  0.61%  google::protobuf::internal::ArenaStringPtr::Set    0.24kB 0.0038% 96.84%  2590.71kB 40.46%  Envoy::Server::InstanceImpl::InstanceImpl         0     0% 96.84%    79.56kB  1.24%  Envoy::(anonymous namespace)::jsonConvertInternal         0     0% 96.84%    84.48kB  1.32%  Envoy::(anonymous namespace)::tryWithApiBoosting         0     0% 96.84%   726.42kB 11.35%  Envoy::Config::ApiTypeOracle::getEarlierVersionDescriptor         0     0% 96.84%    77.22kB  1.21%  Envoy::Config::Utility::createTagProducer         0     0% 96.84%   706.72kB 11.04%  Envoy::Config::Utility::getAndCheckFactory         0     0% 96.84%   707.38kB 11.05%  Envoy::Config::Utility::getFactoryByType...

Ожидаемо, большую часть памяти держит std::make_unique(). И вообще внутри кода Envoy оптимизировать особо нечего главные потребители находятся в сторонних библиотеках. Однако если взглянуть на callgraph или flamegraph, то можно узнать, например, на какие функции обратить внимание, чтобы пореже звать std::make_unique(). Web-интерфейс в этом случае наиболее удобен


$ pprof -http=localhost:8080 /tmp/envoyhprof.0005.heap

В браузере вы увидите что-то вроде


Heap callgraph


и


Heap flamegraph


Можно также проверить, не течет ли память. Вычтем из результата самую первую часть дампа, где происходит инициализация программы:


$ pprof -text -sample_index=inuse_space --base=/tmp/envoyhprof.0001.heap /tmp/envoyhprof.0005.heapFile: envoy-staticType: inuse_spaceShowing nodes accounting for 1460B, 10.38% of 14060B total      flat  flat%   sum%        cum   cum%     1460B 10.38% 10.38%      1460B 10.38%  hist_accumulate         0     0% 10.38%      1460B 10.38%  Envoy::Event::DispatcherImpl::DispatcherImpl(std::__cxx11::basic_string const&, Envoy::Api::Api&, Envoy::Event::TimeSystem&, std::shared_ptr const&)::$_1::operator()         0     0% 10.38%      1460B 10.38%  Envoy::Event::DispatcherImpl::run         0     0% 10.38%      1460B 10.38%  Envoy::Event::DispatcherImpl::runPostCallbacks         0     0% 10.38%      1460B 10.38%  Envoy::Event::LibeventScheduler::run         0     0% 10.38%      1460B 10.38%  Envoy::Event::SchedulableCallbackImpl::SchedulableCallbackImpl(Envoy::CSmartPtr&, std::function)::$_0::__invoke         0     0% 10.38%      1460B 10.38%  Envoy::Event::SchedulableCallbackImpl::SchedulableCallbackImpl(Envoy::CSmartPtr&, std::function)::$_0::operator()         0     0% 10.38%      1460B 10.38%  Envoy::MainCommon::main         0     0% 10.38%      1460B 10.38%  Envoy::MainCommon::run         0     0% 10.38%      1460B 10.38%  Envoy::MainCommonBase::run         0     0% 10.38%      1460B 10.38%  Envoy::Server::InstanceImpl::run         0     0% 10.38%      1460B 10.38%  Envoy::Stats::ParentHistogramImpl::merge         0     0% 10.38%       730B  5.19%  Envoy::Stats::ThreadLocalHistogramImpl::merge         0     0% 10.38%      1460B 10.38%  Envoy::Stats::ThreadLocalStoreImpl::mergeHistograms(std::function)::$_2::operator()         0     0% 10.38%      1460B 10.38%  Envoy::Stats::ThreadLocalStoreImpl::mergeInternal         0     0% 10.38%      1460B 10.38%  __libc_start_main         0     0% 10.38%      1460B 10.38%  event_base_loop         0     0% 10.38%      1460B 10.38%  event_process_active         0     0% 10.38%      1460B 10.38%  event_process_active_single_queue         0     0% 10.38%      1460B 10.38%  main         0     0% 10.38%      1460B 10.38%  std::_Function_handler::_M_invoke         0     0% 10.38%      1460B 10.38%  std::function::operator()

Всё выглядит неплохо, память весьма умеренно тратится только на хранение накопленной статистики.


По закону жанра на этом месте должны начаться проблемы. Заключаются они в том, что не так давно Google выложил в своём аккаунте на GitHub новый, улучшенный tcmalloc. В котором, кэши памяти создаются не для потоков, как в оригинальной версии, а для ядер процессора. В задачах, создающих большое количество потоков, это может заметно улучшить производительность и снизить расход памяти.


Плохая же новость в том, что в новом tcmalloc отсутствует профилировщик. Однако же он теперь не особенно-то и нужен. В частности для профилирования CPU теперь вполне достаточно утилиты perf. Чтобы ею воспользоваться даже не нужно ни с чем линковаться. Всё нужное уже есть в ядре Linux:


$ perf record -g -F 99 -p `pgrep envoy`^C[ perf record: Woken up 1 times to write data ][ perf record: Captured and wrote 0.694 MB perf.data (1532 samples) ]

Результат сохраняется в файл perf.data, формат которого понятен pprof (начиная с некоторой версии):


$ pprof -http=localhost:8080 perf.data

То есть результат совершенно идентичен варианту с использованием gperftools.


Однако имеются подозрения, что perf.data содержит даже больше полезных данных, чем pprof позволяет отобразить. В частности я не смог найти, как сделать разбивку стеков по потокам. Если кто-то знает, напишите, пожалуйста, в комментариях. Такая разбивка может быть полезна для обнаружения узких мест, когда несколько потоков делегируют какую-то работу одному единственному потоку и перегружают его. Пока что для меня незаменимым инструментом для таких случаев служит набор скриптов Брендана Грегга: https://github.com/brendangregg/FlameGraph. Если их применить к имеющемуся файлу perf.data, то получим flamegraph, в котором можно различить три потока один главный и два рабочих.


$ perf script --input=perf.data | ./FlameGraph/stackcollapse-perf.pl > out.perf-folded$ ./FlameGraph/flamegraph.pl out.perf-folded > perf.svg


Впрочем, глядя на такой flamegraph, далеко не всегда мы можем догадаться, что имеем дело с блокировками на перегруженном потоке. Полезнее может оказаться картинка с неработающими потоками, то есть ожидающими снятия какой-нибудь блокировки. Опять же, всё нужное для этого есть в ядре Linux речь идёт о eBPF. Инструментализация кода не требуется. Только убедитесь, что в вашей системе установлен пакет с инструментами для BPF Compiler Collection. В Fedora он называется bcc-tools.


$ /usr/share/bcc/tools/offcputime -df -p `pgrep envoy` 30 > out.stacks$ ./FlameGraph/flamegraph.pl --color=io --title='Off-CPU Time Flame Graph' < out.stacks > off-cpu.svg

Для примера приведу два графика для Envoy под большой нагрузкой запущенного с четырьмя и девятью рабочими потоками.


Это 4 потока
4 workers


Это 9 потоков
9 workers


Можно заметить, что при большем количестве рабочих потоков оные всё большую часть времени ничегонеделания бесполезно ждут в цикле событий вместо того, чтобы с пользой ждать, например, возврата из других системных вызовов. При том, что запросы к серверу остаются необслуженными. Это как раз результат эксперимента по передаче части работы от рабочих потоков одному специализированному потоку плохая идея в общем случае.


А вот с профилированием памяти не всё так радужно, хотя и тут можно кое-чего добиться полагаясь только на eBPF и на утилиту stackcount из bcc-tools. Для того, чтобы получить flamegraph в стиле pprof, надо сделать следующее


$ sudo /usr/share/bcc/tools/stackcount -p `pgrep envoy`  -U "/full/path/to/envoy:_Znwm" > out.stack$ ./FlameGraph/stackcollapse.pl < out.stacks | ./FlameGraph/flamegraph.pl --color=mem --title="operator new(std::size_t) Flame Graph" --countname="calls" > out.svg

stackcount группирует вызовы к некоторой функции по идентичным стекам вызовов к ней (stack traces) и суммирует их. Обратите внимание на странно выглядящий параметр -U "/full/path/to/envoy:_Znwm". Он указывает на некоторую функцию в пространстве пользователя (в противоположность параметру -K, который может указать на функцию в пространстве ядра). В общем случае он задаётся как -U lib:func, где lib это имя библиотеки, а func имя функции в том виде, как оно выглядит выводе objdump -tT /path/to/lib. В нашем случае _Znwm это не что иное как void* operator new(std::size_t). А путь к исполняемому файлу вместо имени библиотеки указан потому, что есть нюанс Envoy статически слинкован с tcmalloc. К сожалению, из документации вы не узнаете о таких мелочах.


Чтобы понять как C++ компилятор видоизменит (mangle) нужную вам функцию, воспользуйтесь вот таким однострочником


$ echo -e "#include <new>\n void* operator new(std::size_t) {} " | g++ -x c++ -S - -o- 2> /dev/null        .file   ""        .text        .globl  _Znwm        .type   _Znwm, @function_Znwm:.LFB73:        .cfi_startproc        pushq   %rbp        .cfi_def_cfa_offset 16        .cfi_offset 6, -16        movq    %rsp, %rbp        .cfi_def_cfa_register 6        movq    %rdi, -8(%rbp)        nop        popq    %rbp        .cfi_def_cfa 7, 8        ret        .cfi_endproc.LFE73:        .size   _Znwm, .-_Znwm        .ident  "GCC: (GNU) 10.2.1 20201016 (Red Hat 10.2.1-6)"        .section        .note.GNU-stack,"",@progbits

Обратите внимание, что имя функции будет разным на 32 и 64 разрядных платформах из-за разного размера size_t. Кроме того void* operator new[](std::size_t) это отдельная функция. Как, впрочем, и malloc() с calloc(), которые будучи C-функциями свои имена не меняют.


Снова должен с сожалением констатировать, что stackcount покажет не сколько именно памяти выделялось в той или иной функции, а как часто. Но зато этот метод анализа применим даже в тех программах, которые не полагаются на готовые менеджеры памяти, а реализуют для работы с кучей свой, родной и ни на что не похожий, бесконечно оптимизированный велосипед (конечно же, с отсутствующими при этом средствами анализа расхода памяти).


Чтобы ответить на вопрос сколько, нужен инструмент, который будет суммировать аргументы вызовов malloc() или new, а лучше все сразу. То есть что-то вроде этого. В идеале хотелось бы, чтобы во внимание принимались ещё free() и delete, но это уже из области магии. Если кто-то уже реализовал такое на eBPF, отпишитесь, пожалуйста, в комментариях.


В качестве заключения хочу порекомендовать всем, кого интересует тема производительности и оптимизации внимательно изучить блог Брендана Грегга. Там бездна полезной информации.

Подробнее..

80-ядерные ARM-процессоры Ampere Altra протестировали производительность на уровне AMD EPYC 7742

27.12.2020 16:14:22 | Автор: admin

Летом этого года компания Ampere представила 128-ядерный ARM-процессор Altra Max. А весной она же анонсировала первый в отрасли 80-ядерный процессор Ampere Altra. Тогда сообщалось, что он предназначен для работы в серверном оборудовании, а не в потребительских устройствах.

На днях стало известно о том, что компания разослала разным обозревателям двухсокетные платформы Mount Jade. Сторонники ARM-архитектуры могут быть довольны результаты тестирования положительные. В ряде тестов чипы не отстают от аналогов x86-64, а в чем-то их и превосходят.

Что собой представляет Ampere Altra



Чипы из этого модельного ряда имеют до 80 ядер с архитектурой ARM v8.2+ (с некоторыми улучшениями из наборов v8.3 и 8.4), связанных между собой mesh-шиной Arm CoreLink CMN-600. Кроме того, есть и развитая система кешей. Это 64+64 Кбайт L2, 1 Мбайт L2 и до 32 Мбайт общего L3. У подсистемы памяти 8 каналов DDR4-3200 (72-бит, 2DPC, до 4 Тбайт суммарно).

Поскольку чип позиционируется, как адаптированный к серверным приложениям, включая аналитику больших данных, нейросейти, базы данных, пограничные вычисления и т.п., то в нем на аппаратном уровне реализована как поддержка форматов данных FP16 (числа половинной точности), так и INT8 (однобайтное представление целого числа). Плюс ко всему, разработчики предусмотрели аппаратное ускорение хэширования AES и SHA-256.

Периферия подключается через PCIe 4.0 на 128 линий. В двухсокетном варианте с каждой стороны отводится по 32 линии на связь с CPU. В результате получается 192 линии, с использованием CCIX. По словам специалистов, сейчас Ampere повторяет путь AMD цена чипов зависит лишь от количества ядер и частоты их работы. А вот функциональность младших и старших моделей одинакова.


Особенность Altra отсутствие многопоточности, что сам производитель называет преимуществом. Дело в том, что отказ от SMT дал возможность снизить уровень энергопотребления это показатель, который очень важен для рынка высокоплотных серверных систем. Еще одна названная причина повышенная безопасность.


Еще одна особенность процессора он всегда работает на максимальной частоте, снижая ее лишь в некоторых случаях. В то же время теплопакет поддерживается на как можно более высоком уровне.

И теперь о тестах


Тестовые образцы процессоров, которые попали к обозревателям две старших модели 80-ядерных процессоров Altra Q80-33, которые работают на частоте 3,3 ГГц. К слову, двухпроцессорный вариант создан в партнерстве с разработчиком и поставщиком OCR-платформ, компанией Wiwynn.


Установка процессора производится при помощи откидной рамки, которая закрепляется пятью винтами. Размеры процессора удивляют 77 66,8 мм. Что касается радиаторов, то площадь контакта у них небольшая, около 25% от общей площади крышки теплораспределителя процессора. Сам кристалл монолитный, производится он по 7-нм технологии. Радиаторы снабжены специальным механизмом отвода тепла испарительной камерой, благодаря которой TDP 250 Вт не составляет проблем.


Что касается аналогов в мире x86-64, ими являются AMD EPYC 7742 (64 ядра, SMT2, 225 Ватт, $6950) и Intel Xeon Platinum 8280 (28 ядер, SMT2, 205 Ватт, $10009). При этом стоимость чипа от Ampere всего $4050 (всего если сравнивать со стоимостью конкурентов). Возможно, именно цена станет на первых порах основным фактором привлечения внимания к чипу корпоративных клиентов.

Результаты тестов, как и говорилось выше, впечатляют. Нет, новые чипы не рвали конкурентов в клочья, где-то результаты были схожими, где-то лучше, еще где-то чуть хуже. В, целом, все на отличном уровне.

Недостаток этих чипов высокие задержки, как в пределах одного разъема, так и межпроцессорные. Это, по словам экспертов из AnandTech, слабая сторона новых процессоров. Но проблема не фатальная.


Что касается тестов на пропускную способность памяти, то результаты у Altra Q80-33 очень хорошие. В этом тесте проиграл Xeon, у которого всего шесть каналов, в отличие от восьми у AMD и Ampere.

В тестах SPECint2017 и SPECfp2017 новый чип показал себя очень неплохо не хуже, чем Xeon Platinum 8280 и близко к AMD EPYC 7742. Результаты оказались низкими только в одном случае в тестах на вычисление с плавающей запятой.


А вот в другом тесте еще один ARM-процессор, AWS Graviton2, показал себя неплохо. Не очень хороший результат теста чипа от Ampere, возможно, связан с тем, что тот же Xeon может разгоняться до 4 ГГц, имея два активных ядра.


Отличные результаты новый чип показал в тестах на многопоточность, опередив Xeon. Altra Q80-33 можно назвать абсолютным чемпионом в классе двухпроцессорных систем.

Хуже дело обстоит с результатами тестов Java но здесь проблема в отсутствии SMT и сыром ПО. Кроме того, проблемой является и отсутствие мультитрединга.


Новый процессор отлично себя показал и в тестах на компиляцию. В LLVM Suite результаты у Altra Q80-33 аналогичны показателям EPYC 7742. При этом у ARM все хорошо с энергоэффективностью. Новый чип шел вровень с AMD в тестах на сжатие, в тестах MariaDB, nginx и файл-серверных сценариях.


Практически все обозреватели, тестировавшие процессоры, отозвались о новинке хорошо. Ей удалось сохранять низкий уровень энергопотребления с демонстрацией высокой производительности по этому показателю процессор был примерно равен AMD EPYC 7742. Есть, конечно, определенные недостатки, но они не фатальны.


Обозреватели публиковали большое количество информации относительно тестов, так что если нужно больше данных о проводимом тестировании, результатах и заключениях экспертов, стоит обратиться к AnandTech, ServeTheHome и Phoronix.

Подробнее..

ARM сервера более производительные и более дешёвые

31.12.2020 14:09:18 | Автор: admin

В этом году Apple потрясла рынок десктопных процессоров чипом Apple M1 и устройствами на нём. Похожее событие произошло в мире облачных вычислений в прошлом году. AWS выпустили новый тип сервера на собственных ARM процессорах Graviton2. По заявлениям Amazon, соотношение производительности к цене у новых процессоров на 40% выше, чем у аналогов на x86. Ещё одно недавнее обновление - сервера Amazon RDS (облачный сервис, предоставляющий сервера баз данных) на Graviton2. Я запустил несколько бенчмарков и нагрузочный тест реального бэкенд приложения, чтобы проверить настолько ли хороши сервера на ARM процессорах и узнать какие проблемы совместимости могут возникнуть.

Производительность

Я сравнивал сервера типов t4g.small (ARM) и t3.small (x86) на AWS. На момент написания статьи цена за 1 час на ARM сервер составляет $0.0208, а на x86 сервер - $0.0168. Сервер на ARM на 20% дешевле.

Сперва я провёл нагрузочный тест при помощи wrk, запустив на серверах свежую установку recap.dev

Это шаблон docker-compose с 4 процессами. Веб-сервер, принимающий запросы и сохраняющий их в RabbitMQ и отдельный фоновый процесс, сохраняющий запросы группами по 1000 в PostgreSQL.

Я запускал wrk на сервере t3.2xlarge, находящемся в том же регионе, используя следующую команду:

wrk -t24 -c1000 -d300s -s ./post.lua <hostname>

Она непрерывно посылает запросы в течение 5 минут, используя 24 потока и 1000 HTTP соединений.

Результат для сервера t4g.small (ARM):

  24 threads and 1000 connections  Thread Stats   Avg      Stdev     Max   +/- Stdev    Latency   473.53ms   53.06ms   1.96s    81.33%    Req/Sec   115.83     96.65   494.00     71.32%  620751 requests in 5.00m, 85.84MB read  Socket errors: connect 0, read 0, write 0, timeout 225Requests/sec:   2068.48Transfer/sec:    292.90KB

Для сервера t3.small (x86):

 24 threads and 1000 connections  Thread Stats   Avg      Stdev     Max   +/- Stdev    Latency   600.28ms   70.23ms   2.00s    72.53%    Req/Sec    92.77     82.25   404.00     70.26%  488218 requests in 5.00m, 67.51MB read  Socket errors: connect 0, read 0, write 0, timeout 348Requests/sec:   1626.87Transfer/sec:    230.37KB

Сервер на ARM обслужил на 27% больше запросов в секунду в среднем на 26% быстрее.

Затем я запустил несколько бенчмарков из набора тестов Phoronix.

В тесте pts/compress-7zip-1.7.1t4g.small (ARM) выдал 6833 MIPS, а сервер t3.small (x86) - 5029 MIPS. ARM сервер был производительнее на 35%.

Сервер на ARM процессоре также завершил бенчмарк pts/c-ray быстрее более чем в 2 раза. 958 секунд ушло у сервера на x86 процессоре против 458 секунд у сервера с ARM процессором.

Я также запустил несколько тестов pts/ramspeed, измеряющих пропускную способность ОЗУ при выполнении различных операций.

Тип бенчмарка

t4g.small (ARM)

t3.small (x86)

Add/Integer

50000 МБ/c

13008 МБ/c

Copy/Integer

58650 МБ/c

11772 МБ/c

Scale/Integer

31753 МБ/c

11989 МБ/c

Triad/Integer

36869 МБ/c

12818 МБ/c

Average/Integer

44280 МБ/c

12314 МБ/c

Add/Floating Point

49775 МБ/c

12750 МБ/c

Copy/Floating Point

58749 МБ/c

11694 МБ/c

Scale/Floating Point

58721 МБ/c

11765 МБ/c

Triad/Floating Point

49667 МБ/c

12809 МБ/c

Average/Floating Point

54716 МБ/c

12260 МБ/c

Вкратце, ОЗУ на сервере t4g.small с процессором Graviton2 была быстрее от 3 до 5 раз.

Если смотреть только на производительность, переход на ARM сервера это одни преимущества. Больше производительности за меньшие деньги.

Совместимость

Безусловно, один из самых больших вопросов при переходе с одной архитектуры процессоров на другую это совместимость программного обеспечения.

Некоторая часть ПО уже перекомпилирована для ARM процессоров. Например, Docker был доступен в форматах .rpm и .deb, как и большая часть образов (да, образы Docker требуют пересборки для разных архитектур). Однако, docker-compose не был скомпилирован для ARM процессоров, что вылилось в несколько часов сборки различных зависимостей из исходного кода. Скорее всего, ситуация улучшится в будущем, когда сервера на ARM станут более распространены. Сейчас, однако, в некоторых случаях, переход на ARM может принести больше затрат, чем преимуществ.

Зато сервера Amazon RDS на Graviton2 не требуют никакой настройки и позволяют получить все преимущества серверов на ARM процессорах без проблем с совместимостью.

Ввиду преимуществ ARM процессоров мы также собрали Docker образы recap.dev для архитектур arm/v7 и arm64.

Подробнее..

Перевод Нагрузочное тестирование выполнять сложно, а инструменты далеки от совершенства. Почему?

07.01.2021 14:19:53 | Автор: admin


Если вы создаёте приложение, которое должно масштабироваться а все мы надеемся, что наши приложения будут расти то в определённый момент нам приходится разбираться, может ли оно это делать на самом деле. Именно тогда на помощь приходит нагрузочное тестирование: если вы хотите узнать, справится ли ваше приложение с крупными масштабами, то мы просто генерируем эти масштабы и проверяем! Звучит достаточно просто.

Но потом мы пробуем действительно сгенерировать нагрузку. Это делается легко, только если ваше приложение ужасно простое, ведь тогда можно использовать что-нибудь типа Apache JMeter для генерации повторяющихся запросов. Если у вас это получится, то я вам завидую: все системы, с которыми мне приходилось работать, сложнее и требовали более изощрённой схемы тестирования.

Если ваше приложение становится чуть сложнее, то вы переходите к инструментам наподобие Gatling. Они позволяют симулировать виртуальных пользователей, выполняющих различные сценарии, что намного полезнее, чем простая осада одного или нескольких URL. Но даже этого недостаточно, если вы пишете приложение, использующее одновременно WebSockets и HTTP-вызовы в течение долговременной сессии, а также требующее повторения по таймеру определённых действий. Возможно, я серьёзно недоглядел чего-то в документации, но мне не удалось найти способа, допустим, настроить периодическое событие, запускаемое каждые 30 секунд и выполняющее определённые действия при ответе на сообщение WebSocket, а также производящее действия по HTTP, и всё это в рамках одной HTTP-сессии. Я не нашёл такой возможности ни в одном инструменте нагрузочного тестирования (и именно поэтому написал на работе свой собственный инструмент, который надеюсь выложить в open source, если найду время на подчистку кода и отделения его от проприетарных частей).

Но предположим, что у вас есть стандартный инструмент наподобие Gatling или Locust, который работает и удовлетворяет вашим нуждам. Отлично! Теперь давайте напишем тест. По моему опыту, сейчас это самая сложная задача, потому что нам сначала нужно разобраться, как выглядит реалистичная нагрузка вас ждёт один-три дня кропотливого изучения логов и ведения заметок по показателям сетевых инструментов в браузере при работе веб-приложения. А после того, как вы узнаете реалистичную нагрузку, то вам придётся написать код, который сводится к следующему: подмножество вашего приложения будет притворяться пользователем, общаться с API и выполнять действия, которые совершает пользователь.

И на этом ещё ничего не закончилось! Здорово, мы написали нагрузочный тест, и он реалистичен. Но задача постоянно меняется, ведь выпускаются обновления. То есть теперь у нас появилась проблема поддержки: как обеспечивать актуальность нагрузочного теста при изменении приложения? Для этого нет качественных инструментов и почти ничто вам не поможет. Придётся сделать это частью процесса и надеяться, что вы ничего не упустите. Такой ответ не радует, и именно поэтому этот аспект является одним из самых сложных при тестировании приложения.

Можно даже опустить все заботы, связанные с запуском, потому что, честно говоря, если вы добились таких успехов в нагрузочном тесте, то его запуск окажется не самой тяжёлой задачей.

Откуда берётся сложность


По сути, ситуация такова:

  • Большинство инструментов нагрузочного тестирования поддерживает только простейшие нагрузки, и даже самые сложные не позволяют выполнять все необходимые действия для симуляции реального использования веб-приложения.
  • Самой сложной частью является написание теста с симуляцией реального использования, даже если инструменты поддерживают нужные вам возможности.
  • Второй по сложности задачей является поддержка теста, и инструменты в этом нам совершенно не помогают.

Давайте подробно рассмотрим каждый из пунктов, и разберёмся, от какой степени сложности можно избавиться.

Симуляция пользователей. Действительно ли она нужна?


Здесь я могу ответить да, хотя это может зависеть от конкретного приложения. В данном случае подразумевается пользователь сервиса: если у вас монолит, то это все ваши пользователи, но в случае микросервисов пользователем может быть один из сервисов! При создании приложений, над которыми я работал, мне удалось достичь незначительного успеха с таргетированными тестами отдельных конечных точек. Но в конечном итоге для этого требовалась такая сложная система, что это оказалось ненамного проще самого нагрузочного теста! И хотя мы получили некоторые результаты и усовершенствования, покрыть всё не удалось (в приложении могут быть взаимодействующие конечные точки) и мы не смогли получить реалистичную нагрузку.

Лучше задаться вопросом: А когда не нужно симулировать пользователей?. Насколько я понимаю, такое бывает, когда вы знаете, что все конечные точки независимы по производительности, отсутствуют запросы с отслеживанием состояния и порядок запросов не влияет на производительность. Это довольно серьёзные допущения, и без проверки их независимости сложно быть уверенным в них, поэтому нам приходится возвращаться к написанию теста.

Вероятно, лучшее, что вы можете здесь сделать, происходит на этапе проектирования API и системы, а не на этапе тестирования. Если упростить API, то тестировать придётся гораздо меньшую площадь поверхности. Если спроектировать систему с более независимыми частями (например, когда у каждого сервиса есть своя база данных), то их проще будет тестировать по отдельности, чем монолит. К тому же, при этом вы сможете использовать более простой инструмент двойная выгода!

Написание тестов сложная задача. Как и их поддержка.


Создавать тесты сложно, потому что требуется выполнить несколько задач: нужно разобраться, каков поток использования API и написать симуляцию этого использования. Для понимания потока необходимо понимание других систем, кроме тестируемой, и поскольку ваша система вряд ли подробно рассматривается в их документации, не будет чёткой диаграммы того, когда и что вызывается; часто приходится разбираться в логах, пока не поймёшь, какова же истинная схема использования. Далее идёт написание этой симуляции тоже нетривиальная задача, поскольку необходимо обрабатывать состояние большого количества акторов, представляющих собой пользователей вашего API!

А, да, и теперь вам нужно писать для всего этого ещё и интеграционные тесты.

Существуют исследования о том, как упростить некоторые из этих задач. Например, можно разобраться в том, что вам нужно для первоначального теста, и распознавать регрессии (отсутствующие новые нагрузки) по автоматизированному анализу логов. Но насколько я понимаю, на GitHub, не говоря уже о коммерческой доступности, нет продукта, способного выполнить за меня эту задачу. Поэтому похоже, что в нашей отрасли подобные системы не получили широкой поддержки. Для самостоятельной реализации этот проект был бы слишком большим, и в этом может быть причина его увядания (или он реализуется в больших компаниях и о нём не говорят).

Может быть, не подвергать всё нагрузочному тестированию?


Нагрузочные тесты содержат в себе большую сложность, помочь справиться с которой может не так уж много инструментов. Поэтому, вероятно, правильный ответ таков: писать меньшее количество тестов таких типов и не ожидать, что они дадут вам все ответы о производительности системы.

Чтобы получить подробную картину производительности системы, можно воспользоваться одним из способов:

  • Старый добрый анализ. Усесться с ноутбуком, ручкой и пониманием системы в целом, выделить на это полдня, и вы получите примерные расчёты основных параметров и ограничений масштабирования системы. Когда вы наткнётесь на бутылочное горлышко или встретитесь с неизвестными переменными (сколько транзакций в секунду может поддерживать наша база данных? Сколько мы их генерируем?), то можно будет протестировать конкретно их!
  • Разворачивание фич. Если вы можете медленно разворачивать фичи на всех пользователей, то вам может и не понадобиться нагрузочное тестирование! Вы можете измерять производительность экспериментально и проверять, достаточно ли её. Достаточно? Разворачиваем дальше. Мало? Откатываемся назад.
  • Повторение трафика. Это совершенно не поможет с новыми фичами (для них воспользуйтесь предыдущим пунктом), но поспособствует пониманию критичных точек системы для уже имеющихся фич, при этом не требуя большого объёма разработки. Вы можете взять отслеженный ранее трафик и повторять его (многократно снова и снова, даже комбинируя трафик различных временных периодов), наблюдая за производительностью системы.



    На правах рекламы


    Серверы для разработки это эпичные от Вдсины.
    Используем исключительно быстрые NVMe накопители от Intel и не экономим на железе только брендовое оборудование и самые современные решения на рынке!

Подробнее..

Китай продолжает разработку процессоров представлен 8-ядерный ARM-чип D2000 для мощных систем

08.01.2021 22:18:59 | Автор: admin

Китай продолжает работу по разработке собственных процессоров стране это нужно и для обхода санкций США в отношении ряда компаний из Поднебесной, и для снижения зависимости от поставок со стороны.

Сейчас в этом направлении работают несколько компаний. Одна из них Phytium Technology, которая недавно представила 8-ядерный ARM-процессор D2000 для производительных ПК. Его можно использовать как для небольших ПК, в форм-факторе Mac Mini, так и для обычных десктопов.

Новый процессор производная от FeiTeng-2000/4, представленного летом 2020 года. Правда, у D2000 в два раза больше ядер 8 вместо 4. Вообще говоря, этот же процессор компания Phytium будет использовать для создания нового суперкомпьютера. Но, как и говорилось выше, чип пригоден для пользовательских систем.


У новинки 8 ядер FTC663, совместимых с ARMv8. Энергопотребление чипа составляет 25 Вт при работе на частотах 2,302,60 ГГц. У каждого ядра четыре конвейера с внеочередным исполнением команд, динамическим предсказателем ветвлений, плюс новыми блоками INT и FP. Для того, чтобы ускорить рабочие нагрузки с плавающей запятой, разработчики добавили поддержку инструкций Arm ASIMD.

У каждой из пары ядер общая кэш-память второго уровня объемом 2 МБ. Все восемь ядер работают с кэшем третьего уровня, объем которого составляет 4 МБ. Процессор поддерживает алгоритмы шифрования SM2, SM3, SM4 и SM9, а также собственную платформу безопасности PSPA 1.0.

Что касается ввода-вывода, то у D2000 128-битный интерфейс памяти DDR4-3200 / LPDDR4, 34 линии PCIe 3.0, два порта GbE, 32 линии GPIO и интерфейсы CAN, UART, I2C, SPI, а также LPC. Кроме того, у чипа есть поддержка звука, но отсутствует встроенная графика.

Как и предшественник, D2000 получит 1144-контактный корпус FCBGA размером 32 35 мм. Выводы у процессоров совместимые, так что при необходимости более старую модель можно обновить на D2000.

А кто производитель?


Это самое интересное, поскольку, как уже говорилось, это полностью китайская разработка. Скорее всего, произвела чипы не компания TSMC. Прошлую модель производила именно она, по 16-нм техпроцессу. Новый же процессор выпущен по 14-нм техпроцессу.

Исходя из этого, можно предположить, что производитель китайская Semiconductor Manufacturing International Corp. (SMIC) или United Microelectronics Corp. из Тайваня. Маловероятно, но все же возможно, что чип поставили GlobalFoundries или Samsung Foundry.

Но совершенно точно это не TSMC, которая, к слову, будет поставлять 64-ядерный процессор S2500 для этой же компании. S2500 тоже выполнен по 16-нм техпроцессу и предназначен для работы в оборудовании ДЦ и суперкомпьютеров. Площадь поверхности чипа 132 мм2 (у A10 от Apple 125 мм2).

К слову, дела у Phytium идут очень неплохо. В прошлом году продажи компании выросли в 7,5 раза.


Это объясняется работающей программой импортозамещения. Сейчас все больше компаний и государственных организаций Поднебесной переходят на отечественное программное и аппаратное обеспечение. В 2020 году компания продала 1,5 млн чипов против 200 000 в 2019. 80% поставок ушли государственным заказчикам.

В этом году компания, скорее всего, продаст еще больше процессоров, чем в прошлом. Первые системы с новым чипом появятся в начале этого года.

Подробнее..

Оплачиваемое время как считать его правильно

11.01.2021 18:14:50 | Автор: admin

Правильный учет оплачиваемого времени критически важен для многих сфер. Желаете ли вы улучшить процедуры биллинга, или ищете возможность сэкономить затраты, необходимо понимать факторы расчета оплачиваемого времени.

Почасовая модель оплаты применима к разнообразным сферам деятельности, ведь это более рыночный и честный способ взаимоотношения работника и работодателя. Работник продает свое время компании, а компания его покупает.

Вот несколько примеров отраслей с этой моделью:

  • Консультирование и PR;

  • IT;

  • Юридические консультации;

  • Креативная сфера (копирайтинг, дизайн, и т.д.);

  • Сбор и обработка данных;

  • Административные услуги;

  • Бухгалтерский учет, и т.д.

Для данной модели учет времени важнейшая метрика расчета и контроля эффективности ведения бизнеса. На первый взгляд, в таком расчете нет ничего сложного: умножьте количество отработанных часов на часовую ставку, и все. Увы, на деле это значительно сложнее. В уравнении участвует еще несколько переменных, которые могут по-разному влиять на результат. К примеру, довольно часто вмешиваются следующие факторы:

  • Разные ставки для разных клиентов;

  • Разные почасовые ставки для разных работников;

  • Необходимость связать оплачиваемое время с дополнительными выплатами (бонусами, премиями, и т.п.).

Отличный пример расчета вознаграждения сотрудника с почасовой оплатой можно взять здесь (и скачать калькулятор на основе Excel) или здесь (имеются разные типы калькуляторов).

Вопросы для самопроверки

Когда дело касается цифр, начать лучше со слов. Или с вопросов, направленных на приведение в равновесие ценности работы и затраченных на ее выполнение усилий.

  • Легко ли клиентам получить подробный счет и понять, за что берется оплата?

  • Хорошо ли оптимизирован процесс расчета?

  • Могут ли быть ошибки из-за человеческого фактора?

  • Правильно ли сбалансированы оплачиваемые и неоплачиваемые часы?

Это всего несколько вопросов, требующих ответа, если ваш успех в целом определяется оплатой за работу. Такая основа помогает, с одной стороны, избежать завышенных счетов и ставок, а с другой обеспечивает точность расчета оплаты в зависимости от затраченных усилий и квалификации исполнителя. Держа в уме ответы на эти вопросы, вы сможете вести расчеты намного проще.

Общее руководство по учету оплачиваемых часов

Расчет оплаты затраченного времени базируется на простой формуле: полученная от клиента оплата должна покрывать зарплату работника и сопутствующие деловые расходы.

Таким образом, структурирование времени наемного сотрудника или подрядчика должно начинаться с определения, что в конкретной области является оплачиваемым временем. Это определение должно быть четко задокументировано.

Далее, для выполнения задач, нужно предпринять следующие шаги:

  1. Определить цели взимания оплаты.

  2. Соотнести эти цели с затрачиваемым временем и ресурсами.

  3. Вести учет рабочих часов в реальном времени.

  4. Регулярно пересматривать данные.

  5. Определить аспекты, нуждающиеся в улучшении.

  6. Проверять и переоценивать запланированные затраты времени.

  7. Повторять циклы обновления.

Также рекомендуем ознакомиться с этой статьей в ней вы найдете больше информации о коэффициентах использования времени в бизнесе и правильном расчете.

Некоторые дополнительные или регулярные платежи также можно привязать к оплачиваемым часам в соответствии с действующим контрактом. Этот вариант доступен при использовании качественного программного обеспечения по учету времени в счетчике нужно только применить нужные настройки к различным типам работ.

Коммуникация: считать или не считать?

Коммуникация неотъемлемая часть работы над совместными проектами, которая также требует времени и ресурсов. Стоит отметить следующие форматы:

  • Переписка по email;

  • Видеоконференции;

  • Совещания всех типов;

  • Регулярное общение по телефону.

Многие бизнесы и агентства не учитывают время, затраченное на коммуникацию, при выставлении счета. Это не самое мудрое решение, а в некоторых случаях оно даже пагубно для бизнеса, так как приводит к выгоранию. Намного эффективнее определить деловую коммуникацию как часть оплачиваемой услуги и включить ее в счет наряду с обычными часами, потраченными на выполнение рабочих задач.

Повышайте ставки

Расширение вашей активности по всему перечню платных услуг самый выигрышный сценарий. Чем ценнее ваш вклад тем выше ваша оплата. Однако, касаясь внешних факторов, влияющих на прибыльность в проектах, нельзя игнорировать вопрос неоплачиваемого времени.

Чтобы повысить ставки к оплате, владельцам бизнеса и подрядчикам необходимо удерживать процент неоплачиваемого времени на низком уровне. Тогда можно получить больше прибыли.

Именно применение специального программного обеспечения для учета времени наиболее эффективно для правильного баланса оплачиваемых и неоплачиваемых часов и прозрачности в расчетах.

Используйте счетчик времени в полную силу

Правильно подобранное устройство должно вести точный учет времени, рассчитывать оплаты, выставлять счета и обеспечивать всеобъемлющее достижение поставленных задач. Такое устройство быстро приносит результат и помогает развивать сотрудничество в рабочих отношениях благодаря прозрачности и надежности данных.

Одним из главных достоинств при использовании счетчиков времени является их способность точно различать типы работ. К примеру, автоматический счетчик позволяет сразу выделить и предоставить полную информацию по таким типам, как:

  • Оплачиваемые часы, непосредственно относящиеся к работе над проектом;

  • Административная рутина;

  • Нерабочее время (отпуска, больничные, и т.п.)

Дополнительное преимущество при внедрении автоматического учета времени возможность выставления счетов. Это способствует максимальному повышению продуктивности, а также позволяет хранить данные для дальнейшего анализа отделом HR. При помощи автоматических процедур становится намного легче и рассчитывать выплаты, и определять рабочие механизмы для карьерного повышения.

Выводы

  • Оплачиваемые часы важнейшая метрика во многих сферах, продающих время специалиста (услуги и консультирование).

  • Бизнесы чаще всего используют учетные ставки для выставления счетов, основанных на продуктивности работы.

  • Биллинг на основе стоимости также важен для определения политики компаний в плане компенсаций и карьерного роста.

  • Чтобы повысить конкурентоспособность, нужно постоянно отслеживать соотношение оплачиваемых и неоплачиваемых часов.

  • Автоматический учет времени при помощи специального программного обеспечения позволяет считать оплачиваемые часы точнее и опираться на объемы именно потраченного, а не только заявленного времени.

  • При правильном балансе оплачиваемого и неоплачиваемого времени прибыль будет расти.

Подробнее..

Intel о топовом процессоре Core i9-11900K быстрее Ryzen 9 5900X, выйдет уже в этом квартале

13.01.2021 00:05:12 | Автор: admin

Компания Intel решила ускорить процесс вывода своего нового процессора Core i9-11900K на рынок. Теперь уже официально известно, что чип поступит в продажу в ближайшие пару месяцев. Компания на CES 2021 представила целое семейство Rocket Lake-S, но больше всего внимания уделила старшему представителю того семейства.

Немудрено ведь это действительно топовый на данный момент чип. Ориентирован он, в первую очередь, на геймеров и пользователей, которым нужны высокопроизводительные системы. По словам представителей Intel, процессоры дают прирост IPC (числа исполняемых за такт инструкций) на 19% по сравнению с Comet Lake.


У Core i9-11900K 8 ядер с поддержкой 16 потоков. Работает чип на частоте вплоть до 4,8 ГГц при полной многопоточной нагрузке. Сообщается, что максимальная частота составляет 5,3 ГГц, такая высокая скорость работы возможна благодаря технологии Thermal Velocity Boost, но только в однопоточном режиме.
Zen 3 Ryzen 5000 Series Processors Cores/Threads Base/Boost Freq. TDP
Ryzen 9 5900X 12 / 24 3.7 / 4.8 105W
Intel Core i9-11900K 8 / 16 ? / 5.3 150W?
Core i9-10900K / F 10 / 20 3.7 / 5.3 125W
Ryzen 7 5800X 8 / 16 3.8 / 4.7 105W
Core i9-10850K 10 / 20 3.6 / 5.2 95W
Core i7-10700K / F 8 / 16 3.8 / 5.1 125W

Процессоры, кроме всего прочего, получили поддержку DDR4-3200 и 20 линий PCIe 4.0, которые служат для подключения графического адаптера и SSD. С памятью DDR4-3200 чип работает в духканальном режиме.

Чипы семейства Intel Rocket Lake-S выпускаются по оптимизированной 14-нм технологии, получив конструктивное исполнение LGA1200. Компания заявила, что устанавливать их можно на платы с чипсетами не только 500-й, но и 400-й серии. Правда, не все модели поддерживаются, но тем не менее.

Системы с наборами логики Z490, Q470 и H470 получат необходимые для корректной работы чипа обновления UEFI. А вот платы B460 и H410 не обновят, так что они будут совместимы лишь с предыдущими поколениями чипов.

Компания уже заявила, что 8C/16T Core i9-11900K новый король игрового мира среди чипов. Он превосходит по производительности AMD Ryzen 9 5900X при разрешении 1080p и настройках качества графики Ultra. При тестировании с графическими адаптерами EVGA RTX 3080 XC3 чип показывает прирост производительности по сравнению со своим конкурентом от 2% до 8%. В среднем получается 4%, что не так много, но все же приятно для пользователя.


Но это при дефолтных показателях. У процессора традиционно высокий потолок для разгона, так что в этом случае прирост может быть и более значительным. На что в действительности способен процессор станет известно уже после старта продаж, когда их смогут протестировать обычные пользователи и эксперты.

В ходе сравнения производительности систем на основе Core i9-11900K и второго чипа компании, Core i9-10900K, выяснилось, что преимущество новинки составляет около 7%. Тестирование проводилось при помощи встроенного бенчмарка игры Hitman III.

Есть у чипа и другие преимущества, включая интегрированную графику Intel Xe, а также совместимость с технологией Intel Deep Learning Boost. Что касается интегрированной графики, то она на 50% более производительна, чем встроенная графика Comet Lake. Ну а совместимость с Intel Deep Learning Boost обеспечивается поддержкой набора инструкций AVX-512.


После Intel Rocket Lake-S и материнских плат на чипсетах 500-й серии компания собирается выпустить 10-нм процессоры Alder Lake. У них сочетаются ядра на двух разных микроархитектурах.

Подробнее..

Business Intelligence на больших данных наш опыт интеграции

20.01.2021 14:20:49 | Автор: admin

В вопросах производительности BI обычно приходится искать компромисс между скоростью работы аналитики и объемами данных, над которыми она реализована. Мы попробовали убить двух зайцев сразу, и сегодня я хочу поделиться нашим опытом интеграции Visiology с платформой Arenadata при построении гибридной модели работы BI.

Если вы читаете наш блог, то уже знаете о платформе Visiology хотя бы в общих чертах (если нет, это можно легко исправить, прочитав наш первый пост). Но сегодня речь пойдет не только о платформе Visiology и BI как таковых, но также о наших новых друзьях Arenadata. А точнее, об интеграции, которая позволила реализовать гибридную работу аналитики с большой скоростью и на больших объемах данных.

Зачем потребовалась интеграция Arenadata и Visiology?

Подходов к работе BI-систем на сегодняшний день несколько. Но когда речь идет о больших данных для самых разных задач, обычно используется ROLAP. Работает он достаточно просто: когда пользователь нажимает что-то на дашборде, например, выбирает какой-то фильтр, внутри платформы формируется SQL-запрос, который уходит на тот или иной бэкэнд. В принципе, под системой BI может лежать любая СУБД, которая поддерживает запросы от Postgres до Teradata. Подробнее о схемах работы OLAP я рассказывал здесь.

Преимущество интеграции BI с СУБД заключается в том, что для работы системы, по сути, нет ограничения по объему данных. Но при этом падает скорость выполнения запросов - конечно, если не использовать специализированную колоночную СУБД, например, ClickHouse или Vertica. И, хотя у ClickHouse спектр возможностей пока еще уже, чем у той же Vertica, система развивается и выглядит очень многообещающей.

Но даже с колоночной СУБД есть свои минусы при работе с BI, и самый первый из них это более низкая эффективность использования кэша на уровне платформы в целом, потому что СУБД, в отличие от самой BI-платформы, "не знает" многого о поведении пользователей и не может использовать эту информацию для оптимизации. Когда большое количество пользователей начинают работать, по-разному делать запросы и обращаться к дашбордам, требования к железу, на котором крутится СУБД даже хорошая, аналитическая и колоночная могут оказаться очень серьезными.

Второй момент это ограничение аналитической функциональности: все, что не укладывается в SQL-запрос, поддерживаемый распределенной СУБД, отсекается автоматически (например, в случае ClickHouse - это оконные функции). И это проблема, потому что в BI есть много вещей, которые с трудом транслируются в SQL-запросы или выполняются неоптимально.

Второй вариант это In-memory OLAP. Он подразумевает перенос всех обрабатываемых данных в специальный движок, который молниеносно прорабатывает базу в 200-300 Гб это порядок единицы миллиардов записей. Кстати, подробнее про ограничения In-Memory OLAP я уже рассказывал здесь. На практике встречаются инсталляции In-Memory OLAP, укомплектованные 1-2-3 терабайтами оперативной памяти, но это скорее экзотика, причем дорогостоящая.

Практика показывает, что далеко не всегда можно обойтись тем или иным подходом. Когда требуются одновременно гибкость, возможность работы с большим объемом данных и поддержка значительного количества пользователей, возникает потребность в гибридной системе, которая с одной стороны загружает данные в движок In-Memory OLAP, а с другой постоянно подтягивает нужные записи из СУБД. В этом случае движок OLAP используется для доступа ко всему массиву данных, без всяких задержек. И в отличие от чистого In-Memory OLAP, который нужно периодически перезагружать, в гибридной модели мы всегда получаем актуальные данные.

Такое разделение данных на горячие и холодные объединяет плюсы обоих подходов ROLAP и In-Memory, но усложняет проект внедрения BI. Например, разделение данных происходит вручную, на уровне ETL процедур. Поэтому для эффективной работы всего комплекса очень важна совместимость между бэкэндом и самой BI-системой. При том, что SQL-запросы остаются стандартными, в реальности всегда есть аспекты их выполнения, нюансы производительности.

Arenadata и Arenadata QuickMarts

Платформа данных Arenadata состоит из нескольких компонентов, построенных на базе открытых технологий, и используется многими российскими и зарубежными компаниями. В состав решения входит собственное MPP решение на базе Greenplum, дистрибутив Hadoop для хранения и обработки неструктурированных и слабоструктурированных данных, система централизованного управления ADCM (Сluster Management) на базе Ansible и другие полезные компоненты, в том числе Arenadata QuickMarts (ADQM).

СУБД ADQM это колоночная СУБД от Arenadata, построенная на базе ClickHouse, аналитической СУБД, которую развивает Яндекс. Изначально ClickHouse создавалась для внутреннего проекта Яндекс.Метрика, но эта СУБД очень понравилась сообществу. В результате исходный код ClickHouse был переведен в OpenSource (лицензия Apache-2) и стал популярен по всему миру. На сегодняшний день насчитывается порядка 1000 инсталляций ClickHouse по всему миру, и только 1/3 из них в России. И хотя Яндекс остается основным контрибьютором развития СУБД, лицензия Apache-2 позволяет абсолютно свободно использовать продукт и вносить изменения в проект.

Современная колоночная СУБД использует аппаратную оптимизацию CPU (SSE). ClickHouse может очень быстро выполнять запросы за счет векторных оптимизаций и утилизации всего ресурса многоядерных CPU. На базе ClickHouse работают огромные кластера сам Яндекс растягивает эту СУБД на несколько сотен серверов. Это гарантирует, что вместе с этим решением вы можете масштабироваться в достаточно больших объемах.

Но главная фича ClickHouse в нашем контексте это эффективная работа с достаточно специфическими аналитическими запросами. Если витрины уже отстроены и вам нужно предоставить доступ пользователей к BI с минимальной латентностью, эта история как раз для ClickHouse. Эта СУБД прекрасно справляется с запросами без джойнов и соединений.

Во многих сравнениях ClickHouse дает серьезную фору даже классическим СУБД, например, той же Oracle Exadata. Результаты этих тестов можно найти на ресурсах Яндекса.

Производительность QuickMarts

  • Типичные запросы быстрей чем за секунду

  • > 100 раз быстрей чем Hadoop и обычные СУБД

  • 100 млн - 1 миллиард строк в секунду на одной ноде

  • До 2 терабайт в секунду для кластера на 400 нод

Но вернемся к Arenadata QuickMarts. Это сборка ClickHouse, которая немного отличается от сборки Яндекса. Наши коллеги из Arenadata даже позже выпускают релизы, потому что проводят больше тестов, чтобы серьезные задачи в продакшене работали только на стабильных версиях.

При этом установка и настройка ADQM происходит из Arenadata Cluster Manager. Кастомизированная СУБД обладает расширенными механизмами авторизации пользователей, a также средствами мониторинга на базе Graphite и Grafana. Но самое главное, что QuickMarts изначально располагает готовыми коннекторами и прозрачно взаимодействует с другими компонентами платформы, в т.ч. с ADB (Greenplum), что позволяет по мере необходимости подгружать данные из ADB в ADQM.

В нашем случае QuickMarts используется для работы с витринами, к которым через BI обращаются сотни или тысячи пользователей. Архитектура системы позволяет выдать им данные здесь и сейчас, а не ждать 20-30 секунд, когда обработается их запрос по витринам в более медленной СУБД.

Как работает интеграция Arenadata и Visiology

Когда Visiology используется вместе с Arenadata, схема работы системы выглядит следующим образом. Основное хранилище данных может быть реализовано на базе ADB (GreenPlum), из которой создаются витрины данных, хранящиеся уже в ADQM. За счет интеграции между компонентами решения система работает как единое целое, а необходимые для запросов данные поднимаются на нужный уровень автоматически.

Фактически в аналитической системе создается только один дашборд, а графику обрабатывает движок In-Memory ViQube ядро платформы Visiology. Пользователь лишь выбирает те или иные фильтры, а задача по выгрузке самих транзакций выполняется уже на бэкенде ресурсами QuickMarts.

Раньше подобная интеграция была только с Vertica, но сейчас мы совместно с коллегами сделали интеграцию для Arenadata QuickMarts. Это радостная новость для сторонников ClickHouse, потому что BI работает с популярной СУБД по гибридной схеме. При этом Arenadata DB, выполняющая функцию корпоративного хранилища данных, обеспечивает необходимую трансформацию данных для дальнейшей работы QuickMarts и Visiology.

Все запросы BI обрабатываются движком ViQube. Если пользователь обращается к тем данным, которых нет в памяти, система автоматически генерирует SQL-запрос, который выполняется на Arenadata QuickMarts.

Чтобы все это заработало, мы реализовали поддержку диалекта ClickHouse для основных аналитических функций и добавили автоматическое переключение между режимами работы OLAP в зависимости от того, где находятся данные на самом деле. Однако для пользователя все остается предельно прозрачным: он даже не знает, как работает система просто делает запросы в интерфейсе BI и получает результаты, причем достаточно быстро.

Конечно, у такой схемы есть и свои минусы. Например, учитывая ограничения SQL, не все аналитические функции будут доступны на полном объеме данных. Но зато появляется возможность отрабатывать огромное количество транзакций, для большого количества людей, которые в основном изучают тренды на готовых дашбордах, и лишь иногда ищут конкретные записи.

Развиваемся дальше

Сейчас интеграция находится на стадии версии v1.0, и мы планируем дальнейшие доработки. В частности, уже сейчас речь идет о том, чтобы расширить набор доступных аналитических возможностей, а также об интеграции в единую консоль управления (например, у Arenadata есть решение Cluster Manager (ADCM), которое позволяет управлять всеми компонентами ландшафта из единой консоли, рассматриваем это как один из вариантов).

Также на повестке дня остро стоит задача автоматизации настройки метаданных. Сейчас их нужно размечать в специальном интерфейсе - это довольно трудоемко. Хочется, чтобы система самостоятельно распознавала бы все необходимые параметры, получив путь к той или иной витрине.

В целом, мы остались очень довольны и сотрудничеством с Arenadata, и той интеграцией с ClickHouse и ADQM, которая получилась. Теперь в аналитической платформе Visiology можно одновременно работать с источниками данных любого масштаба - от Small Data (ручной ввод, Excel) до Big Data (миллиардов или даже сотни миллиардов транзакций из распределенных хранилищ данных). А гибридный режим работы, который мы реализовали вместе с Arenadata, еще и позволяет сделать это с разумными затратами на оборудование.

Будем признательны, если вы напишете в комментариях, с какими сценариями запуска BI на больших данных вы сталкивались. Ну а мы всегда готовы поделиться своим опытом в деталях!

Подробнее..

Qualcomm планирует выпустить конкурента процессора M1 от Apple уже в этом году

21.01.2021 04:07:42 | Автор: admin

ARM-процессоры становятся все более мощными, а сфера их применения расширяется. Сейчас над новым производительным ARM-чипом работает компания Qualcomm. Ее процессор для десктопных ПК и ноутбуков получил название SC8280, и его без проблем можно будет использовать в Windows-компьютерах.

Snapdragon SC828 появится в продаже в середине этого года, более точная дата неизвестна. Насколько можно понять, чип будет прямым конкурентом процессора от Apple, который тоже базируется на архитектуре ARM.

У Qualcomm есть все шансы выпустить собственный мощный процессор, поскольку эта компания один из крупнейших производителей ARM-чипов. Доля рынка Qualcomm составляет около 33% в середине 2019 года. К концу прошлого года этот показатель снизился до 29% компания уступила место китайской MediaTek. Последняя, правда, опережает своего конкурента всего на пару процентов.

Что собой представляет новинка?


Насколько известно, Snapdragon SC8280 улучшенная версия первых решений от Qualcomm для ноутбуков на основе ОС Windows. Речь идет об аппаратных платформах Snapdragon 8cx и 8cx Gen 2. Но есть значительные отличия не только внутренние, но и внешние. Например, отличаются даже размеры у SC8280 это 20х17 мм. Размеры предшественников 20х15 мм. Разница может объясняться большим количеством ядер.

Компания собирается выпустить сразу две модификации процессора, одна из них будет поддерживать сразу 32 ГБ LPDDR4X. Вторая модификация будет работать с недавно появившимися модулями LPDDR5.

На какой стадии находится разработка?


На завершающей. Уже есть прототипы процессора, их тестируют в лабораториях компании, в паре с 14-дюймовыми мониторами. Несмотря на то, что с момента демонстрации 8cx Gen 2 (сентябрь 2020 г.) не прошло и полгода, новые чипы будут выпущены в этом году.

Несмотря на то, что спецификации процессора неизвестны, его частью, скорее всего, станет модем Snapdragon X55. Он позволяет устройству работать с сотовыми сетями пятого поколения, что должно привлечь к устройству внимание корпораций.

Более того, у гаджетов на основе Apple M1 подключения к 5G нет, так что это будет еще и конкурентным преимуществом новинки.

Ну и выполнен этот процессор по 5нм техпроцессу, это еще одно конкурентное преимущество. У Qualcomm уже есть процессор, который выполнен по 5нм технологии. Правда, это мобильный чип Qualcomm 888.

Что у Qualcomm есть сейчас?


Если говорить о процессорах, которые компания поставляет сейчас, то главный из них Snapdragon 8cx Gen 2. Это 64-битный чип, выполненный по 7нм технологии. У него восемь ядер Kryo 495. Есть несколько модифицированная версия, тоже с 8 ядрами, но Kryo 490. Встроенная графика Adreno 675.


У Snapdragon 8cx Gen 2, на смену которому придет новинка, уже есть поддержка 5G-сетей.У первого же поколения есть лишь поддержка LTE и 4G. Он используется в Galaxy Book S, флагманской модели ноутбука от Samsung, и некоторых других системах. Благодаря низкому энергопотреблению заряда батареи такого устройства хватает почти на сутки в режиме просмотра видео.

Snapdragon 8cx Gen 2 и Snapdragon 8c установлены и в IdeaPad 5G и IdeaPad 4G LTE. Каждый из девайсов умеет работать с сотовой сетью, а батарейки хватает на 20 часов работы.

Есть еще и мобильный процессор Snapdragon 888 c Cortex X1, Wi-Fi 6E и 5G. У него 8 ядер. Главное ядро для высокопроизводительных задач, три ядра для выполнения фоновых ресурсоемких задач, еще четыре для выполнения фоновых неприоритетных задач. Основное ядро в чипе, Cortex-X1, работает на частоте 2,84 ГГц. Три дополнительных ядра Cortex A78, четыре остальных A55.

Ближайший конкурент



Что касается конкурента, Apple M1, то на его базе пока выпущено три системы MacBook Air, MacBook Pro 13 и неттоп Mac mini. Процессор включает четыре высокопроизводительных ядра Firestorm и четыре ядра низкого энергопотребления Icestorm.

У высокопроизводительных ядер 192 КБ кэша команд и 128 КБ кэша данных и совместно используют 12 МБ кэша L2. У второй группы ядер 128 КБ кэша команд, 64 КБ кэша данных и общий 4 МБ кэша L2.

Частью чипа является еще и восьмиядерная графика, которая способна справляться одновременно с 25 000 потоков, плюс 16-ядерный нейропроцессор, выполняющий 11 триллионов операций в секунду.

Подробнее..

Категории

Последние комментарии

© 2006-2021, personeltest.ru