С++ на практике

Все мы знаем, что С++ — мощный язык, у которого много сторонников. Но чем могут быть недовольны даже сторонники? Где сталкиваешься с неудобствами и чем они вызваны? Почему в примитивном приложении могут вылезти неожиданные сложности и чего не хватает в стандартной библиотеке? А главное, что можно сделать для улучшения ситуации?

Антон Полухин (antoshkka), состоящий в комитете по стандартизации C++ и работающий в «Яндекс.Такси», рассказал обо всём этом в докладе «C++ на практике». Сам доклад появился ещё в 2019-м, и с выходом C++20 что-то изменилось, но главные тезисы и вывод остались актуальны. Поэтому теперь, готовя новую конференцию C++ Russia 2021, мы решили сделать для Хабра пост на основе этого доклада. Под катом — и текст, и видеозапись. Далее повествование идёт от лица Антона.

Вступление

Меня зовут Антон Полухин, я сотрудник компании «Яндекс.Такси». В свободное и рабочее время я занимаюсь развитием C++: разрабатываю Boost-библиотеки, провожу мастер-классы и читаю лекции о своём любимом языке программирования.

Сегодня мы поговорим о том, насколько приятно пользоваться C++ в повседневной жизни: рассмотрим два совершенно разных приложения и увидим всю боль, ужасы и приятности «плюсов».

В «Википедии» умные люди пишут, что C++ — это «компилируемый, статически типизированный язык программирования общего назначения». А это значит, что на нём можно писать практически всё, что угодно: и консольные вспомогательные утилиты, и игры, и Android-приложения, и поисковые движки! По крайней мере, так говорят. Давайте проверим.


Пример «Ёлочка»

Под Новый год знакомый скинул мне программу на Bash, которая выводила на экран ёлочку в ASCII-графике с красиво мигающими лампочками:

Насколько сложно написать то же самое на «плюсах»? У меня это заняло день. Но написание на Perl, Python или Bash отняло бы столько же времени и было бы чуть неприятнее.

Что собой представляет это мини-приложение? Ёлочка мигает, а при нажатии на клавишу меняется режим «гирлянды»: все лампочки становятся одного цвета.

Суперсерьёзная программа! Она занимает примерно 100 строчек кода, но даже тут есть некрасивости. Например, что происходит в этом блоке?

std::thread t([&lamp]() {     char c;    while (std::cin >> c) {         lamp.change_mode();      }  });

Блок отвечает за управление лампочками, но это приходится делать в отдельном потоке, так как в C++ нельзя одновременно мигать лампочками и смотреть, нажал ли пользователь клавишу. Чтение из потока — блокирующее. Как только мы дошли до std::cin >> c, лампочки перестают мигать, поток останавливается, а мы ждём, когда пользователь нажмёт клавишу.

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

Нам всего лишь хотелось нарисовать ёлочку в терминале, а тут внезапно вылезла многопоточность! Не очень приятно.

А вот и вторая неприятность:

std::ifstream ifs{ filename.c_str() }; std::string tree; std::getline(ifs, tree, '\0');

Есть файл с ASCII-графикой, и мы его никак не меняем — из него нужны только байты. Но в С++ нельзя работать с файлом как с массивом байт: придётся открыть поток, считать информацию из потока в контейнер (string или vector) и только тогда работать с этим контейнером.


Почему всё так криво?

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

Круг задач, которые можно решать с помощью C++, очень широк. И за то, чтобы эти задачи можно было решать, отвечают вот эти люди:

Знакомьтесь — комитет по стандартизации языка C++. Они эксперты в своих областях: одни в машинном обучении, другие в компиляторах, третьи великолепно знают алгоритмы. Но есть маленькая проблема: все члены комитета находятся в круге задач, отвечающих за высокую производительность.

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

Так сложилось исторически.

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

Так уж получается, что самые хардкорные C++ программисты занимаются самыми суровыми вещами — они редко читают вывод с клавиатуры или из файлика, а занимаются только тем местом, которое критично для всего приложения. Если программа на C++, значит, нужна высокая производительность. Поэтому человек из комитета и отвечает за высокую производительность.

Около двадцати лет назад в комитете было 20 человек, а теперь уже ближе к двумстам. И по-прежнему это люди, которые решают задачи, связанные с высокой производительностью.

А наша задача с ёлочкой помечена на картинке звёздочкой, и очень далека от целей комитета.

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


«Ява не тормозит»

В основе «Яндекс.Такси» лежит микросервисная архитектура. Если взять какой-то один сильно нагруженный сервис в отдельном дата-центре, то он будет обрабатывать где-то 20 000 сообщений в секунду. Что-то приходит в виде запроса, что-то он считывает, ищет на графе, обращается к базе и выдает ответ. Каждое такое событие нужно логировать, то есть записать информацию в файл. Один микросервис в отдельном дата-центре порождает 30 гигабайт логов в час, а в сумме все микросервисы генерируют в час больше терабайта логов и миллиарда событий.

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

Раньше для отправки таких сообщений мы использовали Logstash — популярное, бесплатное и открытое приложение, написанное на Java, которым пользуется огромное количество компаний.

Казалось бы, что может пойти не так?

Демон — это то, что принимает запрос, обрабатывает его, обращается к удалённому хранилищу, получает ответ, что-то считает на графе, производит криптографические операции, пишет лог и отдаёт ответ. А Java — это Logstash, который просто отправляет этот лог в удалённое хранилище. При этом потребляет Logstash в два раза больше оперативной памяти и в девять раз больше ресурсов процессора.

Что тут происходит под капотом? Logstash делает следующее:

  • Считывает данные с диска.
  • Разбивает запись на ключ—значение.
  • Применяет простые правила трансформации ключей и значений (например, меняет формат времени).
  • Формирует запись в конечном формате.
  • Отправляет запись в удалённое хранилище.

Вооружимся perf и натравим его на Java. Что мы видим? Logstash постоянно что-то сует в какие-то конкурентные ассоциативные контейнеры, в которых тратится огромное количество времени.

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

Давайте сделаем свой Logstash без излишеств — шустрый, модный и современный. Для этого воспользуемся всеми доступными фишками C++17.


«Пилорама»

Представляю вашему вниманию «Пилораму». Почему такое название? Говоря по-английски, пилорама — это «factory in which logs are sawed». То есть что-то, что «пилит логи»! Вот и наша «Пилорама» пилит их и отправляет куда-то дальше.

Учтём ошибки Logstash и вместо трёх стадий с обменом контейнерами между ними (разделение на ключ—значение, применение правил, формирование записи) сделаем одну. Есть кусок данных, мы знаем, как преобразовать его в конечный формат, и никаких сложных промежуточных шагов не нужно.

Как мы это делаем? Данные приходят в виде std::string_view, то есть в виде указателя на кусок данных, над которыми нужно работать, и size_t — количества этих данных. Со string_view мы делаем следующее:

do {     const std::string key = GetKey();     if  (state_ == State::kIncompleteRecord) {        // Stop parsing to let the producer write more data and finish the record.        return 0;      }      const utils::string_view value = GetValue();       if (state_ == State::kIncompleteRecord) {         return 0;       }      WriteWithFilters(writer, key, value);  } while (state_ == State::kParsing);

Берём ключ, проверяем, нормальный ли он. Затем достаём значение и проверяем, пришло ли оно целиком. Записываем всё в выходной формат, применяя какие-то правила. Повторяем до тех пор, пока есть что разбирать.

Но для чего тут нужны две проверки?

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

С этой функцией конвертации получается более 360 тысяч записей в секунду, а это более 75 мегабайт данных в секунду. Итого больше 270 гигабайт логов в час. Таким образом можно держать 10 самых загруженных микросервисов на одном ядре «Пилорамы» или вообще все логи «Яндекс.Такси» на четырёх ядрах.

Честно говоря, когда я только написал этот конвертер и впервые запустил его, то был недоволен результатом: всего в 30 раз быстрее Java. Но потом я выспался и понял, что собирал всё в режиме отладки. Поэтому сейчас мы примерно в 100 раз быстрее Java, что более-менее приемлемо, но все равно недостаточно.

Почему так медленно? Вот вам немного нашей боли. В приложении есть конвертация данных и преобразование времён, а в C++17 для этого ничего нет. Поэтому приходится пользоваться сторонними библиотеками, например cctz. А эта «редиска» в цикле динамически аллоцирует память.

 // Formats a std::tm using strftime(3).  void FormatTM(std::string* out, const std::string& fmt, const std::tm& tm)    {       for (int  = 2; i !=32; i*=2) {         size_t buf_size = fmt.size() * i;          std::vector<char>  buf(buf_size);          if (size_t len = strftime(&buf[0], buf_size, fmt.c_str(), &tm)) {              out->append(&buf[0], len);             return;          }     } }

Если это безобразие убрать и заменить на работу с датой/временем из C++20, то скорость конвертации увеличится практически в два раза.


С++ на практике

Теперь попробуем считать данные с диска. Стандартный подход в C++ — использовать стримы. Есть файл, с которым нужно что-то сделать. Мы открываем ifstream и даём ему команду читать из конкретного файла в определённый контейнер. Тот, в свою очередь, говорит операционной системе отдать какой-то файл. ОС не умеет работать напрямую с диском и записывать байты прямо с него в пользовательские буферы. Сначала ОС подтягивает большой кусок файла в оперативную память и уже оттуда копирует байты в нужный контейнер.

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

Именно это делает boost::interprocess::mapped_region. Вы называете файл, с которым будете работать, оффсет и примерный требуемый размер. И boost::interprocess::mapped_region возвращает указатель на начало этих данных и размер, что отлично сочетается со string_view. Мы получаем указатель и размер, создаем от них string_view и отправляем на конвертацию. В итоге без всяких динамических аллокаций и промежуточных контейнеров получаем пайплайн, начиная с чтения файла до получения итогового формата. Осталось самое сложное — отправить данные в удалённое хранилище. Выглядит это так:

std::string result;   do {       AppendNewData(result);       if (result.size() < treshold) {           engine::SleepFor(1s);           continue;      }       SendToRemote(result);       result.clear();   } while (true);

Здесь есть какой-то result и функция, которая читает данные с диска, конвертирует и добавляет их в result. Затем мы проверяем, что данных для отправки достаточно, и если их недостаточно, то засыпаем на секунду, а если достаточно, то отправляем их в удаленное хранилище.

Вы можете спросить: «Sleep в коде? Это точно высокопроизводительный сервис?» Если есть 100 файлов, значит, нужно 100 потоков, и все эти потоки иногда спят. Изредка им нужно резко пробудиться, что-то сделать и снова уснуть. ОС будет переключаться между этими потоками и пытаться угадать, какие и когда нужно разбудить. Если файлов 10 тысяч, то и потоков будет 10 тысяч, и тогда все станет ещё хуже: оперативная память будет съедаться, а операционная система — зашиваться. Выглядит не очень производительно.

Но устроено здесь всё хитро и функции engine::SleepFor() и SendToRemote()на самом деле асинхронные методы из нашего асинхронного фреймворка userver. Перепишем их с использованием Coroutines TS, и тогда к методам добавится co_await. В результате получим co_await engine::SleepFor() и co_await SendToRemote(result).

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

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

Аналогичным способом данные отправляются в удаленное хранилище, через асинхронный метод co_await SendToRemote().

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


Чего не хватает в C++

Некоторые вещи, которые хотелось бы использовать для этой задачи, появятся в C++20. Например, появятся таймзоны, тогда можно будет выкинуть cctz. Появится в С++23 flat_map — это как std::map, но намного эффективнее для небольших наборов данных. Там, где всего 10-20 элементов и количество данных расти не будет, отлично впишется flat_map. А еще в С++20 появится замечательная вещь — тип char8_t.

Казалось бы, две одинаковые функции, но вся разница в том, что в одном случае у нас unsigned char*, а в другом char8_t*:

void do_something(unsigned char* data, int& result)  {     result += data[0] - u8’0’;     result += data[1] - u8’0’; }  void do_something(char8_t* data, int& result)  {     result += data[0] - u8’0’;     result += data[1] - u8’0’; }

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

Функция, принимающая char8_t* на треть короче. Почему?

Когда вы работаете с unsigned char*, char* или byte*, компилятор думает, что char* может указывать на все, что угодно: на integer, string или пользовательскую структуру. И когда компилятор видит, что на вход передается char* и что-то извне посылки, то думает, что любая модификация этой переменной может поменять те данные, на которые указывает char*. Это работает и в обратную сторону. Поэтому компилятор генерирует такой код, который лишний раз выгружает это из регистра в оперативную память (ну, если быть полностью корректным, в кеш процессора) и из неё подтягивает в регистр. В результате мы имеем лишние инструкции. В char8_t* можно такое убрать.

Но и в C++20 будет не всё. В С++ по-прежнему не хватает memory map: он нужен и для простых приложений, вроде нашей «Ёлочки», и для высоконагруженных. Предложение по mmap есть в комитете по стандартизации, и оно может быть принято в C++23.

Не хватает стандартной библиотеки побольше. У нас удалённое хранилище, но оно не неоптимальное: написано на Java и работает с JSON. А в С++, к сожалению, из коробки нет возможности работать с JSON. Также хочется Protobuf.

Ну и не хватает правильной работы с вводом/выводом. Есть networking TS, где под капотом используется библиотека Asio, и возможно, её добавят в C++23. Есть Boost.Beast, который работает с HTTP асинхронно — им сложно пользоваться, но код в итоге получается красивым. И есть библиотека AFIO, которая позволяет асинхронно работать с файловой системой и может сказать ОС: «Когда в этой директории появится новый файлик, вызови эту функцию». А там просыпается какая-то корутина и начинает делать с файликом что-то полезное. К несчастью, AFIO — это прототип, работающий, но не быстро.


Что можете сделать лично вы?

Мы рассмотрели два приложения, в каждом из которых есть шероховатости. В случае второго приложения большинство этих недочетов исправят в C++20, но «осадочек остался». Ведь, наверное, с подобными проблемами встречаюсь в разработке не только я. И скорее всего, у разных людей «шероховатости» разные. Что в связи с этим делать?

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

Также требуются люди из тех областей, где C++ не популярен или вообще не используется. Хорошо, если в комитете будет сидеть матерый разработчик embedded-железок, слушать о том, как механизм исключений уместили в 200 байт, и говорить: «А у нас всего 128 байт (нет, не килобайт), думайте дальше».

Если вам что-то не нравится в C++ и вы хотите это улучшить или донести свою боль до комитета по стандартизации, начните с сайта stdcpp.ru. Там люди обмениваются мыслями, желаниями и проблемами, связанными с развитием языка C++. Идеи обсуждаются, обрабатываются и некоторые из них становятся официальными предложениями для международного комитета. При этом рук не хватает, поэтому особенно ценная помощь — когда человек готов не только генерировать идеи, но и браться за написание предложений к ним (хотя бы черновиков).

Драматичным шёпотом: и таким человеком может стать каждый из вас!

Если оптимизация программ на С++ для вас не пустой звук, обратите внимание на C++ Russia 2021, где будет много всего интересного. Конференция пройдёт с 15 по 18 ноября 2021, информация и билеты на сайте.

ссылка на оригинал статьи https://habr.com/ru/company/jugru/blog/563988/

Платформа данных в Леруа Мерлен. Part 2. Обновления 2021 года: Flink и Superset

Итак, в 2021 год мы вступили со следующей архитектурой:

У нас есть DWH, в который мы различными путями укладываем CDC поток с большого количества источников, который обрабатываем с помощью процедур, запускаемых через Airflow и формируем DDS и витрины. Также у нас есть DataLake на S3, в котором лежит сырьё.

Мы добавили возможность работать с CDC нереляционных баз, таких как Mongo (тоже с помощью Debezium выгружали их в Kafka), начали обрабатывать канонические объекты – это, по сути, такие структурированные данные по строго принятым в компании схемам. Также мы добавили возможность работать с event streams, такими как clickstreams с онлайн-площадок, или эвентами CDP (customer data platform). После этого мы собирали их в формат parquet с помощью NiFi и выгружали в Yandex Object Storage, после этого с помощью Spark парсили и загружали в GreenPlum (через PXF). 

Точки роста

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

Пользовательский опыт

  • Оперативные отчеты

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

  • Сложность интеграции

Боль пользователей была в необходимости создавать CDC на стороне источников, проливать данные в Kafka, коммитить в CI DDL GreenPlum’а, рисовать в NiFi ETL процессы. При этом, некоторые пользователи не умели даже работать с GIT’ом, не говоря уже о разработке в локальном докере, запуске тестов в пайплайнах, DAG’ах Airflow и прочем.

  • Время обработки запросов

«Вчера мой SQL запрос обрабатывался 30 секунд, а сегодня уже 10 минут» – с такими словами начали приходить пользователи. Причина была простая – несмотря на то что у GreenPlum есть разграничения ресурсов по ресурсным группам и очередям, пользовательские запросы все равно замедлялись под большой нагрузкой, когда на кластере работали тяжелые ETL-процессы.

  • Прямые запросы (DirectQuery)

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

Административный опыт

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

  • Ресурсы

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

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

  • Контроль

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

  • Платформенность

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

В итоге мы решили сфокусироваться на создании оперативной отчетности и контроле за действиями пользователей. Эти задачи мы решили с помощью 2х новых компонентов архитектуры.

Апдейты 2021

Операционная аналитика на Flink

В начале, чтобы удовлетворить самую большую потребность пользователей (в операционных данных), мы решили создать новый сервис, который назвали «Операционная аналитика». Построили мы его на базе Flink (фреймворк потоковой обработки данных). Начиная с версии 1.11 с июля 2020 года в нем появился функционал для работы с CDC потоками, как-раз генерируемых Debezium. 

Также в версии 1.12 у Flink появилась возможность работать с CDC+Avro и со схемами, хранящимися в Confluent Schema Registry. 

В результате выполнения простых запросов на потоке можно получать постоянно обновляющиеся counter’ы. Мы проверили этот формат работы, он показал прекрасные результаты, и то, что на GreenPlum считалось часами, мы могли уже считать на потоке с помощью Flink, еще и в режиме near-realtime. Кейс этот очень нужный, так как, например, показатели по товарообороту бизнес интересуют с минимальной задержкой. 

При этом не все было так гладко. С какими трудностями мы столкнулись при работе Flink с построением операционной аналитики? Минусы операционной аналитики на Flink:

  • Один источник – один топик

У нас в Kafka данные хранятся таким образом: один источник – один топик. То есть по большому количеству таблиц в одном источнике используется один топик. И если нам необходимо в Flink посчитать данные только на одной таблице для одного источника, нам приходится читать весь поток по источнику.  

  • Нет метаданных по Debezium

Следствие предыдущего минуса – в текущей версии Flink невозможно получить метаданные из полей Debezium. То есть невозможно сейчас точно определить имя таблицы, считав имя сообщения из Kafka CDC потока. Ждем фикс.

  • Ограничения генерации схем

Если вы работаете с данными в avro, но не используете schema registry, а храните схему в заголовках сообщений, на текущий момент Flink не может генерировать схему на основе этого заголовка – необходимо в таком случае схему задавать заранее. 

Несмотря на эти недостатки, мы выявили для себя много плюсов операционной аналитики на Flink:

  • Near-realtime данные на CDC потоках

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

  • Поддержка SQL

Пользователи могут считать каунтеры, написав запросы на FlinkSQL – им не нужно изучать ни Scala, ни Java, никакие другие языки программирования, кроме SQL.

  • Стабильность

Flink – решение, которое существует на рынке давно. Возможность его кластеризации и контейнеризации позволяют нам строить отказоустойчивые HA-решения при правильном использовании savepoint’ов и checkpoint’ов. 

  • Настоящий стриминг

В отличии от Спарка – у флика т.н. “true”-streaming, а не микро батчи, что как раз и позволяет ему работать с потоками CDC.

  • Поддержка Avro Confluent schema registry 

Из коробки можно подключится к Confluent Schema Registry и забирать схемы сообщений из него:

FlinkSQL
CREATE TABLE test1(    `event_time` TIMESTAMP(3) METADATA FROM 'timestamp' VIRTUAL,    `lSequenceNumber` INT,    `Date` STRING,    `RequestType` STRING,    `TransactionDate` STRING,    `TransactionTime` STRING,    `Amount` DECIMAL(10, 4)  )  WITH (    'connector' = 'kafka',    'topic' = 'data.init.database.avro.test',    'properties.bootstrap.servers' = kafka0:9092,kafka1:9092,kafka2:9092, kafka3:9092,kafka4:9092',    'properties.group.id' = 'flink-group-test1',    'properties.security.protocol' = 'SASL_SSL',    'properties.sasl.mechanism' = 'SCRAM-SHA-256',    'properties.sasl.jaas.config' = 'org.apache.kafka.common.security.scram.ScramLoginModule required username="***" password="***";',    'properties.ssl.truststore.location' = '/home/client.truststore.jks',    'properties.ssl.truststore.password' = '***',    'scan.startup.mode' = 'earliest-offset',    'value.format' = 'debezium-avro-confluent',    'value.debezium-avro-confluent.schema-registry.url' = 'sr0:8081'   );
  • Удобный и подробный мониторинг

Для отслеживания состояния джоб флинк предоставляет подробный интерфейс мониторинга (кмк более простой в освоении, чем интерфейс спарка):

UI monitoring
UI monitoring
  • Большое число коннекторов.

За время существования Flink’а, для него было написано множество source и sink коннекторов, что позволяет реализовывать в своих ETL процессах интеграцию со множеством решений. Как пример можем привести clickhouse-sink коннектор, от коллег из ivi, который позволяет укладывать данные напрямую в CH.

Сравнивать Spark с Flink в контексте нашей задачи бессмысленно, т.к. killer-фичей для нас оказалась возможность работать с CDC потоками, чего не умеет делать спарк из коробки.

В итоге на данный момент мы имеем следующую структуру для near-realtime аналитики:

ETL для оперативных данных мы заменяем на Flink, развернув его в Kubernetes, данные по операционной аналитике мы загружаем в Yandex Object Storage с CH над ним.

Пользовательский опыт на Apache Superset

Мы внедрили Apache Superset – быстрый, легкий, интуитивно понятный веб-интерфейс для работы с данными и написания SQL-запросов. Он позволяет писать эти запросы к огромному количеству источников и визуализировать их в графиках и дашбордах.

Когда мы развернули его, мы нашли несколько недостатков:

  • Кодировка UTF-8

С кодировкой UTF-8 русские символы неправильно кодировались при выгрузке в CSV, и отображались т.н. краказябры. В текущей версии (1.2.0) это уже исправлено.

  • Неинтуитивные ограничения

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

  • Сложность подключения проприетарных источников

Проприетарные источники (Oracle, DB2) не так легко подключить, нужно ставить драйвера и пакеты. Например для Oracle в requirements-extra.txt:

cx_Oracle==8.2.0

и в Dockerfile
ADD https://download.oracle.com/otn_software/linux/instantclient/1911000/instantclient-basic-linux.x64-19.11.0.0.0dbru.zip /lib/oracle_instantclient_basic.zip  ADD https://download.oracle.com/otn_software/linux/instantclient/1911000/instantclient-sqlplus-linux.x64-19.11.0.0.0dbru.zip /lib/oracle_instantclient_sqlplus.zip  ADD https://download.oracle.com/otn_software/linux/instantclient/1911000/instantclient-sdk-linux.x64-19.11.0.0.0dbru.zip /lib/oracle_instantclient_sdk.zip  ENV LD_LIBRARY_PATH="/lib/instantclient_19_11:${LD_LIBRARY_PATH}"  RUN export PATH=$PATH:/usr/local/instantclient/bin && \      unzip /lib/oracle_instantclient_basic.zip -d /lib/ && \      unzip /lib/oracle_instantclient_sqlplus.zip -d /lib/ && \      unzip /lib/oracle_instantclient_sdk.zip -d /lib/ && \      echo /lib/instantclient_19_11/ > /etc/ld.so.conf.d/oracle-instantclient.conf && \      ldconfig
  • Требуются частые обновления

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

Но плюсы перекрыли все эти недостатки:

  • Единая точка входа

У нас появилась единая точка входа в платформу, появился контроль за пользователями и количеством их подключений, за написанными запросами. Нам в этом помог функционал user impersonation – когда пользователь заходит в Superset со своей LDAP учеткой, он, авторизовавшись однажды, получает доступ ко всем источникам под своим юзером.

  • Много источников

Нас интересовали в первую очередь GreenPlum, ClickHouse и Postgres. GP/PG работают из коробки, а для CH необходимо добавлять зависимости в requirements-extra.txt:

clickhouse-driver>=0.2.0

clickhouse-sqlalchemy>=0.1.6

и настроить свои URL подключения к CH в формате:

clickhouse+native://<user>:<password>@<host>:<port>/<database>[?options…]

Подробнее прочитать, как подключить Clickhouse к Supeset можно почитать тут. Но у него также есть коннекторы к Presto, Dremio, Druid, Hive, BigQuery, Vertica, Teradata, Exasol и прочим.

  • Удобная архитектура и интеграция с LDAP

Архитектура Superset нам оказалась понятной, масштабируемой, хорошо ложится k8s. LDAP подключается с добавлением в requirements-extra.txt 

python-ldap==3.3.1

и в конфиг superset/config.py:
-AUTH_TYPE = AUTH_DB  +AUTH_TYPE = AUTH_LDAP  +  +AUTH_USER_REGISTRATION = True  +AUTH_USER_REGISTRATION_ROLE = "LDAP_ROLE"  +  +AUTH_LDAP_SERVER = "ldaps://ad.ldap.contoso.com:636"  +AUTH_LDAP_SEARCH = "OU=contoso,DC=com"  +AUTH_LDAP_SEARCH_FILTER = ''  +AUTH_LDAP_BIND_USER = "CN=account,OU=Accounts,OU=Data_Platform,DC=contoso,DC=com"  +AUTH_LDAP_BIND_PASSWORD = "PASSWD"  +AUTH_LDAP_UID_FIELD = "sAMAccountName"  +AUTH_LDAP_USE_TLS = False  +AUTH_LDAP_ALLOW_SELF_SIGNED = False
  • Множество визуализаций

Начиная с версии 1.0.0 superset переехал на Apache Echarts c D3, появилось большое количество новых визуализаций. А кому не хватает встроенных, есть возможность создавать свои Viz плагины

  • Интеграция с DataHub

У DataHub есть интеграция автообновляемой меты с суперсетом — он может подтягивать список чартов и дашбордов.

  • Удобство использования

Вот так выглядит пользовательский интерфейс (SQL Lab) для написания запросов в Superset:

интерфейс SQLlab
интерфейс SQLlab

Он похож на обычные SQL IDE. Но, например, есть функционал для шаринга запроса, то есть пользователи могут сохранять запрос и делиться им с коллегами. Также есть выгрузка в csv, шедуллинг и множество других “плюшек”. 

  • Админский интерфейс

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

Admin interface
Admin interface
  • Удобный API

Как вишенка на торте – API у Superset позволяет нам как инженерам автоматизировать все обслуживание и применять CI/CD для пайпланинга выкатки и работы с ролями, пользователями и группами, чартами и дашбордами и т.д.

/swagger/v1
/swagger/v1

Мы также провели сравнение с конкурентами по критичным для нас показателям:

Criterio

Superset

Metabase

OmniDB

Zeppellin

Redash

PgWeb

SqlPad

PopSql

JackDB

Freeware

Yes

Partially

Yes

Yes

No

Yes

Yes

No

No

Cost

High

Low

High

High

Navigation

Yes

Yes

Yes

No

Yes

Yes

Yes

Yes

Yes

Open-Source

Yes

Yes

Yes

Yes

Yes

Yes

Yes

No

No

Customization

Python

Clojure

Python/Django

Java

Python

Golang

JS

No

No

Security integration

Yes

Yes (requires enterprise)

Yes

So-so

Yes

Yes

So-so (require proxy)

?

Yes

Supported datasources

Multiple (ODBC)

Multiple

Multiple (ODBC)

Multiple (JDBC)

Multiple

PostgreSQL-compatible

Multiple

Multiple

Multiple

Data visualization

Yes (rich)

Yes

Yes

Yes

Yes

No

Yes

Yes

?

Cache

+

GP Stability

OK

only BI —

OK

So-So

OK, but very very slow

Github commits/releases

5*

5*

3*, half year last release

4*, half year last release

2*, 2019 last release

4*

4*

?

?

Contributors

616

261

2

338

354

44

61

?

?

Главными критериями выбора в пользу SS для нас оказались — разработка на Python, активность развития, большое коммьюнити, свободные security фичи (интеграция с LDAP), скорость работы с Greenplum.

Итоговая архитектура изменений платформы: 

Появился презентационный слой – Superset (в k8s), слой хранения – GreenPlum (для аналитической отчетности) и ClickHouse (для операционной, с S3 Table Engine). Также мы в GreenPlum формируем витрины, выгружаем их в Postgres с помощью PXF и дополняем эти витрины оперативными данными. В Superset мы можем быстро получать данные и из Postgres и CH и строить на их основе оперативные дашборды.

Выводы

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

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

Последняя мысль – сотрудники это самая большая ценность для компании, особенно той,  которая хочет оставаться на передовой и внедрять технологии работы с данными. Грамотные специалисты могут провести RnD и запустить MVP буквально за пару недель.


В конце статьи хотелось бы анонсировать наше будущее выступление на Greenplum Community Meetup, которое пройдет 22 июля в 16:00 в формате онлайн:

https://cloud.yandex.ru/events/409

На нем мы расскажем про наш подход к observability и мониторингу Greenplum’а, про наши инструменты и инсайты, которые получаем от этого подхода. Записывайтесь, будет интересно!

В следующих статьях мы подробнее расскажем про опыт company-wide внедрения инструментов MLFlow, DVC и KubeFlow и опыт работы с ними.

ссылка на оригинал статьи https://habr.com/ru/company/leroy_merlin/blog/567874/

WebSecOps: изучаем веб-безопасность

.
.

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

Веб-приложения привлекают злоумышленников не меньше, чем заинтересованных клиентов. Сайты стали идеальной мишенью — доступны 24/7, уследить за действиями всех пользователей сложно, а сами веб-приложения могут содержать уязвимостей больше, чем весь остальной сетевой периметр компании. За примерами далеко ходить не нужно: пример раз, пример два.

Не спешите радоваться, если ваше веб-приложение еще не скомпрометировали. Возможно, злоумышленник уже готовится к приятной для него (и не очень — для вас) встрече. Вот только день X может принести компании «славу» в виде распространения конфиденциальных данных пользователей и утраченной репутации.

Почему так происходит?

  • ошибки, допущенные во время разработки или развертывания веб-приложения;

  • отказ от полноценного тестирования веб-приложения на наличие уязвимостей или недостаток опыта при его проведении;

  • пренебрежение средствами защиты и мониторинга для своевременного реагирования на инциденты ИБ.

И если компания придерживается позиции: «Да кому мы нужны?», то с большой долей вероятности ее инфраструктуру или уже скомпрометировали, или сделают это в скором времени.

Узнали себя? Это прекрасно. Первый шаг на пути решения проблемы — признать, что она существует. Ни к чему проходить все стадии принятия неизбежного и терять драгоценное время. Лучше сразу приступить к поиску выхода.

И у нас он есть — программа обучения WebSecOps, пополнившая линейку курсов этичного хакинга от Pentestit.

Что такое WebSecOps?

Специализация программы подготовки — разработка безопасных веб-приложений, самостоятельное обнаружение и эксплуатация уязвимостей, а главное — предотвращение и анализ инцидентов с помощью WAF.

Приятный бонус — обучение можно проходить дома, занятия проводятся дистанционно.

Теории много не бывает, но она составляет всего 20% обучения, остальные 80% — практика, практика и еще раз практика. Мы подготовились и разработали специальную лабораторию, имитирующую корпоративные веб-ресурсы. Будет сложно, но очень интересно.

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

На кого рассчитана программа подготовки?

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

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

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

Что будем изучать?

Начинать обучение всегда стоит с основ, поэтому на первых занятиях мы познакомимся с архитектурой веб-приложений и ее типичными уязвимостями. Разберемся с понятиями front-end и back-end, узнаем об их различиях. Это необходимо для понимания того, как вообще функционирует веб-приложение.

После этого перейдем к разбору распространенных уязвимостей веб-приложений, таких как SQLi/NoSQLi, XSS, XXE и т. д., разберемся с природой их возникновения, методами обнаружения, эксплуатации и защиты веб-приложения от них.

Изучим различный инструментарий для автоматизации анализа безопасности веб-приложений. В частности, рассмотрим:

  • Burp Suite и OWASP ZAP, предназначенные для анализа взаимодействия браузера пользователя с веб-приложением;

  • Различные сканеры уязвимостей веб-приложения и инструменты автоматизации, такие как Nikto, Wapiti, sqlmap, tplmap и т. д.

Отдельным блоком нашей программы обучения будет знакомство с WAF как со средством защиты веб-приложений. Здесь мы рассмотрим принцип его работы (как WAF защищает веб-приложения, от каких атак можно защититься, какие есть недостатки), проведем сравнение продуктов из бесплатного сегмента (Nemesida WAF Free, ModSecurity и Naxsi). А на примере полноценной версии Nemesida WAF выясним, почему машинное обучение — это круто, как его правильно настроить для защиты веб-приложения, а также как выявлять и анализировать инциденты, связанные с информационной безопасностью и атаками на веб-приложение.

С какими заданиями столкнемся в процессе обучения?

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

Начнем с простого

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

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

В адресной строке видим GET-параметр page, который принимает в качестве значения php-страницу.

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

Вполне вероятно, что данный параметр уязвим для LFI, и если мы правы, то сможем просмотреть локальные файлы сервера. Чтобы это проверить, подставляем путь до файла /etc/passwd в параметр page и действительно получаем содержимое нужного файла.

Так как по условию задачи в качестве Proof of Concept нам необходимо получить некий токен, то в файловой системе необходимо найти и прочитать соответствующий файл. И тут несколько вариантов — угадать точное расположение и название файла или при помощи LFI проэксплуатировать другую уязвимость.

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

Используя LFI, можно получить доступ к различным файлам на сервере, в том числе к журналам веб-сервера, например, access.log:

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

Теперь добавляем к запросу наш новый GET-параметр и проверяем результат:

После недолгих поисков находим искомый файл с токеном и читаем его содержимое:

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

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

Хотите защитить свой сайт или обучить сотрудников/разработчиков/специалистов ИБ (подчеркните нужное) находить веб-уязвимости и предотвращать атаки? Тогда записывайтесь на обучение и погружайтесь в мир информационной безопасности с Pentestit.

ссылка на оригинал статьи https://habr.com/ru/company/pentestit/blog/567734/

Экспериментируем с шаблонами литералов в TypeScript: как покрыть типами DSL

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

Иногда проверка различных идей носит чисто экспериментальный характер. Так, на волне нового релиза TypeScript я решил попробовать типизировать DSL запросов к MongoDB (синтаксис запросов довольно прост, но при этом функционален и широко известен).

Под катом — результат. Я бы написал «не пытайтесь повторить это в своем рабочем проекте», но вдруг какие-то мысли и способы применения TypeScript окажутся полезными и вам.

TypeScript 4.1


В версии TypeScript 4.1, вышедшей в ноябре 2020 года, добавили поддержку шаблонов литералов. В мире JavaScript есть множество применений этой возможности TS, но в основном шаблоны литералов будут полезны в ситуациях, которые могут возникнуть при принятии различных соглашений. Например, когда примеси модифицируют имена свойств объекта стандартным образом.

Если вы еще не знакомы с новыми возможностями, рекомендую взглянуть на описание к релизу TS 4.1. Помимо поддержки шаблонов литералов, в новой версии сняли многие ограничения на рекурсию в условных типах. Это может быть удобно при сглаживании массивов или выводе результата из цепочки обещаний (promise chain).

Идея, которая у меня возникла, может быть выражена вопросом: если у нас есть шаблоны литералов, которые позволяют накапливать информацию, а также условия и рекурсия, которые позволяют эту информацию сглаживать, можем ли мы покрыть типами какой-нибудь простенький DSL с собственной нотацией?

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

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

Перед тем как погрузиться, очень желательно иметь представления о способах модификации типов (есть перевод).

Основы


Литеральным типом может быть представлена строка с известным значением. В TS 4.1 добавили средства для их конкатенации. Выглядит это примерно так:

Также добавили еще несколько методов для изменения регистра букв, чтобы можно было делать преобразования типа ‘value’ → ‘getValue’. Но гораздо интереснее то, что вывод типов в условных выражениях работает и с шаблонами литералов. К примеру, если мы хотим получить из строки кортеж, выглядеть это будет так:

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

Тип never заставляет компилятор отбросить дерево решений, в котором строка не совпала с заданным шаблоном. Чтобы разобрать строку произвольной длины, необходимо воспользоваться рекурсией. Важно учитывать, что существует ограничение на вложенность рекурсии. При этом ограничение работает не идеально, так как мне удавалось несколько раз зациклить языковой сервер TS. К тому же эти вычисления будут чувствительны к входящим данным, поэтому важно тщательно исследовать приемлемость подобных конструкций, особенно для библиотечного кода. Но сами вычисления описываются тривиальным образом:

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

В TypeScript 4.0 для сглаживания кортежей добавили spread-оператор, удобнее всего его будет применить одновременно с разбором строки. Для этого достаточно немного модифицировать блок формирования результата:

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

Валидация объектов, описанных в точечной нотации


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

Запросы к MongoDB отправляются в JSON-формате, но главным препятствием для статической проверки типами становится точечная нотация (dot notation). Например, у нас есть коллекция с такими моделями:

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

В данном случае база данных вернет ошибку, потому что оператор $text можно применять только к строкам. В идеале хотелось бы иметь средство, которое предупредит эту ошибку еще до нажатия кнопки «Сохранить» в редакторе.

Для всех, кто работал с TS, должна быть достаточно ясна проблема описания типа объектов подобной конфигурации. Некоторая функциональная обвязка, позволяющая через pipe/chain формировать итоговый запрос, в этом случае выглядит несколько более предпочтительной. Кроме того, с ее помощью будет гораздо проще выставить ограничения типов. Однако сохранение декларативности языка запросов может тоже иметь свои плюсы — например, это практически свободный доступ к уже существующей документации, с разбором множества ситуаций. В любом случае, я хочу больше сосредоточиться на самой задаче метаописания некоторого языка средствами TypeScript, без учета целесообразности прочих вариантов.

Язык запросов MongoDB в самом общем случае представляет из себя смешивание (merge) дерева хранимого документа и операторов доступа к узлам этого дерева. Также допускается точечная нотация для сквозного доступа через несколько уровней вложенности.

Операторы всегда начинаются с символа $, и каждый оператор может иметь свою структуру и типы принимаемых параметров. Есть классические логические операторы $not, $and, $or, операторы сравнений $eq, $gt, $lt, оператор поиска по тексту $text и еще огромное множество других — с агрегациями, проекциями, вычислениями и всем чем угодно. Поэтому, конечно, я не буду здесь рассматривать полное подмножество языка, а только самую базовую его часть.

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

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

В общем смысле путь обхода формируется через конкатенацию ребер дерева. Для нас в роли ребер выступают имена свойств объектов, а конкатенация — это последовательная запись имен, разделенных точкой (root.node.leaf). В моем случае еще используется специальный управляющий символ $, указывающий на переход к элементу массива.

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

Задача с обходом дерева по заданному пути выглядит достаточно очевидной, поэтому с нее и начнем. Кстати, нашел на Хабре хороший материал по этой теме, с разбором множества неочевидных моментов.

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

Теперь давайте определимся с интерфейсом создания запроса:

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

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

Была гипотеза, что нужного поведения можно попробовать добиться через ThisType<⁄T> или другие вложенные контексты. Но в этом случае я все равно не вижу, каким образом можно будет поднять ошибки валидации к месту объявления запроса. Пока я вижу только то, что валидация должна порождаться контекстом, и это принципиальное ограничение. Более внятного обоснования у меня нет, так что было бы интересно послушать вас. Может, у вас, читатели Хабра, есть какие-то теоретические обоснования или опровержения этого?

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

По своей сути этот обход является сглаживанием, аналогичным тому, что выполняет spread-оператор, о котором я писал в начале статьи. Еще проще это можно назвать flatMap.

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

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

Теперь валидация запроса не должна стать проблемой.

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

Или вот этого:

Посмотреть полный код и то, как он работает, можно здесь.

Плюсы и минусы подобных решений


Начну с минусов:

  1. Разобраться в этих вычислениях непросто. В них все логично, но для быстрого погружения, нужен специфический опыт.
  2. Ввиду сложности вычислений время компиляции может сильно зависеть от входных данных. Лично мне не до конца ясно, каким образом компилирует TypeScript и как под него писать оптимизированный генерирующий код. Так что вполне вероятно, что по мере добавления новых контекстных данных время компиляции будет заметно увеличиваться.

А теперь к плюсам:

  1. В первую очередь это, конечно, внешние интерфейсы. Проверка задается прям в сигнатуре функции, и это очень удобно при росте количества запросов. Добавим к этому рефакторинг и все прочее, что нам обычно дают типы.
  2. Особенности определения операторов обращения к данным. Такая модель может достаточно долго расширяться без потери наглядности. Плюс TS позволяет комбинировать подобные интерфейсы самыми разнообразными способами. Поэтому пока мне кажется, что внедрение типов для тех же агрегаций не должно стать существенной проблемой.
  3. Методы обхода деревьев получились во многом универсальными. Также удалось выяснить, что нет существенных ограничений при движении по дереву в любом из направлений. А значит, можно будет работать сразу с несколькими контекстами — например при объявлении переменных в теле запроса или при связывании нескольких коллекций. Здесь, конечно, необходимы еще различные проверки этих возможностей, но, в любом случае, кажется мне разрешимым.

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

ссылка на оригинал статьи https://habr.com/ru/company/selectel/blog/567758/

Как, где и зачем искать IT-наставника?

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

Важная особенность GetMentor — его некоммерческая суть. Комиссии за проведенные сессии менторинга не существует, а часть наставников и вовсе не берет деньги за помощь. О том, как родился проект, в чем его цель и чем может помочь ментор, мы поговорили с создателем проекта Георгием Могелашвили? @glamcoder 

Расскажи о проекте: как он возник, в чем его суть?

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

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

Но поскольку я человек ленивый, эта история долго откладывалась. Я был уверен, что придется писать сайт, создавать сервис, искать для этого программистов. Со всем этим заморачиваться ни охоты, ни денег не было. Поэтому я носил эту идею в голове, толком не применяя. Но в какой-то момент я наткнулся на несколько инструментов No-code. Они позволяют без участия программистов, с помощью визуальных блоков накидать логику и визуальную часть сайта.

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

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

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

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

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

Суть проекта в том, что наставник, как человек с большим опытом в определенной области, помогает другому решить проблему наименее болезненным способом за разумный срок.

Скольким людям проект уже помог?

Мы получили около 800 заявок на менторство. Больше половины из этих заявок уже помечены, как завершенные, а это значит, помощь была оказана. 

Расскажи о сотрудничестве с Онтико. Почему именно наши менторы помогают обратившимся в GetMentor людям?

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

Я знаю, насколько высокий отбор проходят спикеры. Для меня Онтико — синоним знака качества, и я не сомневаюсь в их экспертизе.

Помочь с развитием нашего проекта мне предложил Олег Бунин. И для меня очень ценно, что Онтико не просто делает бизнес, а помогает развиваться коммьюнити, в том числе вкладываясь в проекты, подобные GetMentor. За счет этого вы поддерживаете всю экосистему в целом.

Какие знаменитые менторы пришли в GetMentor? Чем они занимаются, чем могут помочь? 

Мне трудно выделить кого-то отдельно. Все наставники, которые сотрудничают с конференциями Онтико, классные. 

Один из моих личных фаворитов — Роман Ивлиев. Он глава программного комитета TeamLead Conf, поэтому я представляю уровень его знаний. Это очень умный человек с огромным опытом решения проблем любого рода. Общение с ним помогает мне стать лучшей версией себя в плане руководства командами и людьми, да и просто как человеку.

Хочу отметить и Фрола Крючкова. Он — лидер проекта по количеству заявок на менторство. Фрол работает тимлидом в Авито, и к нему обращаются люди по самым разным вопросом, включая развитие хард- и софт-скиллов.

Еще один классный ментор — Катя Семенова Она помогает разобраться в вопросах, связанных с тестированием. Эта область, на мой взгляд, очень недооценена и традиционно получает меньше внимания, чем разработка. Катя из редкой ниши QA-инженеров, и она помогает развивать сообщество тестировщиков. 

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

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

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

Почему менторство важно?

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

Конечно, вы можете пройти лабиринт с картой в одиночестве. Но если у вас есть проводник, вы найдете выход гораздо быстрее. Наставник поможет вам пройти путь из точки А в точку В так, чтобы набить как можно меньше шишек. А те, что набьете, помогут вам вырасти. 

Насколько удобно общаться с менторами онлайн? 

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

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

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

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

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

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

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

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

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

Для меня это действительно очень важно, и это одна из основ сервиса — сделать наставничество доступным. Убирая историю с оплатой и наше посредничество в этом, я считаю, мы помогаем большому количеству людей общаться с экспертами. Есть много людей, которые только пришли в IT. Например, студенты не очень много зарабатывают и пока не готовы тратить средства на менторство, а помощь им нужна. И у нас они могут найти человека, который готов помочь бесплатно.

А были ли ситуации, когда обращались не джуниоры, а люди, которые находятся на более высоких позициях?

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

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

Ты говорил, что твоя задача в том числе — продвигать наставничество. Где можно узнать про это больше?

Мы активно развиваем свой Telegram-канал, в котором регулярно делимся рассказами о наставниках, их кейсах и другими историями из жизни. Там же будут анонсы митапов и других активностей, где мы участвуем или которые организовываем. 

Картинки, использующиеся в тексте, мы нашли здесь.

Ближайшая конференция Онтико — Saint TeamLead Conf 2021 — пройдет 16 и 17 сентября 2021 в DESIGN DISTRICT DAA in SPB. Приобрести билеты на нее вы можете уже сегодня.

Будем рады видеть вас на конференциях 2021 года!

ссылка на оригинал статьи https://habr.com/ru/company/oleg-bunin/blog/567672/