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

Все категории

Сверхточный Raspberry PI Stratum 1 NTP сервер

07.07.2020 12:09:02 | Автор: admin
В этой статье я расскажу, как собрать Stratum 1 NTP сервер на Raspberry PI для синхронизации времени за скромную сумму и навсегда забыть о проблемах, связанных с не совпадающим временем на всех ваших устройствах. А самое главное, он будет давать результат на два порядка точнее, чем обычный сервер.

В предыдущей статье, посвященной синхронизации времени по радио и СРНС (системы радионавигационной связи), я не успел рассказать про выбор приёмника GPS / ГЛОНАСС с выходом PPS. Между тем от этого зависит точность приёма сигнала, величина может составить от одной миллисекунды до нескольких микросекунд и зачастую это имеет решающее значение.

Для самого точного приема сигнала времени нужен приёмник GPS / ГЛОНАСС с выходом PPS. Дело однако в том, что на российском рынке не просто раздобыть устройство с такими характеристиками по доступной цене. Много таких моделей давно уже перестали выпускать, а в заброшенных интернет магазинах с версткой 1990-х остались лишь их описания с предложением подписаться на уведомление при поступлении товара.


Полный список протестированного GPS оборудования можно найти на GitLab ресурсе NTPSec. Не трудно заметить, что незначительное число представленных в списке устройств имеют отметку 3-4 звезды и опцию PPS. Таким образом, в шорт-лист попадают следующие приёмники.

  • Garmin GPS-18, не USB *** (приблизительная цена 10 тыс. р.)
  • GlobalSat MR-350P ****
  • Jackson Labs FireFly-II ***
  • Magellan Thales AC12 ***
  • Motorola Oncore GT+ ***
  • Navisys GR601-W ****
  • SkyTraq SKG16B ****
  • Trimble Lassen IQ ***
  • u-blox ANTARIS LEA-4T ***
  • u-blox EVK 6H ****
  • u-blox LEA SQ ****

4* Отличная производительность: gpsd распознает приёмник быстро и надежно, а отчеты сформировано полностью и правильно.

3* Хорошая производительность: gpsd с незначительными проблемами или задержкой распознаёт устройства, но отчеты сформировано полностью и правильно.

Если вас не пугает цена этих моделей, а также нет большого желания возиться с железками, можете не читать дальше. Приемник, подключенный к серверу по USB, или RS232 интерфейсу обеспечит гораздо большую точность определения времени, чем NTP сервер, работающий по tcp/ip. Но если путь самурая вам не чужд, тогда давайте собирать свой Raspberry PI NTP сервер с GPS синхронизацией времени.

Собираем Raspberry PI


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

  1. Плата Raspberry Pi 4 Model B, 4 GiB ОЗУ (6200 руб.);
  2. Корпус, например такой (890 руб.);
  3. Micro SD карта на 32 GiB, можно и 16 GiB; (540 руб.)
  4. GPS модуль на чипе u-blox NEO-M8 (1700 руб. с антенной);
  5. GPS антенна на 15 dB;
  6. Паяльник.

Вообще-то, u-blox NEO-M8 оснащен UART интерфейсом, но для PPS выхода необходимо припаять pin-3 на GPS модуле к соответствующему GPIO коннектору на плате Raspberri Pi. Модуль швейцарской компании завоевал популярность у специалистов и это не случайно, характеристики говорят сами за себя.

  • Поддерживаемые СРНС: BeiDou, Galileo, GNSS; GPS/QZSS, GLONASS;
  • Напряжение питания: 2.7...3.6 В;
  • Интерфейсы: UART, USB, SPI, DDC, I2C;
  • Поддерживаемые протоколы: NMEA 0.183 version 4.0, UBX (binary), RTCM 2.3;
  • Чувствительность при обнаружении: -167 дБм;
  • Чувствительность при слежении: -160 дБм;
  • Время холодного старта: 26 с;
  • Время горячего старта: 1.5 с;
  • Потребляемая мощность: 35 мВт;
  • Рабочая температура: -40...+85 С;
  • Размеры: 16х12.2х2.4 мм

В такой конфигурации с новейшим оборудованием примерная общая цена Raspberry PI в собранном виде составит 9330 руб. Можно сэкономить, купив Raspberry PI 3, или четверку с 2 GiB ОЗУ. Можно еще сэкономить на GPS чипе, u-blox NEO-6M с антенной стоит около 650 руб. Тогда цена NTP сервера упадет до 5500 руб.


GPS/Глонасс модуль UBLOX NEO 8M

Может возникнуть вопрос, для чего нужны все эти капиталовложения и какую точность обеспечивает тот, или иной способ синхронизации времени. Небольшая сводная табличка для справки.
Источник сигнала времени Погрешность
GPS с атомными часами 50 nSec
KPPS 1 Sec
PPS 5 Sec
Интерфейс USB 1.1 1 mSec
Интерфейс USB 2.0 100 Sec (100000 nSec)
NTP по сети ~30 mSec

Kernel PPS (KPPS) отличается от PPS тем, что использует функцию ядра Linux / Unix для точной временной отметки изменения состояния в строке PPS. Обычный же PPS реализован в user-space. Если ядро Linux поддерживает KPPS через API RFC 2783, gpsd воспользуется им для увеличения точности.

Во многих дистрибутивах Linux имеется пакет pps-tools, который обеспечивает поддержку KPPS и устанавливает timepps.h заголовочный файл. Обязательно установите этот пакет.

(1:1146)$ sudo emerge -av pps-toolsLocal copy of remote index is up-to-date and will be used.These are the packages that would be merged, in order:Calculating dependencies... done![binary   R    ] net-misc/pps-tools-0.0.20120407::gentoo  0 KiBTotal: 1 package (1 reinstall, 1 binary), Size of downloads: 0 KiBWould you like to merge these packages? [Yes/No] 

Таким образом, подключив GPS приёмник с PPS выходом по USB мы получаем 300-кратное повышение точности синхронизации времени. Чтение с чипа GPS на плате в режиме KPPS даёт прирост точности еще на два порядка.

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


Raspberry Pi GPS/RTC Expansion Board

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

Установка ОС


Существует Raspberry PI OS, а. k. a. Raspbian, можно просто пойти по ссылке, скачать свежую версию и установить её. Многие так и делают, но давайте вспомним, что Raspberry PI 4 поддерживает 64-битную операционную систему, в то время как Raspberry PI OS пока имеет лишь 32-битные модификации Debian Linux для архитектуры Arm.

Существует такая точка зрения, что на 64-битная ОС неоправдана на Raspberry PI 4, так как нет возможности обеспечить прирост производительности из-за особенностей архитектуры и сборки. Мне эта точка зрения представляется сомнительной, об этом уже писали на Хабре 64-битная ОС быстрее.

Существует порт Debian Linux для архитектуры arm64, однако дистрибутив Убунту для Raspberry PI имеет внятную страницу и инструкцию. На странице находим дополнительное подтверждение тому, что лучше выбрать 64-битную ОС.



Инвентарь для установки:

  • Raspberry Pi 4;
  • USB-C кабель питания для Pi 4;
  • Micro SD карта с установочным образом Убунту;
  • Монитор с выходом HDMI;
  • Кабель MicroHDMI;
  • USB клавиатура.

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

sudo dd if=/path/to/ubuntu-core-arm64.iso of=/dev/mmcblk0 status=progress

Точное название устройства видно в выводе dmesg при обнаружении нового устройства.

PM: Adding info for No Bus:179:0device: 'mmcblk0': device_addPM: Adding info for No Bus:mmcblk0

Вставив Micro SD карту, подключив HDMI-монитор, USB-клавиатуру, и кабель питания загружаетесь в Ubuntu Server на Raspberry Pi. Имя пользователя и пароль по умолчанию ubuntu.

Настройка NTP сервера


  1. Если Raspberry PI включен в консольном режиме (headless), то для начала необходимо определить IP адрес устройства. С рабочей станции наберите следующую команду.

    (1:1151)$ arp -na | grep -i "dc:a6:32"
    

    Ели же Pi подключен к HDMI монитору и USB клавиатуре, пропустите шаги 1-2 и переходите сразу к установке пакетов.
  2. Подключитесь по ssh

    (1:1152)$ ssh ubuntu@<Raspberry Pis IP address>
    
  3. Установите необходимые пакеты.

    user@server ~$ sudo apt-get install aptitudeuser@server ~$ sudo aptitude install wpasupplicant gpsd chrony
    
  4. Настройте Wi-Fi соединение с помощью wpasupplicant.
  5. В Linux UART0 интерфейс Pi представлен файлом устройства /dev/ttyAMA0. Для того чтобы освободить UART0 интерфейс для GPS приёмника нужно поменять параметры загрузки ядра Linux. Необходимо отключить console=ttyAMA0,115200, заменив на console=tty1. Для этого в файле /etc/default/grub надо поменять GRUB_CMDLINE_LINUX_DEFAULT. Если существует файл, /boot/config.txt, в нем также можно задать те же опции.

    Raspberry Pi 4 имеет 6 UART-ов
    Название Тип Устройство Назначение
    UART0 PLO11 /dev/ttyAMA0 вторичный (Bluetooth)
    UART1 mini UART /dev/ttyS0 основной
    UART2 PLO11
    UART3 PLO11
    UART4 PLO11
    UART4 PLO11
    По умолчанию UART2-5 выключены.

    Как видно из названия, UART0 полноценный серийный порт и он имеет более высокую производительность, чем обрезанный UART1, он же mini UART. Поэтому будет не лишним перевести Bluetooth на UART1 с тем, чтобы основной поток данных шел через UART0. Для этого в /etc/default/grub, или /boot/config.txt ставим enable_uart=1.
  6. В файле /etc/defaults/gpsd следует выставить.

    DEVICES="/dev/ttyAMA0 /dev/pps0"GPSD_OPTIONS="-n"USBAUTO="false"
    
  7. Запустите, или перезапустите gpsd.

    user@server ~$ sudo /etc/init.d/gpsd startuser@server ~$ sudo /etc/init.d/gpsd restart
    
  8. Проверка работы модуля GPS.

    user@server ~$ cat /dev/ttyAMA0user@server ~$ cgps -suser@server ~$ ppstest /dev/pps0
    
  9. Отредактируем файл /etc/ntp.conf.

    Все строки, содержащие сетевые публичные Stratum 1, 2 NTP сервера (такие, как pool [0-9].subdomain.pool.ntp.org) следует закомментировать, чтобы использовать лишь GPS/PPS источники данных.

    # GPS Serial data reference (NTP0)server 127.127.28.0 minpoll 4fudge 127.127.28.0 flag1 1 time1 0.9999 refid GPS #flag1 - PPS on
    

    # GPS PPS reference (NTP1)server 127.127.22.0 minpoll 4fudge 127.127.22.0 flag3 1 refid PPS #flag3 - enable KPPS API
    

    Верхняя запись NTP0 указывает на универсальный источник времени, доступный почти на всех устройствах GPS. Нижняя запись NTP1 определяет гораздо более точный PPS источник.
  10. Перезапустите ntpd

    user@server ~$ sudo /etc/init.d/ntpd restart
    

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



Подробнее..

Перевод Изучаем навязчивые и скрытные программы, отслеживающие действия работников

07.07.2020 10:10:24 | Автор: admin


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

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

Это неправильно. Когда дом становится офисом, он не перестаёт быть домом. Работники не должны подвергаться несанкционированной слежке, или чувствовать себя под колпаком у себя дома только ради того, чтобы не потерять работу.

Что они умеют?


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

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

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

Некоторые следилки идут ещё дальше, дотягиваясь до окружающего работника физического мира. Компании, предлагающие ПО для мобильных устройств, почти всегда включают в него отслеживание перемещений по GPS. По меньшей мере два сервиса StaffCop Enterprise и CleverControl позволяют работодателям тайно включать веб-камеры и микрофоны в устройствах их работников.

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

Видимая слежка


Иногда работники могут видеть отслеживающую их действие программу. У них может быть возможность отключать слежку, и это часто оформляется как вход в офис и выход из офиса. Естественно, если работник выключает программу, это будет видно работодателю. К примеру, программа Time Doctor даёт работникам возможность удалять определённые снимки экрана, сделанные во время их работы. Однако удаление этого снимка удалит и связанное с ним рабочее время, поэтому работникам зачтётся только то время, которое отслеживалось программой.

Работникам могут дать доступ к определённой части информации, собранной об их работе. Компания Crossover, предлагающая продукт WorkSmart, сравнивает его с фитнес-трекером для работы за компьютером. Его интерфейс позволяет работникам смотреть на выводы системы, касающиеся их активности, представленные в виде графиков и диаграмм.

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

Невидимая слежка


Большая часть компаний, создающих видимое для пользователя следящее ПО, также делают продукты, пытающиеся спрятаться от тех людей, чью деятельность они отслеживают. Teramind, Time Doctor, StaffCop и другие производят следилки, максимально усложняющие их обнаружение и удаление. С технической точки зрения эти программы ничем не отличаются от stalkerware шпионского ПО для слежки за людьми без их ведома. Некоторые компании даже требуют от сотрудников перед установкой своих программ определённым образом настроить свои антивирусы, так, чтобы они не обнаруживали и не блокировали это ПО.


Часть процедуры регистрации в сервисе TimeDoctor, где работодатель выбирает между видимой и невидимой слежкой.

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

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

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

Распространённые возможности слежки в ПО для мониторинга



Насколько распространены следилки?


Бизнес слежки за работниками не нов, и ещё до вспышки пандемии он был довольно большим. И хотя оценить распространённость следилок достаточно сложно, они без сомнения стали более популярными после того, как работники вынуждены были перейти на удалённую работу в связи с коронавирусом. Компания Awareness Technologies, владеющая InterGuard, заявляет, что за первые несколько недель вспышки расширила пользовательскую базу на 300%. Многие из изученных нами производителей используют коронавирус в рекламных заявлениях.

Следилки используют одни из крупнейших компаний. Среди клиентов Hubstaff числятся Instacart, Groupon и Ring. Time Doctor заявляет о наличии 83 000 пользователей; среди её клиентов Allstate, Ericsson, Verizon и Re/Max. ActivTrak используют более 6 500 организаций, включая Аризонский государственный университет, университет Эйморе и администрации городов Денвер и Малибу. Такие компании, как StaffCop и Teramind не раскрывают информацию о клиентах, но заявляют, что их области деятельности включают в себя здравоохранение, банковские услуги, моду, производство и кол-центры. По отзывам клиентов, использующих следилки, можно судить о том, каким образом они используют эти программы.

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

Для чего используются собранные данные?


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

К сожалению, многие варианты использования дают работодателям слишком большую власть над работниками. Вероятно, наибольший класс изученных нами продуктов разработан для отслеживания продуктивности или усовершенствованного отслеживания времени то есть, записи всего, что делают работники, с целью убедиться, что они работают достаточно усердно. Некоторые компании обставляют своё ПО так, будто это благо и для менеджеров, и для работников. Они утверждают, что сбор информации о каждой секунде рабочего дня сотрудника помогает не только боссам, но и самим работникам. Иные производители, например, Work Examiner и StaffCop, прямо рекламируют свои услуги менеджерам, не доверяющим своим работникам. Такие компании часто рекомендуют строить стратегии увольнения или премирования на основании метрик, выдаваемых их продуктами.


Рекламный материал с домашней страницы Work Examiner

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

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

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


С сайта Work Examiner: запись нажатий клавиш что они там печатают?
Запись нажатий записывает все нажатия клавиш, сделанные пользователем. Вы увидите, что он печатал в любой программе, будь то мессенджер, сайт, веб-почта, или MS Word. Можно перехватывать даже пароли, вводимые в программах и на веб-сайтах!


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

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

Что можно с этим сделать?


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

Это должно поменяться. Вместе с законами о конфиденциальности потребительских данных штаты должны защищать работников от их работодателей. Для начала:
  • Слежка за работниками, даже использующими принадлежащие работодателям устройства, должна проводиться по необходимости и быть пропорциональной.
  • Инструменты должны минимизировать количество собираемой информации, и избегать извлечения таких личных данных, как персональные сообщения и пароли.
  • У работников должно быть право знать, какую именно информацию собирают менеджеры.
  • У работников должно быть право на юридическую помощь и возможность засудить работодателей, нарушающих законы о защите частной жизни.


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

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

Наконец, работники могут опасаться заговаривать о слежке за ними, из боязни вылететь с работы в период рекордной безработицы [в США безработица составляет более 11%]. Выбор между навязчивой чрезмерной слежкой и безработицей сложно назвать выбором.

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

Магия WebPush в Mozilla Firefox. Взгляд изнутри

07.07.2020 10:10:24 | Автор: admin

Безусловно одной из самых популярных технологий доставки оповещений на устройства пользователей являются Push уведомления. Технология такова, что для её работы необходим постоянный доступ к интернету, а именно доступ к серверам, на которых регистрируются устройства пользователя для получения уведомлений. В данной статье мы рассмотрим весь спектр механизмов технологии WebPush уведомлений, спрятанных за словами WebSocket, ServiceWorker, vapid, register, broadcast, message encryption и т.д. Основной причиной побудившей меня к реверсу и изучению механизма, являлась необходимость доставки уведомлений мониторинга на рабочие места техподдержки, находящиеся в закрытом сегменте сети без доступа в интернет. И да, это возможно! Подробности под катом.


Disclaimer


В статье рассматривается режим доставки уведомлений пользователям в рамках использования браузера Mozilla Firefox. Это связано с тем, что на данный момент это единственный продукт позволяющий менять настройки push серверов используемых по умолчанию. Настройки браузеров Google Chrome, Chromium и производных в целях безопасности жёстко "зашиты" производителем в коде продукта.


Статья делится на две части


  • Теоретическая информация
  • Практические заметки для реализации механизма WebPush уведомлений

Используемые технологии и термины


WebSocket


Транспортным ядром системы Push уведомлений является протокол WebSocket, позволяющий в рамках стандартного HTTP/HTTPS подключения к Web серверу установить постоянный двусторонний канал связи между клиентом и сервером. В рамках установленного канала связи могут использоваться любые, в том числе бинарные, протоколы клиент-серверного взаимодействия заложенные разработчиками сервиса.


ServiceWorker


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


VAPID


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


WebPush


Механизм доставки сообщений до получателя.
Набор документов и спецификаций по WebPush


Workflow


Документации по WebPush довольно много (см. спойлер), но она существует только в парадигме
Client <-> Push Service <-> Application


Подробные спецификации по работе механизма в продуктах Google и Mozilla

Модель взаимодействия предполагает следующую схему.
image


Таким образом создаётся впечатление, что для работы механизма как минимум нужны специализированные сервисы регистрации и приёма/доставки уведомлений, а для отправки сообщений обязателен VAPID, спецзаголовки и ключи. Документация не описывает некоторых внутренних механизмов взаимодействия Push сервера и клиента, которые реально влияют на работу.
Попробуем рассмотреть все процессы подробно.


Фаза обработки сообщения


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


Блок кода расставил все точки над И
    try {      reply = JSON.parse(message);    } catch (e) {      console.warn("wsOnMessageAvailable: Invalid JSON", message, e);      return;    }    // If we receive a message, we know the connection succeeded. Reset the    // connection attempt and ping interval counters.    this._retryFailCount = 0;    let doNotHandle = false;    if (      message === "{}" ||      reply.messageType === undefined ||      reply.messageType === "ping" ||      typeof reply.messageType != "string"    ) {      console.debug("wsOnMessageAvailable: Pong received");      doNotHandle = true;    }    // Reset the ping timer.  Note: This path is executed at every step of the    // handshake, so this timer does not need to be set explicitly at startup.    this._startPingTimer();    // If it is a ping, do not handle the message.    if (doNotHandle) {      return;    }    // A whitelist of protocol handlers. Add to these if new messages are added    // in the protocol.    let handlers = [      "Hello",      "Register",      "Unregister",      "Notification",      "Broadcast",    ];    // Build up the handler name to call from messageType.    // e.g. messageType == "register" -> _handleRegisterReply.    let handlerName =      reply.messageType[0].toUpperCase() +      reply.messageType.slice(1).toLowerCase();    if (!handlers.includes(handlerName)) {      console.warn(        "wsOnMessageAvailable: No whitelisted handler",        handlerName,        "for message",        reply.messageType      );      return;    }    let handler = "_handle" + handlerName + "Reply";

Ни одно сообщение отправленное через websocket в сторону браузера не будет обработано, если оно не является системным сообщением проверки доступности конечной стороны "{}" или ответом на запрос от Push сервера. Это означает, что Push сервер не имеет никакого способа воздействия на работу клиентской стороны, кроме проверки её доступности. Аналогично, кроме 5 типов ответных сообщений, ничего обработано не будет.


Фаза инициализации


При запуске браузера Firefox, его внутренний механизм автоматически инициирует соединение с WebSocket(WS) сервером находящимся в системной настройке dom.push.serverURL с сообщением следующего формата.


{  "messageType": "hello",  "broadcasts":    {      "remote-settings/monitor_changes": "v923"    },    "use_webpush": True}

При первичной инициализации соединения(первый запуск браузера после установки/запуск нового профиля), поле "uaid" отсутствует, что является сигналом Push серверу о необходимости регистрации нового идентификатора. Как мы видим в разделе "broadcasts" присутствует некая пара "remote-settings/monitor_changes": "v923". Данная пара используется как буфер для хранения информации, отправляемой в сторону сервера при установлении соединения. В продукте Mozilla autopush, промышленной версии webpush сервера используемого на стороне серверов Mozilla, данная переменная используется как идентификатор последнего полученного пользователем сообщения из глобальной очереди сервера. Об изменении данного идентификатора мы поговорим позже. Итак, после принятия сообщения от клиента, сервер отвечает сообщением следующего вида


{  "messageType": "hello",  "status": 200,  "uaid": "b4ab795089784bbb978e6c894fe753c0",  "use_webpush": True}

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


Фаза регистрации


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


  • Проверка разрешения пользователя на получение информации
  • Регистрация ServiceWorker
  • Получение параметров подписки
  • Формирование ключей шифрования для обслуживания подписки

Проверка разрешений пользователя на получение информации


На данном этапе браузер перед установкой ServiceWorker, запрашивает пользователя и системные настройки: "готов ли пользователь получать сообщения о подписке?"
В случае одного из отказов, установка ServiceWorker прерывается


Регистрация ServiceWorker


Как мы ранее оговаривали, ServiceWorker это автономная страница с обработчиками событий в отдельном пространстве браузера недоступном пользователю.
На работу с этим компонентом накладываются довольно серъёзные ограничения:


  • Загрузка компонента ServiceWorker должна производиться через защищённое соединение(HTTPS), либо в целях отладки с localhost. Возможно включение флагов на "небезопасное" использование внешних ресурсов, но это не рекомендуется
  • соединение WebSocket должно устанавливаться по защищённому соединению(WSS), либо в целях отладки по обычному WS соединению с localhost
  • если в локальной сети имя сервера(ресурса) с которого происходит регистрация ServiceWorker, отличается от полного fqdn ресурса на котором находится ServiceWorker, то будет вызвано исключение о небезопасном вызове
    Жизненный цикл ServiceWorker от Google
    Жизненный цикл ServiceWorker от Mozilla

Процесс подписки


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


  • получение публичного ключа шифрования с Web сервера, являющегося промежуточным звеном между Push сервером и приложением осуществляющем отправку сообщений
  • формирование браузером ключей шифрования для защиты сообщений
  • вызов механизма подписки через канал WebSocket и получение точки для отправки сообщений

Получение публичного ключа


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


Запуск процесса генерации ключей шифрования


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


Получение точки для отправки сообщений


После формирования ключей шифрования вызывается процесс внутри браузера называемый register. В сторону Push сервера через WebSocket браузер отправляет запрос вида


{  "channelID": "f9cb8f1c-05e0-403f-a09b-dd7864a03eb7",  "messageType": "register",  "key": "BO_C-Ou.......zKu2U4HZ9XeElUIdRfc6EBbRudAjq4="}

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


{  "messageType": "register",  "channelID": "f9cb8f1c-05e0-403f-a09b-dd7864a03eb7",  "status": 200,  "pushEndpoint": "https://webpush.example.net/wpush/f9cb8f1c-05e0-403f-a09b-dd7864a03eb7/",  "scope": "https://webpush.example.net/"}

В данном сообщении содержится адрес конечной точки сформированный Push(WebSocket) сервером, на которую необходимо отправить зашифрованное сообщение для получения его пользователем. Для передачи сообщения получателю, между WEB сервером, принимающим внешние запросы и WS сервером, отправляющим оповещения, должна быть организована логическая связь.


Итого по окончании процесса регистрации и подписки мы имеем следующий набор данных:


Браузер:


  • приватный ключ шифрования сообщений
  • публичный ключ шифрования сообщений
  • ключ авторизации(DH)
  • конечная точка для доставки сообщений получателю
  • номер канала зарегистрированный на WebSocket сервере
  • идентификатор клиента внутри WS соединения
  • публичный ключ WebPush сервера

WebPush сервер:


  • публичный ключ WebPush сервера
  • приватный ключ WebPush сервера

Push(WebSocket) сервер:


  • публичный ключ WebPush сервера
  • адрес конечной точки клиента
  • номер канала клиента привязанный к конечной точке
  • идентификатор клиента внутри WS соединения

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


  • некто хочет отправить сообщение в браузер пользователя
  • для защиты сообщения необходимо извлечь из браузера настройки текущей подписки к Push серверу (конечную точку для отправки сообщения, публичный ключ шифрования сообщения, ключ авторизации)
  • полученные настройки передаются на промежуточный WebPush сервер вместе с текстом сообщения
  • промежуточный WebPush сервер формирует авторизационный JWT токен, содержащий время создания сообщения, адрес администратора WebPush сервера, время действия сообщения и подписывает его при помощи своего приватного ключа
  • промежуточный WebPush сервер производит шифрование сообщения при помощи публичного ключа и ключа авторизации из браузера
  • промежуточный WebPush сервер вызывает конечную точку полученную из браузера, передавая в неё связку JWT токен+публичный ключ для их проверки в заголовке Authorization, а также бинарный массив зашифрованного сообщения в теле запроса
  • Push сервер по вызываемой конечной точке производит привязку запроса к каналу получателя
  • Push сервер проверяет валидность JWT токена
  • Push сервер конвертирует бинарный массив принятых данных в base64, формирует сообщение типа "notification" с каналом получателя, ставит сообщение в очередь, после чего механизм контроля очереди отправляет сообщение по WebSocket каналу в сторону клиента

Здесь мы прервём процесс для описания формата сообщения типа "notification".
Дело в том, что формат сообщения типа "notification" имеет два варианта. От того, что получил браузер и передал в ServiceWorker зависит логика работы по получению и отображению сообщения. Первый вариант, это "пустое" сообщение:


{  "messageType": "notification",  "channelID": "f7dfeed8-f868-47ca-a066-fbe629879fbf",  "version": "bf82eea1-69fd-4be0-b943-da96ff0041fb"}

"Пустое" сообщение как бы говорит браузеру "Эй, тебя тут ждут данные, приходи за ними". Браузер по логике работы должен сделать GET запрос на URL конечной точки и получить первую запись из очереди для отображения её пользователю. Схема конечно хорошая, только совсем небезопасная. В большинстве случаев она не применяется.
Вторым вариантом является передача данных совместно с сообщением.


{  "messageType": "notification",  "channelID": "f7dfeed8-f868-47ca-a066-fbe629879fbf",  "version": "bf82eea1-69fd-4be0-b943-da96ff0041fb",  "data": "I_j8p....eMlYK6jxE2-pHv-TRhqQ",  "headers":  {    "encoding": "aes128gcm"  }}

Браузер реагирует на поле headers в структуре сообщения типа "notification". При наличии этого поля автоматически включается механизм обработки зашифрованных данных из поля "data". На основании номера канала, событийная машина браузера выбирает набор ключей шифрования и пытается расшифровать полученные данные. После расшифровки расшифрованные данные передаются в обработчик "push" сообщений ServiceWorker. Как вы успели заметить, сообщение типа "notification" имеет поле "version", которое представляет собой уникальный номер сообщения. Уникальный номер сообщения используется в системе доставки и отображения сообщений для дедупликации данных.
Она работает следующим образом:


  • любое принятое сообщение имеющее поле "version" заносится во внутренний реестр исключений
  • корректно принятые и обработанные сообщения остаются в реестре исключений
  • некорректно принятые и не обработанные сообщения из рееста исключений удаляются
    Информация о причинах такого поведения будет ниже.

Продолжим разбор процесса.


  • Если сообщение принято и расшифровано, от браузера в сторону Push сервера формируется новое сообщение с типом "ack", включающее в себя номер канала и номер обработанного сообщения. Это является сигналом удаления сообщения из очереди сообщений для данного канала
  • Если сообщение по какой-то причине не может быть обработано, от браузера в сторону Push сервера формируется новое сообщение с типом "noack", включающее в себя номер канала и номер отвергнутого сообщения. Это является сигналом постановки сообщения на повторную доставку через 60 секунд

Вернёмся к сообщениям с типом "broadcast". Продукт "autopush" от Mozilla использует их в качестве хранилища на стороне клиента, для определения последнего отправленного клиенту сообщения. Дело в том, что отправка сообщения типа "broadcast" со сменой значения ключа "remote-settings/monitor_changes", приводит к срабатыванию механизма, сохраняющего полученное значение в хранилище браузера. При потере соединения или каком-то программном сбое, сохранённое значение будет автоматически передано на сторону Push сервера в момент инициализации соединения и будет являться начальной точкой для последующей переотправки пропущенных сообщений из очереди.


Описывать сообщения типа "unregister" смысла не имеет, т.к. оно ни на что, кроме удаления сессии не влияет.


К чему же было приведено подробное описание всех процессов происходящих при Push оповещениях?
Смысл в том, что на основании этих данных можно довольно быстро построить свой Push сервер с необходимым функционалом. Продукт "autopush" от Mozilla является продуктом промышленного масштаба, который рассчитан на многомилионные подключения клиентов. В его составе присутствует TornadoDB, PyPy, CPython. К сожалению движок написан на Python 2.7, который массово выводится из эксплуатации.
Нам же нужен небольшой сервер с простым, желательно асинхронным кодом. А именно, без промежуточного WebPush сервера, VAPID, лишних межсерверных проверок и прочего. Сервер должен уметь привязывать клиентские подключения Push сервера к именам пользователей, а также иметь возможность организации эндпоинтов и webhook'ов для отправки сообщений этим пользователям.


Пишем свой сервер


У нас есть следующие данные:


  • Пользователь с браузером Mozilla Firefox;
  • Точка регистрации пользователя на сервере уведомлений для получения этих самых уведомлений;
  • WebSocket сервер, обслуживающий подключения движка уведомлений, встроенного в браузер;
  • Web сервер, формирующий интерфейс для пользователя и обслуживающий точки для отправки уведомлений;

Шаг 1
Первым делом мы должны подготовить WebSocket сервер, обслуживающий описанную ранее логику работы и подключения к нему клиентов.
В качестве фреймворка для реализации логики сервера используется AsyncIO Python.
Изначально стоит сразу разделить понятие "регистрация" для WebSocket движка браузера и понятие "регистрация" на сервере уведомлений. Разница заключается в том, что "регистрация" WebSocket движка браузера происходит автоматически без участия пользователя, в то время как разрешение на "регистрацию" на сервере уведомлений это осознанное действие со стороны пользователя.
Первичной задачей WebSocket сервера является принятие входящего соединения и его контроль на протяжении всего времени подключения браузера к серверу. Поэтому мы должны принять внешнее соединение, сделать его привязку к каналу и сохранить для дальнейшей работы.


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


WebSocket Handler
# внешнее имя сервераSERVERNAME='webpush.example.net'# вебсокетыWS = set()# каналыCHANNELS = dict()async def register(websocket):    try:        WS.add(websocket)        websocket.handler = PushConnectionHandler(websocket)    except Exception as ex:        logger.error('Register exception: %s' % ex)async def unregister(websocket):    try:        CHANNELS.remove(websocket.handler.channel_id)        WS.remove(websocket)        logger.debug('UnregisterWebsocket[websocket]: %s'%websocket)    except Exception as ex:        logger.error('Unregister exception: %s' % ex)async def pushserver(websocket, path):    await register(websocket)    try:        await websocket.send(json.dumps({}))        async for message in websocket:            data = json.loads(message)            logger.info('Incoming message[data]: %s => %s '%(message, data))            if message == '{}':                await websocket.send(json.dumps({}))            elif 'messageType' in data:                logger.info('Processing WebSocket Data')                # Подключение к вебсокету из браузера                if data['messageType'] == 'hello':                    # Если это первичное подключение, то нужно задать идентификатор подключения и вернуть его браузеру                    if 'uaid' not in data:                        data['uaid'] = '%s' % uuid.uuid4()                    # Принудительно включить webpush                    if 'use_webpush' not in data:                        data['use_webpush'] = True                    helloreturn = {                        "messageType": "hello",                        "status": 200,                        "uaid": data['uaid'],                        "use_webpush": data['use_webpush']                        }                    websocket.handler.uaid = data['uaid']                    if 'broadcasts' in data:                        websocket.handler.register_broadcasts(data['broadcasts'])                    logger.debug('Hello websocket: %s' % vars(websocket.handler))                    CHANNELS.update({ data['uaid'] : websocket.handler })                    await websocket.send(json.dumps(helloreturn))                elif data['messageType'] == 'register':                    # Регистрация serviceWorker                    logger.debug('Register[data]: %s'%data)                    registerreturn = {                        "messageType": "register",                        "channelID": data['channelID'],                        "status": 200,                        "pushEndpoint": "https://%s/wpush/%s/" % (SERVERNAME,data['channelID']),                        "scope": "https://%s/" % SERVERNAME                    }                    websocket.handler.channel_id = data['channelID']                    if 'key' in data:                        websocket.handler.server_public_key = data['key']                    logger.debug('Register[registerreturn]: %s'%registerreturn)                    CHANNELS.update({ data['channelID'] : websocket.handler })                    await websocket.send(json.dumps(registerreturn))                elif data['messageType'] == 'unregister':                    unregisterreturn = {                        "messageType": "unregister",                        "channelID": data['channelID'],                        "status": 200                    }                    if data['channelID'] in CHANNELS:                        del CHANNELS[data['channelID']]                    logger.debug('Unregister[unregisterreturn]: %s'%unregisterreturn)                    logger.debug('Unregister[CHANNELS]: %s'%CHANNELS)                    await websocket.send(json.dumps(unregisterreturn))                elif data['messageType'] == 'ack':                    logger.debug('Ack: %s' % data)                    for update in data['updates']:                        if CHANNELS[update['channelID']].mqueue.count(update['version']) > 0:                            CHANNELS[update['channelID']].mqueue.remove(update['version'])                    logger.debug('Mqueue for channel %s is %s' % (websocket.handler.channel_id, websocket.handler.mqueue))                    await websocket.send('{}')                elif data['messageType'] == 'nack':                    await websocket.send('{}')            else:                logger.error("unsupported event: {}", data)    finally:        await unregister(websocket)

Как вы видите, никакой большой магии в WebSocket сервисе не заложено. Обрабатывается только список основных команд внутри WS сессии согласно спецификации.


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


main.js
'use strict';let isSubscribed = false;let swRegistration = null;var wait = ms => new Promise((r, j)=>setTimeout(r, ms));function urlB64ToUint8Array(base64String) {    const padding = '='.repeat((4 - base64String.length % 4) % 4);    const base64 = (base64String + padding)        .replace(/\-/g, '+')        .replace(/_/g, '/');    const rawData = window.atob(base64);    const outputArray = new Uint8Array(rawData.length);    for (let i = 0; i < rawData.length; ++i) {        outputArray[i] = rawData.charCodeAt(i);    }    return outputArray;}function subscribeUser() {    const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey');    const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);    swRegistration.pushManager.subscribe({            userVisibleOnly: true,            applicationServerKey: applicationServerKey        })        .then(function(subscription) {            console.log('User is subscribed.', JSON.stringify(subscription));            localStorage.setItem('sub_token',JSON.stringify(subscription));            isSubscribed = true;            fetch(subscription.endpoint, {                method: 'POST',                cache: 'no-cache',                body: JSON.stringify(subscription)            })            .then(function(response) {                console.log('Push keys Update Response: ' + JSON.stringify(response));            })        })        .catch(function(err) {            console.log('Failed to subscribe the user: ', err);        });}function unsubscribeUser() {    swRegistration.pushManager.getSubscription()        .then(function(subscription) {            if (subscription) {                return subscription.unsubscribe();            }        })        .catch(function(error) {            console.log('Error unsubscribing', error);        })        .then(function() {            console.log('User is unsubscribed.');            isSubscribed = false;        });}function initializeUI() {    // Set the initial subscription value    swRegistration.pushManager.getSubscription()        .then(function(subscription) {            isSubscribed = !(subscription === null);            if (isSubscribed) {                console.log('User IS subscribed. Unsubscribing.');                subscription.unsubscribe();            } else {                console.log('User is NOT subscribed. Subscribing.');                subscribeUser();            }        });    (async () => {    await wait(2000);    console.warn('Wait for operation is ok');         swRegistration.pushManager.getSubscription()                .then(function(subscription) {                        isSubscribed = !(subscription === null);                        if (!isSubscribed) {                                console.log('ReSubscribe user');                                subscribeUser();                        }                })    })()}console.log(navigator);console.log(window);if ('serviceWorker' in navigator && 'PushManager' in window) {    console.log('Service Worker and Push is supported');    navigator.serviceWorker.register("/sw.js")        .then(function(swReg) {            console.log('Service Worker is registered', swReg);            swRegistration = swReg;            initializeUI();        })        .catch(function(error) {            console.error('Service Worker Error', error);        });} else {    console.warn('Push messaging application ServerPublicKey is not supported');}$(document).ready(function(){    $.ajax({        type:"GET",        url:'/subscription/',        success:function(response){            console.log("response",response);            localStorage.setItem('applicationServerPublicKey',response.public_key);        }    })});

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


sw.js
'use strict';/* eslint-disable max-len *//* eslint-enable max-len */function urlB64ToUint8Array(base64String) {  const padding = '='.repeat((4 - base64String.length % 4) % 4);  const base64 = (base64String + padding)    .replace(/\-/g, '+')    .replace(/_/g, '/');  const rawData = window.atob(base64);  const outputArray = new Uint8Array(rawData.length);  for (let i = 0; i < rawData.length; ++i) {    outputArray[i] = rawData.charCodeAt(i);  }  return outputArray;}function getEndpoint() {  return self.registration.pushManager.getSubscription()  .then(function(subscription) {    if (subscription) {      return subscription.endpoint;    }    throw new Error('User not subscribed');  });}self.popNotification = function(title, body, tag, icon, url) {  console.debug('Popup data:', tag, body, title, icon, url);  self.registration.showNotification(title, {      body: body,      tag: tag,      icon: icon    });  self.onnotificationclick = function(event){      console.debug('On notification click: ', event.notification.tag);      event.notification.close();      event.waitUntil(        clients.openWindow(url)      );  };}var wait = ms => new Promise((r, j)=>setTimeout(r, ms));self.addEventListener('push', function(event) {   console.log('[Push]', event);  if (event.data) {    var data = event.data.json();    var evtag = data.tag || 'notag';    self.popNotification(data.title || 'Default title', data.body || 'Body is not present', evtag, data.icon || '/static/images/default.svg', data.url || '/getevent?tag='+evtag);  }  else {    event.waitUntil(      getEndpoint().then(function(endpoint) {        return fetch(endpoint);      }).then(function(response) {          return response.json();      }).then(function(payload) {          console.debug('Payload',JSON.stringify(payload), payload.length);          var evtag = payload.tag || 'notag';          self.popNotification(payload.title || 'Default title', payload.body || 'Body is not present', payload.tag || 'notag', payload.icon || '/static/images/default.svg', payload.url || '/getevent?tag='+evtag);      })    );  }});self.addEventListener('pushsubscriptionchange', function(event) {  console.log('[Service Worker]: \'pushsubscriptionchange\' event fired.');  const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey');  const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);  event.waitUntil(    self.registration.pushManager.subscribe({      userVisibleOnly: true,      applicationServerKey: applicationServerKey    })    .then(function(newSubscription) {      // TODO: Send to application server      console.log('[Service Worker] New subscription: ', newSubscription);    })  );});

Согласно представленного кода, Javascript файл main.js инициирует при своём запуске получение публичного VAPID ключа и принудительно вызывает подписку браузера на оповещения.
Для простоты отладки WebSocket сервер во время регистрации подписки отдаёт URL вида: https://webpush.example.net/wpush/ChannelGuid.
Откуда же берётся имя пользователя в сервере уведомлений. Вся суть в том, что инициирование подписки /subscription/ происходит полуавтоматически. Соответственно в зависимости от того, что вы хотите увидеть в качестве идентификатора пользователя, вы можете передать после оформления подписки в момент передачи ключей.
Это происходит путём вызова метода POST по адресу WebPush endpoint присланного сервером из модуля ServiceWorker.


function subscribeUser() {    const applicationServerPublicKey = localStorage.getItem('applicationServerPublicKey');    const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);    swRegistration.pushManager.subscribe({            userVisibleOnly: true,            applicationServerKey: applicationServerKey        })        .then(function(subscription) {            console.log('User is subscribed.', JSON.stringify(subscription));            localStorage.setItem('sub_token',JSON.stringify(subscription));            isSubscribed = true;            fetch(subscription.endpoint, {                method: 'POST',                cache: 'no-cache',                body: JSON.stringify(subscription)            })            .then(function(response) {                console.log('Push keys Update Response: ' + JSON.stringify(response));            })        })        .catch(function(err) {            console.log('Failed to subscribe the user: ', err);        });}

Как было написано ранее, в сервере используется обработчик точек подключения. Это отдельная часть кода в скрипте сервера, но обрабатывающая вместо WebSocket, клиентский WEB трафик от браузера.
В качестве обрабатываемого заголовка, содержащего идентификатор пользователя, в базовом варианте сервиса использовался basiclogin полученный при авторизации пользователя в LDAP.


        location ~ /subscription|/pushdata|/getdata|/wpush|/notify {            proxy_pass http://localhost:8090;            proxy_set_header LDAP-AuthUser $remote_user;            proxy_set_header 'X-Remote-Addr' $remote_addr;            add_header "Access-Control-Allow-Origin" "*";            add_header Last-Modified $date_gmt;        proxy_hide_header "Authorization";            add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';            if_modified_since off;            expires off;            etag off;        }

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


USERIDHEADERNAME='X-Remote-Addr'async def update_channel_keys(request, data):    channel = request.path.replace('wpush','').replace('/','')    logger.debug('update channel keys data: %s'%data)    logger.debug('Update Channel keys Headers: %s' % request.headers)    if USERIDHEADERNAME not in set(request.headers):        return False    basiclogin = request.headers[USERIDHEADERNAME]    logger.debug('Login %s' % basiclogin)    if basiclogin not in LOGINS_IN_CHANNELS:        LOGINS_IN_CHANNELS.update({ '%s'%basiclogin : {} })    LOGINS_IN_CHANNELS['%s'%basiclogin].update({'%s' % channel : {} })    logger.debug('LOGINS_IN_CHANNELS: %s' % LOGINS_IN_CHANNELS)    try:        jdata = json.loads(data)        if 'endpoint' in jdata and 'keys' in jdata:            logger.debug('Saving Keys for Channel: %s => %s' % (channel, jdata))            CHANNELS[channel].register_keys(jdata['keys'])            logger.debug('Registered channel keys %s:' % vars(CHANNELS[channel]))        return True    except Exception as ex:        logger.error('Exception %s'%ex)        return False

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


Шаг 3
Сессия зарегистрирована, ключи на сервер переданы, пора отправлять и получать сообщения.
Как я описывал в самом начале статьи, у сервиса оповещений существует два способа доставки сообщений:


  • пустое push уведомление, когда браузер "приходит" в очередь сообщений сам
  • push уведомление содержащее зашифрованные данные.

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


self.addEventListener('push', function(event) {   console.log('[Push]', event);  if (event.data) {    var data = event.data.json();    var evtag = data.tag || 'notag';    self.popNotification(data.title || 'Default title', data.body || 'Body is not present', evtag, data.icon || '/static/images/default.svg', data.url || '/getevent?tag='+evtag);  }  else {    event.waitUntil(      getEndpoint().then(function(endpoint) {        return fetch(endpoint);      }).then(function(response) {          return response.json();      }).then(function(payload) {          console.debug('Payload',JSON.stringify(payload), payload.length);          var evtag = payload.tag || 'notag';          self.popNotification(payload.title || 'Default title', payload.body || 'Body is not present', payload.tag || 'notag', payload.icon || '/static/images/default.svg', payload.url || '/getevent?tag='+evtag);      })    );  }});

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


Блок шифрования сообщений передаваемых был взят из кода сервера "autopush", дабы не нарушать совместимости.


Блок шифрования сообщения
    def encrypt_message(self, data, content_encoding="aes128gcm"):        """Encrypt the data.        :param data: A serialized block of byte data (String, JSON, bit array,            etc.) Make sure that whatever you send, your client knows how            to understand it.        :type data: str        :param content_encoding: The content_encoding type to use to encrypt            the data. Defaults to RFC8188 "aes128gcm". The previous draft-01 is            "aesgcm", however this format is now deprecated.        :type content_encoding: enum("aesgcm", "aes128gcm")        """        # Salt is a random 16 byte array.        if not data:            logger.error("PushEncryptMessage: No data found...")            return        if not self.auth_key or not self.receiver_key:            raise WebPushException("No keys specified in subscription info")        logger.debug("PushEncryptMessage: Encoding data...")        salt = None        if content_encoding not in self.valid_encodings:            raise WebPushException("Invalid content encoding specified. "                                   "Select from " +                                   json.dumps(self.valid_encodings))        if content_encoding == "aesgcm":            logger.debug("PushEncryptMessage: Generating salt for aesgcm...")            salt = os.urandom(16)        # The server key is an ephemeral ECDH key used only for this        # transaction        server_key = ec.generate_private_key(ec.SECP256R1, default_backend())        crypto_key = server_key.public_key().public_bytes(            encoding=serialization.Encoding.X962,            format=serialization.PublicFormat.UncompressedPoint        )        if isinstance(data, str):            data = bytes(data.encode('utf8'))        if content_encoding == "aes128gcm":            logger.debug("Encrypting to aes128gcm...")            encrypted = http_ece.encrypt(                data,                salt=salt,                private_key=server_key,                dh=self.receiver_key,                auth_secret=self.auth_key,                version=content_encoding)            reply = CaseInsensitiveDict({                'data': base64.urlsafe_b64encode(encrypted).decode()            })        else:            logger.debug("Encrypting to aesgcm...")            crypto_key = base64.urlsafe_b64encode(crypto_key).strip(b'=')            encrypted = http_ece.encrypt(                data,                salt=salt,                private_key=server_key,                keyid=crypto_key.decode(),                dh=self.receiver_key,                auth_secret=self.auth_key,                version=content_encoding)            reply = CaseInsensitiveDict({                'crypto_key': crypto_key,                'data': base64.urlsafe_b64encode(encrypted).decode()            })            if salt:                reply['salt'] = base64.urlsafe_b64encode(salt).strip(b'=')        reply['headers'] = { 'encoding': content_encoding }        return reply

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


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


WebPush AsyncIO server


Для развёртывания сервера необходимо:


  • Установить необходимые Python модули, а также настроить nginx по примеру приложенного конфигурационного файла.
  • Поместить содержимое директории web в корень ранее настроенного виртуального сервера
  • Перезапустить/перечитать конфиг nginx
  • В браузере через about:config поменять параметр dom.push.serverURL на адрес wss://ваш.сервер/ws
  • Перед сменой адреса push сервера можно очистить поле dom.push.userAgentID, которое автоматически заполнится если ваш Push сервер работает корректно и принимает соединения.
  • Для тестирования оповещений необходимо зайти на страницу https://ваш.сервер/indexpush.html и открыв окно отладки удостовериться в корректной регистрации ServiceWorker
  • Нажать кнопку "Check Push Notify"
  • Если всё правильно настроено, появится всплывающее сообщение

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


var req = new CurlHttpRequest();req.AddHeader('Content-Type: application/x-www-form-urlencoded');var jv = JSON.parse(value);if (jv.recovery_nstatus == '{EVENT.RECOVERY.VALUE}') {jv.icon = '/static/images/problem/' + jv.event_severity + '.svg';}else{jv.icon = '/static/images/recovery/' + jv.event_severity + '.svg';}value = JSON.stringify(jv);Zabbix.Log(2, 'webhook request value='+value);req.Post('https://webpush.server.net/pushdata/',  value);Zabbix.Log(2, 'response code: '+req.Status());return JSON.stringify({  'tags': {    'endpoint': 'webpush'  }});

c параметрами


Ключ Значение
url /zabbix/tr_events.php?triggerid={TRIGGER.ID}&eventid={EVENT.ID}
recipient {ALERT.SENDTO}
title {ALERT.SUBJECT}
body {ALERT.MESSAGE}
event_severity {EVENT.NSEVERITY}
recovery_nstatus {EVENT.RECOVERY.VALUE}

Если добавить красивых картинок из FontAwesome, то получится вот так


WebPush сервер поддерживает следующие вызовы :


  • POST https://webpush.example.net/wpush/channelId сохранение ключей шифрования и имени пользователя
  • GET https://webpush.example.net/wpush/channelId получение тестового сообщения
  • GET https://webpush.example.net/subscription получение публичного VAPID ключа
  • POST https://webpush.example.net/pushdata отправка JSON структуры передаваемой в качестве сообщения в браузер
    {        "url": "http://personeltest.ru/away/habr.com/", // URL на который необходимо перейти при клике        "recipient": login, // Логин или идентификатор пользователя        "title": "Заголовок сообщения",        "body": "Тело сообщения",         "icon": "/static/images/new-notification.png", // путь к иконке сообщения        "version": uuid, // идентификатор сообщения        "tag": uuid, // тег сообщения для получения        "mtime": parseInt(new Date().getTime()/1000) //Время }
    
  • GET https://webpush.example.net/getdata Получение очереди сообщений
  • POST https://webpush.example.net/notify/login Отправка пустого оповещения пользователю
  • POST https://webpush.example.net/notifychannel/channelId Отправка пустого оповещения в канал

Вот в принципе и всё. Надеюсь у вас снялась часть вопросов с тем, как работает WebPush. Спасибо за потраченное время на чтение материала.


Aborche 2020
Aborche

Подробнее..

Интеграция ЭЦП НУЦ РК в информационные системы на базе веб технологий

07.07.2020 14:13:48 | Автор: admin

Я расскажу о тонкостях внедрения электронной цифровой подписи (ЭЦП) в информационные системы (ИС) на базе веб технологий в контексте Национального Удостоверяющего Центра Республики Казахстан (НУЦ РК).


В центре внимания будет формирование ЭЦП под электронными документами и, соответственно, NCALayer предоставляемое НУЦ РК криптографическое программное обеспечение. В частности уделю внимание вопросам связанным с UX и объемом поддерживаемого функционала NCALayer.


Процесс разделю на следующие этапы:


  • формирование неизменного представления подписываемого документа (под подписываемым документом я буду подразумевать любые данные которые необходимо подписать, такие как: договор, бланк заказа, форма аутентификации и т.п.);
  • подписание документа в веб интерфейсе с помощью NCALayer;
  • проверка подписи на стороне сервера;
  • (при необходимости) подготовка подписи к долгосрочному хранению.

Формирование неизменного представления подписываемого документа


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


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


  • извлечь все поля записи, привести их к строкам и соединить в одну строку;
  • сформировать XML или JSON представление;
  • сформировать PDF документ на базе шаблона с каким-то оформлением содержащий данные из записи;
  • и т.п.

Далее возможны два сценария каждый из которых имеет свои подводные камни:


  • ИС не хранит сформированного представления и каждый раз для проверки подписи под документом (в частности проверки неизменности данных) формирует его;
  • ИС хранит сформированное представление и использует его для проверки подписи.

В том случае, если ИС не хранит сформированного представления, разработчикам необходимо гарантировать что в будущем формируемые представления будут бинарно идентичны тем, которые были сформированы в первый раз не смотря на:


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

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


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


Подписание документа в веб интерфейсе с помощью NCALayer


Для формирования цифровых подписей на стороне клиента НУЦ РК предоставляет единственный инструмент сертифицированное программное обеспечение NCALayer которое представляет из себя WebSocket сервер, запускаемый на 127.0.0.1, которому можно отправлять запросы на выполнение криптографических (а так же некоторых смежных) операций. При выполнении некоторых операций NCALayer отображает диалоговые окна то есть берет часть работы по получению пользовательского ввода на себя.


Описание API NCALayer доступно в составе комплекта разработчика. Для того, чтобы поэкспериментировать со взаимодействием с NCALayer по WebSocket можно воспользоваться страницей интерактивной документации KAZTOKEN mobile (KAZTOKEN mobile повторяет API NCALayer).


Взаимодействовать с NCALayer из браузера можно напрямую с помощью класса WebSocket, либо можно воспользоваться библиотекой ncalayer-js-client которая оборачивает отправку команд и получение ответов в современные async вызовы.


Замечу что весь основной функционал NCALayer доступен в модуле kz.gov.pki.knca.commonUtils, использовать модуль kz.gov.pki.knca.applet.Applet (наследие Java аплета) не рекомендую, так как, на мой взгляд, это не даст никаких преимуществ, но шансов выстрелить себе в ногу с ним больше к примеру можно случайно разработать интерфейс который не будет поддерживать аппаратных носителей (токенов или смарт-карт) с несколькими ключевыми парами.


Модуль kz.gov.pki.knca.commonUtils берет на себя взаимодействие с пользователем связанное с выбором конкретного хранилища, которое нужно использовать для выполнения операции (так же он берет на себя выбор конкретного сертификата и соответствующего ключа, а так же ввод пароля или ПИН кода), но ему необходимо указать какой тип хранилищ нужно использовать. Типы хранилищ стоит разделить на два класса:


  • файловые, поддерживается единственный тип заданный константой 'PKCS12',
  • аппаратные (токены и смарт-карты), для перечисления тех типов, экземпляры которых в данный момент подключены в ПК пользователя, следует использовать запрос getActiveTokens.

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


  • гибкий отправить запрос getActiveTokens, в ответ получить массив имен доступных а данный момент типов хранилищ, добавить в него 'PKCS12' и спросить у пользователя каким он хотел бы в данный момент воспользоваться;
  • простой отправить запрос getActiveTokens, в том случае, если он вернул массив несколькими именами, попросить пользователя отключить лишнее, если в массиве один элемент, использовать его, если массив пустой, использовать 'PKCS12'.

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


  • createCAdESFromBase64 вычислить подпись под данными и сформировать CMS (CAdES);
  • createCMSSignatureFromBase64 вычислить подпись под данными, получить на подпись метку времени (TSP) и сформировать CMS (CAdES) с внедренной меткой времени;
  • signXml вычислить подпись под XML документом, сформированную подпись добавить в результирующий документ (XMLDSIG);
  • signXmls аналогично signXml, но позволяет за один раз подписать несколько XML документов.

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


Модуль kz.gov.pki.knca.commonUtils поддерживает следующие типы сертификатов:


  • 'AUTHENTICATION' сертификаты для выполнения аутентификации;
  • 'SIGNATURE' сертификаты для подписания данных.

NCLayer предоставит пользователю выбирать только из тех сертификатов, которые соответствуют указанному типу.


Упрощенный пример подписания произвольного блока данных с использованием ncalayer-js-client:


async function connectAndSign(base64EncodedData) {  const ncalayerClient = new NCALayerClient();  try {    await ncalayerClient.connect();  } catch (error) {    alert(`Не удалось подключиться к NCALayer: ${error.toString()}`);    return;  }  let activeTokens;  try {    activeTokens = await ncalayerClient.getActiveTokens();  } catch (error) {    alert(error.toString());    return;  }  const storageType = activeTokens[0] || NCALayerClient.fileStorageType;  let base64EncodedSignature;  try {    base64EncodedSignature = await ncalayerClient.createCAdESFromBase64(storageType, base64EncodedData);  } catch (error) {    alert(error.toString());    return;  }  return base64EncodedSignature;}

Проверка подписи на стороне сервера


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


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


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


Выполнять проверки необходимо с применением сертифицированных средств, к примеру с помощью библиотек входящих в состав комплекта разработчика НУЦ РК, либо можно воспользоваться готовым решением SIGEX.


Подготовка подписи к долгосрочному хранению


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


Для фиксации момента подписания принято использовать метки времени TSP. Метку времени на подпись можно получить либо на клиенте (запрос createCMSSignatureFromBase64 интегрирует метку времени в CMS), либо на сервере. Метка времени позволит удостовериться в том, что момент подписания попадает в срок действия сертификата.


Для того, чтобы удостовериться в том, что сертификат не был отозван в момент подписания, следует использовать CRL или OCSP ответ. Этот нюанс и рекомендации по реализации описаны в разделе APPENDIX B Placing a Signature At a Particular Point in Time документа RFC 3161.

Подробнее..

Dell EMC PowerStore коротко о нашей новейшей СХД корпоративного класса

07.07.2020 12:09:02 | Автор: admin
Совсем недавно наша компания представила новый продукт Dell EMC PowerStore. Это универсальная платформа с ориентированным на производительность дизайном, обеспечивающим многомерное масштабирование, постоянное сокращение данных (компрессия и дедупликация) и поддержку носителей нового поколения. PowerStore использует микросервисную архитектуру, передовые технологии хранения и интегрированное машинное обучение.

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




Преимущества новых систем хранения:


  • Современная микросервисная архитектура. Система базируется на контейнерной архитектуре, когда отдельные компоненты ОС выделяются в отдельные микросервисы. Микросервисная архитектура обеспечивает переносимость функций и быстрое внедрение нового функционала. Такая архитектура позволяет быстро адаптировать ранее написанный функционал на новую платформу т.к. микросервисы автономны и не влияют друг на друга, микросервисная архитектура позволяет обеспечить более высокую надежность работы всей системы по сравнению с монолитной архитектурой. Например, обновление микрокода часто затрагивает только отдельные модули, а не всю систему (или ее ядро) и, как следствие, проходит более гладко.
  • Использование передовых технологий хранения данных. Поддержка памяти Storage Class Memory (SCM) Intel Optane и NVMe All-Flash позволяет устранить узкие места в системе и существенно увеличить производительности и сократить время отклика системы.
  • Непрерывное сокращение объема данных на лету. Всегда включенные механизмы компрессии и дедупликации данных, позволяют уменьшить объёмы, занимаемые данными внутри системы и оптимизировать хранение. Это позволяет сократить затраты на приобретение и эксплуатацию системы и повысить эффективность работы.
  • Гибкая масштабируемость решения. Архитектура решений Dell EMC PowerStore поддерживает как вертикальное, так и горизонтальное масштабирование, благодаря чему вы можете эффективно планировать расширение инфраструктуры наращивая ёмкость или вычислительные ресурсы независимо друг от друга.
  • Встроенные механизмы защиты данных. Системы PowerStore обладают широким спектром встроенных механизмов защиты данных от мгновенных снимков и репликации до шифрования данных и интеграции с антивирусными программами. Система также широко интегрируется с внешними решениями, как от Dell Technologies, так и от других производителей.
  • AppsON. Благодаря интегрированному в систему гипервизору VMware ESX заказчики могут запускать пользовательские виртуальные машины непосредственно внутри системы.
  • Интеграция с VMware. PowerStore предназначен для глубокой интеграции с VMware vSphere. Интеграция включает поддержку VAAI и VASA, уведомления о событиях, управление снимками, vVols, а также обнаружение и мониторинг виртуальных машин в PowerStore Manager.
  • Унифицированный доступ к данным. PowerStore обеспечивает хранение данных приложений в различных форматах, от физических и виртуальных томов до контейнеров и традиционных файлов благодаря возможности работы по множеству протоколов блочных, файловых и VMware vSphere Virtual Volumes (vVols). Эта способность обеспечивает высокую гибкость данной системы и позволяет ИТ-отделам упростить и консолидировать инфраструктуру.
  • Простой, современный интерфейс управления. Интерфейс управления системой PowerStore Manager разрабатывался на основе требований наших заказчиков к простоте управления системой. Это web-интерфейс, запускающийся на контроллерах системы PowerStore. Доступен по протоколу HTML5 и не требует установки дополнительных плагинов.
  • Программируемая инфраструктура. Упрощает разработку приложений и сокращает сроки их развёртывания с нескольких дней до нескольких секунд благодаря интеграции с VMware и поддержке ведущих сред управления и оркестрации, включая Kubernetes, Ansible и VMware vRealize Orchestrator.
  • Интеллектуальная автоматизация. Встроенные алгоритмы машинного обучения автоматизируют трудоемкие рабочие процессы: такие как начальное планирование и размещение томов, миграция данных, балансирование нагрузки и устранение проблем
  • Аналитическая информация об инфраструктуре. Программное обеспечение Dell EMC CloudIQ для мониторинга и анализа хранилищ сочетает в себе преимущества машинного обучения и человеческого интеллекта для анализа производительности и ёмкости систем в режиме реального времени, а также хранения исторических данных, чтобы получить единое представление об инфраструктуре Dell EMC. Компания Dell Technologies планирует интегрировать CloudIQ с полным портфелем своих решений для еще более глубокой аналитики.

Платформа представлена двумя типами систем:

  1. PowerStore T выступает в качестве классической СХД.
  2. PowerStore X выступает как гиперконвергентное решение, позволяющее запускать виртуальные машины заказчика, совмещенное с выделенной, классической СХД.

Благодаря интегрированным возможностям VMware ESXi, модели PowerStore X предоставляют возможность размещать приложения с интенсивным вводом-выводом непосредственно внутри системы PowerStore. При использовании встроенных механизмов VMware (vMotion) обеспечивается возможность перемещения приложений между системой хранения PowerStore и внешними решениями. Встроенный гипервизор VMware ESXi запускает приложения заказчиков вместе с операционной системой PowerStore в виде виртуальных машин VMware. Этот инновационный дизайн идеально подходит для приложений с большим объёмом хранилища, обеспечивая дополнительные вычислительные и высокопроизводительные хранилища для существующей среды или любого сценария, в котором плотность, производительность и доступность являются основными факторами.

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

  • Выделенная инфраструктура для одного приложения. Например, для базы данных, которой требуется выделенный сервер, СХД, а также какая-то дополнительная обвязка, например, для резервного копирования. В этом случае вы можете купить одну единственную систему PowerStore которая закроет все задачи, т.к. само приложение и сервер резервного копирования можно развернуть в рамках узла PowerStore без необходимости в дополнительной инфраструктуре.
  • ROBO (удаленные филиалы и офисы). Многие заказчики сталкиваются с задачей в каком-то виде реплицировать инфраструктуру основного ЦОД на периферию, чтобы обеспечить работу удаленных филиалов своих компаний. Раньше приходилось для этого покупать отдельные сервера, СХД, коммутаторы для их соединения, а также ломать голову на тему того, как защитить инфраструктуру и самое главное данные. Мы предлагаем, как и в предыдущем примере, пойти по пути консолидации инфраструктуры в рамках одного решения Dell EMC PowerStore. Вы получите полностью готовую инфраструктуру в рамках шасси в 2U, состоящую из пары отказоустойчивых серверов, соединенных с высокоскоростной СХД.

Оба типа систем представлены в виде линейки моделей с разными техническими характеристиками:



Важной особенностью систем PowerStore является возможность модернизации уже купленной системы в будущем. Вот как это можно сделать.

  • Традиционный апгрейд младших моделей до старших позволяет избегать лишних инвестиций на этапе закупки системы: нет необходимости сразу покупать дорогое решение, потенциал которого в полной мере раскроется только через несколько лет. Процедура апгрейда это штатная замена одних контроллеров на другие, она выполняется без остановки доступа к данным.
  • Благодаря правильно выбранной архитектуре систему можно будет модернизировать до нового поколения, что позволит поддерживать её в актуальном состоянии и увеличить срок эксплуатации.
  • Есть способ заложить возможность модернизации системы на этапе закупки. Для этого существует специальная опция Anytime Upgrade, которая позволяет либо актуализировать систему за счёт модернизации её до нового поколения, либо модернизировать систему до более старшей и более производительной модели.

Лицензирование


Dell EMC PowerStore лицензируется по модели Всё включено. Весь доступный функционал заказчик получает вместе с системой без дополнительных инвестиций. По мере выхода нового функционала массива он также станет доступен заказчикам после модернизации микрокода.

Оптимизация физического объема данных


Dell EMC PowerStore включает несколько методов повышения эффективности хранения данных за счёт сокращения используемого данными физического объёма:

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

Динамические пулы


Dell EMC PowerStore оснащается системой RAID на основе экстентов для обработки сбоев дисков. Большое количество элементов RAID представляет собой единое логическое пространство, формирующее пул, с которым работает конечный пользователь.

Архитектура динамический RAID обеспечивает 5 основных преимуществ:

  • сокращение времени восстановления после сбоя диска путем параллельного восстановления с множества дисков;
  • равномерное распределение запросов записи на все диски;
  • возможность смешивать диски разного объема в одном пуле;
  • возможность расширения емкости системы путем добавления дисков по одному и более;
  • возможность отказаться от физически выделенного диска горячего резерва позволяет системе перестраивать блоки данных с использованием всех исправных дисков.



High availability


СХД Dell EMC PowerStore полностью защищена от сбоев и включает ряд методов обеспечения высокой доступности. Данные методы предназначены для противостояния сбоям компонентов в самой системе и во внешней инфраструктуре, таких как перебои в сети или отключение питания. В случае сбоя отдельного компонента система хранения продолжает обслуживать данные. Система также может противостоять нескольким сбоям, если они происходят в отдельных наборах компонентов. После того, как администратор получит уведомление о сбое, он может заказать и заменить неисправный компонент без последствий.

NVMe SCM


Носители хранения данных SCM (Storage Class Memory) это высокопроизводительные энергонезависимые накопители, созданные на основе технологии Intel Optane. Накопители NVMe SCM имеют меньшую задержку и улучшенную производительность по сравнению с другими SSD. NVMe это протокол, который позволяет осуществлять доступ напрямую через шину PCIe. NVMe разработан с учетом низкой задержки высокопроизводительных носителей. Накопители NVMe SCM служат уровнем хранения для PowerStore, используются для пользовательских данных или метаданных. На данный момент доступны объёмы в 375 и 750 ГБ.

NVMе NVRAM


NVMe NVRAM это высокопроизводительные накопители, используемые для улучшения системы кэширования PowerStore. Они доступны с обоих контроллеров системы и позволяют системе легко кэшировать входящие записи. Накопители работают на скоростях DRAM по PCIe, обеспечивая исключительную производительность. Их конструкция позволяет им функционировать как энергонезависимые носители, и PowerStore может быстро сохранять входящие записи и подтверждать операции хосту без оповещения второго контроллера. Хранилища данных устанавливаются парами для зеркалирования данных между собой на случай аппаратного сбоя.

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

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



Кластеризация


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



Кластеризация устройств Dell EMC PowerStore даёт много преимуществ.

  • Scale-Out масштабирование для увеличения количества ресурсов системы за счёт добавления дополнительных вычислительных узлов процессора, памяти, ёмкости и интерфейсов подключения к хостам.
  • Независимое увеличение ресурсов хранения или вычислительных ресурсов.
  • Централизованное управление много узлового кластера.
  • Автоматическая балансировка нагрузки между узлами кластера.
  • Повышенная надёжность и отказоустойчивость.

PowerStore Manager


PowerStore Manager предоставляет администраторам СХД простой и интуитивно понятный интерфейс для настройки и управления кластером. Он основан на HTML5, не требует установки дополнительного программного обеспечения на клиенте и помогает выполнить следующие задачи:

  • Начальная настройка нового узла PowerStore.
  • Добавление или удаление узлов из существующего кластера.
  • Управление ресурсами кластера.

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

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

Вместо заключения


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

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

Я не понимаю, что хочу. Как пользователю сформулировать требования к CRM

07.07.2020 14:13:48 | Автор: admin
Когда кто-то трогает крестик, должен плакать персиковый медвежонок*, это, пожалуй, самое милое требование из тех, что мне приходилось встречать (но, к счастью, не реализовывать). Оно было сформулировано сотрудницей с 12 годами опыта работы в одной компании. Вы поняли, что ей нужно (ответ в конце)? Уверенное второе место занимает это: Биллинг должен запускаться по моему желанию, желание выражается на мобильнике**.

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


Пользователь избегает ответственности за требования


Если посмотреть те запросы, которые люди пишут о CRM в социальных сетях или в профильных сообществах, есть чему удивиться. Множество возмущённых постов о том, что нельзя найти CRM для длинных продаж, дистрибуции машинного масла, агентства наружной рекламы и т.д. И если человек занимается оптовыми продажами сена, то они ищут именно CRM Seno version и никак иначе. А вот в процессе общения с вендором такие запросы как-то сразу отпадают, потому что человек, выбирающий CRM, погружается в тему и понимает, что современные CRM-системы способны решить задачи практически любого бизнеса дело не в отраслевой версии, а в настройках и индивидуальных доработках.

Так откуда возникают неадекватные требования?

  • Главная причина непонимание сущности CRM-системы как технологии. Любая современная CRM-система в своей основе имеет множество разных таблиц, которые между собой связаны ключевыми полями с определёнными значениями (кто не знаком с СУБД, но когда-то работал с MS Access, легко вспомнит эту визуализацию). Поверх этих таблиц строится интерфейс: десктопный или в вебе, без разницы. Работая с интерфейсом, вы фактически работаете с теми самыми таблицами. Как правило, задачи абсолютно любого бизнеса можно решить с помощью настройки интерфейса, создания новых объектов и новых связей в совокупности с одновременным обеспечением логики их взаимодействия. (доработка).

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

    Но в целом компании малого бизнеса могут использовать CRM-систему даже без доработок и покрывать все нужды оперативной работы. Именно потому что CRM создаётся как универсальное решение для автоматизации бизнеса. А то, насколько она будет эффективна для вашей компании, зависит от того, как она будет настроена и наполнена данными (например, в RegionSoft CRM есть несколько классных инструментов, которые можно адаптировать точно под нужды конкретного бизнеса и даже его отделов: редактор бизнес-процессов, настраиваемый калькулятор для построения расчётов параметров продукции, механизм для настройки сложных KPI и это подходящие механизмы для любой компании).
  • Представитель бизнеса о CRM знает от других, мнение основано на чужом негативном опыте. Он полагает, что и с ним случится что-то подобное, не подозревая, что его знакомый никогда не скажет я не разобрался в CRM или я зажал деньги на внедрение и обучение, а теперь страдаю, нет, он обвинит разработчика или вендора втюхали мне эту CRM, продали и в кусты и т.д. Такие ребята нередко решают, что вендор должен тратить часы времени сотрудников совершенно бесплатно (не могу понять, почему они же не требуют бесплатного обслуживания и ежедневной мойки автомобиля от завода-изготовителя или дилера, а спокойно оплачивают стоимость ТО официального дилера.
  • Потенциальные клиенты считают, что раз на рынке есть кто-то, кто предлагает CRM бесплатно (с кучей ограничений и звёздочек), то и остальные должны просто раздавать CRM-системы. Бесплатные CRM в Яндексе ищут около 4000 человек ежемесячно. На что они надеются непонятно, ведь по сути любая бесплатная CRM, если она рассчитана более чем на одного человека, всего лишь урезанная демо-версия и маркетинговый инструмент.

Есть и другие причины, но эти три идут с большим отрывом. С такими клиентами работать довольно непросто, поскольку у них уже есть сформированный образ идеальной по их мнению CRM и они нередко ждут ответа на свой вопрос типа: Нет, вы мне дадите CRM для продаж холодильного торгового оборудования марки Север или мне звонить в Германию и заказывать SAP?. При этом бюджета на внедрение CRM хватит разве что на звонок в эту самую Германию. Звучит немного зло, но на самом деле идти с ультиматумом к разработчикам CRM гораздо менее продуктивно, чем обсуждать требования и прислушиваться к опытным внедренцам.

Как формулировать требования?


Функциональные требования


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

  1. Повышение эффективности работы. Если команда продаж в первую очередь и вся компания в целом увязла в администрировании, упускает важные события и теряет клиентов, забывает выполнить задачи в срок, то необходима помощь программы в управлении временем и задачами. А это значит, что среди ваших первых требований должны быть классная карточка клиента, разнообразные планировщики и возможность быстро собирать информацию по клиентам в единой базе. Довольно стандартные требования. На этом этапе можно предъявить дополнительное требование автоматизацию бизнес-процессов, которая упорядочивает рутину в бизнесе любого размера.
  2. Повышение объёма продаж. Если вам нужно больше продаж, особенно в кризис, который тут кружит над нашими уже седыми от нервов головами, то у вас есть ключевые подзадачи: сбор полной информации о клиенте, сегментация и персонализация обращений к клиентам, быстрая работа с оформлением сделки и информативная воронка продаж. Это всё тоже есть в стандартных CRM-системах.
  3. Отслеживание эффективности работников (не путать с контролем времени сотрудников, мы на этом поле не играем!). Вот здесь всё уже интереснее. Найти CRM, которая решит две предыдущие задачи, очень просто, найти CRM с KPI сильно сложнее, найти CRM с настоящим, многокритериальным, аналитическим механизмом KPI совсем непросто (если ищете, то у нас есть RegionSoft CRM Professional 7.0 и выше, а в ней KPI). Если в выбранной вами CRM-системе нет системы KPI, вы можете попросить такую доработку, однако она скорее всего будет довольно дорогой, потому что это практически отдельный модуль для любого ПО.
  4. Безопасность. На первый взгляд, CRM не относится к инструментам обеспечения корпоративной безопасности. Но автоматизация без управления безопасностью выглядит несостоятельно. Нередко к выбору CRM приводит желание руководителя избавиться от серых схем, откатов и своих персональных клиентов у продажников. CRM-система хранит данные, сохраняет клиентскую базу от попыток копирования и передачи третьим лицам, благодаря разделению прав доступа помогает контролировать круг клиентов и компетенций каждого сотрудника. И заметьте вы контролируете и делаете безопасной непосредственно рабочую деятельность, а не время сотрудников на работе.

Как правило, требования формулируются не по одной из перечисленных задач, а по нескольким. Это справедливо: раз современная CRM давно стала CRM++, почему бы не использовать её возможности не только для отдела продаж, но и для всей компании сразу. Например, календарём, телефонией, планировщиками, записями о клиентах и бизнес-процессами могут пользоваться все сотрудники компании. Как итог вся команда собрана в одном интерфейсе. Оптимальный путь, особенно сейчас, в условиях удалённой и частично удалённой работы.

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

Дополнительные требования к CRM


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

Как оценивать стоимость CRM?


У нас была большая статья о том, сколько стоит CRM, но там изложен универсальный подход, который можно применить и для ИП на 3 человек, и для оператора связи на 1500 сотрудников. Для малого бизнеса ситуация выглядит несколько иначе и тем более мы призываем вас посмотреть на неё иначе в условиях нынешнего кризиса.

Итак, вам нужна CRM и у вас в компании 10 сотрудников, каждого из которых вы хотите подключить к единому информационному ресурсу компании пусть к RegionSoft CRM Professional (мы не имеет права рассматривать чужие решения).

Если вы решите купить CRM, то заплатите за все лицензии один раз 134 700 рублей (по состоянию на июль 2020 года). Это, с одной стороны, оптимальный путь: заплатил и забыл, эти 134.7 тыс. не прирастут ни через год, ни через три. Если вы, например, арендуете облачную CRM, то в первый месяц вы заплатите всего 9000 рублей, но через год это будет уже 108 000, через два 216 000, через три 324 000 (и то, если обойдётся без ежегодной индексации цен).

Но! Мы знаем, что у бизнеса сейчас может не быть 134 700, а CRM в кризис нужна больше чем когда-либо. Поэтому у нас есть рассрочка 26 940 в месяц и аренда 11 233 в месяц с правом выкупа. При этом вы получаете не какой-то уменьшенный пакет функций, а всё ту же мощную редакцию.

Мы сделали эту выкладку далеко не только ради рекламы. Если вы приходите к вендору, стоит правильно формулировать ценовые требования.

  • Не просите бесплатную версию вы по факту продадите её сами себе (потому что это бесплатно) и попадёте на маркетинговый крючок: в итоге всё равно купите, но вас немного задолбают общением, а вы потом задолбаетесь из-за ограничений функциональности.
  • Если вы не готовы оплатить год аренды или всю стоимость решения on-premise, обсуждайте возможность рассрочки и дискретных платежей.
  • Никогда не заказывайте доработку сразу, если вы не уверены, что функция понадобится прямо сейчас и её нет в CRM. Лучше начните использовать CRM-систему и постепенно сформулируйте, что вам необходимо доработать и как эта доработка будет использоваться в компании.
  • Уточните у вендора, какие дополнительные затраты обязательны: у кого-то это платный внешний почтовый клиент, обязательное подключение к единственному оператору IP-телефонии, пакет технической поддержки и проч. Эти затраты могут стать внезапным и неприятным сюрпризом.
  • Узнайте стоимость внедрения и обучения в 90% случаев это оправданные расходы, которые окупаются благодаря быстрому и правильному старту работы в CRM-системе.

И помните: деньги не должны быть единственным требованием! Если вы будете ориентироваться только на стоимость программы, то скорее всего просто не сможете выбрать то решение, которое нужно бизнесу.

Итак, мы с вами разобрались с двумя самыми важными требованиями: функциональностью CRM-системы и с деньгами, которые предстоит за неё заплатить.

Какие ещё требования могут быть к CRM?


  • Нагрузка на CRM-систему. Расскажите вендору, какое количество информации планируется ежедневно вносить в базу, как она должна храниться и какие резервные копии иметь. Для большинства современных CRM это по-прежнему принципиальный момент, который может сказаться на скорости работы, стоимости, модели поставки и т.д.
  • Возможные настройки. Обговорите заранее, какие настройки для вас особенно важны. Это может быть воронка продаж, почтовый клиент, рассылки, обязательно распределение прав доступа и т.д. Как правило, тут пожелания бывают самые специфические.
  • Совместимость с существующей инфраструктурой. Уточните, какие интеграции возможны, как организована телефония, какое серверное оборудование требуется и требуется ли (для десктопных CRM-систем). Посмотрите, с каким ПО из вашего зоопарка пересекается CRM и откажитесь от него для экономии и наведения порядка в делах.
  • Безопасность. Если у вас есть особые требования к безопасности, обговорите их отдельно, поскольку не все они могут быть выполнены для некоторых типов поставки ПО. Уточните сроки и периодичность создания бэкапов, а также уточните, платная эта услуга или нет.
  • Техническая поддержка. Мы рекомендуем у всех поставщиков CRM на первый год покупать пакет платной приоритетной поддержки так вам будет гораздо спокойнее. В любом случае, убедитесь, что техническая поддержка есть и уточните границы её оказания.
  • Облако или десктоп. Вечный спор из разряда Apple vs Samsung, Canon vs Nikon, Linux vs Windows. Если коротко, то десктоп в конечном итоге дешевле, местами безопаснее и быстрее в работе, лицензии принадлежат вам и не пропадут вместе с вендором. Облако удобнее для молодых, начинающих команд, когда не требуется персональное внедрение или доработка. Масштабируемость у обоих типов поставки CRM одинаковая.

Главные ошибки пользователей при описании требований


  • Упираться в мелочи. Как правило, почти любую мелочь можно настроить, гораздо важнее обратить внимание на то, как CRM согласуется с вашими бизнес-процессами. Если вы считаете самым важным в CRM дашборд с данными или возможность заменить лого разработчика на своё (кстати, в RegionSoft CRM легко), пообщайтесь с коллегами они помогут вам собрать требования, весьма красочно описав все недостатки своих бизнес-процессов.
  • Превращать требования к ПО в список покупок. Вы внимательно читаете все отзывы, социальные сети, Хабр, другие порталы, смотрите демо-версии всех CRM-систем и методично записываете всё, что вас хоть как-то заинтересовало, а затем весь этот длинный список вываливаете на наиболее подходящего вендора. А он, бедный, не понимает, почему должен разработать корпоративный портал, систему управления претензиями, модуль бухучёта и систему контроля трафика и документооборота для небольшой торговой компании в одном флаконе.

Выбирайте только то, что вам реально нужно и с чем вы можете работать. Потому что мы для вас при определённом уровне оплаты можем и экраноплан спроектировать, но а) это будет дорого; б) зачем он вам? В общем, выбирайте CRM-систему для нормальной рабочей жизни, а не для любования набором модулей и возможностей это может просто не окупиться.

  • Включать в требования фантазии и желания. Указывайте в требованиях то, что вы реально хотите сделать в бизнесе и будете использовать; задачи, поставленные в вакууме и в отрыве от реальности, причинят вред: вы убьёте время на их обсуждение и не получите результат.
  • Говорить с вендором, как с роботом. Если вы общаетесь непосредственно с разработчиком CRM (а не с партнёрской сетью), то знайте: мы не только программисты и инженеры, мы прежде всего такой же бизнес, как и вы. Поэтому расскажите о ваших проблемах, мы их отлично поймём и подскажем, как CRM эти проблемы решит. Мы не просто поставщики решений, в большинстве случаев мы сочетаем рассказ о CRM с разбором проблем вашего бизнеса. Поэтому говорите с разработчиками на обычном, человеческом языке. Скажите, почему вам вдруг стала интересна CRM-система и мы объясним вам, как её внедрить лучшим образом.
  • Быть негибким и упрямым в каждой формулировке. Обращайте внимание на то, как вендор предлагает решать ваши задачи у него уже есть опыт работы в сотнях проектах автоматизации и его инженеры нередко предлагают наиболее эффективное решение из всех возможных. Например, клиент может настаивать на требовании нотации BPMN 2.0 для описания процессов (потому что её хорошо продавали на конференции для CIO) и не признавать альтернатив, а потом попробовать удобный нативный редактор бизнес-процессов и убедиться, что с ним ВСЕ его сотрудники смогут справиться с бизнес-процессами. Выбирать удобные и практичные, а не модные и дорогие решения идеальная практика для малого бизнеса, который тратит на автоматизацию свои деньги, а не бездонный бюджет корпорации.
  • Говорить о CRM в целом, а не о конкретной системе. Во время общения с вендором говорите именно о его CRM-системе, запросите подробную презентацию, задавайте подробные вопросы по существу. Так вы сможете понять, какие задачи вашего бизнеса сможет решить эта конкретная CRM-система.

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


И напоследок, простой способ определить, удалось ли внедрение CRM: если вы используете CRM и скорость бизнес-процессов выросла, внедрение проведено правильно и ваш бизнес стал эффективнее.



(осторожно, 77 МБ)

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

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

Контур стал организатором ICFPC 2020

07.07.2020 10:10:24 | Автор: admin

Ничего не планируйте с 17 по 20 июля, потому что в это время пройдет ежегодное международное соревнование ICFPC 2020. Собирайте команду и трое суток решайте секретную задачу от Контура. Чтобы быть в курсе всех новостей, получать подсказки и не пропустить регистрацию, подписывайтесь на Твиттер.


15 лет команда Контура участвовала в соревновании, а в этом году нас пригласили провести ICFPC 2020. Мы первая команда из России, которой доверили организацию, и это очень круто! Какую задачу мы приготовили пока секрет. Все участники узнают ее условия одновременно 17 июля, но уже сейчас в Твиттере можно увидеть некоторые спойлеры.




Почему нужно участвовать в ICFPC 2020


ICFPC командное соревнование. Соревнований для одиночек много: например, Facebook Hacker Cup и Google Code Jam. Если вам нравятся AI для игр, то codingame.com проводят отличные челенджи раз в 2-3 месяца. В одиночных соревнованиях топ обычно забит какими-то гениями, а в командных можно хорошо выступить за счет упорства и хорошей организации.


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


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


Задачи небанальные. Организатор соревнования меняется каждый год и старается превзойти предыдущего, поэтому задачи год от года становятся все интереснее. Обычно это университет, поэтому они добавляют в задачи отсылки к научным проблемам и классике computer science. Кроме того, ICFPC приурочен к научной конференции по функциональному программированию ICFP, и это влияет на задачи. Через раз приходится читать описания на функциональном псевдоязыке (не бойтесь, понятном для обывателей!), а потом программировать виртуальные машины и компиляторы.


Задача одна! За 72 часа команда неограниченного размера должна решить всего одну задачу. Но многогранную и трудную. Её нельзя решить оптимально, но можно решить лучше других команд. Самыми необычными и яркими задачами считаются задачи 2006 и 2007 годов, в которых балом правили виртуальные машины внутри виртуальных машин и а также реверс-инжениринг виртуальных машин.


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


Подробности соревнования появляются в Твиттере. Там же скоро появится ссылка на регистрацию.


Желаем удачи!

Подробнее..

Применение CQRS amp Event Sourcing в создании платформы для проведения онлайн-аукционов

07.07.2020 14:13:48 | Автор: admin
Коллеги, добрый день! Меня зовут Миша, я работаю программистом.

В настоящей статье я хочу рассказать о том, как наша команда решила применить подход CQRS & Event Sourcing в проекте, представляющем собой площадку для проведения онлайн-аукционов. А также о том, что из этого получилось, какие из нашего опыта можно сделать выводы и на какие грабли важно не наступить тем, кто отправится путем CQRS & ES.
image


Прелюдия


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

Теперь чуть-чуть терминологии. Аукцион это когда продаются некие предметы лоты (lots), а покупатели (bidders) делают ставки (bids). Обладателем лота становится покупатель, предложивший самую большую ставку. Timed-аукцион это когда у каждого лота заранее определен момент его закрытия. Покупатели делают ставки, в какой-то момент лот закрывается. Похоже на ebay.

Timed-платформа была сделана классически, с применением CRUD. Лоты закрывало отдельное приложение, запускаясь по расписанию. Работало все это не слишком надежно: какие-то ставки терялись, какие-то делались как будто бы от лица не того покупателя, лоты не закрывались или закрывались по несколько раз.

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

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

Своего опыта работы с CQRS & ES у нас не было, так что мы посовещались с коллегами, у которых он был (компания у нас большая), презентовали им наши бизнес-реалии и совместно пришли к заключению, что CQRS & ES должен нам подойти.

Какая еще есть специфика работы онлайн-аукционов:

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

Краткий обзор подхода CQRS & ES


Не буду подробно останавливаться на рассмотрении подхода CQRS & ES, материалы об этом есть в интернете и в частности на Хабре (например, вот: Введение в CQRS + Event Sourcing). Однако кратко все же напомню основные моменты:

  • Самое главное в event sourcing: система хранит не данные, а историю их изменения, то есть события. Текущее состояние системы получается последовательным применением событий.
  • Доменная модель делится на сущности, называемые агрегатами. Агрегат имеет версию. События применяются к агрегатам. Применение события к агрегату инкрементирует его версию.
  • События хранятся в write-базе. В одной и той же таблице хранятся события всех агрегатов системы в том порядке, в котором они произошли.
  • Изменения в системе инициируются командами. Команда применяется к одному агрегату. Команда применяется к последней, то есть текущей, версии агрегата. Агрегат для этого выстраивается последовательным применением всех своих событий. Этот процесс называется регидратацией.
  • Для того, чтобы не регидрировать каждый раз с самого начала, какие-то версии агрегата (обычно каждая N-я версия) можно хранить в системе в готовом виде. Такие снимки агрегата называются снапшотами. Тогда для получения агрегата последней версии при регидратации к самому свежему снапшоту агрегата применяются события, случившиеся после его создания.
  • Команда обрабатывается бизнес-логикой системы, в результате чего получается, в общем случае, несколько событий, которые сохраняются в write-базу.
  • Кроме write-базы, в системе может еще быть read-база, которая хранит данные в форме, в которой их удобно получать клиентам системы. Сущности read-базы не обязаны соответствовать один к одному агрегатам системы. Read-база обновляется обработчиками событий.
  • Таким образом, у нас получается разделение команд и запросов к системе Command Query Responsibility Segregation (CQRS): команды, изменяющие состояние системы, обрабатываются write-частью; запросы, не изменяющие состояние, обращаются к read-части.



Реализация. Тонкости и сложности.


Выбор фреймворка


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

В целом наш технологический стек это Microsoft, то есть .NET и C#. База данных Microsoft SQL Server. Хостится все в Azure. На этом стеке была сделана timed-платформа, логично было и live-платформу делать на нем.

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

Зачем вообще нужен фреймворк CQRS & ES? Он может из коробки решать такие задачи и поддерживать такие аспекты реализации как:

  • Сущности агрегата, команды, события, версионирование агрегатов, регидратация, механизм снапшотов.
  • Интерфейсы для работы с разными СУБД. Сохранение/загрузка событий и снапшотов агрегатов в/из write-базы (event store).
  • Интерфейсы для работы с очередями отправка в соответствующие очереди команд и событий, чтение команд и событий из очереди.
  • Интерфейс для работы с веб-сокетами.

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

  • Azure Service Bus в качестве шины команд и событий, Chinchilla поддерживает его из коробки;
  • Write- и read-базы Microsoft SQL Server, то есть обе они SQL-базы. Не скажу, что это является результатом осознанного выбора, скорее по историческим причинам.

Да, фронтенд сделан на Angular.

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

Выбор агрегатов


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

В нашем случае доменная модель состоит из нескольких основных сущностей, примерно таких:

public class Auction{     public AuctionState State { get; private set; }     public Guid? CurrentLotId { get; private set; }     public List<Guid> Lots { get; }}public class Lot{     public Guid? AuctionId { get; private set; }     public LotState State { get; private set; }     public decimal NextBid { get; private set; }     public Stack<Bid> Bids { get; }} public class Bid{     public decimal Amount { get; set; }     public Guid? BidderId { get; set; }}


У нас получилось два агрегата: Auction и Lot (с Bidами). В общем, логично, но мы не учли одного того, что при таком делении состояние системы у нас размазалось по двум агрегатам, и в ряде случаев для сохранения консистентности мы должны вносить изменения в оба агрегата, а не в один. Например, аукцион можно поставить на паузу. Если аукцион на паузе, то нельзя делать ставки на лот. Можно было бы ставить на паузу сам лот, но аукциону на паузе тоже нельзя обрабатывать никаких команд, кроме как снять с паузы.

В качестве альтернативного варианта можно было сделать только один агрегат, Auction, со всеми лотами и ставками внутри. Но такой объект будет довольно тяжелым, потому что лотов в аукционе может быть до нескольких тысяч и ставок на один лот может быть несколько десятков. За время жизни аукциона у такого агрегата будет очень много версий, и регидратация такого агрегата (последовательное применение к агрегату всех событий), если не делать снапшотов агрегатов, будет занимать довольно продолжительное время. Что для нашей ситуации неприемлемо. Если же использовать снапшоты (мы их используем), то сами снапшоты будут весить очень много.

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

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

Мы на данном этапе эволюции проекта живем с двумя агрегатами, Auction и Lot, и нарушаем архитектуру, меняя в рамках некоторых команд оба агрегата.

Применение команды к определенной версии агрегата


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

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

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

Ошибки при выполнении команды с использованием очереди


В нашей реализации, в большой степени обусловленной использованием Chinchilla, обработчик команд читает команды из очереди (Microsoft Azure Service Bus). Мы у себя явно разделяем ситуации, когда команда зафейлилась по техническим причинам (таймауты, ошибки подключения к очереди/базе) и когда по бизнесовым (попытка сделать на лот ставку той же величины, что уже была принята, и проч.). В первом случае попытка выполнить команду повторяется, пока не выйдет заданное в настройках очереди число повторений, после чего команда отправляется в Dead Letter Queue (отдельный топик для необработанных сообщений в Azure Service Bus). В случае бизнесового эксепшена команда отправляется в Dead Letter Queue сразу.



Ошибки при обработке событий с использованием очереди


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

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



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

В итоге, в качестве временной меры мы отказались от использования Azure Service Bus для передачи событий из write-части приложения в read-часть. Вместо нее используется так называемая In-Memory Bus, что позволяет обрабатывать команду и события в одной транзакции и в случае неудачи откатить все целиком.



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

Отправка команды в качестве реакции на событие


Такое в принципе уместно, но только в случае, когда невыполнение этой второй команды не ломает состояние системы.

Обработка множества событий одной команды


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



Обработка одного события несколькими обработчиками


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

Например, обработчик события в read-базе добавляет ставку на лот величиной 5 рублей. Первая попытка сделать это будет успешной, а вторую не даст выполнить constraint в базе.



Выводы/Заключение


Сейчас наш проект находится в стадии, когда, как нам кажется, мы наступили уже на бОльшую часть существующих граблей, актуальных для нашей бизнес-специфики. В целом мы считаем свой опыт довольно успешным, CQRS & ES хорошо подходит для нашей предметной области. Дальнейшее развитие проекта видится в отказе от Chinchilla в пользу другого фреймворка, дающего больше гибкости. Впрочем, возможен и вариант отказа от использования фреймворка вообще. Также вероятно будут какие-то изменения в направлении поиска баланса между надежностью с одной стороны и быстротой и масштабируемостью решения с другой.

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

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

Перевод Чеклист для проекта по машинному обучению

07.07.2020 12:09:02 | Автор: admin
image

В этом посте я собрал чеклист, на который я постоянно ссылаюсь, работая над комплексным проектом по машинному обучению.

Зачем мне вообще нужен чеклист?

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

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

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

Как говорит Атул Гаванде в своей книге The Checklist Manifesto,
объем и сложность того, что мы знаем, превзошли нашу индивидуальную способность правильно, безопасно и надежно предоставлять свои преимущества.
Итак, позвольте мне провести вас по этому четкому и краткому списку действий, которые уменьшат вашу рабочую нагрузку и улучшат ваши результаты

Чеклист проектов по машинному обучению


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

1. Определите проблему с точки зрения высокого уровня


Это чтобы понять и сформулировать бизнес-логику проблемы. Это должно сказать вам:

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

2. Определите источники данных и получите данные


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

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

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

3. Первоначальная разведка данных


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

Шаги:

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

4. Исследовательский анализ данных для подготовки данных


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

  • Написание функций для преобразования данных и автоматизации процесса для предстоящих пакетов данных.
  • Напишите функции для очистки данных (вменяя пропущенные значения и обрабатывая резко отличающиеся значения)
  • Напишите функции для выбора и проектирования особенностей удалите избыточные особенности, отформатируйте преобразование объектов, и другие математические преобразования.
  • Масштабирование особенностей (features) стандартизация особенностей (features) .

5. Разработайте базовую модель, а затем изучите другие модели, чтобы отобрать лучшие


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

  • Обучите несколько часто используемых моделей, таких как модель наивного байеса, модель линейной регрессии, SVM и т. д., используя параметры по умолчанию.
  • Измерьте и сравните производительность каждой модели с базовой и со всеми остальными.
  • Используйте N-кратную перекрестную проверку для каждой модели и вычислите среднее и стандартное отклонение показателей эффективности по N-кратным показателям.
  • Изучите особенности, которые оказывают наибольшее влияние на цель.
  • Проанализируйте типы ошибок, которые допускают модели при прогнозировании.
  • Проектируйте функции по-разному.
  • Повторите вышеупомянутые шаги несколько раз (методом проб и ошибок), чтобы убедиться, что мы использовали правильные функции в правильном формате.
  • Составьте шорт-лист лучших моделей на основе их показателей эффективности.

6. Точно настройте свои модели из шорт-листа и проверьте наличие методов ансамбля


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

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

7. Документируйте код и сообщайте свое решение


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

  • Документируйте код, а также ваш подход ко всему проекту.
  • Создайте информационную панель, например, voila, или проницательную презентацию с визуализацией, которая не требует пояснений.
  • Напишите блог/отчет о том, как вы анализировали особенности, тестировали различные преобразования и т. д. Опишите свое обучение (неудачи и методы, которые сработали)
  • Закончите с основным результатом и будущим объемом (если таковые имеются)

8. Разверните свою модель в продакшен, мониторинг


Если ваш проект требует тестирования развертывания на реальных данных, вы должны создать веб-приложение или REST API для использования на всех платформах (web, Android, iOS). Основные пункты (будут варьироваться в зависимости от проекта) включают в себя:

  • Сохраните вашу окончательную обученную модель в файл h5 или pickle.
  • Обслуживайте свою модель с помощью веб-сервисов, Вы можете использовать Flask для разработки этих веб-сервисов.
  • Подключите источники входных данных и настройте конвейеры ETL.
  • Управляйте зависимостями с помощью pipenv, docker/Kubernetes (на основе требований масштабирования)
  • Вы можете использовать AWS, Azure или Google Cloud Platform для развертывания своего сервиса.
  • Делайте мониторинг производительности на реальных данных или просто для людей, чтобы они могли использовать вашу модель со своими данными.

Примечание. Чеклист может быть адаптирован в зависимости от сложности вашего проекта.

image

Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя платные онлайн-курсы SkillFactory:



Читать еще


Подробнее..

Перевод Использование методов анализа графов для поиска аномалий

07.07.2020 10:10:24 | Автор: admin
Несмотря на то, что описание данных с помощью графов практикуется еще с позапрошлого столетия, использование их в решении повседневных задач по анализу данных лишь набирает обороты. Хотя основное внимание уделяется, как водится, графовым эмбеддингам и сверточным сетям, маленькие шаги предпринимаются и в алгоритмах по поиску аномалий или антифроде. Основная обзорная статья, на которую ссылается большинство специалистов в своих в докладах и публикациях, Graph based anomaly detection and description: a survey от авторов Leman Akoglu, Hanghang Tong, Danai Koutra (Akoglu, 2015). Мы в CleverDATA решили рассказать Хабру об этом практически единственном материале по теме и предлагаем вашему вниманию его саммари.

Первый граф Российского царства Борис Петрович Шереметев. Аномалий не обнаружено.

Во вступлении авторы отвечают на вопрос: В чем же преимущества поиска аномалий с использованием теории графов?:

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

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

Таким образом, перспективность использования теории графов для поиска аномалий обоснована. Перейдем к описанию самих методов.

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


Поиск аномалий в графах без свойств, приписанных вершинам и/или ребрам


Для простых графов выделяют три направления структурных подходов: поиск аномалий на основе признаков узлов, диад узлов, триад, эгонетов (egonet), на основе сообществ и на основе метрик близости (proximity): pagerank, personalized pagerank, simrank. При этом предлагается для решения задачи поиска аномалий на графе использовать обычные алгоритмы (например, Isolation Forest, или если есть разметка, то стандартные классификаторы), но на основе графовых признаков.

Пример Эгонета

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

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

Также авторы описывают и другие подходы:

  • кластеризация узлов на основании сходства их ближайшего окружения; предлагается реорганизация матрицы смежности для получения более плотных и менее плотных блоков (Chakrabarti 2004; Rissanen, 1999);
  • матричная факторизация; предлагается аналог Non-negative matrix factorization (NMF) (Tong and Lin 2011).

Поиск аномалий в графах с узлами и/или ребрами, обладающими свойствами


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

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

Также перечисляются и другие возможности: SAX (Symbolic Aggregate approXimation) (Lin et al., 2003), MDL-binning (Minimum description length) (Kontkanen and Myllymki, 2007) и дискретизация по минимуму энтропии (Fayyad and Irani, 1993). Авторы этой статьи (Eberlie and Holder, 2007) иначе подходят к определению аномалии в графовых данных, считая аномальными те подграфы, которые похожи по отношению к условно нормальному графу в определенных пределах. Такой подход авторы обосновывают тем, что самые успешные мошенники будут стараться максимально подражать реальности. Они также предлагают учитывать стоимость модификации показателя и формулируют показатели аномальности с учетом этой стоимости (чем меньше стоимость, тем более аномальным является показатель).

Поиск аномалий для графов с атрибутированными узлами рассматривается и в основанный на поиске сообществ парадигме. Предлагается подразделять графы на сообщества. Далее, в рамках каждого сообщества, искать аномалии по атрибутам. Например, курильщик в команде по бейcболу. Курильщик не является аномалией для общества в целом, но в своем сообществе является. Другой подход (Mller, 2013) основывается на выборе пользователем (аналитиком) набора узлов, для которых далее определяется подпространство показателей, схожих для них. А аномалиями в таком подходе являются узлы, которые структурно принадлежат к кластеру этих узлов, но в выбранном подпространстве показателей находятся далеко от них.

Отдельно рассматриваются методы semi-supervised, в предположении, что какая-то часть узлов размечена как нормальные и аномальные, а остальные узлы можно классифицировать, используя соответствующие методики, а в самом простом случае им можно присваивать метки соседних с ними узлов. Перечисляются основные подходы: iterative classification algorithm, gibbs sampling (подробнее про эти подходы пишут здесь), loopy belief propagation, weighted-vote relational network classifier.

Поиск аномалий в динамическом графе


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

  1. выделяется некоторое сжатие или интегральная характеристика каждого статического графа;
  2. вычисляется расстояние последовательно идущих графов;
  3. аномальными принимаются те графы, для которых расстояние выше порога.

В качестве мер расстояния предлагаются:

  • maximum common subgraph (MCS) distance;
  • error correcting graph matching distance, то есть расстояние, измеряющее, сколько шагов нужно сделать, чтобы из одного графа сделать другой;
  • graph edit distance (GED), то же, что и предыдущее, но возможны лишь топологические изменения;
  • расстояния между матрицами смежности (например, Хэмминга);
  • различные расстояния на основе весов ребер;
  • расстояния между спектральным представлением графов (распределениями собственных векторов);
  • описывается также и более экзотическая мера: эвклидово расстояние между перроновскими собственными векторами графа.

В статье от Bunke et al. (2006) авторы предлагают считать расстояние не только между последовательно идущими графами, но вообще между всеми графами в последовательности, и потом применять многомерное шкалирование, переводя графы в двумерное пространство. Далее выбросы ищутся в этом двумерном пространстве.

Описан также следующий способ работы с динамическими графами (Box and Jenkins, 1990): графу ставится в соответствие некое число (расчетный показатель) и далее применяются стандартные методы поиска аномалий во временных рядах. Например, расхождения с моделью ARIMA.

В статье Akoglu and Faloutsos (2010) авторы осуществляют следующую последовательность операций:

  1. выделяют для каждого узла графа для каждого момента времени F-признаков;
  2. для каждого признака с временным окном W считают корреляционные матрицы между узлами;
  3. выделяют собственные векторы и далее рассматривают лишь первый собственный вектор;
  4. параллельно выделяют типичное поведение собственных векторов корреляционной матрицы (для этого делается еще одно SVD-разложение над матрицей изменения всех собственных векторов корреляционной матрицы во времени);
  5. сравнивают (через косинусное произведение) с реальным поведением этого вектора, получая таким образом показатель аномальности рассматриваемого временного окна.

Матричное разложение используется также и в статье Rossi (2013):

  1. аналогично предыдущему подходу выделяется по F-признаков на узел на каждый временной промежуток;
  2. для каждого временного промежутка производится NMF-разложение, при котором каждому узлу ставится в соответствие роль;
  3. далее мониторится изменение ролей каждого узла.

Матричное разложение для интерпретации результатов


Отдельно хочется отметить приведенные авторами методы аппроксимации матриц альтернативные по отношению к давно известным SVD, PCA, NMF: CUR (Drineas et al., 2006), CMD (Sun et al. 2007b) и Colibri (Tong et al. 2008). Основным преимуществом этих методов является интерпретируемость, поскольку в отличии от SVD, переводящего точки в другое пространство, эти методы оставляют пространство нетронутым, лишь сэмплируя точки из него. Наиболее простым из них является CUR, у которого авторы отмечают два недостатка: в нем точки выбираются из матрицы с повторением. В CMD удается убрать этот недостаток, однако как и в CUR, этому методу присуща линейная избыточность, которую удается избежать авторам алгоритма Colibri. Хотя методы были изобретены именно для решения задач поиска аномалий в графах методами матричной аппроксимации, их использование может быть перспективным и для других задач.

В задачах, обсуждаемых в этом обзоре, эти подходы применяются по следующему принципу: производится аппроксимация и оценивается, насколько различные столбцы/строки отличаются у аппроксимированной матрицы от изначальной. Также авторы отмечают метод NrMF (Tong and Lin 2011), модификация NMF, в котором ограничение на неотрицательность накладывается на матрицу остатков R, поскольку именно в ней сосредоточена основная информация по отличию апроксимации от изначальной матрицы, а отрицательные значения в таком случае было бы сложно интерпретировать. Тем не менее, до конца не ясно, почему нельзя аналогичным образом использовать SVD для декомпозиции, последующей реконструкции и последующим расчетом отличия от изначальной матрицы.


Определение узлов, связывающих аномальные


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

  1. Определение связного подграфа (connection subgraph) (Faloutsos et al., 2004). Проблему предлагается решать в терминах электротехники, присваивая одному узлу положительный потенциал, а другим узлам нулевой, и смотреть, как будет течь ток между ними, если присвоить ребрам некое сопротивление.
  2. Center-Piece Subgraphs (CePS) (Tong and Faloutsos, 2006). В отличие от предыдущего метода предпринимается попытка выделить лишь k-узлов из всех аномальных, поскольку совершенно не обязательно все узлы заданы. При этом k необходимо задавать.
  3. Dot2Dot (Akoglu et al., 2013b; Chau et al., 2012). В этом подходе авторы решают задачу группировки выбранных узлов и уже далее выделяют узлы, их связывающие.

Примеры поиска аномалий в различных сферах


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

Телекоммуникации. Целью являются люди, пользующиеся услугами бесплатно. Cortes et al. (2002) искали подграфы, тесно связанные с ключевым узлом по параметрам числа и продолжительности звонков. Наблюдения, которые авторы обнаружили: фродовые аккаунты оказались связаны, то есть нарушители или сами звонили друг другу, или звонили на одни и те же телефоны. Второе наблюдение нарушителей можно обнаружить по схожести их подграфов, определенных предложенным образом.

Онлайн-аукцион. Нарушители создают себе фейковые аккаунты и накручивают им рейтинги. Их не получается отследить по обычным агрегатным показателям, но возможно увидеть по графу. Аккаунты нарушителей более связаны с фейковыми аккаунтами, чем с хорошими аккаунтами. Фейки связаны примерно в равной степени с аккаунтами нарушителей и с хорошими. Последние в основном связаны с подобными себе аккаунтами. Pandit et al. (2007) решают эту задачу через приведение к реляционным марковским сетям (relational markov networks) и далее классифицируют узлы через Loopy Belief Propagation (метки класса итеративно распространяются по графу).

Транзакции. McGlohon et al. (2009) решают эту задачу через реляционную (relational) классификацию в предположении, что нарушители будут близко расположены друг к другу. То есть аналогично подходу из предыдущего примера.

Брокеры, которые жульничают с ценными бумагами. Здесь Neville et al. (2005) анализируют мультимодальный граф, выделяя подграфы, включающие человека под подозрением, его коллег, фирмы, с которыми он связан и т. п. Они рассчитывают агрегированные атрибуты и приписывают их центральному узлу. Далее используют реляционные вероятностные деревья (relational probability trees (Neville et al. 2003)) для реляционной классификации.

Поиск фейковых постов на форумах, дающих заведомо ложную информацию. Авторы описывают несколько подходов, с использованием признакового описания, анализа текста и графовых методов. Графовые методы, использованные Wang et al. (2011a), применялись для ситуации, когда в задаче присутствуют обзоры какого-то товара. В предложенном ими алгоритме предлагалось присваивать рецензентам показатели степени доверия им, их обзорам достоверности и товарам показатели надежности. Все эти показатели взаимосвязаны. Так, насколько можно доверять рецензенту, зависит от того, насколько у него достоверные обзоры. Надежность товаров зависит от степени доверия рецензентам, которые его описывают, а достоверность обзоров зависит от надежности товара, по которым они пишутся, и от доверия их авторам. Предлагаемый алгоритм сначала их случайно инициализирует, а затем итеративно улучшает оценку.

Трейдинг. Мошенники сначала совершают большое число транзакций друг с другом по какому-то типу акций, увеличивая их привлекательность, а затем, когда акции повышаются в цене, распродают их другим трейдерам. Оба этих последовательных инцидента можно отследить по графовым данным. В первом случае будет выделяться подграф, где нет никаких внешних транзакций (именуется черная дыра), а через временной промежуток этот же подграф преобразуется к подграфу, в котором сильно преобладают транзакции из подграфа в другую часть графа (именуется вулкан). Авторы ссылаются на работу Li et al. (2010).

Веб-ресурсы. Одним из первых подходов для борьбы с плохими веб-сайтами предлагалось распространение показателей надежности и ненадежности ресурсов. Если есть ссылка с одной страницы на другую, то для последней это увеличивает ее статус надежной. Если страница указывает на другую страницу, для которой известно, что она спам, то это снижает надежность изначальной страницы. Упоминается алгоритм TrustRank (Gyngyi et al., 2004) модификация PageRank для борьбы с веб-спамом. Для него требуется, чтобы изначально эксперты разметили часть сайтов, как надежные. Эти показатели далее распространяются по графу, постепенно затухая. Anti-TrustRank (Krishnan and Raj, 2006) следует этому же принципу, но с распространением показателей ненадежности от размеченных заведомо ненадежных сайтов. У обоих методов есть недостаток, что надежность делится по числу дочерних узлов. Benczr et al. (2005) предлагают совершенно иной подход: анализировать PageRank узла и соседних с ним. Распределение PageRank таких подграфов должно подчиняться некому степенному закону. Для тех же узлов, для которых распределение PageRank их соседей выбивается из этого закона, присваивается штраф. В работе (Castillo et al., 2007) предлагается сначала обучать классификатор на известных надежных и ненадежных страницах, а затем размывать результат скоринга остальных веб-сайтов по графу.

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

Атаки в компьютерных сетях. Sun et al. (2008) успешно применяют для решения этой задачи матричное разложение (CMD). Ding et al. (2012) используют подход основанный на поиске сообществ, выделяя в качестве подозрительных узлы-мосты между сообществами.

Заключение


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

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

Если вас интересует тема, то, чтобы получить больше информации, обязательно нужно идти в слак ODS (OpenDataScience), в каналы #network_analysis и #class_cs224w, смотреть курс Стэнфорда cs224w.

Еще недавно был прочитан курс по Knowledge Graphs. Ну и, конечно, нужно прочитать саму статью Graph based anomaly detection and description: a survey от авторов Leman Akoglu, Hanghang Tong, Danai Koutra (Akoglu, 2015), о которой идет речь в этом посте. Я перевел ее не всю, а только те фрагменты, которые счел важными, и понял в какой-то степени. Большинство авторов ссылаются именно на эту статью, потому что больше обзоров такого уровня и широты по теме нет. По крайней мере, я не нашел таких.

Список литературы

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

Из песочницы Зависимость от новостей как слезть с крючка отслеживания событий. Личный опыт специалиста по цифровой психологии

07.07.2020 12:09:02 | Автор: admin
image

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

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

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

Новости в моей ладони


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

image

Неполезный источник знаний


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

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

Опасность горячих новостей


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

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

Триггеры новостной аддикции


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

Данный эффект описан Ниром Эйялем в работе Hooked. На крючке, где раскрывается цепочка формирования привычек постоянно обновлять почту, проверять новые сообщения в мессенджерах и соцсетях. В отношении новостного серфинга все то же самое: ссылки на стартовых страницах поисковиков (Яндекс, Рамблер, Mail.ru) и мобильных браузеров (Opera, Chrome), а также Push-уведомления, всплывающие окна все это внедренные триггеры, заботливо презентуемые производителями софта и контента для усиления вовлеченности клиента (читай, усугубления зависимости).

image

Новостные ленты: зачем нам все это?


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

  1. Стремление переключиться с негативных мыслей о работе, проблем в личных отношениях и финансовых трудностей. Таким образом, мы даем краткосрочные передышки нервной системе от изнуряющих житейских рефлексий.
  2. Откладывание принятия сложного решения или начала работы. Залипание в соцсетях или новостном агрегаторе популярное проявление эффекта прокрастинации. Благодаря иллюзии полезного времяпрепровождения (ведь новости это важно!) мы стремимся обмануть самих себя, бесконечно оттягивая принятие решения или начала действия.
  3. Взращивание псевдокомпетентности. Благодаря нахождению в постоянном информационном потоке мы создаем ментальный конструкт эрудита человека, который в курсе самых последних событий и трэндов, осведомленного собеседника. Подобное потребление новостей сопоставимо с неструктурированной навигацией по гиперссылкам при попытке изучить новое явление или понятие. Итогом такого познания становится поверхностное и фрагментарное представление о чем-либо, пустое и никчемное знание.

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

Как с этим бороться: личный опыт


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

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

  1. Осмыслить запрос. Каждый раз при включении смартфона и нажатии иконки браузера необходимо задавать себе вопрос Зачем?. Ответ должен быть таким же четким напр., посмотреть курс доллара, погоду на завтра, сделать конкретный запрос в поисковике. Питательная среда интернет-зависимости работа на автомате, неосознаваемое действие по потреблению того, что покажут, а самый эффективный антидот осмысленность любого, даже самого небольшого действия.
  2. Только одна новость. Принципиальное следование правилу один день одна новость ограничивает беспорядочный новостной серфинг. На практике это выглядит так: в конце дня я обращаюсь все к тому же списку событий дня, выбираю наиболее интересное и изучаю в первоисточнике. Общая продолжительность даже с учетом изучения комментариев 10 -12 минут.
  3. Не комментировать. Каждый коммент, оставленный под публикацией, провоцирует последующий возврат к нему, чтобы посмотреть, поспорить, а где-то и потроллить. Осознав все это, я поставил запрет на любое текстовое выражение мнений под новостями.
  4. Цифровые детоксикации. Этот прием хорошо известен желающим уменьшить время, проводимое с цифровыми устройствами. В моем случае это сработало в полной мере. В период с 21.00 до 9.00 по будним полный запрет на выход в интернет. То же самое, но на протяжении всего дня в воскресенье. Запрет распространяется на все, включая мессенджеры и почту. Только телефонные звонки. Лучше всего в этот период отключать передачу мобильных данных и Wi-fi.
  5. Отключение триггеров. Сразу после принятия решения я удалил приложения с новостями со смартфона, которые ранее регулярно присылали мне уведомления о важных и неважных событиях. В Telegram отписался от большинства каналов, а в оставшихся отключил уведомления. Также я стал использовать поисковики без информации на титульной странице (у Яндекса ya.ru, у Google первая страница и так только с окошком ввода запроса). То же самое применил к мобильным браузерам, отключив ссылки на новости на стартовом экране.

Список литературы:

1. Olds J. Self-stimulation of the brain; its use to study local effects of hunger, sex, and drugs". Science. 127 (3294): 31524.
2. Травкина Н. Дофаномика: как рынок обманывает наш мозг и как перестать проверять смартфон 80 раз в день.
3. Эйяль Н. Hooked. На кючке. Как создавать продукты, формирующие привычку.
Подробнее..

Перевод Как я написал интро 4K на Rust и оно победило

07.07.2020 12:09:02 | Автор: admin
Недавно я написал своё первое интро 4K на Rust и представил его на Nova 2020, где оно заняло первое место в конкурсе New School Intro Competition. Написать интро 4K довольно сложно. Это требует знания многих различных областей. Здесь я сосредоточусь на методах, как максимально сократить код Rust.


Можете просмотреть демо-версию на Youtube, скачать исполняемый файл на Pouet или получить исходный код с Github.

Интро 4K это демо, в которой вся программа (включая любые данные) занимает 4096 байта или меньше, поэтому важно, чтобы код был максимально эффективным. Rust имеет некоторую репутацию создания раздутых исполняемых файлов, поэтому я хотел выяснить, можно ли написать на нём эффективный и лаконичный код.

Конфигурация


Всё интро написано на комбинации Rust и glsl. Glsl используется для рендеринга, но Rust делает всё остальное: создание мира, управление камерой и объектами, создание инструментов, воспроизведение музыки и т.д.

В коде есть зависимости от некоторых функций, которые ещё не включены в стабильный Rust, поэтому я использую набор инструментов Nightly Rust. Чтобы установить и использовать этот набор по умолчанию, нужно запустить следующие команды rustup:

rustup toolchain install nightlyrustup default nightly

Я использую crinkler для сжатия объектного файла, сгенерированного компилятором Rust.

Я также использовал shader minifier для препроцессинга шейдера glsl, чтобы сделать его меньше и удобнее для crinkler. Shader minifier не поддерживает вывод в .rs, так что я брал необработанную выдачу и вручную копировал её в свой файл shader.rs (задним умом ясно, что нужно было как-то автоматизировать этот этап. Или даже написать пул-реквест для shader minifier).

Отправной точкой послужила моё прошлое интро 4K на Rust, которое тогда мне казалось довольно лаконичным. В той статье также более подробная информация о настройке файла toml и о том, как использовать xargo для компиляции крошечного бинарника.

Оптимизация дизайна программы для уменьшения кода


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

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

Анализ ассемблерного кода


В какой-то момент придётся посмотреть на скомпилированный ассемблер и разобраться, во что компилируется код и какие оптимизации размера стоят того. В компиляторе Rust есть очень полезная опция --emit=asm для вывода ассемблерного кода. Следующая команда создаёт файл ассемблера .s:

xargo rustc --release --target i686-pc-windows-msvc -- --emit=asm

Не обязательно быть экспертом в ассемблере, чтобы извлечь выгоду из изучения выходных данных ассемблера, но определённо лучше иметь базовое понимание синтаксиса. Опция opt-level = "z заставляет компилятор максимально оптимизировать код для наименьшего размера. После этого немного сложнее выяснить, какая часть кода ассемблера соответствует какой части кода Rust.

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

Дополнительные функции


Я работал с двумя версиями кода. Одна протоколирует процесс и позволяет зрителю манипулировать камерой для создания интересных траекторий. Rust позволяет определить функции для этих дополнительных действий. В файле toml есть раздел [features], который позволяет объявлять доступные функции и их зависимости. В toml соего интро 4K есть следующий раздел:

[features]logger = []fullscreen = []

Ни одна из дополнительных функций не имеет зависимостей, поэтому они эффективно работают как флаги условной компиляции. Условным блокам кода предшествует оператор #[cfg(feature)]. Использование функций само по себе не делает код меньше, но сильно упрощает процесс разработки, когда вы легко переключаетесь между различными наборами функций.

        #[cfg(feature = "fullscreen")]        {            // Этот код компилируется только в том случае, если выбран полноэкранный режим        }        #[cfg(not(feature = "fullscreen"))]        {            // Этот код компилируется только в том случае, если полноэкранный режим не выбран        }

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

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

Использование get_unchecked


При размещении кода внутри блока unsafe{} я вроде как предполагал, что все проверки безопасности будут отключены, но это не так. Там по-прежнему выполняются все обычные проверки, и они дорого обходятся.

По умолчанию range проверяет все обращения к массиву. Возьмите следующий код Rust:

    delay_counter = sequence[ play_pos ];

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

Преобразуем код следующим образом:

    delay_counter = *sequence.get_unchecked( play_pos );

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

Более эффективные циклы


Изначально все мои циклы использовали выполнялись идиоматически как положено в Rust, используя синтаксис for x in 0..10. Я предполагал, что он будет скомпилирован в максимально плотный цикл. Удивительно, но это не так. Простейший случай:

for x in 0..10 {    // do code}

будет скомпилирован в ассемблерный код, который делает следующее:

    setup loop variableloop:    проверить условие цикла        если цикл закончен, перейти в end    // выполнить код внутри цикла    безусловно перейти в loopend:

тогда как следующий код

let x = 0;loop{    // do code    x += 1;    if i == 10 {        break;    }}

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

    setup loop variableloop:    // выполнить код внутри цикла    проверить условие цикла        если цикл не закончен, перейти в loopend:

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

Другая, гораздо более трудная для понимания проблема с идиоматическим циклом Rust заключается в том, что в некоторых случаях компилятор добавлял некоторый дополнительный код настройки итератора, который действительно раздувал код. Я так и не понял, что вызывает эту дополнительную настройку итератора, поскольку всегда было тривиально заменить конструкции for {} конструкцией loop{}.

Использование векторных инструкций


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

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

Чтобы использовать встроенные функции, я бы преобразовал следующий код

        global_spheres[ CAMERA_ROT_IDX ][ 0 ] += camera_rot_speed[ 0 ]*camera_speed;        global_spheres[ CAMERA_ROT_IDX ][ 1 ] += camera_rot_speed[ 1 ]*camera_speed;        global_spheres[ CAMERA_ROT_IDX ][ 2 ] += camera_rot_speed[ 2 ]*camera_speed;

в такое:

        let mut dst:x86::__m128 = core::arch::x86::_mm_load_ps(global_spheres[ CAMERA_ROT_IDX ].as_mut_ptr());        let mut src:x86::__m128 = core::arch::x86::_mm_load_ps(camera_rot_speed.as_mut_ptr());        dst = core::arch::x86::_mm_add_ps( dst, src);        core::arch::x86::_mm_store_ss( (&mut global_spheres[ CAMERA_ROT_IDX ]).as_mut_ptr(), dst );

что будет немного меньше по размеру (и гораздо менее читабельно). К сожалению, по какой-то причине это сломало отладочную сборку, хотя прекрасно работало в релизной. Ясно, что здесь проблема с моим знанием внутренних средств Rust, а не с самим языком. На это стоит потратить больше времени при подготовке следующего 4K-интро, поскольку сокращение объёма кода было значительным.

Использование OpenGL


Есть множество стандартных крейтов Rust для загрузки функций OpenGL, но по умолчанию все они загружают очень большой набор функций. Каждая загруженная функция занимает некоторое пространство, потому что загрузчик должен знать её имя. Crinkler очень хорошо сжимает такого рода код, но он не в состоянии полностью избавиться от оверхеда, поэтому пришлось создать свою собственную версию gl.rs, включающую только нужные функции OpenGL.

Заключение


Главная цель состояла в том, чтобы написать конкурентоспособное корректное интро 4K и доказать, что язык Rust пригоден для демосцены и для сценариев, где каждый байт имеет значение и вам действительно нужен низкоуровневый контроль. Как правило, в этой области рассматривали только ассемблер и C. Дополнительная цель состояла в максимальном использовании идиоматического Rust.

Мне кажется, что я довольно успешно справился с первой задачей. Ни разу не возникало ощущения, что Rust каким-то образом сдерживает меня или что я жертвую производительностью или функциями, потому что использую Rust, а не C.

Со второй задачей я справился менее успешно. Слишком много небезопасного кода, который на самом деле не должен быть там. unsafe несёт разрушающий эффект; очень легко его использовать, чтобы быстро что-то выполнить (например, используя изменяемые статические переменные), но как только появляется небезопасный код, он порождает ещё более небезопасный код, и внезапно он оказывается повсюду. В будущем я буду гораздо осторожнее использовать unsafe только тогда, когда действительно нет альтернативы.
Подробнее..

Анонс онлайн-митапа по тестированию три доклада про плохие процессы в команде, хотфиксы и первые шаги в автоматизации

07.07.2020 10:10:24 | Автор: admin

image


Наши тестировщики из Новосибирска соскучились по встречам с единомышленниками и приготовили онлайн-митап, который нельзя пропустить. Катя Синько порассуждает о том, как занять проактивную позицию и улучшить выстроенные процессы в команде. Инна Шундеева расскажет, как стать автоматизатором и не отступать перед трудностями. А Люда Малеева из Miro поделится советами, как организовать релизы без багов и что правильно делать, если на боевой их всё-таки нашли.


Когда: 9 июля в 16:00 (Мск)
Где: Ютуб-канал Контура


Менять процессы нельзя страдать Катя Синько, Контур


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


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


Качество релизов ответственность команды Люда Малеева, Miro


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


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


5 открытий: дневники автоматизатора Инна Шундеева, Контур


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


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


До встречи! Жмите колокольчик на Ютубе, готовьте прохладительные напитки, подключайтесь и смотрите онлайн-митап Kontur Tech Talks по тестированию.

Подробнее..

Готовим PostgreSQL в эпоху DevOps. Опыт 2ГИС. Павел Молявин

07.07.2020 10:10:24 | Автор: admin


Всем привет! Меня зовут Павел! Я работаю в компанию 2ГИС. Наша компания это городской информационный справочник, навигационный сервис. Это очень хорошая штука, которая помогает жить в городе.




Работаю я в подразделении, которое занимается веб-разработкой. Команда моя называется Infrastructure & Operations, сокращенно IO. Мы занимаемся поддержкой инфраструктурой для веб-разработки. Предоставляем свои сервисы командам как сервис и решаем все проблемы внутри нашей инфраструктуры.



Stack, который мы используем, технологии очень разнообразные. В основном это Kubernetes, мы пишем на Golang, на Python, на Bash. Из баз данных мы используем Elasticsearch, используем Cassandra и, конечно, используем Postgres, потому что мы его очень любим, это один из базовых элементов нашей инфраструктуры.



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


Мой доклад будет не про внутренности Postgres, не про работу внутри Postgres, а о том, как мы строим инфраструктуру вокруг него. Казалось бы, все просто берешь и делаешь, но на самом деле нет, все гораздо сложнее. Я расскажу об этом.



Начнем с постановки задачи. У нас продуктовые команды пишут приложения. Количество приложений постоянно растет. Приложения раньше мы писали в основном на PHP, Python и на Java Scala. Потихоньку к нам незаметно вползли модные языки типа Golang, без Node.js тоже сейчас никуда, тем более во FrontEnd.


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



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



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



Команды у нас пытались использовать для установки Postgres и его настройки Ansible. Кто-то, более отважный, использовал Chef, у кого был с ним опыт работы.


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


Соответственно, нагрузка увеличивалась на команды, которые занимались администрированием Postgres. Нагрузка увеличивалась на нас, потому что они тратили свое время для того, чтобы найти проблему. Потом приходили к нам, мы тратили время. В общем, выяснилось, что всего очень много. У нас даже были особо отличившиеся. Во времена 9.4 Postgres умудрялись ставить Postgres 8-ой версии.



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



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



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



Обязательно сделать резервные копии с приемлемой глубиной хранения, чтобы еще можно было осуществлять point in time recovery. И нужно было сделать обязательную архивацию WAL-файлов.



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



И поскольку мы придерживаемся парадигмы: infrastructure as code, то мы решили, что наше решение будет выглядеть в виде деплоя. Мы решили написать его на общеизвестном в нашем подразделении инструменте. У нас это был Ansible. И еще мы немножко кое-что написали на Python и Bash мы тоже активно используем.


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



И поскольку мы используем корпоративный Gitlab, хотелось, чтобы все это работало через встроенные возможности Gitlab, через continuous integration, continuous delivery. Т. е. чтобы можно было нажать кнопку deploy в репозитории и через некоторое время появлялся бы рабочий кластер.


И теперь мы все эти элементы рассмотрим в отдельности.



Кластер PostgreSQL



  • Мы выбрали PostgreSQL 9.4. Это было примерно 3,5 года назад. Тогда как раз был 9.4 в baseline. 9.6, по-моему, только вышел или его еще не было, я точно не помню. Но мы достаточно быстро наше решение проапгрейдили до 9.6, т. е. оно стало его поддерживать.
  • Для обеспечения репликации мы не стали ничего выдумывать. Выбрали стандартный метод потоковую репликацию. Она наиболее надежная, наиболее известная. С ней особых проблем нет.
  • Для кластеризации мы выбрали менеджер репликации с псевдо HA. Это repmgr от 2ndQuadrant. В защиту repmgr могу сказать. В предыдущие два дня в него летели активно камни, что он не обеспечивает настоящий HA, у него нет фенсинга, но на самом деле за три года эксплуатации я могу сказать, что у нас не было ни одного инцидента, когда переключения failovers произошли неправильно или закончились ошибкой. Т. е. всегда repmgr переключал кластер по делу. Всегда обеспечивал нам надежность. Не без downtime, конечно. Примерно 2 минуты repmgr соображает, что нужно выполнить failover и после этого его выполняет.
  • Для repmgr мы написали кастомный failover_command. Это такая команда, которую repmgr выполняет, когда собирается провести failover, т. е. запромоутить нового мастера. Т. е. запускается специальный скрипт. Он идет на балансировщики, проверяет, что балансировщики работают, что они доступны с нового мастера. И после этого происходит переключение. Т. е. он выполняет failover, переписывает бэкенды на балансировщиках и после этого завершает свою работу. Соответственно, мы получаем нового мастера. И балансировщики подключены к новому мастеру.
  • С репликой в другом ДЦ. У нас основное место присутствия это Новосибирск. Несколько ДЦ в Новосибирске. Но есть площадка в Москве, поэтому нам нужно было обеспечить, чтобы две реплики находились в Москве. Сначала мы их добавили в кластер repmgr.

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



В итоге мы сделали вот такую схему, т. е. мы вытащили из repmgr ноды, которые находятся в Москве. Сделали из них обычный hot standby, не стали ничего выдумывать. Они у нас через restore забирают с бэкап-сервера архивы WALs. Соответственно, проблему лага мы не разрешили. Если у нас на мастере начинается активная работа, то понятно, что реплики в Москве немножко отстают.


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



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



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



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


Эти адреса балансируются в DNS через round robin, т. е. когда приложение подключается к кластеру, они получают либо виртуальный адрес первого балансировщика, либо виртуальный адрес второго балансировщика.


Адреса keepalive у нас получает PgBouncer. Отличный pooler, у нас с ним особых проблем нет. Он работает, не доставляет никаких неприятностей. Но у него есть одна проблема. У него бэкенд может быть только один и он должен смотреть на мастера. Поэтому мы поставили у него на бэкенде Pgpool. Нам он понравился тем, что он знает, где в кластере находится мастер, умеет балансировать запросы на чтение, на slave.



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



Мы избавились от Pgpool. PgBouncer смотреть теперь только на мастер. Деплой проставляет PgBouncerу бэкенд. И failover_command с будущего мастера переключает адрес бэкенда и делает мягкую перезагрузку PgBouncer. И таким образом PgBouncer всегда смотрит на мастер.


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



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


Соответственно, мы сделали такую же пару. Я тут один нарисовал, потому что он уже не влезал. Мы по такому же подобию сделали балансировщики на чтение. Только на бэкенде у PgBouncer поставили HAProxy, который с помощью специально скрипта ходит в кластер и точно знает, где у него находятся slave и отправляет по round robin запросы на них.


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



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



Взяли Barman.


  • Barman у нас делает бэкап базы через cron каждые два дня, когда нагрузка минимальная.
  • Также у нас через archive_command происходит архивация WAL-файлов к Barman, чтобы можно было PITR осуществлять.

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


Недостатки в этой схеме довольно стандартные:


  • Тот же самый archive_command.
  • И требуется очень много места, потому что WAL-файлы мы не сжимаем, потому что экономим процессор, экономим время, чтобы при восстановлении можно было максимально быстро восстановиться.


Мониторинг и логи



  • У нас используется в качестве мониторинга Prometheus. Он использует pool-модель сбора метрик. Т. е. он сам ходит к сервисам и спрашивает их метрики. Соответственно, нам для каждого компонента системы (а это почти все компоненты, которые не умеют отдавать метрики) пришлось найти соответствующий exporter, который будет собирать метрики и отдавать их Prometheus, либо написать самостоятельно.
  • Мы свои exporters пишем на Golang, на Python, на Bash.
  • Мы используем в основном стандартные exporters. Это Node exporter основной системный exporter; Postgres exporter, который позволяет собирать все метрики с Postgres; exporter для PgBouncer мы написали сами на Python. И мы написали еще кастомный Cgroups exporters на Golang. Он нам нужен для того, чтобы четко знать, какое количество ресурсов системных сервер баз данных потратил на обслуживание каждой базы.
  • Собираем мы все системные метрики: это память, процессор, загрузка дисков, загрузка сети.
  • И метрики всех компонентов. В exporterе для Postgres вообще ситуация такая, что вы сами exporterу пишите запросы, которые хотите выполнять в базе. И, соответственно, он вам выполняет их и формирует метрики. Т. е., в принципе, метрики, которые можете добыть из Postgres ограничены только вашей фантазией. Понятно, что нам важно собирать количество транзакций, количество измененных tuples, позицию WAL-файлов для того, чтобы отслеживать постоянно отставание, если оно есть. Обязательно нужно знать, что происходит на балансировщиках. Сколько сессий находятся в каком статусе. Т. е. все-все мы собираем в Prometheus.


Метрики мы смотрим в Grafana. Вот так примерно выглядит график Cgroups с exporterа. Мы всегда можем посмотреть, какая база у нас, сколько процессора употребила за определенное время, т. е. очень удобно.



Как в любой системе мониторинга у нас есть alerts. Alerts в Prometheus занимается AlertManager.



У нас написано большое количество alerts, касательно кластера. Т. е. мы всегда знаем, что у нас начался failover, что failover закончился, что failover закончился успешно или не успешно. И все эти вопросы мы обязательно мониторим. У нас есть alerts, которые либо сигналят нам в почту, либо сигналят в корпоративный Slack, либо звонят по телефону и говорят: Просыпайся, у тебя проблема!.


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


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



С логами у нас не очень богато. У нас в компании используется ELK stack для сбора логов, т. е. мы используем Elasticsearch, Kibana и LogStash. Соответственно, Postgres и все компоненты нашего решения складывают логи на диск в CSV-формате, а с диска их собирает Python Beaver. Есть такой проект. Он их собирает и отдает LogStash. LogStash обогащает какую-то информацию, добавляет информацию к какой команде относится кластер, какой кластер, в общем, вносит какую-то дополнительную информацию.


И после этого в Kibana можно легко логи просматривать и агрегировать, в общем, сортировать и все, что угодно делать.


Также команды нас попросили поставить Pgbadger. Это такая штука, которая позволяет легко и непринужденно анализировать, например, slow логи. Это основное, для чего они хотели эту штуку. Она написана на Perl. Она нам не очень понравилась, потому что под нее нужно было отдельный хост размещать. Мы его в итоге задвинули в Kubernetes. Он у нас работает в Kubernetes. Получает он логи из LogStash. LogStash отдает ему по HTTP логи и в Pgbadger потом можно просматривать, и искать свои медленные запросы. В общем, грустить и выяснять, как это починить.



С деплоем следующая история. Нужно было деплой сделать максимально простым, чтобы инструмент был понятным.


Мы решили написать деплой на Ansible. Хотелось его сделать максимально повторяемым, максимально идемпотентным, чтобы его можно было использовать не только при первоначальном деплое, но и можно было использовать для эксплуатации. Мы хотели автоматизировать все операции, которые могут быть снаружи самого Postgres. В том числе изменение конфигурации в Postgresql.conf, чтобы все-все можно было делать через деплой. Чтобы всегда были версии, чтобы всегда четко можно было по истории репозитория отследить что происходило, и кто вносил изменения.



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


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


И нам пришлось все роли локализовать. Т. е. те, которые раньше мы доставали из интернета, мы их локализовали, потому что Роскомнадзор довольно активизировался. Он постоянно банит хосты GitHub, поэтому мы решили не тащить роли снаружи. И все роли у нас лежат локально в наших репозиториях.


Также мы использовали встроенную возможность Ansible. Это использование для деплоя разные окружения. Т. е. по дефолту в нашем деплое есть testing, production и staging. Под staging мы понимаем такой почти production, но без трафика пользователей. В принципе, используя встроенную возможность Ansible можно добавить какие угодно окружения. Если вам нужно два-три testing, чтобы протестировать какую-то фичу, то вы добавляете в дополнительный environments, добавляете переменные, нажимаете deploy. И после этого у вас есть рабочий кластер.


Ключи, пароли мы храним в секретах. Шифруем все Ansible Vault. Это встроенная в Ansible возможность.


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


Мы постарались автоматизировать все рутинные операции. Т. е. любое изменение конфигурации: добавление баз, добавление пользователей, добавление real only пользователей, добавление каких-то дополнительных штук в базу, изменение режима в балансировке, потому что у нас одна часть сервисов использует транзакционную балансировку, другой части нужны prepared statements, а они используют сессионную балансировку.


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


Деплой у нас лежит в виде Git-репозитории, как я уже говорил, в нашем внутреннем Gitlab.


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


Также мы написали кастомную обвязку для развертывания кластера в OpenStack. Это очень удобно. Особенно, если вам нужна куча тестингов. Мы можем использовать нативное Openstack API через модуль Ansible, либо через одну штуку в OpenStack, которая называется heat-api, которая позволяет шаблонами запускать машины по несколько штук. Это очень удобно. Если вам нужно в ветке что-то потестировать, вы подключаете эту обвязку, которая позволяет через heat-api разворачивать машинки. Разворачиваете себе кластер. С ним работаете. После говорите: Мне это не надо. Удалить. Это все удаляется, т. е. виртуальные машинки в OpenStack удаляются. Все очень просто и удобно.


Также для железных машин мы написали bootstrap, но это обычный пресет образ. Это встроенная возможность Debian, Ubuntu. Он задает несколько вопросов. У него сделана специальное меню. Задает несколько вопросов про диск, про сеть. После того, как вы закончили, вы имеете ноду, которая уже готова для того, чтобы ее можно было использовать в деплое. Т. е. вы прописываете ее в инвентарь и после этого можете запускать деплой, и все будет хорошо.



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



И последний пункт. Мы используем встроенные возможности Gitlab.



Gitlab умеет запускать специальные jobs. Jobs это отдельные шаги, которые выполняются.



Т. е. у нас отдельными шагами являются syntax check, деплой, тесты. Здесь у нас маленькая схема. После мержа в мастер автоматически запускается pipeline, который производит syntax check, деплоится на тестинг. На тестинге запускаются тесты. После этого, если все Ok, все зеленое, разблокируется ручной запуск деплоя staging и деплоя production.


Ручной запуск у нас сделан, чтобы люди, которые это делают, понимали, что они делают и отдавали отчет, что сейчас будет деплой на testing или на staging или на production.



Подведем итоги. Что же нам удалось? Мы хотели сделать такой инструмент, который будет нам все разворачивать, чтобы получить очень быстро рабочий кластер со всеми интеграциями. В принципе, нам это удалось. Такой деплой мы создали. Нам он очень помогает. Даже при не очень хороших обстоятельствах в течение 15 минут после начала нажатия кнопки deploy, вы получаете рабочий кластер. Вы можете заходить на endpoint и использовать в работе. Соответственно, команды могут его встраивать прямо в pipeline, т. е. полностью с ним работать.



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


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


Мы получили огромный опыт. Разобрались как работают rep managers, балансировка, репликация, Postgres. Это неоценимый опыт, всем советую.


Failover у нас работает. Failover, я бы не сказал, что происходит часто. За три года порядка десяти раз у нас был failover. Failover практически всегда по делу был.


Мы делаем вручную switchover, т. е. это failover вручную. Switchover мы делаем для всяких интересных штук. Например, когда нам нужно было перевести один из кластеров из одного ДЦ в другой, мы просто забутстрапили с помощью деплоя ноду в другом ДЦ. Сделали на нее switchover, переключили балансировщики. И тогда у нас появился рабочий мастер в новом ДЦ практически без downtime, только на время переключения. А для repmgr (Replication Manager) это, по-моему, две минуты дефолтные.



Без falls у нас тоже не обошлось:


  • Мы хотели сделать решение простым, чтобы команды могли его сами использовать. Но тут ничего не вышло. Все равно надо знать нюансы, как все работает внутри, все равно нужно понимать, как работает репликация, как работает rep managers. Именно поэтому количество форков нашего репозитория равно нулю. Команды не занимаются этим. Может быть, это и неплохо, потому что все проблемы мы бы решали.
  • Все равно для каждого кластера приходится что-то допиливать, дописывать. Не удается использовать один и тот же деплой.
  • И от ручных операций тоже не удалось избавиться. Все равно апгрейды, перезапуски Postgres, когда нужно внести какие-то изменения в postgresql.conf, приходится делать вручную. Потому что автоматике это можно доверить, но нужно понимать, что происходит. Без оператора не обойтись. Человек должен понимать, что он делает. Без ручных операций все равно не получается.
  • Обновление это отдельная боль. Это нужно все апгрейдить руками. Балансировщики, например, можно проапгрейдить автоматом, но будет downtime. А без автоматики вручную это можно сделать без downtime.
  • И мы не очень любим собирать пакеты, потому что на это тратится дополнительное время. Нужно делать инфраструктуру для сборки, инфраструктуру для pushes и репозиторий.


У нас в компании довольно активно развивается Kubernetes, поэтому есть ощущение попробовать что-нибудь новое. Например, попробовать запустить отказоустойчивый Postgres в Kubernetes. Для этого мы хотим посмотреть на Stolon, Patroni. Возможно, это будет первым, чем мы займемся.


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


И наше решение мы будем делать апгрейд на PostgreSQL 10-11. Я предвижу, что это будет очень веселое развлечение. Вызов принят, у нас вариантов нет, мы используем наше решение и будем апгрейдиться.




Вопросы


Спасибо за доклад! На разработку всех плейбуков в Ansible, сколько потратилось времени?


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


Какие-то были особенности именно внедрения Ansible в связке с Postgres, с вашим решением? Например, что-то пришлось кардинально перерабатывать?


Модули писать для Ansible?


Да.


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


Спасибо за доклад! Почему на Barman остановились? И насчет апгрейда у меня созрел вопрос еще в самом начале, когда про 9.6 рассказывали. Я увидел, что у вас есть планы на 10 и 11 версию, да?


Не знаю, но почему-то решили выбрать Barman. Он нам понравился тем, что конфигурируется нормально. Т. е. у нас была для него уже написанная роль в Ansible. Мы хотим посмотреть на что-нибудь другое, например, на WAL-G. Но проблема в том, насколько я помню, WAL-G не умеет в диск (Уточнение: уже умеет писать на диск). А у нас требование, чтобы все бэкапы и все-все лежали внутри инфраструктуры. А городить для этого отдельный S3, конечно, можно, но зачем?


Апгрейд мы делали вручную. Мы также подеплоили рядом кластер 9.6. В него смигрировали данные и все. И после этого выкинули 9.4 и у нас все стало хорошо.


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


Да.


По какому принципу новые кластера добавляются помимо staging?


Я понял вопрос. Мы все базы консолидировали в трех больших своих кластерах. У нас там есть testing, staging. И когда приложение работает уже на staging, мы можем понять, что что-то. Допустим, процессинговые базы, про которые я говорил. И такое пускать в кластер не понятно зачем, потому что будет постоянная нагрузка на WALs, будет постоянно большой бэкап. Потому что если бэкап пришел, пока у тебя процессинговая база лежит, ты получаешь . И, в принципе, по профилю нагрузки мы понимаем, что происходит внутри приложения. И после этого решаем дать отдельный кластер этим ребятам. И деплоим кластер, а что делать? Приложения в production все.


Здравствуйте! Спасибо за доклад! У меня немного двойной вопрос. Вы для половины части используете ванильные сборки Postgres?


Да.


Я сейчас тоже использую ванильные, но присматриваюсь к бесплатной части команды Postgres Professional, т. е. Postgres Pro Standard. И коснулась тема, что выбирать для резервного копирования. И них есть утилита pg_probackup, которая похожа на Barman, но у нее есть встроенная проверка бэкапов уже внутри. Почему вы не обратили внимание на эту утилиту?


Я даже не знаю, как вам правильно ответить. В принципе, мы как-то вообще не рассматривали Postgres Proшные штуки. Как-то так получилось. Мы привыкли, что мы сами с усами, строим свои велосипеды. Вроде бы у нас все работает.


Т. е. это выбор каждого: либо городить свой велосипед, либо пойти по проторенному пути.


Да.


Спасибо!


Спасибо большое!

Подробнее..

Как разогнать Parallels Desktop до космических скоростей

07.07.2020 12:09:02 | Автор: admin


С Parallels Desktop пользователи знакомы более 10 лет. До сих пор потребность в работе с Windows (у кого-то Linux) на Mac не теряет свою актуальность. Дизайнеры, бухгалтеры, геймеры, разработчики, музыканты, полицейские, список пользователей можно продолжать бесконечно.

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

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

Улучшение производительности виртуальной машины


Очень часто, в погоне за улучшением производительности виртуальной машины (ВМ) пользователи Parallels Desktop выделяют ей слишком много ресурсов. Разработчики не рекомендуют выделять более 50 % всех ресурсов Mac: ЦПУ и оперативной памяти. В ситуациях, когда виртуальная машина имеет больше ресурсов, чем сам Mac, начинаются проблемы с производительностью самого Mac, что в свою очередь негативно влияет на производительность виртуальной машины.

Также на производительность влияет количество процессов, работающих внутри виртуальной машины и на стороне Mac. Особенно, если эти операции затрагивают диск. Поэтому, при недостаточной производительности виртуальной машины попробуйте использовать только Parallels Desktop и виртуальную машину, не запуская других ресурсоемких приложений. Так же, рекомендуется попробовать чистую загрузку Windows.

Parallels рекомендует использование SSD, т.к. от скорости диска напрямую зависит производительность Mac и виртуальной машины в результате. Альтернативой, при отсутствии внутреннего SSD диска, может быть использование внешнего высокоскоростного SSD накопителя при условии наличия у Mac высокоскоростного порта передачи данных (USB 3.0, Thunderbolt).

Управление обновлениями Windows


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

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

Режим поездки


Если вам предстоит использовать Mac долгое время без доступа к зарядке, можно активировать Режим Поездки. В данной конфигурации производительность Windows, конечно, немного снизится, но при этом система будет работать в энергосберегающем режиме, увеличив жизнь батарейки.

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

Автоматическая видео память


Начиная с Parallels Desktop 14 для виртуальных машин Windows 8 и выше, в данном ПО появилась возможность использования автоматического режима для определения размера видеопамяти. С этим изменением размер видеопамяти определяется самой Windows исходя из собственных нужд и берется из оперативной памяти, выделенной на виртуальную машину. Это позволяет увеличить максимальный размер видео памяти более 2 Гб. В стандартных ситуациях размер видеопамяти равен примерно половине оперативной памяти виртуальной машины.

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

Интеграция Mac и Windows


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

На самом деле папки Рабочий Стол, Документы, Загрузки и прочее, доступные в Windows, являются ссылками на соответствующие папки Mac. Таким образом, не нужно копировать файлы в виртуальную машину, вы работаете с ними напрямую. То же самое касается и удаления файлов. Если Общий Профиль включен (настройка по умолчанию) то удаляя файл в папках Общего профиля в виртуальной машине вы удалите файл на Mac.

Так же, Parallels Desktop позволяет использовать программы установленные и на Mac и в Windows для открытия файлов на обеих сторонах. Т.е. вы можете использовать iTunes на Mac для воспроизведения музыки в Windows, щелкнув по файлу в Windows, и наоборот, начать смотреть видео используя Windows Media Player, щелкнув по файлу в Finder.

Совсем скоро нас ждет очередное обновление Parallels Desktop. Вероятнее всего, любимый миллионами пользователей продукт станет еще быстрее, умнее и проще!
Подробнее..

System.Threading.Channels высокопроизводительный производитель-потребитель и асинхронность без алокаций и стэк дайва

07.07.2020 14:13:48 | Автор: admin
И снова здравствуй. Какое-то время назад я писал о другом малоизвестном инструменте для любителей высокой производительности System.IO.Pipelines. По своей сути, рассматриваемый System.Threading.Channels (в дальнейшем каналы) построен по похожим принципам, что и Пайплайны, решает ту же задачу Производитель-Потребитель. Однако имеет в разы более простое апи, которое изящно вольется в любого рода enterprise-код. При этом использует асинхронность без алокаций и без stack-dive даже в асинхронном случае! (Не всегда, но часто).



Оглавление




Введение


Задача Производитель/Потребитель встречается на пути программистов довольно часто и уже не первый десяток лет. Сам Эдсгер Дейкстра приложил руку к решению данной задачи ему принадлежит идея использования семафоров для синхронизации потоков при организации работы по принципу производитель/потребитель. И хотя ее решение в простейшем виде известно и довольно тривиально, в реальном мире данный шаблон (Производитель/Потребитель) может встречаться в гораздо более усложненном виде. Также современные стандарты программирования накладывают свои отпечатки, код пишется более упрощенно и разбивается для дальнейшего переиспользования. Все делается для понижения порога написания качественного кода и упрощения данного процесса. И рассматриваемое пространство имен System.Threading.Channels очередной шаг на пути к этой цели.

Какое-то время назад я рассматривал System.IO.Pipelines. Там требовалось более внимательная работа и глубокое осознание дела, в ход шли Span и Memory, а для эффективной работы требовалось не вызывать очевидных методов (чтобы избежать лишних выделений памяти) и постоянно думать в байтах. Из-за этого программный интерфейс Пайплайнов был нетривиален и не интуитивно понятен.

В System.Threading.Channels пользователю представляется гораздо более простое api для работы. Стоит упомянуть, что несмотря на простоту api, данный инструмент является весьма оптимизированным и на протяжении своей работы вполне вероятно не выделит память. Возможно это благодаря тому, что под капотом повсеместно используется ValueTask, а даже в случае реальной асинхронности используется IValueTaskSource, который переиспользуется для дальнейших операций. Именно в этом заключается весь интерес реализации Каналов.

Каналы являются обобщенными, тип обобщения, как несложно догадаться тип, экземпляры которого будут производиться и потребляться. Интересно то, что реализация класса Channel, которая помещается в 1 строку (источник github):

namespace System.Threading.Channels{    public abstract class Channel<T> : Channel<T, T> { }}

Таким образом основной класс каналов параметризован 2 типами отдельно под канал производитель и канал потребитель. Но для реализованых каналов это не используется.
Для тех, кто знаком с Пайплайнами, общий подход для начала работы покажется знакомым. А именно. Мы создаем 1 центральный класс, из которого вытаскиваем отдельно производителей(CannelWriter) и потребителей(ChannelReader). Несмотря на названия, стоит помнить, что это именно производитель/потребитель, а не читатель/писатель из еще одной классической одноименной задачи на многопоточность. ChannelReader изменяет состояние общего channel (вытаскивает значение), которое более становится недоступно. А значит он скорее не читает, а потребляет. Но с реализацией мы ознакомимся позже.

Начало работы. Channel


Начало работы с каналами начинается с абстрактного класса Channel<T> и статического класса Channel, который создает наиболее подходящую реализацию. Далее из этого общего Channel можно получать ChannelWriter для записи в канал и ChannelReader для потребления из канала. Канал является хранилищем общей информации для ChannelWriter и ChannelReader, так, именно в нем хранятся все данные. А уже логика их записи или потребления рассредоточения в ChannelWriter и ChannelReader, Условно каналы можно разделить на 2 группы безграничные и ограниченные. Первые более простые по реализации, в них можно писать безгранично (пока память позволяет). Вторые же ограничены неким максимальным значением количества записей.

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

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

Статический класс Channel содержит 4 метода для создания вышеперечисленных каналов:

Channel<T> CreateUnbounded<T>();Channel<T> CreateUnbounded<T>(UnboundedChannelOptions options);Channel<T> CreateBounded<T>(int capacity);Channel<T> CreateBounded<T>(BoundedChannelOptions options);

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

UnboundedChannelOptions содержит 3 свойства, значение которых по умолчанию false:

  1. AllowSynchronousContinuations просто сумасводящая опция, которая позволяет выполнить продолжение асинхронной операции тому, кто ее разблокирует. А теперь по-простому. Допустим, мы писали в заполненный канал. Соответственно, операция прерывается, поток освобождается, а продолжение будет выполнено по завершению на новом потоке из пула. Но если включить эту опцию, продолжение выполнит тот, кто разблокирует операцию, то есть в нашем случае читатель. Это серьезно меняет внутреннее поведение и позволяет более экономно и производительно распоряжаться ресурсами, ведь зачем нам слать какие-то продолжения в какие-то потоки, если мы можем сами его выполнить;
  2. SingleReader указывает, что будет использоваться один потребитель. Опять же, это позволяет избавиться от некоторой лишней синхронизации;
  3. SingleWriter то же самое, только для писателя;

BoundedChannelOptions содержит те же 3 свойства и еще 2 сверху

  1. AllowSynchronousContinuations то же;
  2. SingleReader то же;
  3. SingleWriter то же;
  4. Capacity количество вмещаемых в канал записей. Данный параметр также является параметром конструктора;
  5. FullMode перечисление BoundedChannelFullMode, которое имеет 4 опции, определяет поведение при попытке записи в заполненный канал:
    • Wait ожидает освобождения места для завершения асинхронной операции
    • DropNewest записываемый элемент перезаписывает самый новый из существующих, завершается синхронно
    • DropOldest записываемый элемент перезаписывает самый старый из существующих завершается синхронно
    • DropWrite записываемый элемент не записывается, завершается синхронно


В зависимости от переданных параметров и вызванного метода будет создана одна из 3 реализаций: SingleConsumerUnboundedChannel, UnboundedChannel, BoundedChannel. Но это не столь важно, ведь пользоваться каналом мы будем через базовый класс Channel<TWrite, TRead>.

У него есть 2 свойства:

  • ChannelReader<TRead> Reader { get; protected set; }
  • ChannelWriter<TWrite> Writer { get; protected set; }

А также, 2 оператора неявного приведения типа к ChannelReader<TRead> и ChannelWriter<TWrite>.

Пример начала работы с каналами:

Channel<int> channel = Channel.CreateUnbounded<int>();//Можно делать такChannelWriter<int> writer = channel.Writer;ChannelReader<int> reader = channel.Reader; //Или такChannelWriter<int> writer = channel;ChannelReader<int> reader = channel;

Данные хранятся в очереди. Для 3 типов используются 3 разные очереди ConcurrentQueue<T>, Deque<T> и SingleProducerSingleConsumerQueue<T>. На этом моменте мне показалось, что я устарел и пропустил кучу новых простейших коллекций. Но спешу огорчить они не для всех. Помечены internal, так что использовать их не получится. Но если вдруг они понадобятся на проде их можно найти здесь (SingleProducerConsumerQueue) и здесь (Deque). Реализация последней весьма проста. Советую ознакомится, ее очень быстро можно изучить.

Итак, приступим к изучению непосредственно ChannelReader и ChannelWriter, а также интересных деталей реализации. Они все сводятся к асинхронности без выделений памяти с помощью IValueTaskSource.

ChannelReader потребитель


При запросе объекта потребителя возвращается одна из реализаций абстрактного класса ChannelReader<T>. Опять же в отличие от Пайплайнов АПИ несложное и методов немного. Достаточно просто знать список методов, чтобы понять, как использовать это на практике.

Методы:

  1. Виртуальное get-only свойство Task Completion { get; }
    Обьект типа Task, который завершается, когда закрывается канал;
  2. Виртуальное get-only свойство int Count { get; }
    Тут сделает заострить внимание, что возвращается текущее количество доступных для чтения объектов;
  3. Виртуальное get-only свойство bool CanCount { get; }
    Показывает, доступно ли свойство Count;
  4. Абстрактный метод bool TryRead(out T item)
    Пытается потребить объект из канала. Возвращает bool, показывающий, получилось ли у него прочитать. Результат помещается в out параметр (или null, если не получилось);
  5. Абстрактный ValueTask<bool> WaitToReadAsync(CancellationToken cancellationToken = default)
    Возвращается ValueTask со значением true, когда в канале появятся доступные для чтения данные, до тех пор задача не завершается. Возвращает ValueTask со значением false, когда канал закрывается(данных для чтения больше не будет);
  6. Виртуальный метод ValueTask<T> ReadAsync(CancellationToken cancellationToken = default)
    Потребляет значение из канала. Если значение есть, возвращается синхронно. В противном случае асинхронно ждет появления доступных для чтения данных и возвращает их.

    У данного метода в абстрактном классе есть реализация, которая основана на методах TryRead и WaitToReadAsync. Если опустить все инфраструктурные нюансы (исключения и cancelation tokens), то логика примерно такая попытаться прочитать объект с помощью TryRead. Если не удалось, то в цикле while(true) проверять результат метода WaitToReadAsync. Если true, то есть данные есть, вызвать TryRead. Если TryRead получается прочитать, то вернуть результат, в противном случае цикл по новой. Цикл нужен для неудачных попыток чтения в результате гонки потоков, сразу много потоков могут получить завершение WaitToReadAsync, но объект будет только один, соответственно только один поток сможет прочитать, а остальные уйдут на повторный круг.
    Однако данная реализация, как правило, переопределена на что-то более завязанное на внутреннем устройстве.


ChannelWriter производитель


Все аналогично потребителю, так что сразу смотрим методы:

  1. Виртуальный метод bool TryComplete(Exception? error = null)
    Пытается пометить канал как завершенный, т.е. показать, что в него больше не будет записано данных. В качестве необязательного параметра можно передать исключение, которое вызвало завершение канала. Возвращает true, если удалось завершить, в противном случае false (если канал уже был завершен или не поддерживает завршение);
  2. Абстрактный метод bool TryWrite(T item)
    Пытается записать в канал значение. Возвращает true, если удалось и false, если нет
  3. Абстрактный метод ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken = default)
    Возвращает ValueTask со значением true, который завершится, когда в канале появится место для записи. Значение false будет в том случае, если записи в канал более не будут разрешены;
  4. Виртуальный метод ValueTask WriteAsync(T item, CancellationToken cancellationToken = default)
    Асинхронно пишет в канал. Например, в случае, если канал заполнен, операция будет реально асинхронной и завершится только после освобождения места под данную запись;
  5. Метод void Complete(Exception? error = null)
    Просто пытается пометить канал как завершенный с помощью TryComplete, а в случае неудачи кидает исключение.

Небольшой пример вышеописанного (для легкого начала ваших собственных экспериментов):

Channel<int> unboundedChannel = Channel.CreateUnbounded<int>();//Объекты ниже можно отправить в разные потоки, которые будут использовать их независимо в своих целяхChannelWriter<int> writer = unboundedChannel;ChannelReader<int> reader = unboundedChannel;//Первый поток может писать в каналint objectToWriteInChannel = 555;await writer.WriteAsync(objectToWriteInChannel);//И завершить его, при исключении или в случае, когда записал все, что хотелwriter.Complete();//Второй может читать данные из канала по мере их доступностиint valueFromChannel = await reader.ReadAsync();

А теперь перейдем к самой интересной части.

Асинхронность без алллокаций


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

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

Интерфейс IValueTaskSource


Начнем наш путь с истоков структуры ValueTask, которая была добавлена в .net core 2.0 и дополнена в 2.1. Внутри этой структуры скрывается хитрое поле object _obj. Несложно догадаться, опираясь на говорящее название, что в этом поле может скрываться одна из 3 вещей null, Task/Task<T> или IValueTaskSource. На самом деле, это вытекает из способов создания ValueTask.

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

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

Итак, IValueTaskSource. Данный интерфейс имеет 3 метода, реализовав которые вы будете успешно экономить память и время на выделении тех заветных байт.

  1. GetResult Вызывается единожды, когда в стейт машине, образованной на рантайме для асинхронных методов, понадобится результат. В ValueTask есть метод GetResult, который и вызывает одноименный метод интерфейса, который, как мы помним, может хранится в поле _obj.
  2. GetStatus Вызывается стейт машиной для определения состояния операции. Также через ValueTask.
  3. OnCompleted Опять же, вызывается стейт машиной для добавления продолжения к невыполненной на тот момент задаче.

Но несмотря на простой интерфейс, реализация потребует определенной сноровки. И тут можно вспомнить про то, с чего мы начали Channels. В данной реализации используется класс AsyncOperation, который является реализацией IValueTaskSource. Данный класс скрыт за модификатором доступа internal. Но это не мешает разобраться, в основных механизмах. Напрашивается вопрос, почему не дать реализацию IValueTaskSource в массы? Первая причина (хохмы ради) когда в руках молоток, повсюду гвозди, когда в руках реализация IValueTaskSource, повсюду неграмотная работа с памятью. Вторая причина (более правдоподобная) в то время, как интерфейс прост и универсален, реальная реализация оптимальна при использований определенных нюансов применения. И вероятно именно по этой причине можно найти реализации в самых разных частях великого и могучего .net, как то AsyncOperation под капотом каналов, AsyncIOOperation внутри нового API сокетов и тд.

CompareExchange


Довольно популярный метод популярного класса, позволяющий избежать накладных расходов на классические примитивы синхронизации. Думаю, большинство знакомы с ним, но все же стоит описать в 3 словах, ведь данная конструкция используется довольно часто в AsyncOperation.
В массовой литературе данную функцию называют compare and swap (CAS). В .net она доступна в классе Interlocked.

Сигнатура следующая:

public static T CompareExchange<T>(ref T location1, T value, T comparand) where T : class;

Имеются также перегрузи с int, long, float, double, IntPtr, object.

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

Допустим, вы хотите инкрементировать переменную, если ее значение меньше 10.

Далее идут 2 потока.

Поток 1 Поток 2
Проверяет значение переменной на некоторое условие (то есть меньше ли оно 10), которое срабатывает -
Между проверкой и изменением значения Присваивает переменной значение, не удовлетворяющее условию (например, 15)
Изменяет значение, хотя не должен, ведь условие уже не соблюдается -


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

location1 переменная, значение которой мы хотим поменять. Оно сравнивается с comparand, в случае равенства в location1 записывается value. Если операция удалась, то метод вернет прошлое значение переменной location1. Если же нет, то будет возращено актуальное значение location1.
Если говорить чуть глубже, то существует инструкция языка ассемблера cmpxchg, которая выполняет эти действия. Именно она и используется под капотом.

Stack dive


Рассматривая весь этот код я не раз наткнулся на упоминания Stack Dive. Это очень крутая и интересная штука, которая на самом деле очень нежелательна. Суть в том, что при синхронном выполнении продолжений мы можем исчерпать ресурсы стека.

Допустим, мы имеем 10000 задач, в стиле

//code1await ...//code2

Допустим, первая задача завершает выполнение и этим освобождает продолжение второй, которое мы начинаем тут же выполнять синхронно в этом потоке, то есть забирая кусок стека стек фреймом данного продолжения. В свою очередь, данное продолжение разблокирует продолжение третей задачи, которое мы тоже начинаем сразу выполнять. И так далее. Если в продолжении больше нет await'ов или чего-то, что как-то сбросит стек, то мы просто будем потреблять стековое пространство до упора. Что может вызвать StackOverflow и крах приложения. В рассмотрении кода я упомяну, как с этим борется AsyncOperation.

AsyncOperation как реализация IValueTaskSource


Source code.

Внутри AsyncOperation есть поле _continuation типа Action<object>. Поле используется для, не поверите, продолжений. Но, как это часто бывает в слишком современном коде, у полей появляются дополнительные обязанности (как сборщик мусора и последний бит в ссылке на таблицу методов). Поле _continuation из той же серии. Есть 2 специальных значения, которые могут хранится в этом поле, кроме самого продолжения и null. s_availableSentinel и s_completedSentinel. Данные поля показывают, что операция доступна и завершена соответственно. Доступна она бывает как раз для переиспользования для совершенно асинхронной операции.

Также AsyncOperation реализует IThreadPoolWorkItem с единственным методом void Execute() => SetCompletionAndInvokeContinuation(). Метод SetCompletionAndInvokeContinuation как раз и занимается выполнением продолжения. И данный метод вызывается либо напрямую в коде AsyncOperation, либо через упомянутый Execute. Ведь типы реализующие IThreadPoolWorkItem можно забрасывать в тред пул как-то вот так ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false).

Метод Execute будет выполнен тред пулом.

Само выполнение продолжения довольно тривиально.

Продолжение _continuation копируется в локальную переменную, на ее место записывается s_completedSentinel искусственный объект-марионетка (иль часовой, не знаю, как глаголить мне в нашей речи), который указывает, что задача завершена. Ну а далее локальная копия реального продолжения просто выполняется. При наличии ExecutionContext, данные действия постятся в контекст. Никакого секрета тут нет. Этот код может быть вызван как напрямую классом просто вызвав метод, инкапсулирующий эти действия, так и через интерфейс IThreadPoolWorkItem в тред пуле. Теперь можно догадаться, как работает функция с выполнением продолжений синхронно.

Первый метод интерфейса IValueTaskSource GetResult (github).

Все просто, он:

  1. Инкрементирует _currentId.
    _currentId то, что идентифицирует конкретную операцию. После инкремента она уже не будет ассоциирована с этой операцией. Поэтому не следует получать результат дважды и тп;
  2. помещает в _continuation делегат-марионетку s_availableSentinel. Как было упомянуто, это показывает, что этот экземпляр AsyncOperation можно испоьзовать повторно и не выделять лишней памяти. Делается это не всегда, а лишь если это было разрешено в конструкторе (pooled = true);
  3. Возвращает поле _result.
    Поле _result просто устанавливается в методе TrySetResult который описан ниже.

Метод TrySetResult (github).

Метод тривиален. он сохраняет принятый параметр в _result и сигнализирует о завершении, а именно вызывает метод SignalCompleteion, который довольно интересен.

Метод SignalCompletion (github).

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

В самом начале, если _comtinuation == null, мы записываем марионетку s_completedSentinel.

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

  1. Если _schedulingContext == null, т.е. нет захваченного контекста (это первый if).
    Далее необходимо проверить _runContinuationsAsynchronously == true, то есть явно указано, что продолжения нужно выполнять как все привыкли асинхронно (вложенный if).
    При соблюдении данный условий в бой идет схема с IThreadPoolWorkItem описанная выше. То есть AsyncOperation добавляется в очередь на выполнение потоком тред пула. И выходим из метода.
    Следует обратить внимание, что если первый if прошел (что будет очень часто, особенно в коре), а второй нет, то мы не попадем в 2 или 3 блок, а спустимся сразу на синхронное выполнение продолжения т.е. 4 блок;
  2. Если _schedulingContext is SynchronizationContext, то есть захвачен контекст синхронизации (это первый if).
    По аналогии мы проверяем _runContinuationsAsynchronously = true. Но этого не достаточно. Необходимо еще проверить, контекст потока, на котором мы сейчас находимся. Если он отличен от захваченного, то мы тоже не можем просто выполнить продолжение. Поэтому если одно из этих 2 условий выполнено, мы отправляем продолжение в контекст знакомым способом:
    sc.Post(s => ((AsyncOperation<TResult>)s).SetCompletionAndInvokeContinuation(), this);
    

    И выходим из метода. опять же, если первая проверка прошла, а остальные нет (то есть мы сейчас находимся на том же контексте, что и был захвачен), мы попадем сразу на 4 блок синхронное выполнение продолжения;
  3. Выполняется, если мы не зашли в первые 2 блока. Но стоит расшифровать это условие.
    Хитрость в том, что _schedulingContext может быть на самом деле захваченным TaskScheduler, а не непосредственно контекстом. В этом случае мы поступаем также, как и в блоке 2, т.е. проверяем флаг _runContinuationsAsynchronously = true и TaskScheduler текущего потока. Если планировщик не совпадает или флаг не тот, то мы сетапим продолжение через Task.Factory.StartNew и передаем туда этот планировщик. И выходим из метода.
  4. Как и сказал в начале просто выполняем продолжение на текущем потоке. Раз мы до сюда дошли, то все условия для этого соблюдены.

Второй метод интерфейса IValueTaskSource GetStatus (github)
Просто как питерская пышка.

Если _continuation != _completedSentinel, то возвращаем ValueTaskSourceStatus.Pending
Если error == null, то возвращаем ValueTaskSourceStatus.Succeeded
Если _error.SourceException is OperationCanceledException, то возвращаем ValueTaskSourceStatus.Canceled
Ну а коль уж до сюда дошли, то возвращаем ValueTaskSourceStatus.Faulted

Третий и последний, но самый сложный метод интерфейса IValueTaskSource OnCompleted (github)

Метод добавляет продолжение, которое выполняется по завершению.

При необходимости захватывает ExecutionContext и SynchronizationContext.

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

Если сохранение продолжения прошло, то возвращается значение, которое было в переменной до обновления, то есть null. Это означает, что операция еще не завершилась на момент записи продолжения. И тот, кто ее завершит сам со всем разберется (как мы смотрели выше). И нам нет смысла выполнять какие-то дополнительные действия. И на этом работа метода завершается.

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

Таким образом проверяем возвращенное значение, равно ли оно s_completedSentinel именно оно было бы записано в случае завершения.

  • Если это не s_completedSentinel, то нас использовали не по плану попытались добавить более одного продолжения. То есть то, которое уже записано, и то, которое пишем мы. А это исключительная ситуация;
  • Если это s_completedSentinel, то это один из допустимых исходов, операция уже завершена и продолжение должны вызвать мы, здесь и сейчас. И оно будет выполнено асинхронно в любом случае, даже если _runContinuationsAsynchronously = false.
    Сделано это так, потому что если мы дошли до этого места, значит мы внутри метода OnCompleted, внутри awaiter'а. А синхронное выполнение продолжений именно здесь грозит упомянутым стек дайвом. Сейчас вспомним, для чего нам нужна эта AsyncOperation System.Threading.Channels. А там ситуация может быть очень легко достигнута, если о ней не задуматься. Допустим, мы читатель в ограниченном канале. Мы читаем элемент и разблокируем писателя, выполняем его продолжение синхронно, что разблокирует очередного читателя(если читатель очень быстр или их несколько) и так далее. Тут стоит осознать тонкий момент, что именно внутри awaiter'а возможна эта ситуация, в других случаях продолжение выполнится и завершится, что освободит занятый стек фрейм. А постоянный зацеп новых продолжений вглубь стека порождается постоянным выполнением продолжения внутри awaiter'а.
    В целях избежания данной ситуации, несмотря ни на что необходимо запустить продолжение асинхронно. Выполняется по тем же схемам, что и первые 3 блока в методе SignalCompleteion просто в пуле, на контексте или через фабрику и планировщик

А вот и пример синхронных продолжений:

class Program    {        static async Task Main(string[] args)        {            Channel<int> unboundedChannel = Channel.CreateUnbounded<int>(new UnboundedChannelOptions            {                AllowSynchronousContinuations = true            });            ChannelWriter<int> writer = unboundedChannel;            ChannelReader<int> reader = unboundedChannel;            Console.WriteLine($"Main, before await. Thread id: {Thread.CurrentThread.ManagedThreadId}");            var writerTask = Task.Run(async () =>            {                Thread.Sleep(500);                int objectToWriteInChannel = 555;                Console.WriteLine($"Created thread for writing with delay, before await write. Thread id: {Thread.CurrentThread.ManagedThreadId}");                await writer.WriteAsync(objectToWriteInChannel);                Console.WriteLine($"Created thread for writing with delay, after await write. Thread id: {Thread.CurrentThread.ManagedThreadId}");            });            //Blocked here because there are no items in channel            int valueFromChannel = await reader.ReadAsync();            Console.WriteLine($"Main, after await (will be processed by created thread for writing). Thread id: {Thread.CurrentThread.ManagedThreadId}");            await writerTask;            Console.Read();        }    }

Output:

Main, before await. Thread id: 1
Created thread for writing with delay, before await write. Thread id: 4
Main, after await (will be processed by created thread for writing). Thread id: 4
Created thread for writing with delay, after await write. Thread id: 4
Подробнее..

Портирование приложений с QWidget на QML под Desktop

07.07.2020 14:13:48 | Автор: admin
Привет, Хабровчане!

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

Кратко о QML и QWidget


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

QWidget это предшествующая QML технология для создания пользовательского интерфейса, основанная на использовании заранее созданных desktop-style компонентов и предоставляющая API для взаимодействия с ними.

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


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

Стандартное окно(кликабельно)


Дизайн приложения и его влияние на портирование


Классически Qt нам советует придерживаться паттернов MVC\MVP, где всё взаимодействие с UI вынесено в отдельные сущности, что позволяет нам изменять их, не затрагивая остальные части приложений и бизнес логику. Абстрагирование элементов и возможность встраивать QML-компонент в стандартный QWidget позволяет гибко подойти к плану дальнейшей работы. Если вы сейчас на этом пункте, то я бы хотел вам посоветовать продумать требования и функциональность будущего приложения, нарисовать его макет, посоветоваться с дизайнерами, опросить пользователей или заказчика. В целом решить как вы будете жить ближайшее время, и какие вопросы будете решать. И выбрать один из двух путей, предложенных ниже.

План А С чистого листа


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

  1. Библиотека элементов. Создайте свою начальную базу элементов, где пропишите базовые настройки для элементов, их вид и состояния по умолчанию. Если есть возможность, оберните их тестами. В одной из конфигураций проекта сделайте их копируемыми в папку проекта, а не компилируемыми в бинарный*.qrc, это позволит вам править эти сборки в runtime без дополнительных компиляций и подключать qmllive и его аналоги.
  2. Отсутствие QAction. В приложениях на QWidget, частым гостем является и QAction, расширяющий возможности перехвата действий пользователя как по нажатию кнопки на экране, так и перехватом горячих клавиш. К сожалению в QML QAction переехал в несколько ином виде и позволяет только из QML обертки перехватывать заранее определенные горячие клавиши, что накладывает ограничения на использование его внутри C++ или создавать динамически. Здесь вы можете написать свою реализацию, использовать event-filter от qobject или использовать сторонние проекты, такие как QHotkey .

В остальном здесь всё типично для приложения на C++ и QML.

План Б У нас типо agile


Так как для Agile одной из главной ценностью является работающий продукт, то мы должны делать переезд поэтапно, без регресса функционала. Для этого мы делим весь UI на логические сегменты с минимальным количеством взаимозависимостей и реализуем их отдельно. На примере gitAhead можно рассмотреть следующий вариант(выделил зеленым):

Разделение UI на сегменты(кликабельно)


Для каждой из областей мы создадим родительский виджет, в который и положим корневой QML элемент. Это можно сделать двумя способами.

Создать QQuickWidget

QQuickWidget *view = new QQuickWidget;view->setSource(QUrl::fromLocalFile("myqmlfile.qml"));view->show();

Или вызывать метод QWidget::createWindowContainer(...)

QQuickView *view = new QQuickView();...QWidget *container = QWidget::createWindowContainer(view);container->setMinimumSize(...);container->setMaximumSize(...);...widgetLayout->addWidget(container);

Разница между предложенными способами в том что QQuickWidget является более гибкой альтернативой чем QWidget::createWindowContainer(...), ведя себя больше как обычный QWidget. При этом большая функциональность достигается из-за незначительных потерь в производительности.

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

  1. Ограниченный контекст. У каждого родительского QWidget будет свой QML-контекст, что в свое очередь накладывает ограничения на область видимости. Свои property, функции, объекты и т.д. Каждый widget по сути становится независимой песочницей
  2. Ограничение по размеру виджета. В ходе создания виджета, можно создать правило чтобы размеры виджета следовали за размерами контента внутри и по сути это правильно. Но это распространяется только на корневые элементы внутри этого QML, а все временные объекты с размерами превышающими виджет, будут обрезаться границами. Хорошим примером тут может служить всплывающая подсказка для кнопок, если кнопки маленькие, при это есть большое описание, то часть подсказки скроется за границей виджета. Если размеры виджета-контейнера поставлены не по размеру контента, то это так же может привести к проблемам при масштабировании интерфейса. Частично эта проблема решается элементами из lab.

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

  3. 3D графика. Здесь всё усложнено и полно ограничений, рекомендуется использовать QQuickWidget и есть не маленькая вероятность что именно ваш сценарий либо потребует костылей, либо больших трудовложений, поэтому ознакомьтесь с ограничениями заранее, либо сразу реализуйте под использование в QML

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

Итоги


Над gitAhead мне предстоит еще много работы, и не только по UI, но приблизительно как это происходит я описал. Если вам понравилась идея и вам хочется поучаствовать, то буду рад сотрудничеству. Мой репозиторий будет тут.
Подробнее..

Как ультрафиолет запускает фотолиз прямо в вашей коже

07.07.2020 14:13:48 | Автор: admin
image

На КДПВ водитель-дальнобойщик, который 28 лет водил фуры по просторам США. Стекло было закрыто, кондиционер работал. Вот только ультрафиолет UVA-спектра прекрасно проникает сквозь него и вызывает повреждения кожи и фотостарение. Научную публикацию по его случаю можно посмотреть тут. Ультрафиолет запускает кучу неприятных реакций в организме и рвёт на куски ДНК. Это явление называется фотолизом.

Загар штука симпатичная, но он всегда патология и способ защититься от повреждения. Вот раньше была отличная, на мой взгляд, мода на бледность и зонтики от солнца. Сейчас же все старательно загорают на пляже и в солярии. Поэтому, если вы не хотите выглядеть в 30 лет как пожилой крестьянин с рисовых полей, надо обязательно защищать кожу специальными SPF-составами. Sun Protection Factor на этикетке показывает, насколько долго вы можете пробыть на солнце с этим средством. Например, если SPF 50+, а вы выгораете до состояния томата через десять минут, то с ним вы сможете продержаться 10 * 50 минут, то есть почти восемь часов.

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

Ура, карантин снимают


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

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

Особенности ультрафиолета


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

На первого человека направим мощный инфракрасный прожектор. Сначала ему станет тепло, а потом он начнёт ругаться и пытаться уйти, потому что жжётся. Инфракрасное излучение регистрируется нашими рецепторами сразу же и имеет быстро проявляющиеся эффекты. Видели людей, которые просидели долгое время в сухой сауне? Вот примерно такая же эритема (покраснение) будет при воздействии инфракрасных лучей. Быстро возникает и быстро проходит в течение 3060 минут. Если жарить долго и регулярно, что характерно, например, для дачников, то на коже будет появляться некрасивая бурая пигментация пятнистого вида.

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

Чуть-чуть физики и фотохимии


Фотоны с длиной волны в районе ультрафиолета уже несут достаточное количество энергии, чтобы инициировать химические реакции. Собственно, именно поэтому они относятся к ионизирующему излучению. Чем короче длина волны, тем больше энергии несёт фотон, в полном соответствии с формулой E = hv.

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

image
Ультрафиолетовый мутагенез

А ещё фотон может прицельно попасть в ценную белковую молекулу или нить ДНК, вызвав их повреждение. Если очень не повезёт, можно получить одну из разновидностей рака кожи, например, меланому.

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

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

Есть несколько вариантов классификации участков спектра в ультрафиолетовой зоне. Если не брать в расчёт экстремальный ультрафиолет (EUV), то актуальными для нас являются три диапазона:

  1. UVA. Длина волны 400315 нм. Самая мягкая часть ультрафиолета. Энергия минимальная, проходит атмосферу без проблем.
  2. UVB. Длина волны 315280 нм. По большей части поглощается озоновым слоем атмосферы и до поверхности почти не долетает.
  3. UVC. Длина волны 280100 нм. Жёсткий ультрафиолет, полностью поглощается атмосферой. Встречается только в искусственных источниках вроде кварцевых ламп.

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

С точки зрения повседневного повреждения кожи нас больше интересует диапазон UVA и UVB. UVB сразу просто и понятно сделает вас пунцовым томатом. До росткового слоя базальноклеточного доходит примерно 15 % излучения. UVA мягкий, эритему почти не вызывает. Но он коварен тем, что неплохо проходит сквозь оконное стекло и проникает вглубь кожи. До 68 % излучения проходит ростковый слой и поглощается дермой.

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

Защищаем кожу




Thomas Leveritt, английский художник, смог наглядно продемонстрировать, что происходит с нами из-за ультрафиолетовых лучей. Он использовал крайне дорогие объективы, прозрачные для ультрафиолета, и специальную камеру. На видео очень хорошо видна разница в структуре кожи детей и взрослых, чья кожа несёт следы многочисленных микроповреждений от солнечного излучения.

image
Лысая мыша породы Skh:HR-1. Тоже надо мазать кремом от загара

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

Мы провели кучу исследований, после которых наша команда опытных химиков смогла подготовить оптимальную защитную формулу. В результате получился препарат Mультипротектор SPF50+ oil free. Сейчас расскажу, что мы туда добавили и зачем там столько компонентов одновременно.

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

image
Пример поглощения ультрафиолета защитным кремом

Дальше идут компоненты, которые поглощают ультрафиолет, не позволяя ему достигнуть кожи. Здесь уже мы подобрали сложную композицию, которую редко используют в дешёвых защитных кремах:

  1. Этилгексил метоксициннамат: органический UVB-фильтр с высоким профилем безопасности, не вызывающий образование комедонов.
  2. Гомосалат: безопасный УФ-фильтр, поглощающий UVB-лучи.
  3. Диэтиламино гидроксибензоил гексил бензоат: фотостабильный фильтр, поглощающий UVA-лучи и обеспечивающий эффективную защиту от свободных радикалов и фотостарения.
  4. Tinosorb M: фотостабильный УФ-фильтр в виде органических мелкодисперсных частиц, обеспечивающий эффективную защиту от широкого спектра ультрафиолетовых лучей. Данный фильтр охватывает значительную часть диапазона излучения от длинноволнового ультрафиолета UVA и средневолнового UVB, которые вызывают пигментацию и солнечные ожоги, до видимого света (HEV).
  5. Этилгексил триазон: фотостабильный UVB-фильтр.

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

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

Пора на море


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

Если уж планируете загорать, то всегда ограничивайте время на пляже. Старайтесь не появляться на солнце с 10 до 16, чтобы не подвергать кожу лишней опасности. Субэритемные дозы, когда кожа не покраснела, лучший вариант. Такой загар может быть менее интенсивным, но продержится дольше. Как придёте домой вымойтесь в душе, чтобы не оставлять защитный крем. После этого нанесите что-нибудь из увлажняющих составов. Можно наш Гель увлажняющий +. Он относительно недорогой и тоже с фукогелем для микрофлоры.

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

Из песочницы На пороге квантового сознания

07.07.2020 14:13:48 | Автор: admin
Предпосылки появления ИИ, превосходящего мозг человека:

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

Предположим...


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

Поэтому принципиально неразрешимой проблемы переноса сознания на другой носитель нет.
Бессознательно в человеке работают наиболее автоматические элементы мозга, управляющие работой жизненно важных органов. Как только возникает необходимость принять решение и сделать выбор говорят о появлении некого я сознания. Выбор делает элемент мозга человека, по-видимому имеющий квантовую природу аналог квантового ИИ. Правда, наличие сознательного выбора не обязательно означает появление самоосознания некого Я. Для появления Я требуется активное взаимодействие с памятью с той памятью, которая может быть визуализирована, услышана либо виртуализирована (представлена) каким-либо другим способом, накопленным опытом человека. Причем различные блоки содержания опыта, доступ к которым осуществляется, например, во сне или наяву, порождает различные Я человека. Хотя иногда часть опыта различных состояний проникает друг в друга и таким образом одно Я индивида объединяется с другим его Я, обогащая друг друга.

image

Перенос человеческого сознания на машинный носитель


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

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

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

Мы называем сознанием некую высшую функцию мозга, которая делает выбор, принимает решение. Сознание непосредственно связано с мыслительной деятельностью, прогнозированием и моделированием, оценкой и анализом ситуации, воображением и выбором, т.е. это комплексная многомерная работа мозга и она связанна с перебором и анализом всех возможных вариантов. Рассмотрим простой пример человеку захотелось съесть кусочек торта В некий момент времени в организме человеке стало не хватать сахара и он посылает сигнал поступает в мозг возникает мысль: Я хочу торт. На самом деле сознание интерпретировало первоначальную потребность: Нужен сахар. Почему этот процесс проходит через этап осознавания этой потребности? Можно было бы, минуя сознательную часть мозга, отправить человека к холодильнику и достать оттуда торт. Человеку как бы дается право принять окончательное решение: есть или не есть торт. При этом появляется возможность смоделировать ситуацию, спрогнозировать возможные последствия, определить его важности, отранжировать потребности, определить насколько оно сейчас своевременно и пр. т.е. подумать над этим и принять решение.

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

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

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

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

(Про возникновение сознания в процессе обучения в своих работах писал еще Э.Шредингер)

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

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

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

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

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

см. Нейроны сознания Е.Н. СОКОЛОВ
Подробнее..

Издательство Питер. Летняя распродажа

07.07.2020 12:09:02 | Автор: admin
image

Привет, Хаброжители! На этой неделе у нас большие скидки. Подробности внутри.

image

В хронологическом порядке представлены книги, вызвавшие интерес читателей за последние 3 мес.

Отдельные категории на сайте Бестселлеры O'Reilly, Head First O'Reilly, Manning, No Starch Press, Packt Publishing, Классика Computer Science, программирование для детей, научно-популярная серия New Science.

Условия акции: 714 июля, скидка 40% на все бумажные книги по купону Бумажная книга, скидка 50% на все электронные книги по купону Электронная книга
Подробнее..

Категории

Последние комментарии

© 2006-2020, personeltest.ru