-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

Приложения для Tarantool 1.7. Часть 1. Хранимые процедуры

Вторник, 01 Августа 2017 г. 12:29 + в цитатник

Привет, Хабр! Сегодня я хочу поделиться с вами опытом написания приложений для Tarantool 1.7. Этот цикл статей будет полезен тем, кто уже собирается использовать Tarantool в своих проектах, либо тем, кто ищет новое решение для оптимизации проектов.


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


Tarantool — это NoSQL база данных, которая хранит данные в памяти либо на диске (в зависимости от подсистемы хранения). Хранилище персистентно за счет продуманного механизма write ahead log. В Tarantool встроен LuaJIT (Just-In-Time Compiler), позволяющий исполнять код на Lua. Также можно писать хранимые процедуры на C.


image


Содержание цикла «Приложения для Tarantool 1.7»


  • Часть 1. Хранимые процедуры
  • Часть 2. Сторонние модули
  • Часть 3. Тестирование и запуск

Зачем создавать свои приложения для Tarantool


Есть две причины:


  1. Это ускорит работу сервиса. Обработка данных на стороне хранилища сокращает объем передаваемых данных, а объединение нескольких запросов в одну хранимую процедуру позволит сэкономить на сетевых задержках.
  2. Готовые приложения можно переиспользовать. Сейчас экосистема Tarantool активно развивается, появляются новые opensource-приложения на Tarantool, часть которых со временем переносится в сам Tarantool. Такие модули позволяют создавать новые сервисы быстрее.

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


Рассмотрим, как было создано одно из приложений для Tarantool. Оно реализует API для регистрации и аутентификации пользователей. Функционал приложения:


  • регистрация и аутентификация по email в два этапа: создание аккаунта, подтверждение аккаунта с установкой пароля;
  • регистрация через социальные сети (FB, VK, Google+);
  • возможность восстановить пароль.

В качестве примера написания хранимой процедуры Tarantool мы разберем первый этап регистрации по email — получение кода подтверждения. Чтобы оживить примеры, можно воспользоваться исходным кодом, который доступен на github.


Поехали!


Установка Tarantool


О том, как установить Tarantool, прочитайте в документации. Например, для Ubuntu нужно выполнить в терминале:


curl http://download.tarantool.org/tarantool/1.7/gpgkey | sudo apt-key add -
release=`lsb_release -c -s`

sudo apt-get -y install apt-transport-https

sudo rm -f /etc/apt/sources.list.d/*tarantool*.list
sudo tee /etc/apt/sources.list.d/tarantool_1_7.list <<- EOF
deb http://download.tarantool.org/tarantool/1.7/ubuntu/ $release main
deb-src http://download.tarantool.org/tarantool/1.7/ubuntu/ $release main
EOF

sudo apt-get update
sudo apt-get -y install tarantool

Проверим, что установка прошла успешно, вызвав в консоли tarantool и запустив интерактивный режим работы.


$ tarantool
version 1.7.3-202-gfe0a67c
type 'help' for interactive help
tarantool>

Здесь можно попробовать свои силы в программировании на Lua.
Если сил нет, то наберитесь их в этом небольшом tutorial.


Регистрация по email


Идем дальше. Напишем первый скрипт, позволяющий создать пространство (space) с пользователями. Space — это аналог таблиц для хранения данных. Cами данные хранятся в виде кортежей (tuple). Space должен содержать один первичный (primary) индекс, в нем также может быть несколько вторичных (secondary) индексов. Индекс бывает и по одному ключу, и сразу по нескольким. Tuple представляет собой массив, в котором хранятся записи. Рассмотрим схему space’ов сервиса аутентификации:


image


Как видно из схемы, мы используем индексы двух типов: hash и tree. Hash-индекс позволяет находить кортежи по полному совпадению первичного ключа и обязан быть уникальным. Tree-индекс поддерживает неуникальные ключи, поиск по первой части составного индекса и позволяет оптимизировать операции сортировки по ключу, так как значения в индексе хранятся упорядоченно.


В space session хранится ключ (session_secret), которым подписывается сессионная кука. Хранение ключей сессий позволяет разлогинивать пользователей на стороне сервиса, если нужно. Сессия имеет опциональную ссылку на space social. Это необходимо для валидации сессий пользователей, входящих через социальные сети (проверки валидности хранимого OAuth2-токена).


Перейдем к написанию приложения. Для начала рассмотрим структуру будущего проекта:


tarantool-authman
+-- authman
|   +-- model
|   |   +-- password.lua
|   |   +-- password_token.lua
|   |   +-- session.lua
|   |   +-- social.lua
|   |   +-- user.lua
|   +-- utils
|   |   +-- http.lua
|   |   +-- utils.lua
|   +-- db.lua
|   +-- error.lua
|   +-- init.lua
|   +-- response.lua
|   +-- validator.lua
+-- test
    +-- case
    |   +-- auth.lua
    |   +-- registration.lua
    +-- authman.test.lua
    +-- config.lua

Модули в Lua импортируются из путей, указанных в package.path переменной.
В нашем случае модули импортируются относительно текущей директории, т. е. tarantool-authman. Однако при необходимости пути импорта можно дополнить:


lua
-- Добавляем новый путь с самым высоким приоритетом (в начало строки)
package.path = "/some/other/path/?.lua;" .. package.path

Прежде чем мы создадим первый space, вынесем необходимые константы в модели. Каждый space и каждый индекс должен определить свое название. Также необходимо определить порядок хранения полей в кортеже. Так выглядит модель пользователя authman/model/user.lua:


-- Наш модуль — это Lua-таблица
local user = {}

-- Модуль содержит единственную функцию — model, которая возвращает таблицу с полями и методами модели
-- На входе функция принимает конфигурацию в виде опять же lua-таблицы
function user.model(config)
    local model = {}

    -- Название спейса и индексов
    model.SPACE_NAME = 'auth_user'
    model.PRIMARY_INDEX = 'primary'
    model.EMAIL_INDEX = 'email_index'

    -- Номера полей в хранимом кортеже (tuple)
    -- Индексация массивов в Lua начинается с 1 (!)
    model.ID = 1
    model.EMAIL = 2
    model.TYPE = 3
    model.IS_ACTIVE = 4

    -- Типы пользователя: email-регистрация или через соцсеть
    model.COMMON_TYPE = 1
    model.SOCIAL_TYPE = 2

    return model
end

-- Возвращаем модуль
return user

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


Модуль authman/db.lua содержит метод для создания space’ов:


local db = {}

-- Импортируем модуль и вызываем функцию model
-- При этом в параметр config попадает nil — пустое значение
local user = require('authman.model.user').model()

-- Метод модуля db, создающий пространства (space) и индексы
function db.create_database()

    local user_space = box.schema.space.create(user.SPACE_NAME, {
        if_not_exists = true
    })
    user_space:create_index(user.PRIMARY_INDEX, {
        type = 'hash',
        parts = {user.ID, 'string'},
        if_not_exists = true
    })
    user_space:create_index(user.EMAIL_INDEX, {
        type = 'tree',
        unique = false,
        parts = {user.EMAIL, 'string', user.TYPE, 'unsigned'},
        if_not_exists = true
    })
end

return db

В качестве id пользователя берем uuid, тип индекса hash, ищем по полному совпадению. Индекс для поиска по email состоит из двух частей: (user.EMAIL, 'string') — email, (user.TYPE, 'unsigned') — тип пользователя. Типы были определены ранее в модели. Составной индекс позволяет искать не только по всем полям, но и по первой части индекса, поэтому доступен поиск только по email (без типа пользователя).


Теперь запустим интерактивную консоль Tarantool в директории с проектом и попробуем воспользоваться модулем authman/db.lua.


$ tarantool
version 1.7.3-202-gfe0a67c
type 'help' for interactive help
tarantool> db = require('authman.db')
tarantool> box.cfg({listen=3331})
tarantool> db.create_database()

Отлично, первый space создан! Внимание: перед обращением к box.schema.space.create необходимо сконфигурировать и запустить сервер методом box.cfg. Теперь рассмотрим несколько простых действий внутри созданного space:


-- Создание пользователей
tarantool> box.space.auth_user:insert({'user_id_1', 'exaple_1@mail.ru', 1})
---
- ['user_id_1', 'exaple_1@mail.ru', 1]
...
tarantool> box.space.auth_user:insert({'user_id_2', 'exaple_2@mail.ru', 1})
---
- ['user_id_2', 'exaple_2@mail.ru', 1]
...
-- Получие Lua-таблицы (массива) всех пользователей
tarantool> box.space.auth_user:select()
---
- - ['user_id_2', 'exaple_2@mail.ru', 1]
  - ['user_id_1', 'exaple_1@mail.ru', 1]
...

-- Получение пользователя по первичному ключу
tarantool> box.space.auth_user:get({'user_id_1'})
---
- ['user_id_1', 'exaple_1@mail.ru', 1]
...

-- Получение пользователя по составному ключу
tarantool> box.space.auth_user.index.email_index:select({'exaple_2@mail.ru', 1})
---
- - ['user_id_2', 'exaple_2@mail.ru', 1]
...

-- Обновление данных с заменой второго поля
tarantool> box.space.auth_user:update('user_id_1', {{'=', 2, 'new_email@mail.ru'}, })
---
- ['user_id_1', 'new_email@mail.ru', 1]
...

Уникальные индексы ограничивают вставку неуникальных значений. Если необходимо создавать записи, которые уже могут находиться в space, воспользуйтесь операцией upsert (update/insert). Полный список доступных методов можно найти в документации.


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


    function model.get_space()
        return box.space[model.SPACE_NAME]
    end

    function model.get_by_email(email, type)
        if validator.not_empty_string(email) then
            return model.get_space().index[model.EMAIL_INDEX]:select({email, type})[1]
        end
    end

    -- Создание пользователя
    -- Поля, не являющиеся частями уникального индекса, необязательны
    function model.create(user_tuple)
        local user_id = uuid.str()
        local email = validator.string(user_tuple[model.EMAIL]) and user_tuple[model.EMAIL] or ''
        return model.get_space():insert{
            user_id,
            email,
            user_tuple[model.TYPE],
            user_tuple[model.IS_ACTIVE],
            user_tuple[model.PROFILE]
        }
    end

    -- Генерация кода, который отправляется в письме, с просьбой активировать аккаунт
    -- Как правило, такой код подставляется GET-параметром в ссылку
    -- activation_secret — один из настраиваемых параметров при инициализации приложения
    function model.generate_activation_code(user_id)
        return digest.md5_hex(string.format('%s.%s', config.activation_secret, user_id))
    end

В приведенном фрагменте кода применены два стандартных модуля Tarantool — uuid и digest, а также один пользовательский — validator. Перед использованием их необходимо импортировать:


-- Стандартные модули Tarantool
local digest = require('digest')
local uuid = require('uuid')
-- Модуль нашего приложения (отвечает за валидацию данных)
local validator =  require('authman.validator')

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


А теперь создадим основной модуль authman/init.lua. В этом модуле будут собраны все методы api приложения.


local auth = {}

local response = require('authman.response')
local error = require('authman.error')
local validator = require('authman.validator')
local db = require('authman.db')
local utils = require('authman.utils.utils')

-- Модуль возвращает единственную функцию — api, которая конфигурирует приложение и возвращает его
function auth.api(config)
    local api = {}
    -- Модуль validator содержит проверки различных типов значений
    -- Здесь же выставляются значения по умолчанию
    config = validator.config(config)

    -- Импортируем модели для работы с данными
    local user = require('authman.model.user').model(config)

    -- Создаем space
    db.create_database()

    -- Метод api создает неактивного пользователя с указанным адресом электронной почты
    function api.registration(email)
        -- Перед работой с email — приводим его к нижнему регистру
        email = utils.lower(email)

        if not validator.email(email) then
            return response.error(error.INVALID_PARAMS)
        end

        -- Проверяем, нет ли существующего пользователя с таким email
        local user_tuple = user.get_by_email(email, user.COMMON_TYPE)
        if user_tuple ~= nil then
            if user_tuple[user.IS_ACTIVE] then
                return response.error(error.USER_ALREADY_EXISTS)
            else
                local code = user.generate_activation_code(user_tuple[user.ID])
                return response.ok(code)
            end
        end

        -- Записываем данные в space
        user_tuple = user.create({
            [user.EMAIL] = email,
            [user.TYPE] = user.COMMON_TYPE,
            [user.IS_ACTIVE] = false,
        })

        local code = user.generate_activation_code(user_tuple[user.ID])
        return response.ok(code)
    end

    return api
end

return auth

Отлично! Теперь пользователи смогут создавать аккаунты.


tarantool> auth = require('authman').api(config)
-- Воспользуемся api для получения кода регистрации
tarantool> ok, code = auth.registration('example@mail.ru')
-- Этот код необходимо передать пользователю на email для активации аккаунта
tarantool> code
022c1ff1f0b171e51cb6c6e32aefd6ab

На этом все. В следующей части рассмотрим использование готовых модулей, сетевое взаимодействие и реализацию OAuth2 в tarantool-authman.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334266/


Метки:  

hh и в продакшн: как выпустить новую фичу

Вторник, 01 Августа 2017 г. 12:11 + в цитатник
HH и в продакшн

Однажды техдепу в HeadHunter сделали толстовки с принтом на спине «hh и в продакшн». Вроде бы и забавно, но меня всё время смущала эта надпись, т.к. в оригинале это не то, чем стоит гордиться. Это натолкнуло меня написать пост, что же на самом деле означают буквы hh.

Я хочу рассказать о том, как рождается фича в HeadHunter на примере команды API, в которой я тружусь. Какой путь ей предстоит пройти от идеи до выхода в продакшн. Затрону как технические, так и менеджерские аспекты.

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


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

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

Этап первый: идея


Этап первый: идея
Итак, нам нужна новая фича. Чтобы она была, для начала её стоит придумать. Придумывать могут разные люди, но чаще всего это делают те, кто испытывает в ней необходимость. Будем называть таких людей заказчиками. Один из самых крупных заказчиков у команды API — департамент разработки мобильных приложений, которому постоянно что-то нужно для соискательских и работодательских приложений.

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

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

Этап второй: планирование


Этап второй: планирование
У нас есть записанная от заказчика идея. На этом этапе требуется чётко сформулировать задачу для разработчиков. Выяснить, насколько сложно реализовать задачу в целом, определить ключевые подходы к решению задачи. Найти подводные камни. Иногда реализовать идею может быть очень затратно, поэтому приходится находить определённый баланс между сложностью и требованиями. На этом этапе может быть задействовано большое количество людей, пока все договорятся. Если задача требует сложного или неоднозначного архитектурного решения, то оно нередко выносится на AB (Architecture Board). В hh.ru это еженедельное собрание тимлидов с техническим директором для решения и обсуждения насущных проблем и шаринга знаний между командами.

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

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

Этап третий: декомпозиция и оценка


Декомпозиция


Этап третий: декомпозиция и оценка
Когда становится понятно, что представляет из себя задача в техническом плане, её можно декомпозировать — разбить на этапы и подзадачи. Чаще всего декомпозицией занимается один из разработчиков. Он проводит более детальное исследование портфеля с технической стороны. Выясняет непонятные моменты у мейнтейнеров кода, с которым придётся работать. На каждую подзадачу заводится таска в джире. Каждая таска представляет собой отдельную задачу, изменение, которое можно выкатить на прод — и ничего не сломается. Какие бывают таски? У нас в команде API, если расширяется интерфейс, — а он в идеале, кстати, может только расширяться, чтобы не нарушить обратную совместимость, — то заводится задача на доработку документации.

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

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

Оценка


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

Раньше мы давали оценки задач в story point-ах (sp). 1sp — это один идеальный рабочий человеко-день, т. е. примерное представление, за сколько ты сделаешь эту задачу, если сядешь, будешь заниматься только ей и никто и ничто тебя не будет отвлекать. Но оценка в sp оказалась для нас не слишком удобной, т. к. она довольно точная, что требовало определённого времени на каждую задачу для всех и оценка иногда затягивалась. Тогда мы решили оценивать в майках.

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

  • 0 — ничего делать не надо;
  • XS — ерунда, можно очень быстро сделать;
  • S — простая задача;
  • M — обычная задача, всё в ней понятно;
  • L — сложная, есть непонятные места;
  • XL — сложная, мало что понятно. Таких задач не должно быть. Либо надо отправить её на дополнительное исследование, либо она должна быть разбита на подзадачи;
  • XXL — в jira добавили эту майку, но не знаю зачем — таких тоже быть не должно. Эта задача как целый портфель, наверное.


Есть много приложений для телефонов для игры в planning-покер с майками и сторипоинтами.

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

Этап четвёртый: разработка


Стендап


Прежде чем рассказывать о самой разработке, хочу рассказать о таком полезном процессе, как стендап (или планёрка, если хотите), сопровождающем разработку. Каждый рабочий день команда собирается в переговорке. У нас есть список тем, которые мы ежедневно обсуждаем и 30 минут времени на это обсуждение. Впереди всегда самая насущная тема: проходимся по портфелям, начиная с самых готовых, и выясняем, что ещё требуется, чтобы выпустить этот портфель, и стараемся все силы пустить на него. Также на стендапах мы обсуждаем проблемы, с которыми сталкиваемся в работе, делимся важной информацией. Вообще, про коммуникации в hh.ru можно рассказывать много, это тема отдельного поста.

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

Выбор задачи


Мы постепенно постигаем философию Kanban, одно из правил которого гласит, что необходимо уменьшать количество портфелей, находящихся одновременно в работе. Задачи иногда блочатся друг на друге и следующую нельзя взять, пока не будет готова предыдущая. Или, бывает, портфель приостанавливается, т. к. ожидает решения каких-то других проблем. Из-за этого у нас обычно одновременно в работе 2–4 портфеля. В нашей команде сейчас 4 разработчика и мы ищем пятого (миг-миг). В команде приветствуется кросс-функциональность. Любой из нас может выбрать из портфелей задачу на Java, Python или взять задачу на автотесты или документацию. Можно брать любую задачу из портфелей, которая нравится, но есть и некоторые нестрогие правила. Например, если портфель по расширению интерфейса API, то первой обычно выполняется задача по документации, т. к. на этапе документирования проявляется более чёткая картина того, как интерфейс будет выглядеть, и в деталях будут учтены все правила. Имея документацию легче разрабатывать. Очерёдность задач диктуется приоритетом портфелей, ну и здравым смыслом — проще взять задачу во фронтэнде, когда на бекэнде для неё будут готовы данные, но и такие задачи часто можно делать параллельно.

Разработка


Этап четвёртый: разработка
Мы выбрали задачу. Наконец-то можно начать разрабатывать! Открываем свою любимую IDE (слава мавену) и начинаем писать код. Для тестирования есть большой простор. У каждого разработчика есть свой тестовый стенд. На стенде развёрнуто окружение, очень близкое к продакшн-среде. Самый простой способ проверить сервис — запустить его локально. Настройки сервиса позволяют направлять запросы на тестовый стенд для взаимодействия с остальными сервисами. Иногда этого бывает недостаточно и нужно запустить сервис в контексте других сервисов, например, подправить бэкэнд и подёргать нужные страницы сайта. Для этого можно задеплоить сервис из ветки git-а на стенд. Код принято покрывать юнит-тестами. Они прогоняются перед деплоем на стенд, и, если что-то пошло не так, мы об этом узнаем.

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

Ревью


Обязательная часть после внесения правок в код — это ревью. Ревью помогает шарить знания между разработчиками и улучшать качество кода. Как я выбираю ревьювера. Если правил что-то сложное, то лучше отнести код человеку, который лучше всего в этом разбирается. Задачи из сервиса API обычно ревьювятся членами API — хорошо знать, кто что делает в команде, плюс мне нравится, что у нас ревью проходит оперативно. У сервисов есть мейнтейнеры и другие неравнодушные, подписанные на проект на гитхабе, некоторые из них любят посматривать pull-реквесты, даже если их не просят, за что им спасибо. Вообще, ревьювить стоит как можно скорее, чтобы задача не зависала по этой причине. Ревью обычно не требует много времени, а задачи надо проталкивать вперёд.

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


Ещё одной обязательной частью выпуска задачи является автотестирование. Это нужно для того, чтобы проверить работоспособность основных мест после внесённых правок. Это не серебряная пуля, но у нас хорошее покрытие тестами и от большого количества проблем это спасает. Для того чтобы прогнать автотесты, надо задеплоить изменённый сервис на стенд и запустить джобу в jenkins. Запустятся TestNg-тесты, которые будут ходить по основным интерфейсам сайта, API и других мест и проверять корректность их работы.

Релиз


После того как автотесты прошли, задачу можно перевести в джире в статус Ready to release. Все задачи из одного сервиса с таким статусом и не заблокированные другими задачами можно выпустить за один раз. Есть сервисы, по которым выпускается много задач. Такие релизятся каждый день. Остальные выпускаются по мере необходимости. Я пришёл в HeadHunter два года назад, тогда надо было произвести достаточно много действий, чтобы выпустить релиз. Была длинная инструкция. До того, как я пришёл, говорят, было ещё сложнее. За ежедневные релизы отвечали по очереди разные команды. В команде обычно выбирался один ответственный. В его задачи входило выполнять действия из инструкции, заниматься проблемами, если они возникали в процессе релиза, и следить за тем, чтобы всё было хорошо. На сегодняшний день мы очень сильно продвинулись в выпуске релизов, которые теперь выходят в автоматическом режиме и вмешательства человека требуется не много, но тем не менее команды продолжают дежурить с релизами. Если коротко, то при релизе происходит следующее:
необходимо обновить релизный стенд до актуального состояния';
собрать в git-ветку release-candidate, где будут смержены все выпускающиеся задачи;
задеплоить сервис из release-candidate на тестовый стенд;
запустить автотесты;
задеплоить release-candidate на прод. Этот этап происходит отдельно средствами службы эксплуатации;
вмержить release-candidate в master.

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

A/B-тестирование


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

Slack


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

Этап пятый: демонстрация и фидбэк


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

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

А ещё?


А ещё я хочу сказать, хотя наблюдательный читатель уже заметил, что жизненный цикл портфелей и задач управляется в Jira. У них есть разные статусы, на которые завязаны всяческие процессы. Ещё эти статусы полезны тем, что можно, например, смотреть за статистикой/динамикой отдельной команды или всех команд в целом, делать выводы, в каких местах затыки, и, может, там возможно что-то улучшить. Для отображения портфелей и задач мы используем kanban-доски.

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

В заключение хочу сказать, что не надо устраивать из процесса карго-культ. Надо осознавать, что приносит пользу, а что нет. И не стоит менять всё сразу. Но надо пробовать, экспериментировать, выбирать лучшее, постепенно меняться.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334562/


Метки:  

[Перевод] Перевод статьи Ганса Бувалды «Основные принципы проектирования тестов»

Вторник, 01 Августа 2017 г. 12:07 + в цитатник
Ребята из Luxoft Training подготовили перевод статьи о проектировании тестов от одного из первых разработчиков популярной сегодня методологии тестирования и автоматизации на основе ключевых слов — Ганса Бувалды.
Ганс Бувалда (Hans Buwalda) за свою профессиональную карьеру приобрел огромный опыт работы в качестве разработчика ПО, менеджера и главного консультанта в ведущих компаниях и организациях в разных странах мира. Предложенные им методы тестирования (на основе действий и в стиле мыльной оперы) помогли многим заказчикам разработать масштабируемые и легко поддерживаемые решения для большого объема сложных задач по тестированию. Ганс часто выступает в качестве докладчика на международных конференциях. Также он является соавтором книги Integrated Test Design and Automation.

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

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

Существует три главные цели, к которым нужно стремиться в проектировании тестов, как в легенде о короле Артуре и рыцарях Круглого стола, – «Три Святых Грааля проектирования тестов», приходится сталкиваться с огромными трудностями, как и в случае с поиском Святого Грааля рыцарями короля Артура. Итак, в этой статье я представлю три «грааля», которые нужно найти при проектировании тестов.

Терминология, используемая в этой статье, основывается на методологии тестирования на основе действий (Action Based Testing, ABT), которая применяется компанией LogiGear для тестирования и автоматизации тестирования. Более подробную информации о методологии ABT можно найти на сайте LogiGear.

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

Три цели проектирования тестов




Можно выделить три важнейшие цели проектирования тестов.

1. Эффективная декомпозиция тестов


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

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


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

3. Правильный уровень детализации тестов


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

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

Вывод


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

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

Больше статей от Ганса Бувалды можно найти здесь.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334612/


Метки:  

Как отрефакторить 2 500 000 строк кода и не сойти с ума

Вторник, 01 Августа 2017 г. 12:06 + в цитатник

5 июня 2017 года на РИТ я рассказал доклад про то, как мы рефакторим свое огромное клиентское приложение на 2 500 000 строк кода.
Недавно я получил запись выступления. Думаю, что это видео может быть кому-то полезно, поэтому я попросил у Олега Бунина разрешение на то, чтобы выложить его в открытый доступ. Он согласился. Надеюсь, вам будет интересно. В любом случае буду рад любым комментариям.
P.S. Заранее прошу прощение за качество видео. К сожалению, ничего с этим поделать не могу.




Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334590/


Метки:  

[Перевод] Как я проник на сервер PayPal через баг в загрузке файлов и получил доступ к удаленному выполнению кода

Вторник, 01 Августа 2017 г. 11:53 + в цитатник
Пентестер (тестировщик на проникновение) рассказывает, как ему удалось найти баг в загрузке файлов и проникнуть на сервер платежной системы PayPal.

image

Привет всем!

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

На самом деле это был довольно простенький взлом, направленный на проверку уязвимости (так называемый POC). Единственный момент, с которым мне повезло — поиск и успешное определение уязвимого домена.

image

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

Нормальные люди: выходные полные выпивки, тусовок, веселья, похмелья и прочего. Вопросы вроде: «Ты смотрел нового «Человека-паука»? Игру престолов?»

Люди вроде меня:

image

Итак, в один из выходных, просматривая кое-какие блоги и ролики на YouTube, я наткнулся на материал по PayPal, подумал зайти на их страницу программы поощрения поиска багов с помощью Burp и обнаружил следующее:

image

На скриншоте показан обычный ответ от http://paypal.com/bugbounty/. Присмотревшись, можно увидеть любопытный список доменов PayPal, в заголовке ответа Content Security Policy. Меня заинтересовал https://*.paypalcorp.com. Этот типовой подход, который я использую в отлавливании багов. Я ищу как можно больше рабочих поддоменов конечной цели, поскольку именно их, как правило, оставляют без должного внимания, и, такие как я, в итоге, что-нибудь там находят.

Вы можете воспользоваться Subbrute, Knockpy, enumall или другими похожими утилитами. Обычно я так и делаю, но в этот выходной лень взяла верх и я просканил их в VirusTotal. Итоговый список можно посмотреть здесь.

Я скопировал список поддоменов на локальную машину, запустил dig -f paypal +noall +answer чтобы получить удобную картину того, куда на самом деле указывают все субдомены.

image

Один из них, brandpermission.paypalcorp.com, указывал на https://www.paypal-brandcentral.com/ — сайт для регистрации запросов в службу онлайн-поддержки вендоров, поставщиков и партнеров PayPal, посредством которого они запрашивают разрешение на использование бренда. В системе доступна функция загрузки черновых вариантов логотипов и графики во время оформления запроса.

Реакция любого охотника за багами, увидевшего форму загрузки файлов:

image

Пахнет уловом

Поэтому для начала я создал «тикет», загрузив простое изображение с названием finished.jpg. На сервер оно попало под именем finished_thumb.jpg по пути /content/helpdesk/368/867/. Я быстро проверил, загрузился ли файл, который мы отправляли через форму с именем finished.jpg и да, к счастью (этот факт еще сыграет свою роль позже) он там присутствовал.

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

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

image

Скрипт вернул 302 код (то есть, по сути, 200 ОК). Это означало, что приложение не делало никаких проверок типов файлов, контента и прочего. Есть! Мое лицо в этот момент:

image

Да ладно...

Увидев 302 код, я ринулся открывать новый тикет чтобы скопировать ссылку как это было в случае загрузки файла изображения. Но при отправке .php увидеть путь загружаемого файла было нельзя. Единственное что можно было узнать — номер тикета. Что делать дальше?

image

Ну а теперь...

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

Но мы знаем имя файла — success.php (поскольку проверили до этого, что example.jpg оказался в той же папке, что и example_thumb.jpg).

Итак, мы знаем, что success.php также существует, а помещенный в success_thumb.php код отработал. Мы также знаем идентификатор папки (867), полученный при загрузке ранее простого изображения. Номер тикета для загрузки проверочного php — 366.

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

image

Нельзя просто взять и забрутфорсить

Поэтому я по-быстрому запустил в Intruder’е следующий перебор с идентификаторами 500–1000:
https://www.paypal-brandcentral.com/content/_helpdesk/366/$(bruteforced 500-1000)$/success.php. В итоге 200 код был получен на идентификаторе 865.

image
Моя реакция:

image

Круто! Теперь, когда мы узнали «айдишник» каталога с файлом давайте попробуем выполнить код: https://www.paypal-brandcentral.com/content/_helpdesk/366/865/success.php?cmd=uname-a;whoami

image

Немного магии cat /etc/passwd чтобы самому убедиться, что возможность удаленного исполнения кода действительно есть:

image

На сервере также была страница авторизации для сотрудников PayPal.

Надеюсь, вам понравился этот материал! Буду очень признателен обратной связи в комментах ;)

Хронология обработки обращения:
~ 8 июля, 2017 18:03 — Сообщил о баге в PayPal
~ 11 июля, 2017 18:03 — Баг исправлен
~ 25 июля, 2017 3:30 — Получил награду

Но подождите-ка! На этом история не заканчивается. Вот еще немного материала для затравки. О нем я поведаю в своем блоге на следующих выходных:

image

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

image

От переводчика:
На момент публикации перевода, сайт www.paypal-brandcentral.com стал недоступен, отдавая ошибку HTTP 500. Вероятно публикация оригинальной статьи послужила источником обнаружению более серъезных уязвимостей другими исследователями.

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/322802/


Метки:  

Во втором квартале зафиксирован 40% рост числа атакованных устройств

Вторник, 01 Августа 2017 г. 11:41 + в цитатник


С точки зрения информационной безопасности второй квартал 2017 года стал одним из самых ужасных за всю историю. Без преувеличения, атака WannaCry в мае и атака GoldenEye/Petya в июне были вне конкуренции, т.к. от них пострадали почти все страны мира и огромное множество компаний, ряд из которых восстанавливают свои системы до сих пор. По разным оценкам, общий ущерб от этих атак составил от 1 до 4 млрд. долларов.

Эти атаки тесно связаны с кибер-войнами и усилиями различных стран по борьбе с ними. Обе атаки воспользовались уязвимостью, обнаруженной АНБ, которая была украдена группой хакеров под названием Shadow Brokers и опубликована в апреле. Существует ряд доказательств, которые указывают на КНДР как источника атаки WannaCry, в то время как, по мнению многих экспертов, атака GoldenEye/Petya была направлена на срыв работы компаний и учреждений на Украине за день до их Дня конституции, причем они предполагают, что за этой атакой стояла Россия.

Пока мы не можем официально сказать, что уже ведется глобальная кибер-война, однако тем или иным образом атаки, подобные WannaCry или Petya, затрагивают каждого из нас. На фоне шума об этих двух заметных атаках, другие атаки спокойно проходят без должного внимания. Но это не просто серьезные атаки, а, возможно, даже более опасные инциденты. Дерзкие попытки повлиять на выборы в таких странах как Франция и США, используя тактики кибер-шпионажа в пользу кандидатов, чьи политические взгляды совпадают с целями авторов атак (как это было в случае с Трампом в США и Ле Пен во Франции), — вот яркие примеры скрытых войн, которые осуществляются в кибер- пространстве и способны заметно повлиять на события в мире.

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

Квартал в цифрах


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

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

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

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

Но как мы будем измерять то, что мы не можем обнаружить?

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

Этот уровень Контектсного интеллекта помог нам достичь выдающихся уровней обнаружения в тестах, имитирующих атаки, происходящие в реальном мире. В тестах AV-Comparatives в первом полугодии 2017 года Panda Security показала наилучшие результаты в Real-World Protection Test, получив самую высокую награду “Advance+” с помощью нашего Panda Free Antivirus — самого простого решения в нашей линейке решений информационной безопасности.

Далее, мы проанализировали полученные данные об атаках. Из всех машин, которые были защищены решениями Panda Security, 3,44% из них были атакованы неизвестными угрозами, что выше почти на 40% по сравнению с предыдущим кварталом. Если мы посмотрим на тип клиента, то среди домашних пользователей и малых предприятий таких машин было порядка 3,81%, в то время как среди средних и крупных предприятий — около 2,28%.

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

2,67% устройств, защищенных традиционными решениями, столкнулись с неизвстными угрозами, в то время как таких устройств, защищенных с помощью Adaptive Defense, было всего 1,21%, что показывает более высокие уровни предотвращения атак во времени.

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









Этот квартал был четко отмечен двумя главными атаками. Первая атака WannaCry случилась в мае, и она устремилась во все корпоративные сети в каждом уголке нашей планеты.
WannaCry — одна из крупнейших атак в истории. Хотя в прошлом были атаки, когда количество жертв или скорость их распространения были выше (например, Blaster или SQL Slammer), но все же ущерб, вызванный теми атаками, остался в тени от их стремительного распространения. В случае же с WannaCry, мы говорим о шифровальщике с функциональностью червя, а это означает, что каждая зараженная сеть не смогла избежать шифрования. Учтите, что мы говорим о более чем 230 000 пораженных компьютеров, при этом ущерб составил от 1 до 4 миллиардов долларов США. Получается, что средний ущерб составил от 4 300 до свыше 17 000 долларов в пересчете на каждый компьютер. Поэтому можно с уверенностью сказать, что это была самая разрушительная атака в истории.

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

Вторая серьезная атака в этом квартале — это GoldenEye/Petya, своего рода остаточные толчки после землетрясения WannaCry. Несмотря на то, что большинство его жертв были сосредоточены в определенном регионе (особенно на Украине), тем не менее, от нее пострадали компании из более чем 60 стран мира.

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

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

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

Через несколько дней после атаки правительство Украины открыто обвинило Россию в совершении нападения.

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

Шифровальщики

WannaCry и GoldenEye/Petya отвлекли на себя все внимание общественности, но была масса и других шифровальщиков. Был атакован веб-хостинг Nayana в Южной Корее, где шифровальщики зашифровали данные на 153 серверах Linux.

Злоумышленники требовали выкуп в размере 1,62 млн. долларов США. Компания вела переговоры с преступниками и сократила эту цифру до 1 млн. долларов, выплатив ее в три платежа.

Кибер-войны

Две крупные атаки 2017 года породили подозрения, что за ними могут стоять правительства определенных стран (КНДР в случае с WannaCry и Россия в случае с GoldenEye/Petya). Но это только всего лишь два случая в море более или менее таинственных войн, которые происходят в кибер-пространстве.

Основные игроки в этой игре кибер-войн — это обычные подозреваемые: США, Россия, КНДР… но удивительно, что Китай за последние несколько месяцев как-то выпал из этого списка, т.к. он не был вовлечен во все эти скандалы. Единственным объяснением этому может быть соглашение о кибер-безопасности, подписанное между США и Китаем в 2015 году, хотя вполне возможно, что они продолжают свои атаки, которые попросту еще не были выявлены.

США явно обеспокоены атаками на американские компании и учреждения. Самуил Лайлс, Исполнительный директор Кибер- дивизиона в Министерстве внутренней безопасности (DHS), свидетельствовал перед Комитетом по разведке Сената США о том, что хакерские атаки, поддерживаемые российским правительством, направлены на системы, связанные с президентскими выборами в более чем 21 штате.

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

В июне правительство США выпустило предупреждение, обвинив правительство КНДР в серии кибер-атак, проведенных с 2009 года, и предупредив о том, что в будущем могут быть совершены новые удары. Предупреждение, которое поступило от Министерства внутренней безопасности и ФБР, относилось к группе хакеров “Hidden Cobra”, которые помимо всего прочего атаковали СМИ, аэрокосмическую и финансовую отрасли, а также критическую инфраструктуру в США и других странах мира.



Название «Hidden Cobra» не так известно, но эта группа известна также под названием “Lazarus Group,” и она была связана с такими атаками, как взлом Sony в 2014 году.
Анализируя все данные и улики по деятельности Hidden Cobra/ Lazarus Group, то вы сможете прямиком выйти на саму WannaCry, делая по пути остановки на атаки финансовых учреждений, таких как атака на ЦБ Бангладеша.

Во время саммита Gartner Security & Risk Management, который состоялся в июне в Вашингтоне, бывший директор ЦРУ Джон Бреннан сказал, что предполагаемый союз между российским правительством и кибер-преступниками, которые осуществили кражу аккаунтов в Yahoo, — это всего лишь вершина айсберга, и что будущие кибер-атаки правительств будут использовать эту формулу и они станут более частыми.

В том же выступлении о заявил, что российские спецслужбы, на деле, не контролируются законом, в то время как в США все наоборот. Кто-то может найти эти высказывания странными, т.к. всем известно (благодаря WikiLeaks), что на протяжении многих лет ЦРУ взламывала роутеры домашних, корпоративных и публичных Wi-Fi сетей для осуществления тайной слежки.

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

Более поздние исследования связали взлом с группой “Fancy Bear”, предположительно поддерживаемой правительством РФ.

По информации от Financial Times, в отношении членов британского парламента были проведены попытки взлома их почтовых аккаунтов с помощью методов brute-force. В этой атаке также подозреваются хакеры, которые спонсировались иностранной державой.
Этот вихрь уловок и международных конфликтов затронул технологические компании. ФСБ России запросила у CISCO, SAP и IBM исходный код их решений безопасности, чтобы проверить возможные бэкдоры. Спустя несколько дней правительство США запретило всем федеральным ведомствам использовать решения Kaspersky из-за своей близости к российским правительством и ФСБ.

Кибер-преступления

Согласно отчету 2016 Internet Crime Report, опубликованному IC3 (Центр обработки жалоб по Интернет-преступлениям, относится к ФБР США), потери от кибер-преступлений выросли на 24% и превысили 1.3 млрд. долларов США.

Мы должны иметь в виду, что это число учитывает только тот ущерб, о котором было сообщено в IC3, которая подсчитала, что это всего лишь примерно 15% от реальных суммарных потерь. Значит, в 2016 году только в США суммарный ущерб мог составить порядка 9 млрд. долларов США.



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

Медицинские карты как минимум 7000 пациентов были скомпрометированы в результате нарушения безопасности в медицинском центре Bronx Lebanon Hospital Center в Нью-Йорке.

Были и другие инциденты безопасности, в которых злоумышленники не принимали непосредственного участия. В тех случях, в результате технической ошибки или просто по неосторожности, данные, которые должны быть серьезно защищены, фактически стали доступны любому, кто хотел получить к ним доступ. Это случилось в Автомобильной ассоциации Automobile Association (AA), которая в апреле на несколько дней оставила «в открытую» 13 ГБ данных, среди которых можно было найти свыше 100 000 адресов электронной почты, связанных с информацией по кредитным картам.

Похожий случай произошел в США на еще более высоком уровне. Маркетинговые компании, которые были наняты Республиканской партией США, открыли публичный доступ к данным 198 миллионов избирателей (всего в США чуть более 200 миллионов избирателей). Эти данные, которые были доступны пару дней, содержали подробную информацию о каждом избирателе: ФИО, дата рождения, адрес и пр.

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

InterContinental Hotels Group (IHG) сообщила о том, что пала жертвой кражи данных, повлиявшей на ее клиентов. Хотя в феврале компания сообщила о том, что от атаки пострадало порядка десяти отелей, но теперь уже стало известно о заражении POS-терминалов в более чем 1000 ее заведений. В своем заявлении компания подтвердила о проблемах с картами, которыми расплачивались в период с 29 сентября до 29 декабря 2016 года. В компании также пояснили, что не располагают информацией о несанкционированном доступе к платежной информации после 29 декабря, но и подтверждения о полном искоренении вредоносных программ не было до марта 2017 года. Среди различных пострадавших сетей отелей, которыми владеет данная группа компаний, оказались Holiday Inn, Holiday Inn Express, InterContinental, Kimpton Hotels и Crowne Plaza.
Сервис OneLogin, который предлагает пользователям единый вход на все платформы в облаке, обеспечивая более удобную и безопасную работу, по иронии судьбы также взломали. Компания сообщила в своем блоге, что она была атакована, а хакеры сумели проникнуть в их дата-центр в США, получив доступ к базам данных и оставив пользовательские информацию, приложения и пароли открытыми для хакеров.

Мобильные устройства


Начиная с 1 июня, Google стала предлагать более высокие вознаграждения тем, кто находит наиболее серьезные уязвимости безопасности в их продуктах (ранее не обнаруженные ). Первое вознаграждение возросло с 50000 до 200000 долларов, а второе — с 30000 до 150000.

Уязвимость (CVE-2017-6975) в прошивке чипов Broadcom Wi-Fi HardMAC SoC, которая проявляется при переподключении к Wi-Fi сети, вынудила Apple выпустить обновление iOS (10.3.1).

Впрочем, эта уязвимость влияет не только на iPhone и iPad, но также и на другие мобильные устройства (например, Samsung или Google Nexus), которые получили в апреле новое обновление безопасности для решения этой проблемы безопасности.

Интернет вещей

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

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

Смарт-города с гипервысокими уровнями сетевых соединений и состоящие из миллиона подключенных к Сети устройств являют собой наглядный пример внедрения технологий в нашу повседневную жизнь. Во всем мире города становятся все более «умными», и прогнозируется, что к 2020 году свыше 50 миллиардов устройств будет подключено к Интернету. Это значительно повысит риски безопасности, которые будут способны негативно отразиться на работе городской инфраструктуры, светофоров или городских систем водоснабжения. В июне WannaCry в Австралии заразил 55 камер, расположенных на светофорах и осуществляющих контроль скорости, после того как субподрядчик подключил зараженный компьютер к сети, в которой они были расположены. После этого инцидента полиция была вынуждена отменить 8000 выписанных штрафов.

7 апреля в 23:30 156 аварийных сирен одновременно зазвучали в Далласе (США, штат Техас). Официальным властям удалось их отключить только через 40 минут после перевода всей системы оповещения о чрезвычайных ситуациях в автономный режим работы (оффлайн). Следователи до сих пор не знают, кто стоял за данной атакой, которая привела к этому инциденту.

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

Заключение


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

Наибольший риск заражения существует у домашних пользователей и малых предприятий. Среди стран, которые в большей степени подвержены риску со стороны неизвестных угроз, — это Сальвадор, Бразилия, Бангладеш, Гондурас, Россия и Венесуэла.

WannaCry и Petya показали нам, что правительства различных стран мира могут и не стесняться «нажимать на кнопку», когда необходимо запустить кибер-атаки. Каждый, кто пользуется Интернетом и подключенным к нему устройством, в конечном итоге может стать жертвой глобальной кибер-войны. Поэтому мы призываем все государства мира искать пути к заключению какого-то международного договора (некий аналог Женевской конвенции), чтобы ограничить возможности государств по совершению кибер-атак.

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

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



Постоянный рост числа подключений к Интернету, начиная от мобильных устройств и заканчивая всеми типами устройств «Интернет вещей», значительно увеличивает количество атак до таких уровней, с которыми мы еще ни разу не сталкивались в прошлом.

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

Рекомендации



Традиционные решения безопасности пока еще эффективны в защите от большинства вредоносных программ, но при этом они не способны бороться с атаками, которые используют невредоносные инструменты и другие передовые техники.
Надо использовать решения безопасности, адекватные уровню угроз, с которыми мы сталкиваемся. Такие EDR-решения (Endpoint Detection & Response, обнаружение атак на конечные устройства и реагирование на них), как Adaptive Defense, — это единственные решения, которые способны предоставить все необходимые инструменты для защиты от новых угроз и сложных атак.

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

Многие правительственные учреждения, частные компании и общественные организации в разных странах мира уже сделали ставку на предлагаемую нами стратегию, благодаря чему Adaptive Defense стал самым продаваемым решением безопасности в истории Panda Security. Крупные корпорации в различных секторах экономики (финансы, ИТ, вооружение, энергетика и пр.) защищают свои системы с помощью Adaptive Defense.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334608/


Как JVM аллоцирует объекты?

Вторник, 01 Августа 2017 г. 11:23 + в цитатник

Как JVM создает новые объекты? Что именно происходит, когда вы пишете new Object()?


На конференциях периодически рассказывают, что для аллокации объектов используются TLAB'ы (thread-local allocation buffer): области памяти, выделенные эксклюзивно каждому потоку, создание объектов в которых очень быстрое за счет отсутствия синхронизации.


Но как правильно подобрать размер TLAB'а? Что делать, если нужно выделить 10% от размера TLAB'а, а свободно только 9%? Может ли объект быть аллоцирован вне TLAB'а? Когда (если) обнуляется выделенная память?
Задавшись этими вопросами и не найдя всех ответов, я решил написать статью, чтобы исправить ситуацию.


Перед прочтением полезно вспомнить как работает какой-нибудь сборщик мусора (например, прочитав этот цикл статей).



Введение


Какие шаги необходимы для создания нового объекта?


Прежде всего, необходимо найти незанятую область памяти нужного размера, потом объект нужно иницализировать: обнулить память, инициализировать какие-то внутренние структуры (информация, которая используется при вызове getClass() и при синхронизации на объекте etc.) и в конце нужно вызвать конструктор.


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


Disclaimer: некоторые части сознательно упрощены без потери общности. Говоря о сборке мусора я подразумеваю любой compacting-коллектор, а говоря об адресном пространстве — eden молодого поколения. Для других [стандартных или широко-известных] сборщиков мусора детали могут меняться, но не слишком значительно.

TLAB 101


Первая часть — выделить свободную память под наш объект.
В общем случае эффективная аллокация памяти — задача нетривиальная, полная боли, страданий и драконов. Например, заводятся связные списки для размеров, кратных степени двойки, в них осуществляется поиск и, если нужно, области памяти разрезаются и переезжают из одного списка в другой (aka buddy allocator).


К счастью, в Java-машине есть сборщик мусора, который берет сложную часть работы на себя. В процессе сборки young generation все живые объекты перемещаются в survivor space, оставляя в eden'е один большой непрерывный регион свободной памяти.


Так как память в JVM освобождает GC, то аллокатору нужно лишь знать, где эту свободную память искать, фактически управлять доступом к одному указателю на эту самую свободную память. То есть, аллокация должна быть очень простой и состоять из пони и радуг: нужно прибавить к указателю на свободный eden размер объекта, и память наша (такая техника называется bump-the-pointer).


Память при этом могут выделять несколько потоков, поэтому нужна какая-то форма синхронизации. Если сделать её самым простым способом (блокировка на регион кучи или атомарный инкремент указателя), то выделение памяти запросто может стать узким местом, поэтому разработчики JVM развили предыдущую идею с bump-the-pointer: каждому потоку выделяется большой кусок памяти, который принадлежит только ему. Аллокации внутри такого буфера происходят всё тем же инкрементом указателя (но уже локальным, без синхронизации) пока это возможно, а новая область запрашивается каждый раз, когда текущая заканчивается. Такая область и называется thread-local allocation buffer. Получается эдакий иерархический bump-the-pointer, где на первом уровне находится регион кучи, а на втором TLAB текущего потока. Некоторые на этом остановиться не могут и идут еще дальше, иерархически укладывая буферы в буферы.



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


start = currentThread.tlabTop;
end = start + sizeof(Object.class);

if (end > currentThread.tlabEnd) {
  goto slow_path;
}

currentThread.setTlabTop(end);
callConstructor(start, end);

Выглядит слишком хорошо, чтобы быть правдой, поэтому воспользуемся PrintAssembly и посмотрим, во что компилируется метод, который создает java.lang.Object:


; Hotspot machinery skipped
mov    0x60(%r15),%rax    ; start = tlabTop
lea    0x10(%rax),%rdi    ; end = start + sizeof(Object)
cmp    0x70(%r15),%rdi    ; if (end > tlabEnd)
ja     0x00000001032b22b5 ; goto slow_path
mov    %rdi,0x60(%r15)    ; tlabTop = end
; Object initialization skipped

Обладая тайным знанием о том, что в регистре %r15 всегда находится указатель на VM-ный поток (лирическое отступление: за счет такого инварианта thread-local'ы и Thread.currentThread() работают очень быстро), понимаем, что это именно тот код, который мы и ожидали увидеть. Заодно заметим, что JIT-компилятор заинлайнил аллокацию прямо в вызывающий метод.


Таким способом JVM почти бесплатно (не вспоминая про сборку мусора) создает новые объекты за десяток инструкций, перекладывая ответственность за очистку памяти и дефрагментацию на GC. Приятным бонусом идет локальность аллоцируемых подряд данных, чего могут не гарантировать классические аллокаторы. Есть целое исследование про влияние такой локальности на производительность типичных приложений. Spoiler alert: делает все немного быстрее даже несмотря на повышенную нагрузку на GC.



Влияние размера TLAB на происходящее


Каким должен быть размер TLAB'а? В первом приближении разумно предположить, что чем меньше размер буфера, тем чаще выделение памяти будет проходить через медленную ветку, а, значит, и TLAB нужно делать побольше: реже ходим в относительно медленную общую кучу за памятью и быстрее создаем новые объекты.


Но существует и другая проблема: внутренняя фрагментация.
Рассмотрим ситуацию, когда TLAB имеет размер 2 мегабайта, eden регион (из которого и выделяются TLAB'ы) занимает 500 мегабайт, а у приложения 50 потоков. Как только место под новые TLAB'ы в куче закончится, первый же поток, у которого кончится свой TLAB, спровоцирует сборку мусора. Если предположить, что TLAB'ы заполняются ± равномерно (в реальных приложениях это может быть не так), то в среднем оставшиеся TLAB'ы будут заполнены примерно наполовину. То есть, при наличии еще 0.5 * 50 * 2 == 50 мегабайт незанятой памяти (аж 10%), начинается сборка мусора. Получается не очень хорошо: существенная часть памяти еще свободна, а GC все равно вызывается.



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


А если место в TLAB'е еще есть, но новый объект слишком большой? Если выбрасывать старый буфер и выделять новый, то фрагментация лишь увеличится, а если в таких ситуациях всегда создавать объект прямо в eden, то приложение начнет работать медленнее, чем могло бы?


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


Что делать-то?


Выбирать какую-нибудь константу — занятие неблагодарное, но инженеры Sun не отчаялись и пошли другим путем: вместо указания размера указывается процент фрагментации — часть кучи, которой мы готовы пожертвовать ради быстрых аллокаций, а JVM дальше как-нибудь разберется. Отвечает за это параметр TLABWasteTargetPercent и по умолчанию имеет значение 1%.


Используя всю ту же гипотезу о равномерности выделения памяти потоками, получаем простое уравнение: tlab_size * threads_count * 1/2 = eden_size * waste_percent.
Если мы готовы пожертвовать 10% eden'а, у нас 50 потоков, а eden занимает 500 мегабайт, то в начале сборки мусора 50 мегабайт может быть свободно в полупустых TLAB'ах, то есть в нашем примере размер TLAB'а будет 2 мегабайта.


В таком подходе есть серьезное упущение: используется предположение, что все потоки аллоцируют одинаково, что почти всегда неправда. Подгонять число к скорости аллокации самых интенсивных потоков нежелательно, обижать их менее быстрых коллег (например, scheduled-воркеров) тоже не хочется. Более того, в типичном приложении существуют сотни потоков (например в тредпулах вашего любимого app-сервера), а создавать новые объекты без серьезной нагрузки будут лишь несколько, это тоже нужно как-то учесть. А если вспомнить вопрос "Что делать, если нужно выделить 10% от размера TLAB'а, а свободно только 9%?", то становится совсем неочевидно.


Деталей становится слишком много, чтоб просто их угадать или подсмотреть в каком-нибудь блоге, поэтому пришло время выяснить, как же все устроено на самом деле™: заглянем в исходники хотспота.
Я пользовался мастером jdk9, вот CMakeLists.txt, с которым CLion начинает работать, если захотите повторить путешествие.


Tumbling down the rabbit hole


Интересующий нас файл находится с первого грепа и называется threadLocalAllocBuffer.cpp, который описывает структуру буфера. Несмотря на то, что класс описывает буфер, он создается один раз для каждого потока и переиспользуется при аллокации новых TLAB'ов, заодно в нем же хранятся различные статистики использования TLAB'ов.


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


void ThreadLocalAllocBuffer::resize() {
  // ...
  size_t alloc =_allocation_fraction.average() * 
                     (Universe::heap()->tlab_capacity(myThread()) / HeapWordSize);
  size_t new_size = alloc / _target_refills;
  // ...
}

Ага! Для каждого потока отслеживается интенсивность его аллокаций и в зависимости от нее и константы _target_refills (которая заботливо подписана как "количество TLAB'ов, которые хотелось бы, чтоб поток запросил между двумя сборками") высчитывается новый размер.


_target_refills инициализируется один раз:


  // Assuming each thread's active tlab is, on average,  1/2 full at a GC
  _target_refills = 100 / (2 * TLABWasteTargetPercent);

Это ровно та гипотеза, которую мы предполагали выше, только вместо размера TLAB'а вычисляется количество запросов нового TLAB для потока. Чтобы на момент сборки у всех потоков было не более x% свободной памяти, необходимо, чтоб размер TLAB'а каждого потока был 2x% от всей памяти, что он обычно аллоцирует между сборками. Поделив 1 на 2x получается как раз желаемое количество запросов.


Долю аллокаций потока нужно когда-то обновлять. В начале каждой сборки мусора происходит обновление статистики всех потоков, которое находится в методе accumulate_statistics:


  • Проверяем, обновил ли поток свой TLAB хотя бы один раз. Незачем пересчитывать размер для потока, который ничего не делает (или, по крайней мере, не аллоцирует).
  • Проверяем, была ли использована половина eden'а, чтоб избежать влияния full GC или патологических случаев (например, явный вызов System.gc()) на расчеты.
  • В конце концов, считаем, какой процент eden'а потратил поток, и обновляем его долю аллокаций.
  • Обновляем статистику того, как поток использовал свои TLAB'ы, как и сколько аллоцировал и сколько памяти потратил впустую.

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


Результат


Полученной информации хватает, чтоб ответить на интересующий нас вопрос про размер TLAB'а:


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


Если у приложения сто потоков, 3 из которых вовсю обслуживают запросы пользователей, 2 по таймеру занимаются какой-то вспомогательной деятельностью, а все остальные простаивают, то первая группа потоков получит большие TLAB'ы, вторая совсем маленькие, а все остальные — значения по умолчанию. И что самое приятное — количество "медленных" аллокаций (запросов TLAB'а) у всех потоков будет одинаковое.



Аллокация в C1


С размерами TLAB'ов разобрались. Чтоб далеко не ходить, поковыряем исходники дальше и посмотрим, как именно выделяются TLAB'ы, когда это быстро, когда медленно, а когда очень медленно.


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


Нас интересует два метода: C1_MacroAssembler::allocate_object, в котором описано аллоцирование объекта в TLAB'е и инициализация и Runtime1::generate_code_for, который исполняется, когда быстро выделить память не удалось.


Интересно посмотреть, всегда ли объект может быть создан быстро, и цепочка "find usages" приводит нас к такому вот комментарию в instanceKlass.hpp:


  // This bit is initialized in classFileParser.cpp.
  // It is false under any of the following conditions:
  //  - the class is abstract (including any interface)
  //  - the class has a finalizer (if !RegisterFinalizersAtInit)
  //  - the class size is larger than FastAllocateSizeLimit
  //  - the class is java/lang/Class, which cannot be allocated directly
  bool can_be_fastpath_allocated() const {
    return !layout_helper_needs_slow_path(layout_helper());
  }

Из него становится понятно, что очень большие объекты (больше 128 килобайт по умолчанию) и finalizeable-классы всегда идут через медленный вызов в JVM. (Загадка — причем тут абстрактные классы?)
Возьмем это на заметку и вернемся обратно к процессу аллокации:


  1. tlab_allocate — попытка быстро аллоцировать объект, ровно тот код, что мы уже видели, когда смотрели на PrintAssembly. Если получилось, то на этом заканчиваем аллокацию и переходим к инициализации объекта.


  2. tlab_refill — попытка выделить новый TLAB. С помощью интересной проверки метод решает, выделять ли новый TLAB (выкинув старый) или аллоцировать объект прямо в eden'е, оставив старый TLAB:


    // Retain tlab and allocate object in shared space if
    // the amount free in the tlab is too large to discard.
    cmpptr(t1, Address(thread_reg, in_bytes(JavaThread::tlab_refill_waste_limit_offset())));
    jcc(Assembler::lessEqual, discard_tlab);

    tlab_refill_waste_limit как раз отвечает за размер TLAB'а, которым мы не готовы пожертвовать ради аллокации одного объекта. По умолчанию имеет значение в 1.5% от текущего размера TLAB (для этого конечно же есть параметр — TLABRefillWasteFraction, который внезапно имеет значение 64, а само значение считается как текущий размер TLAB'а, деленный на значение этого параметр). Этот лимит поднимается при каждой медленной аллокации, чтобы избежать деградации в неудачных случаях, и сбрасывается в конце каждого цикла GC. Еще одним вопросом меньше.


  3. eden_allocate — попытка выделить память (объект или TLAB) в eden'е. Это место очень похоже на аллокацию в TLAB'е: проверяем, есть ли место, и если да, то атомарно, используя инструкцию lock cmpxchg, забираем себе память, а если нет, то уходим в slow path. Выделение в eden'е не является wait-free: если два потока попробуют аллоцировать что-то в eden'е одновременно, то с некоторой вероятностью у одного из них ничего не выйдет и придется повторять все заново.

JVM upcall


Если не получилось выделить память в eden'е, то происходит вызов в JVM, который приводит нас к методу InstanceKlass::allocate_instance. Перед самим вызовом проводится много вспомогательной работы — выставляются специальные структуры для GC и создаются нужные фреймы, чтобы соответствовать calling conventions, так что операция это небыстрая.
Кода там много и одним поверхностным описанием не обойдешься, поэтому чтобы никого не утомлять, приведу лишь примерную схему работы:


  1. Сначала JVM пытается выделить память через специфичный для текущего сборщика мусора интерфейс. Там происходит та же цепочка вызовов, что и была выше: сначала попытка аллоцировать из TLAB'а, потом попытка аллоцировать TLAB из кучи и создание объекта.
  2. В случае неудачи вызывается сборка мусора. Там же где-то замешана ошибка GC overhead limit exceeded, всевозможные нотификации о GC, логи и другие проверки, не имеющие отношения к аллокации.
  3. Если не помогла сборка мусора, то происходит попытка аллокации прямо в Old Generation (здесь поведение зависит от выбранного алгоритма GC), а в случае неудачи происходит еще одна сборка и попытка создания объекта, и, если не получилось и тут, то в конце концов кидается OutOfMemoryError.
  4. Когда объект успешно создался, проверяется, не является ли он, часом, finalizable и если да, то происходит его регистрация, которая заключается в вызове метода Finalizer#register (вас ведь тоже всегда интересовало, почему этот класс есть в стандартной библиотеке, но никогда никем не используется явно?). Сам метод явно написан очень давно: создается объект Finalizer и под глобальным (sic!) локом добавляется в связный список (с помощью которого объекты потом будут финализироваться и собираться). Это вполне себе оправдывает безусловный вызов в JVM и (частично) совет "не пользуйтесь методом finalize, даже если очень хочется".

В итоге мы теперь знаем про аллокации почти всё: объекты аллоцируются быстро, TLAB'ы заполняются быстро, объекты в некоторых случаях выделяются сразу в eden'е, а в некоторых идут через неспешные вызовы в JVM.



Мониторинг медленных аллокаций


Как память выделяется мы выяснили, а вот что с этой информацией делать — пока нет.
Где-то выше я писал, что вся статистика (медленные аллокации, среднее количество refill'ов, количество аллоцирующих потоков, потери на внутреннюю фрагментацию) куда-то записывается.


Это куда-то — perf data, которая в конечном счете попадает в файл hsperfdata, и посмотреть на которую можно с помощью jcmd или программно с помощью sun.jvmstat.monitor API.


Другого способа для получения хотя бы части этой информации нет, но если вы пользуетесь Oracle JDK, то JFR умеет её показывать (пользуясь приватным API, недоступным в OpenJDK), причем сразу в срезе стек-трейсов.
Важно ли это? В большинстве случаев скорее всего нет, но вот например есть отличный доклад от Twitter JVM team, где замониторив медленные аллокации и покрутив нужные параметры, они смогли уменьшить время ответа своего сервиса на несколько процентов.



Prefetch


Пока мы ходили по коду, там периодически всплывали какие-то выравнивания и дополнительные проверки для prefetch'а, которые я коварно игнорировал.


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

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

У prefetch'а есть несколько режимов, которые контролируются флагом AllocatePrefetchStyle: можно делать prefetch после каждой аллокации, можно иногда, можно после каждой аллокации, да еще и несколько раз. Вдобавок флагом AllocatePrefetchInstr можно менять инструкцию, которой этот prefetch осуществляется: можно загружать данные только в L1-кэш (например, когда вы что-то аллоцируете и сразу выбрасываете), только в L3 или во все сразу: список вариантов зависит от архитектуры процессора, а соответствие значений флага и инструкций можно посмотреть в .ad файле для нужной архитектуры.


Почти всегда эти флаги в вашем продакшне трогать не рекомендуется, разве что вы вдруг JVM-инженер, который пытается обогнать конкурентов на SPECjbb-бенчмарке пишете на Java что-то крайне высокопроизводительное, и все ваши изменения подтверждены воспроизводимыми замерами (тогда вы, наверное, не дочитали до этого места, потому что и так всё знаете).



Иницализация


С выделением памяти все прояснилось, осталось только узнать, из чего состоит инициализация объекта до вызова конструктора. Смотреть будем все в тот же C1-компилятор, но в этот раз на ARM — там более простой код, и есть интересные моменты.


Нужный метод называется C1_MacroAssembler::initialize_object и не отличается большой сложностью:
  1. Сначала объекту устанавливается заголовок. Заголовок состоит из двух частей — mark word,
    который содержит в себе информацию о блокировках, identity hashcode (или biased locking) и сборке мусора, и klass pointer, который указывает на класс объекта — на то самое нативное представление класса, которое находится в metaspace, и из которого можно получить java.lang.Class.



    Указатель на класс обычно сжат и занимает 32 бита вместо 64. Получается, что минимально возможный размер объекта это 12 байт (плюс существует обязательное выравнивание, которое увеличивает это число до 16).


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


  3. В конце ставится StoreStore барьер (подробнее про барьеры можно прочитать в статье gvsmirnov), запрещающий (ну, почти) процессору дальнейшие записи, пока не закончатся текущие.
    // StoreStore barrier required after complete initialization
    // (headers + content zeroing), before the object may escape.
    membar(MacroAssembler::StoreStore, tmp1);

    Это необходимо для небезопасной публикации объекта: если в коде есть ошибка, и где-то объекты публикуются через гонку, то вы все еще ожидаете увидеть (и спецификация языка вам это гарантирует) в его полях либо значения по умолчанию, либо то, что проставил конструктор, но никак не случайные (out of thin air) значения, а виртуальная машина ожидает увидеть корректный заголовок. На x86 более сильная модель памяти, и эта инструкция там не нужна, поэтому мы и смотрели на ARM.



Забавный факт

Спецификация гарантирует безопасную публикацию объектов, у которых все поля final. На деле, если компилятор видит, что у объекта есть хотя бы одно final-поле, то он ставит в конец конструктора StoreStore и LoadStore барьеры, которые обеспечивают безопасность публикации (пользоваться этим фактом на практике настоятельно не рекомендуется).
На большинстве архитектур LoadStore либо отсутствует, либо совмещен со StoreStore барьером, поэтому сделать все объекты безопасно-публикуемыми (почти) ничего не стоит с точки зрения производительности. Про всю эту историю есть отдельный большой пост Алексея Шипилёва All fields are final


Проверяем на практике


Beware of bugs in the above code; I have only proved it correct, not tried it.

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

Проверим это вернувшись к PrintAssembly и полностью посмотрев на сгенерированный код для вызова new Long(1023):


  0x0000000105eb7b3e: mov    0x60(%r15),%rax
  0x0000000105eb7b42: mov    %rax,%r10
  0x0000000105eb7b45: add    $0x18,%r10             ; Аллоцируем 24 байта: 8 байт заголовок, 
                                                    ; 4 байта указатель на класс, 
                                                    ; 4 байта на выравнивание,
                                                    ; 8 байт на long поле
  0x0000000105eb7b49: cmp    0x70(%r15),%r10
  0x0000000105eb7b4d: jae    0x0000000105eb7bb5
  0x0000000105eb7b4f: mov    %r10,0x60(%r15)         
  0x0000000105eb7b53: prefetchnta 0xc0(%r10)        ; prefetch
  0x0000000105eb7b5b: movq   $0x1,(%rax)            ; Устанавливаем заголовок 
  0x0000000105eb7b62: movl   $0xf80022ab,0x8(%rax)  ; Устанавливаем указатель на класс Long
  0x0000000105eb7b69: mov    %r12d,0xc(%rax)    
  0x0000000105eb7b6d: movq   $0x3ff,0x10(%rax)      ; Кладем 1023 в поле объекта  

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


  1. Происходит попытка аллокации объекта в TLAB'е.
  2. Если места в TLAB'е нет, то либо из eden'а выделяется новый TLAB, либо объект создается прямо в eden'е, в этот раз используя атомарные инструкции.
  3. Если и в eden'е нету места, то происходит сборка мусора.
  4. Если и после этого недостаточно места, то происходит попытка аллокации в старом поколении.
  5. Если не получилось, то кидается OOM.
  6. Объекту устанавливается заголовок и вызывается конструктор.

На этом теоретическую часть можно закончить и перейти к практике: сильно ли становится быстрее, нужен ли prefetch и влияет ли размер TLAB'а на что-нибудь.


Эксперименты


Теперь мы знаем, как создаются объекты и какими флагами можно этот процесс контролировать, самое время проверить это на практике. Напишем тривиальный бенчмарк, который просто создает java.lang.Object в несколько потоков, и покрутим опции JVM.
Эксперименты запускались на Java 1.8.0_121, Debian 3.16, Intel Xeon X5675. По оси абсцисс — количество потоков, по оси ординат — количество аллокаций в микросекунду.



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


  • По умолчанию скорость аллокаций растет почти линейно в зависимости от количества потоков, и это как раз то, чего мы ожидаем от new. С ростом количества потоков становится чуть хуже, но это и неудивительно: если между аллокациями делать хоть какую-нибудь полезную работу (например, пользуясь Blackhole#consumeCPU), то нахлест аллокаций между потоками уменьшится, и скорость роста вернется к линейной.
  • Выключенный prefetch делает аллокации немного медленнее. В нашем бенчмарке мы просто перегружаем JVM аллокациями, и в реальных приложениях все может быть совсем по-другому, поэтому никаких выводов о пользе этой оптимизации делать не будем. Рецептов тут никаких нет, в конце концов всегда можно эту оптимизацию отключить и замерить для вашего конкретного приложения.
  • При выключенных аллокациях из TLAB'а все становится очень плохо: разница в два с половиной раза для одного потока — это цена вызова JIT -> JVM, а с увеличением количества потоков конкуренция за один единственный указатель лишь усиливается, и ни о какой масштабируемости речи не идет.

Ну и напоследок о пользе finalize, сравним аллокации из eden'а с аллокациями finalizable-объектов:



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


Заключение


JVM делает очень много вещей для того, чтобы создание новых объектов было как можно более быстрым и безболезненным, а TLAB'ы — основной механизм, которым она это обеспечивает. Сами же TLAB'ы возможны только благодаря тесной кооперации со сборщиком мусора: переложив ответственность за освобождение памяти на него, аллокации стали почти бесплатными.
Применимо ли это знание? Может быть, но в любом случае всегда полезно понимать, как [ваш] инструмент устроен внутри и какими идеями он пользуется.


Отдельное спасибо apangin и gvsmirnov за ревью, без которого вы бы умерли от скуки, не дойдя и до середины статьи, наполненной неясными формулировками, листингами кода и очепятками.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/332708/


Метки:  

«На полпути»: Пятерка главных новостей компании ServiceNow за 2017 год

Вторник, 01 Августа 2017 г. 11:19 + в цитатник
Динамичная сфера управления услугами предполагает постоянные изменения. Поэтому неудивительно, что провайдеры соответствующего софта попросту не имеют права на отдых. Этой статьей мы подводим итог более чем полугодовой работы компании ServiceNow, чтобы отметить векторы развития одноименной платформы и продемонстрировать, в каком направлении движется индустрия в 2017 году.

/ Flickr / Dafne Cholet / CC

Приобретение DxContinuum


Компания ServiceNow начала 2017 год со стратегического шага, который задал вектор развития на следующее полугодие. В январе стало известно, что компания приобретает стартап в сфере машинного обучения DxContinuum для углубления автоматизации элементов управления услугами.

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

Применяя алгоритмы машинного обучения DxContinuum к уникальному набору данных каждого клиента, платформа ServiceNow получила возможность определять категорию запросов и перенаправлять их ответственной команде с учетом сопутствующих рисков. Предсказуемость и автоматизация позволяет предприятиям значительно сократить расходы и ускорить время разрешения проблем, объяснял тогда Дэйв Райт (Dave Wright), руководитель по стратегическому планированию в ServiceNow.

Сам же январь завершился для ServiceNow на высокой ноте. После подведения годовых и квартальных итогов акции компании выросли более чем на 3%. В отчете фигурировали данные о росте сервисных подписок на 52% за год. Исполнительный директор ServiceNow Фрэнк Слутман (Frank Slootman) тогда заявил, что это результат успешной стратегии платформы, над которой он работал много лет. По его словам, все части головоломки «наконец встали на свои места».

Стратегическое партнерство с IBM и MapAnything


В середине февраля было объявлено об объединении ServiceNow с IBM в части внедрения интеллектуальных решений автоматизации для клиентов по всему миру. Компании согласились на многолетнее стратегическое партнерство, сосредоточенное опять же на повышении эффективности рабочих процессов своих клиентов, особенно представителей Global 2000.

В рамках нового партнерства IBM и клиенты корпорации смогут использовать платформу ServiceNow для создания бизнес-приложений, которые автоматизируют процессы в любом отделе. Решение интегрировано в технологические сервисы IBM, включая IBM Cognitive Business Solutions, инфраструктуру Bluemix и IBM Cloud Orchestrator.

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

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

На момент объявления новости об инвестициях в MapAnything Доминик Филлипс (Dominic Phillips), вице-президент по финансам и корпоративному развитию в ServiceNow, заявил: «Мы хотим стимулировать сторонних разработчиков создавать приложения на базе платформы, а не просто внедрять интеграцию с ней. Инвестиционная стратегия ServiceNow направлена на рост и расширение наших решений».

Смена руководства


Несмотря на успехи ServiceNow при Фрэнке Слутмане, в конце февраля на его место был нанят бывший исполнительный директор eBay Джон Донахо (John Donahoe). Слутман остался в совете компании.

Донахо имеет 30-летний опыт управления и заседает в правлении PayPal Holdings, Nike и Intel. Его назначение в ServiceNow связывают с выходом компании в новые области — управление операциями и трудовыми ресурсами. Донахо заявил, что его политика будет направлена на интересы потребителей.

ServiceNow делает ставку на искусственный интеллект


В мае, после публикации финансовых результатов первого квартала, представители ServiceNow объявили о новых инвестициях в сферу интеллектуальной автоматизации и искусственного интеллекта. Компанией был приобретен виртуальный агент Qlue. Благодаря этим инвестициям ServiceNow расширяет возможности своей платформы по обработке запросов.

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

Кроме того, отдельная инвестиционная организация ServiceNow Ventures объявила о вложении средств в корпорацию BuildOnMe, которая выпускает приложения с поддержкой искусственного интеллекта на базе Now Platform. В BuildOnMe разработан первый интеллектуальный самообучающийся HR-чат-бот для платформы.

Наряду с этими событиями, ServiceNow на конференции Knowledge в Орландо было анонсировано решение Intelligent Automation Engine. Оно привносит расширенные возможности машинного обучения в Now Platform для автоматизации бизнес-процессов, чтобы помочь клиентам предотвращать сбои, перенаправлять запросы, а также прогнозировать и оценивать эффективность IT-отдела. Возможности машинного обучения будут внедрены в облачные сервисы ServiceNow. Алгоритмы Intelligent Automation Engine основаны на технологиях приобретенной DxContinuum.

Запуск Jakarta


В июле ServiceNow открыла доступ к новой версии своей платформы — Jakarta. Вместе с ней клиенты получают 7 новых приложений и более 30 основных усовершенствований.

В списке самых значимых изменений находится дебют Intelligent Automation Engine с машинным обучением и предиктивными моделями, доставшимися в наследство от DxContinuum. Кроме того, Jakarta привносит в платформу такие инструменты, как единая панель мониторинга Instance Security Dashboard, база для сбора и обработки больших объемов данных MetricBase.

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

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

О компании DxContinuum

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

О компании MapAnything

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

О компании BuildOnMe

BuildOnMe — это компания-производитель продуктов, использующих преимущества искусственного интеллекта и IBM Watson для улучшения облачных приложений ServiceNow.

P.S. О чем еще мы пишем в нашем блоге? Вот несколько статей по теме:

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334516/


Метки:  

Хотите зашифровать вообще любое TCP соединение? Теперь у вас есть NoiseSocket

Вторник, 01 Августа 2017 г. 11:13 + в цитатник

Привет, %username%!
Не всё в этом мире крутится вокруг браузеров и бывают ситуации, когда TLS избыточен или вообще неприменим. Далеко не всегда есть необходимость в сертификатах, очень часто хватает обычных публичных ключей, взять тот же SSH.
А еще есть IoT, где впихивать TLS целиком это вообще задача не для слабонервных. И бэкенд, который, я почти уверен, у всех после балансера общается друг с другом по обычному HTTP. И P2P и еще и еще и еще…

Не так давно в сети появилась спецификация Noise Protocol Framework. Это по сути конструктор протоколов безопасной передачи данных, который простым языком описывает стадию хэндшейка и то, что происходит после неё. Автор — Trevor Perrin, ведущий разработчик мессенджера Signal, а сам Noise используется в WhatsApp. Так что, был отличный повод рассмотреть этот протокольный фреймворк поближе.

Он так понравился нам своей простотой и лаконичностью, что мы решили на его основе запилить аж целый новый протокол сетевого уровня, который не уступает TLS в безопасности, а в чём-то даже превосходит. Мы презентовали его на DEF CON 25, где он был очень тепло принят. Пора поговорить о нём и у нас.


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

Noise Protocol Framework.



По сути, любой из описанных Noise Framework протоколов представлет собой последовательность из передаваемых публичных ключей и производимых над ними операций Diffie-Hellman.

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



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





HandshakeState отвечает за процессинг токенов и сообщений.

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

CipherState это просто инициализированный каким то ключом симметричный AEAD шифр + nonce (счетчик), который инкрементится с каждым вызовом функции шифрования.

Протоколы в Noise описаны специальным языком, который состоит из паттернов, сообщений и токенов



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

Noise_XX(s, rs):
-> e
<- e, ee, s, es
-> s, se


Noise_XX — это паттерн. Он описывает последовательность сообщений и их содержимое.

(s, rs) обозначает, что клиент и сервер инициализируются своими статическими (s) ключевыми парами. Это те, что генерируются единожды. r означает remote.

Как мы видим тут есть три строчки со стрелками. Одна строка — одно сообщение. Стрелка означает кто кому шлёт. Если направо, то клиент серверу, иначе наоборот.

Каждая строка состоит из токенов. Это одно или двухбуквенные выражения, разделенные запятыми. Однобуквенные токены бывают лишь e и s и означают эфемерный и статический публичные ключи соответственно. Эфемерный генерируется один раз на соединение, статический многоразовый.
Вообще в Noise все протоколы начинаются с передачи эфемерного ключа. Таким образом достигается Perfect Forward Secrecy. Примерно то же самое придумали в TLS 1.3 когда отменили все не-эфемерные ciphersuites.

Двухбуквенные токены означают Diffie-Hellman между одним из ключей клиента и сервера. Они бывают, как несложно догадаться, четырех видов:
ee, es, se, ss. В зависимости от того, между какими ключами делается DH, он выполняет различные функции. ee, к примеру, нужен чтобы рендомизировать итоговый ключ для транспортной сессии, а DH с участием статических ключей отвечают за взаимную аутентификацию.

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



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



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

Помимо паттерна Noise Protocol характеризуется алгоритмами, которые использует в каждом конкретном случае. В спецификации указаны алгоритмы для DH, AEAD и хэш. Большe Noise ничего не нужно.

DH: Curve25519, Curve448,
AEAD: AES-GCM, ChachaPoly1305,
Hash: Blake2, SHA2

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

NoiseSocket



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

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

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

  1. Negotiation data
  2. Padding
  3. Processing rules


Negotiation data



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



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

Padding



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



Processing rules



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

Why?



В Virgil есть свой PKI и нам очень не хватало возможности устанавливать защищенные соединения сразу с использованием публичных ключей, а не с помощью сертификатов и потом сверху всё еще раз валидировать. А теперь мы сделали NGINX модуль и можем весь бекенд обслуживать через NoiseSocket, добавив к нему цифровые подписи статических ключей.

Вы наверное думаете, что нужно всё менять для того, чтобы перейти на NoiseSocket? А вот нет.
Если вы пишете на Go и у вас уже есть HTTP сервисы, то нужно лишь подменить метод DialTLS для клиентов и Listen для серверов, всё остальное будет думать, что работает по TLS. Это всё благодаря Go библиотеке, которую мы тоже реализовали.



Конечно, предстоит еще много работы по коду и спецификации, но, черт возьми, у нас есть альтернатива TLS!

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

SSH, VPN, всевозможные туннели могут добавить цифровые подписи статических ключей и получить полноценный защищенный линк с минимальным оверхедом без необходимости тащить к себе openssl, можно обойтись Libsodium или вообще Nacl.

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

Заключение



У меня были большие надежды на TLS 1.3, так как там и handshake roundtrips уменьшены с 8-9 до трех, как в Noise, и добавили 25519. Но

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

К тому же, не так давно один из паттернов Noise, IK (который 0-RTT) получил формальное доказательство корректности от авторов WireGuard VPN, что лишь усилило нашу уверенность в правильности выбора.

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

Ссылки



Спецификация Noise Socket: http://noiseprotocol.org/specs/noisesocket.html

Github: https://github.com/noisesocket/spec

Спецификация Noise Protocol Framework: http://noiseprotocol.org/noise.html
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334506/


Метки:  

Измерение интенсивности входящего потока событий в модели распада

Вторник, 01 Августа 2017 г. 11:07 + в цитатник
В классе поточных алгоритмов имеется подкласс, решающий задачу поиска тяжелых элементов (heavy hitters). В общем виде эта задача формулируется как «выявление во входящем потоке наиболее часто повторяющихся событий и измерение их интенсивности». В данной публикации сотрудника компании Qrator Labs Артема janatem Шворина предлагается эффективный алгоритм для решения этой задачи.

Введение


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

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

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

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

В работе представлен разработанный нами алгоритм Decay-based count, основанный на модели распада радиоактивного вещества (см. раздел «Модель распада»), с высокой эффективностью решающий задачу поиска тяжелых элементов.

Задача поиска тяжелых элементов


Классификация задач


В описываемом классе задач можно выделить следующие подклассы:
  • Threshold-$t$. Требуется выделить потоки, имеющую большую интенсивность чем заданная доля $t$ интенсивности всего входящего трафика.
  • Top-$k$. Требуется выделить заданное количество $k$ самых интенсивных потоков.
  • Выделение потоков, интенсивность которых превышает некоторое заданное абсолютное значение.

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

Целевая архитектура


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

Параметры задачи


  • Абсолютное значение интенсивности потока, которое считается «опасным». Задачей алгоритма является выявление потоков, интенсивность которых превышает заданный порог.
  • Размер ключа — идентификатора, по которому определяется тип события. В данной реализации, как и во многих других алгоритмах, требуется хранить значения ключей в таблице, поэтому размер ключа влияет на затраты памяти.
  • Способ вычисления оценки интенсивности одного потока по временам прихода однотипных событий. По сути это алгоритмическое определение того, что такое интенсивность потока. В этом случае вычисляется экспоненциальное скользящее среднее, которое имеет единственный параметр — характерное время $\tau$, в течение которого учитывается вес события после его прихода.

Точность решения


  • Оценкой качества алгоритма может быть относительная или абсолютная погрешность в оценке интенсивности потоков. Также используют $(\varepsilon,\delta)$-аппроксимацию в качестве оценки точности: если с вероятностью $1-\delta$ погрешность составляет не более $\varepsilon$, то говорят, что алгоритм имеет характеристику точности $(\varepsilon, \delta)$.
  • Если ошибка имеет качественный, а не количественный характер, как, например, включение или невключение данного потока в список самых интенсивных в задаче top-$k$, то для оценки берутся вероятности ложноположительного и ложноотрицательного срабатывания.

Накладные расходы


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

Методы оценки интенсивности потока


Для оценки интенсивности повторений событий заданного типа требуется посчитать количество событий в течение некоторого времени. Для этого нужно фиксировать время наступления события и каким-то образом сохранять его. Обычно для этой цели используют счетчик, который инкрементируется при наступлении соответствующего события. Тогда интенсивность оценивается как отношение значения счетчика к интервалу времени, в течение которого проводится измерение. Более аккуратные способы измерения актуального значения интенсивности используют различные варианты скользящего среднего. Например, экспоненциальное скользящее среднее (exponential moving average, EMA) — разновидность взвешенного скользящего среднего с экспоненциально убывающими весами.

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

Методы учета множества потоков


В задаче поиска тяжелых элементов проблемой является не только высокая интенсивность входного трафика, но и большое количество различных потоков (типов событий), за которыми приходится следить. Наивная реализация предполагает заведение отдельного счетчика на каждый поток, что приводит к значительному расходу памяти. При этом есть опасность не только в исчерпании всей доступной памяти, но в существенной замедлении скорости работы из-за промахов в кэш. Поэтому, как правило, отказываются от точного решения задачи поиска тяжелых элементов, выбрасывая часть накопленной информации о потоках. Известно множество методов ограничения объема используемой памяти и уменьшения времени обработки события, некоторые из которых приведены ниже:
  • Packet sampling
  • Space saving algorithm
  • HashParallel
  • HashPipe
  • Count-min sketch

Формализация задачи


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

Последовательность однотипных событий задается в виде множества элементарных событий $\{event_k\}_{k\in I}$, где индекс $k$ пробегает конечный или бесконечный дискретный интервал $I\subset\mathbb{Z}$.

В простейшем случае событие определяется только временем его наступления: $event_k = t_k$, причем события упорядочены во времени: $t_{k_1} < t_{k_2}$ для $k_1<k_2$. В большинстве реализаций систем учета событий время считается дискретной величиной: $t_k\in\mathbb{Z}$, однако для теоретических рассуждений бывает удобно обобщить и считать время непрерывным: $t_k\in\mathbb{R}$.

Основной вопрос, на который должны отвечать системы учета событий, — это оценка интенсивности потока. Интенсивность может быть строго формализована для равномерного потока событий. Равномерный поток $\{t_k\}$ определяется следующим образом:

$t_k = \varphi+pk,\quad k\in\mathbb{Z},$


где $p>0$, $\varphi\in[0,p)$ — параметры потока — период и фаза, соответственно. То есть события наступают через равные промежутки времени. Тогда интенсивность такого потока, по определению, выражается как $r=1/p$.

Для неравномерных потоков формальное определение интенсивности $r=r(\{t_k\})$ может отличаться в зависимости от постановки задачи. Модель распада остается работоспособной и полезной и в этом случае, однако оценка качества вычисления истинной интенсивности дана ниже только для случая равномерных потоков.

В некоторых моделях требуется дополнительно учитывать некоторую характеристику события, например, его вес $c_k$ — величина вклада данного события в измеряемую интенсивность. Тогда событие определяется не только временем его наступления, но и весом: $event_k = (t_k, c_k)$.

В типичных реализациях систем учета событий заводится счетчик $s$, который некоторым образом накапливает информацию о потоке событий, и в любой момент времени по его текущему значению можно получить оценку интенсивности потока $\hat{r}=\hat{r}(s)$, такую что $r\approx\hat{r}$. Обновление счетчика происходит по приходу очередного события $event_k$:

$s \leftarrow update(s, event_k),$


где $update()$ — некоторая функция, которая определяется реализацией. Ниже приведено несколько примеров с вариантами реализации функции $update()$:
  • Простой подсчет количества событий: $update_1(s, event_k)=s+1$;
  • Подсчет количества событий с учетом веса: $update_c(s, event_k)=s+c_k$;
  • Вычисление экспоненциального скользящее среднего (EMA) с параметром $\beta$. Здесь счетчик $s=(v,t)$ хранит две величины: собственно значение EMA и время последнего обновления.

    $update_{EMA}((v, t), event_k) = (v', t'),$

    где $v'=\beta + (1-\beta)^{t_k-t}\cdot v,\quad t'=t_k$.

В некоторых задачах требуется различать потоки событий. Пусть имеется множество различных типов событий, занумерованных индексом $i=1,\dots N$, и требуется учитывать отдельно потоки сообщений каждого типа. Тогда событие описывается как $event_k = (t_k, i_k)$ (или, с учетом веса события, $event_k = (t_k, i_k, c_k)$), где $i_k$ — тип события. Множество индексов $k$, относящихся к данному типу события $i$ обозначим $I_i = \{k\mid i_k = i\}$.

Модель распада


Модель распада описывается следующим образом:

$v(t) = v_0 e^{-\lambda t},$


где $v_0$ — «количество вещества» в нулевой момент времени, $v(t)$ — количество в момент времени $t$, $\lambda$ — параметр модели (так называемая постоянная распада). Название данной модели отражает тот факт, что она описывает физическое явление радиоактивного распада.

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

$\tau = 1/\lambda,\quad\alpha = e^{\lambda}.$


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

Будем по определению считать, что каждому типу события соответствует величина $v$, имеющая физический смысл «количества вещества» и зависящая от времени, которая при наступлении события скачком увеличивается на единицу, а в остальное время уменьшается по приведенному выше экспоненциальному закону.

На рис. 1 показано, как меняется со временем значение $v$ для равномерных потоков с разной интенсивностью и для неравномерного потока.



Рисунок 1: Значение $v$ для разных потоков

Величина $v$ не хранится явно в памяти, но может быть вычислена в любой момент. Хранится же значение $s$, такое что в момент времени $t_{now}$ величина $v$ выражается следующим образом:

$v=\alpha^{s-t_{now}}.$


Данное представление соответствует модели распада.

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

Обновление счетчика


Нетривиальной операцией является обновление значения счетчика $s$ при наступлении очередного события. В этом случае требуется заменить $s$ на новое значение $s'$, которое определяется из следующего равенства:

$\alpha^{s'-t_{now}} = \alpha^{s-t_{now}} + 1.$


Здесь слева стоит значение $v$ сразу после наступления события, а справа — значение $v$ непосредственно до события, увеличенное на вклад события (равный единице). Ниже будут предложены эффективные способы вычисления результата обновления.

В терминах функции $update()$ из определения операция обновления выражается так:

$update_1(s, event_k) = t_k + \log_\alpha(1 + \alpha^{s-t_k}).$


Здесь время исполнения операции $t_{now}$ совпадает со временем $t_k$ прихода сообщения.

Определение интенсивности


Пусть имеется равномерный поток событий интенсивности $r$, то есть события наступают с периодом $p=1/r$. Равномерный поток задается согласно определению.

Если измерение производится сразу после наступления очередного события, то есть $t_{now}=t_n$ для некоторого $n$, то накопленное в течение длительного времени значение счетчика $s$ будет выражаться следующим рядом:

$\alpha^{s-t_{now}} = \sum_{k=-\infty}^n\alpha^{t_k-t_{now}}=\sum_{k=0}^\infty \alpha^{-kp},$


откуда

$\alpha^{-\Delta s} + \alpha^{-p} = 1,$


где $\Delta s = s-t_{now}$ — относительное значение счетчика.

В более общем случае момент измерения может оказаться между событиями:
$t_{now}=t_n+\psi$, $\psi\in[0,p)$. В этом случае значение счетчика будет отличаться в меньшую сторону на величину $\psi$:

$\alpha^{-(\Delta s+\psi)} + \alpha^{-p} = 1.$


Задача измерения интенсивности заключается в том, чтобы по значению счетчика оценить интенсивность. В предположении, что поток равномерный, можно получить оценки истинной интенсивности равномерного потока сверху и снизу, подставляя в предыдущее уравнение крайние значения $\psi=0$ и $\psi=p$:

$r^-\le r < r^+,$


где

$\begin{align}r^+&=r^+(\Delta s)=\frac1{\log_\alpha(1+\alpha^{-\Delta s})}\\r^-&=r^-(\Delta s)= \left\{\begin{array}{ll}\frac1{-\log_\alpha(1-\alpha^{-\Delta s})}&\mbox{при }\Delta s>0\\ 0&\mbox{иначе}\end{array}\right.\end{align}$


Обе оценки $r^+$ и $r^-$ монотонно зависят от значения счетчика $s$ (см. рис. 2), поэтому, например, сравнение текущего значения счетчика с пороговым значением не требует дополнительных вычислений.



Рисунок 2: График функций $r^-$ и $r^+$ для $\tau=15$

В модели распада интенсивность произвольных (не обязательно равномерных) потоков по определению задается приведёнными выше оценками $r^+$ и $r^-$. Причем, если измерение происходит сразу после обработки события, то интенсивность считается в точности равной нижней оценке.

Границы применимости модели распада


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

Во-первых, если время наступления событий измеряется как дискретная величина, то период потока не может быть меньшим единицы. То есть интенсивность потока не должна превышать $r_{max}=1$. Отсюда следует ограничение на относительное значение счетчика $\Delta s = s-t_{now}$ — оно не должно превышать значения $\sigma_{max}$, которое определяется из формулы:

$r^-(\sigma_{max}) = r_{max}.$


Величина $\sigma_{max}$ оценивается следующим образом:

$\sigma_{max}=\tau\ln\tau+\omega(\tau),$


где $0\le\omega(\tau)\le1/2$.

Во-вторых, оценка интенсивности слабых (малоинтенсивных) потоков затруднена: при малых относительных значениях счетчика $\Delta s$ точность верхней и нижней оценки $r^+$ и $r^-$ ухудшается, а при отрицательных значениях $\Delta s$ нижняя оценка интенсивности вырождается в нуль.

Также есть ограничение на время работы реализаций модели распада, связанное с переполнением счетчиков. Поскольку значение счетчика не может убежать от $t_{now}$ более, чем на $\sigma_{max}$, время работы системы фактически определяется разрядностью счетчика и длительностью одного такта. Если для хранения счетчика используется 64-разрядный регистр, то он не переполнится в течение 100 лет. Но в реализациях с малоразрядными регистрами должен быть предусмотрен механизм сброса счетчиков.

Алгоритмы учета множества потоков


Особенности модели распада


Особенностью использования EMA в качестве значения счетчика является то, что при прекращении потока событий накопленное значение быстро (экспоненциально по времени) деградирует и становится неотличимым от нуля. В модели распада этот факт используется для автоматического сброса счетчика: хотя значение счетчика $s$ будет всё время возрастать с приходом событий, через некоторое время после прекращения потока событий величину $v=\alpha^{s-t_{now}}$ можно будет считать равной нулю, не меняя явно значения счетчика. Время $T_{vanish}$, за которое любое накопленное ранее значение распадается до условного нуля, зависит от параметра $\tau$ и требуемой точности. Оно выражается как $T_{vanish}=T_{min}+\sigma_{max}$, где $T_{min}$ — время распада значения, накопленного в результате единичного события. В разделе «Табличная реализация» дано точное выражение $T_{min}$, но здесь достаточно иметь в виду следующий факт: $T_{vanish} = O(\tau\ln\tau)$ при фиксированной точности.

Отсюда следует оценка сверху на размер хранилища счетчиков при учете множества потоков для случая, когда информация вообще не будет теряться — достаточно иметь $T_{vanish}\cdot r_{max}$ ячеек. Есть множество применений задачи поиска тяжелых элементов, где не требуется больших значений $\tau$, и всё хранилище помещается оперативную память или даже в кэш процессора L3 или L2. В этом случае обеспечивается малое время доступа к хранилищу, так что задача становится выполнимой при высоких значениях интенсивности входного потока.

Для реализации хранилища годится хэш-таблица, где ключом является тип события. При этом пустыми считаются ячейки, у которых значение счетчика $s$ удовлетворяет условию $s-t_{now} \le T_{min}$.

Численная реализация Decay-based count


Операция обновления значения


Введем следующее обозначение:

$\rho_\tau(x) = \tau\ln(1+e^{x/\tau}) = \log_{\alpha}(1+\alpha^x).$


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

$s' = t_{now} + \rho_\tau(s-t_{now}).$


Таким образом, задача сводится к эффективному вычислению приближения функции $\rho_\tau$. Время удобно представлять целым числом, например, измерять его в тактах процессора, так что требуется построить целочисленное отображение ${R:\mathbb{Z}\to\mathbb{Z}}$, такое, что:

$R_\tau(T) \approx \rho_\tau(T)\quad\mbox{для }T\in\mathbb{Z}.$


Для данной задачи точность важна не относительная, а абсолютная, поскольку со значениями времени используются в основном аддитивные операции. Очевидно, что из-за целочисленности представления времени погрешность меньше 0.5 такта недостижима.
Кроме того, операция измерения текущего времени, как правило, дает некоторую погрешность. Например, если есть способ измерения времени с точностью в 10 тактов, то достаточно потребовать, чтобы $R_\tau$ давало приближение примерно такой же точности: $\left|R_\tau(T) - \rho_\tau(T)\right| \le 10.$

Можно предложить несколько разных способов вычисления $R_\tau$ разной сложности и степени эффективности.

Вычисление $R_\tau$: FPU


Проще всего использовать арифметику с плавающей точкой и непосредственно вычислять $\rho_\tau$ по определению. Время выполнения этой операции составляет около 100 тактов процессора, что довольно много по сравнению предлагаемым ниже методом.

Вычисление $R_\tau$: табличная реализация


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

Во-первых, используя тождество

$\rho_\tau(-x) = -x + \rho_\tau(x),$


достаточно строить $R_\tau(T)$ только для $T\le0$.

Во-вторых, поскольку

$\rho_\tau(x)\to 0 \mbox{ при } x\to-\infty,$


существует $T_{min}>0$, такое что $R_\tau(T)=0$ при $T\le-T_{min}$. Где $T_{min}$ можно найти из следующих соображений:

$T_{min}=\lceil t_{min}\rceil,\quad \rho_\tau(t_{min})=1/2,$

откуда
$T_{min}=\lceil-\log_\alpha(\alpha^{1/2}-1)\rceil=\lceil-\tau\ln(e^{1/{2\tau}}-1)\rceil.$

Таким образом, достаточно определить $R_\tau(T)$ для ${T_{min}<T\le0}$.

График функции $\rho_\tau(x)$ представлен на рис. 3. Особенность этой функции такова, что с изменением параметра $\tau$ будет происходить одинаковое масштабирование по обеим осям.



Рисунок 3: График функции $\rho_\tau(x)$

Очевидная реализация $R_\tau$ состоит в построении таблицы из $T_{min}$ ячеек, где будут храниться предвычисленные значения. Поскольку все значения функции на данном интервале находятся в промежутке между 0 и $\rho_\tau(0)=\tau\ln2$, итоговый размер таблицы составляет $T_{min}\log_2(\tau\ln2)$ бит.

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

$\begin{align} t_{now}& \leftarrow getTime()\\ \delta & \leftarrow \min\{|s-t_{now}|, T_{min}\}\\ \delta' &\leftarrow R(-\delta)\\ s' &\leftarrow \delta' + \max\{s, t_{now}\} \end{align}$



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

Результаты измерения эффективности


Сравнивались между собой следующие три реализации экспоненциального скользящего среднего:
1. наивная реализация EMA;
2. модель распада через FPU (то есть с вызовом функций exp() и log() математической библиотеки);
3. модель распада табличным методом.

Исходный код теста на си: pastebin.com/wiiEe6MP.

Время выполнения одного вызова функции update() при $\tau=100000$ (в этом случае таблица для вычисления $R_\tau$ помещается в кэш L1) в реализациях 1, 2 и 3 составляет 125, 100, 11 тактов процессора, соответственно.

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

Благодарность


Данная публикация подготовлена нами в пробном режиме в рамках проекта по освещению механизмов работы сети фильтрации Qrator. Спасибо Антону Орлову, Артему Гавриченкову и Евгению Наградову за наводящие вопросы и предложения.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334354/


Метки:  

[Перевод] Эволюция паролей: руководство по аутентификации в современную эпоху

Вторник, 01 Августа 2017 г. 11:06 + в цитатник
Начиналось все просто: у вас есть два набора символов (имя пользователя и пароль) и тот, кто знает оба, может войти в систему. Ничего сложного.

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



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

Пару месяцев назад я писал о многоразовом использовании паролей, вбросе регистрационных данных и о том, что в базу сервиса Have I been pwned был добавлен очередной миллиард записей. Дела обстоят так: миллиарды реквизитов доступа лежат и ждут, когда какой-нибудь злодей начнет использовать их для взлома любого сайта, какой ему понравится. Это ставит перед нами очень интересный вопрос: как защитить себя в таких обстоятельствах? В смысле, вы тут пытаетесь удержать на плаву онлайн-систему, а у какого-то типа со стороны есть рабочие данные от учетных записей некоторых пользователей — как ему запретишь войти в систему? Простым сопоставлением символов родом из шестидесятых тут уже не обойдешься.

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

Слушайте, что говорит правительство (и толковые IT-компании)


Начну с отсылок к другим источникам, потому что в Сети за последнее время появилось много хороших материалов на эту тему, и я буду к ним обращаться. Хочу выложить все это сразу, чтобы прояснить один момент: большая часть рекомендаций, которые будут приводиться дальше, — это не просто мой личный взгляд на проблему, а прямое цитирование таких организаций, как Национальный институт стандартов и технологий (National Institute of Standards and Technologies, или NIST). На самом деле, их труд Digital Identity Guidelines, который вышел буквально в прошлом месяце, пожалуй, можно считать тем толчком, который побудил меня взяться за этот пост — столько там было всего интересного.

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

Также я буду обращаться к исследованию Microsoft's Password Guidance от команды Identity Protection. На первой его странице говорится, что Microsoft находится в «уникальном положении для понимания роли паролей в захвате аккаунтов», благодаря тому, что ежедневно переживает по 10 миллионов атак, так что опыта у этих ребят точно хватает! Это очень практичное руководство, составленное людьми, которые явно досконально продумали, как обезопасить свои онлайн-аккаунты.

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

Процесс аутентификации не должен сводиться к переключению между двумя состояниями


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

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

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

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

Чем длиннее, тем лучше (как правило)


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



Первое предложение во всплывающем окошке («Длина пароля должна составлять 8-10 символов») представляет, наверное, самый распространенный из ошибочных анти-паттернов по созданию паролей: маленькая заданная длина пароля. Это убивает менеджеры паролей (позже мы остановимся на них подробнее), это убивает пароли-фразы и, как следствие, это убивает юзабилити. Кстати о юзабилити: приведенный скриншот я взял из своей прошлогодней статьи о том, как мы допускаем ошибки в самых базовых вещах, и, помимо неэффективной политики Etihad (принятой, между прочим, «ради безопасности»), я описываю в нем случай, когда Paypal фактически перекрыл мне доступ к аккаунту благодаря политике подобного же рода.

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

Какое тогда ограничение по длине нужно ставить? «Никакого» — неправильный ответ, потому что за определенным рубежом у вас появятся уже другие проблемы. Например, если размер превысит четыре мегабайта, вы не уложитесь в размеры запроса по умолчанию в ASP.NET. Вот что говорит по этому поводу NIST:

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

Ни один нормальный человек, регистрируясь на сайте с ограничением по длине пароля в 64 символа, не скажет: «Что за лажа у них безопасностью, мне даже не дали сделать пароль длиннее 64 символов». Но на всякий случай можете задать ограничение в 100 символов. Или подхватить идею NIST, и установить максимум в 256 символов — какая разница, все равно после хеширования все выровняется.

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

Усечение секретного кода не допускается.

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

Все символы особенные (но включать их необязательно)


Я хочу рассмотреть два аспекта использования специальных символов. Начну с этого:



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

NIST выражается по этому поводу вполне однозначно — не делайте так:

В секретных кодах должно допускаться использование любых печатных символов ASCII [RFC 20], включая пробел. Символы Юникода [ISO/ISC 10646] также должны приниматься.

Стоит особо отметить их оговорку насчет символов Юникода — были прецеденты, когда разработчики накладывали запрет на вполне приемлемые с точки зрения языка символы просто потому, что они якобы выходили за рамки «нормальных». С другой стороны, конечно, встречаются и пароли вроде такого:



Что ж, если кому-то так уж хочется поставить паролем первый куплет «Отпусти и забудь» из «Холодного сердца», записанный смайликами — пожалуйста!

О другом аспекте, которого я хотел коснуться, также упоминает NIST:

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

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

В документе, о котором я упоминал выше, Microsoft вторит рекомендации от NIST:

Откажитесь от требований к составу пароля.

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

По большей части люди прибегают к одним и тем же паттернам (первая буква — заглавная, специальный символ или две цифры в конце). Кибер-мошенникам это известно, поэтому, осуществляя перебор по словарю, они включают все замены, выполненные по стандартным схемам («$» вместо «s», «@» вместо «a», «1» вместо «l» и так далее).

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

Подсказки? Ни в коем случае!


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

Компания Adobe хранила подсказки к паролям в своей базе данных, которая попала в открытый доступ в 2013 году. Чтобы продемонстрировать, насколько все плохо, я просмотрел эту базу и решил поделиться отдельными образчиками:

  • мое имя
  • adobe
  • как обычно
  • пароль
  • e-mail

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

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

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

Полюбите менеджеры паролей


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

  1. Мы знаем, что пароли должны быть «надежными», то есть их должно быть сложно угадать или вычислить методом полного перебора. Иными словами, чем больше символов и чем более случайным образом они подобраны, тем лучше.
  2. Мы знаем, что использовать один пароль несколько раз нельзя: если один сервис взломают, то аккаунты пользователя на других ресурсах окажутся под угрозой. Проблема с вбросами регистрационных данных, о которой я упоминал выше, заключается как раз в этом.
  3. Люди не могут придумывать и заучивать надежные, уникальные пароли для каждого сервиса, полагаясь только на свою память.


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

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



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

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

NCSC прямым текстом говорит об этой проблеме: в инфографике к руководству Password Guidance: Simplifying Your Approach у них есть такой фрагмент:



Создавайте для пользователей возможность безопасного хранения паролей

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

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

Задумайтесь о том, в какой обстановке работаете в данный момент. Есть ли какие-то критерии надежности паролей? Проводятся ли ежегодные тренинги или, может, на стенах висят плакаты, призывающие создавать уникальные, сложные пароли? Конечно, какой-то контроль и инструктаж, связанный с паролями, точно присутствует, но обеспечивают ли вам «санкционированный механизм», который помогал бы достичь поставленных целей? Скорее всего нет, и я говорю об этом с такой уверенностью потому, что часто задаю этот вопрос, когда провожу тренинги в различных организациях. Удивительно, как часто приходится видеть такую несостыковку.

Пусть вставляют пароли


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



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

NCSC меня и здесь поддерживают:



Они даже ввели специальный термин для обозначения этого анти-паттерна — SPP (Stop Pasting Passwords, «Хватит вставлять пароли») и развенчивают популярные мифы о рисках, связанных со вставкой паролей. Они упоминают и мою статью, где говорилось об эффекте кобры — приятно получать такую протекцию от британского правительства!

NIST присоединяется к позиции NCSC, о чем свидетельствует их высказывание:

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

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

Не требуйте регулярно менять пароль


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

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



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

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


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

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

Они выносят эту мысль и в инфографику:



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

Microsoft высказывается в том же духе:

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

Их слова, конечно не следует понимать как «не вынуждайте пользователей менять пароли, и больше от вас ничего не требуется». Это скорее указание на то, что необходим более широкий, более продвинутый подход к управлению паролями. Например, NCSC также дает и такие рекомендации:

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


И тут мы плавно переходим к следующему разделу.

Сообщайте пользователям о нетипичном поведении


Это важный аспект «продвинутого» подхода к процессу аутентификации и вполне возможно, что вы уже наблюдали его в действии. Недавно я зашел на свой аккаунт в Yammer cо своего нового Lenovo Yoga 910 — до того момента я не пользовался их сервисом на этой машине. Чуть позже мне пришло такое оповещение:



Позже я зашел на Dropbox через бразуер на той же машине и сразу же получил сообщение:



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



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



Как и в случае с Dropbox и Yammer, здесь также предлагается опция выйти из системы на любом девайсе из списка. А это значит, что законный владелец может хоть в какой-то степени вернуть себе контроль над аккаунтом в ситуации, когда кто-то получил к нему несанкционированный доступ. Многие из современных сервисов предлагают такую возможность; хороший пример тому — Github, который приводит еще более подробную информацию, включая IP адрес и конкретные события, значимые с точки зрения безопасности, например, запрос на двухфакторную идентификацию или создание открытого ключа.

Вот еще один возможный сценарий:



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

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

Блокируйте скомпрометированные пароли


Вернемся на минутку к вбросам регистрационных данных и всему с ними связанному. Если пароли где-то засветили, их следует считать «оскверненными» — в том смысле, что использовать их больше нельзя. Никогда. Теперь, когда они побывали в открытом доступе, огромное количество людей располагает информацией об этих реквизитах, что существенно повышает риски для тех, кто их использует. Только представьте: доступ к миллиарду пар e-mail адрес-пароль, взятых из утечек реальных данных, о которых я распространялся в посте о вбросах регистрационных данных:



NIST говорит об этой проблеме следующее:

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

Говоря простым языком: когда кто-нибудь создает аккаунт или меняет пароль, вы должны следить, чтобы этот пароль не входил в число тех, которые фигурировали в утечках данных. Неважно, что это, возможно, не тот же самый пользователь, который устанавливал пароль на взломанный аккаунт — одно то, что пароль оказался в публичном доступе, повышает вероятность его использования в хакерских атаках. В цитате также упоминается, что пароль не должен представлять собой слово в его словарной форме или «слово, порожденное контекстом». Описывая случай, когда база данных CloudPets оказалась в общем доступе, я обращал внимание читателей на то, что хеши-строки bcrypt крайне легко взломать при помощи небольшого словаря паролей, в число которых входят и такие слова как «cloudpets». Не давайте пользователям возможность ставить в качестве пароля название ресурса, на котором они регистрируются — а то ведь они именно так и поступят!

Следующий уровень


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



В январе прошлого года только 1% пользователей Dropbox использовали двухфакторную идентификацию (еще до утечки данных)

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



Также я не разбирал подробно способы хранения паролей, решив уделить основное внимание тем составляющим политики аутентификации, которые можно увидеть невооруженным глазом. Чтобы не оставлять эту тему совсем не затронутой, предлагаю вам ознакомиться с Password Storage Cheat Sheet от OWASP и почерпнуть там все необходимые напутствия. Вдобавок советую внимательно прочитать статью о том, как Dropbox хранит ваши пароли — это очень интересный текст, где современный подход к хешированию сочетается с шифрованием.

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

Обобщая сказанное


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

И вот еще что: теоретически все эти руководства – вещь хорошая, однако реализовать их на практике может быть несколько сложнее. Я собираюсь развить эту тему в следующем посте, который выйдет на этой неделе. Там я собираюсь рассказать кое-что интересное и новое, что поможет владельцам сайтов обеспечить безопасность своим подписчикам. Я работал над ним в течение нескольких недель, потратил немало сил и собираюсь поделиться с вами результатом бесплатно, так что заглядывайте в ближайшее время на мой сайт. Также я намерен обновить этот пост, добавив ссылку на ресурс, как только выложу его в открытый доступ. Следите за обновлениями!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334600/


Метки:  

[recovery mode] Кейс iOS приложения BINO CX: Uber для управления потребительским опытом

Вторник, 01 Августа 2017 г. 10:47 + в цитатник


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

Почувствовав, что технологические компании, подобно Uber и AirBnB год за годом убивают агентский бизнес, мы решились на перемены и направились в Нью-Йорк создавать сервис, который сможет удовлетворить потребности бизнеса в контроле большой сети и желании людей подзаработать в свободное время. Так и появился сервис BINO CX. В этой статьей я расскажу каким образом создавался дизайн клиентского iOS приложения.


Цель бизнеса


Starbucks активно использует тайных покупателей, чтобы поддерживать одинаковое качество напитков и обслуживания по всему миру. Лично я подтверждаю, что капучино в Нью-Йорке и Москве одинаково невкусный. Мы так же знаем примеры отелей Four Seasons, Ritz, которые, несмотря на адаптацию к культуре каждый страны, сохраняют одинаковое качество обслуживания в любой точки мира. Исходя из этих примеров и опыта уже упомянутого агентства, был сделан вывод, что основная цель владельцев бизнеса — сохранение одинакового уровня обслуживания во всех точках сети.

Как оценить качество работы сети?


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

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

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

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



Прием проверок


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

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


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



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



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



Настройки


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

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

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



Профиль


В профиле пользователя содержаться данные о компании, платежная информация и стандартные настройки.



Инструменты


В работе над приложением использовались:
  • Коллеги и эксперты для продумывания бизнес-логики.
  • Sketch для проектирования и дизайна.
  • InVision для тестирования, демонстрации и передачи макетов разработчикам.
  • Slack для общения и отвлечения внимания.


Спасибо за внимание!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334602/


Метки:  

Индексы в PostgreSQL — 5

Вторник, 01 Августа 2017 г. 10:28 + в цитатник

В прошлые разы мы рассмотрели механизм индексирования PostgreSQL, интерфейс методов доступа, и два метода: хеш-индекс и B-дерево. В этой части займемся индексами GiST.

GiST


GiST — сокращение от «generalized search tree». Это сбалансированное дерево поиска, точно так же, как и рассмотренный ранее b-tree.

В чем же разница? Индекс b-tree жестко привязан к семантике сравнения: поддержка операторов «больше», «меньше», «равно» — это все, на что он способен (зато способен очень хорошо!). Но в современных базах хранятся и такие типы данных, для которых эти операторы просто не имеют смысла: геоданные, текстовые документы, картинки…

Тут на помощь и приходит индексный метод GiST. Он позволяет задать принцип распределения данных произвольного типа по сбалансированному дереву, и метод использования этого представления для доступа по некоторому оператору. Например, в GiST-индекс можно «уложить» R-дерево для пространственных данных с поддержкой операторов взаимного расположения (находится слева, справа; содержит и т. п.), или RD-дерево для множеств с поддержкой операторов пересечения или вхождения.

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


Устройство


GiST — сбалансированное по высоте дерево, состоящее из узлов-страниц. Узлы состоят из индексных записей.

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

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

Поиск в дереве GiST использует специальную функцию согласованности (consistent) — одну из функций, определяемых интерфейсом, и реализуемую по-своему для каждого поддерживаемого семейства операторов.

Функция согласованности вызывается для индексной записи и определяет, «согласуется» ли предикат данной записи с поисковым условием (вида «индексированное-поле оператор выражение»). Для внутренней записи она фактически определяет, надо ли спускаться в соответствующее поддерево, а для листовой записи — удовлетворяют ли индексированные данные условию.

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

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

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

Мы не будем рассматривать алгоритмы вставки и удаления значений в GiST — для этого используется еще несколько интерфейсных функций. Но есть один важный момент. При вставке в индекс нового значения для него выбирается такая родительская запись, чтобы ее предикат пришлось расширить как можно меньше (в идеале, не расширить вовсе). Но при удалении значения предикат родительской записи уже не сужается. Это происходит только в двух случаях: при разделении страницы на две (когда на странице не хватает места для вставки новой индексной записи) и при полном перестроении индекса (командами reindex или vacuum full). Поэтому эффективность GiST-индекса при часто меняющихся данных может со временем деградировать.

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


R-дерево для точек


Продемонстрируем сказанное выше на примере индекса для точек на плоскости (похожие индексы можно построить и для других геометрических объектов). Обычное B-дерево не подходит для такого типа данных, так как для точек не определены операторы сравнения.

Идея R-дерева состоит в том, что плоскость разбивается на прямоугольники, которые в сумме покрывают все индексируемые точки. Индексная запись хранит прямоугольник, а предикат можно сформулировать так: «искомая точка лежит внутри данного прямоугольника».

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

Листовые узлы, по идее, должны содержать индексируемые точки, однако тип данных во всех индексных записях должен совпадать; поэтому хранятся все те же прямоугольники, но «схлопнутые» до точек.

Чтобы представить себе такую структуру наглядно, ниже приведены рисунки трех уровней R-дерева; точки представляют координаты аэропортов (аналогично таблице airports демо-базы, но здесь взято больше данных с сайта openflights.org).


Первый уровень; видны два больших пересекающихся прямоугольника.


Второй уровень; большие прямоугольники распадаются на области поменьше.


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

Рассмотрим теперь подробнее совсем простой «одноуровневый» пример:



postgres=# create table points(p point);
CREATE TABLE
postgres=# insert into points(p) values
  (point '(1,1)'), (point '(3,2)'), (point '(6,3)'),
  (point '(5,5)'), (point '(7,8)'), (point '(8,6)');
INSERT 0 6
postgres=# create index on points using gist(p);
CREATE INDEX

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



Созданный индекс может использоваться для ускорения, например, такого запроса: «найти все точки, входящие в заданный прямоугольник». Это условие формулируется так: p <@ box '(2,1),(6,3)' (оператор <@ из семейства points_ops означает «содержится в»):

postgres=# set enable_seqscan = off;
SET
postgres=# explain(costs off) select * from points where p <@ box '(2,1),(7,4)';
                  QUERY PLAN                  
----------------------------------------------
 Index Only Scan using points_p_idx on points
   Index Cond: (p <@ '(7,4),(2,1)'::box)
(2 rows)

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



Поиск начинается с корневого узла. Прямоугольник (2,1)-(7,4) пересекается с (1,1)-(6,3), но не пересекается с (5,5)-(8,8), поэтому во второе поддерево спускаться не нужно.



Придя в листовой узел, перебираем три содержащиеся там точки и две из них возвращаем в качестве результата: (3,2) и (6,3).

postgres=# select * from points where p <@ box '(2,1),(7,4)';
   p  
-------
 (3,2)
 (6,3)
(2 rows)


Внутри


Обычный pageinspect, увы, не позволяет заглянуть внутрь GiST-индекса. Но есть другой способ — расширение gevel. Оно не входит в стандартную поставку; смотрите инструкцию по установке.

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

postgres=# select * from gist_stat('airports_coordinates_idx');
                gist_stat                
------------------------------------------
 Number of levels:          4            +
 Number of pages:           690          +
 Number of leaf pages:      625          +
 Number of tuples:          7873         +
 Number of invalid tuples:  0            +
 Number of leaf tuples:     7184         +
 Total size of tuples:      354692 bytes +
 Total size of leaf tuples: 323596 bytes +
 Total size of index:       5652480 bytes+
 
(1 row)

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

На самом деле индекс для восьми тысяч точек будет занимать гораздо меньше места: здесь для наглядности он был создан с заполнением 10%.

Во-вторых, можно вывести дерево индекса:

postgres=# select * from gist_tree('airports_coordinates_idx');
                                       gist_tree                                              
-----------------------------------------------------------------------------------------
 0(l:0) blk: 0 numTuple: 5 free: 7928b(2.84%) rightlink:4294967295 (InvalidBlockNumber) +
     1(l:1) blk: 335 numTuple: 15 free: 7488b(8.24%) rightlink:220 (OK)                 +
         1(l:2) blk: 128 numTuple: 9 free: 7752b(5.00%) rightlink:49 (OK)               +
             1(l:3) blk: 57 numTuple: 12 free: 7620b(6.62%) rightlink:35 (OK)           +
             2(l:3) blk: 62 numTuple: 9 free: 7752b(5.00%) rightlink:57 (OK)            +
             3(l:3) blk: 72 numTuple: 7 free: 7840b(3.92%) rightlink:23 (OK)            +
             4(l:3) blk: 115 numTuple: 17 free: 7400b(9.31%) rightlink:33 (OK)          +
 ...

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

postgres=# select level, a from gist_print('airports_coordinates_idx')
  as t(level int, valid bool, a box) where level = 1;
 level |                                   a                                  
-------+-----------------------------------------------------------------------
     1 | (47.663586,80.803207),(-39.2938003540039,-90)
     1 | (179.951004028,15.6700000762939),(15.2428998947144,-77.9634017944336)
     1 | (177.740997314453,73.5178070068359),(15.0664,10.57970047)
     1 | (-77.3191986083984,79.9946975708),(-179.876998901,-43.810001373291)
     1 | (-39.864200592041,82.5177993774),(-81.254096984863,-64.2382965088)
(5 rows)

Собственно, рисунки выше были подготовлены как раз на основе этих данных.

Поисковые и упорядочивающие операторы


Операторы, рассмотренные до сих пор (такие, как <@ в предикате p <@ box '(2,1),(7,4)'), можно назвать поисковыми, так как они задают условия поиска в запросе.

Есть и другой тип операторов — упорядочивающие. Они используются для указания порядка выдаваемых результатов во фразе order by там, где обычно применяется простое указание полей. Вот пример такого запроса:

postgres=# select * from points order by p <-> point '(4,7)' limit 2;
   p  
-------
 (5,5)
 (7,8)
(2 rows)


Здесь p <-> point '(4,7)' — выражение, использующее упорядочивающий оператор <->, который обозначает расстояние от одного аргумента до другого. Смысл запроса: выдать две точки, ближайшие к точке (4,7). Такой поиск известен как k-NN — k-nearest neighbor search.

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

postgres=# select amop.amopopr::regoperator, amop.amoppurpose, amop.amopstrategy
from pg_opclass opc, pg_opfamily opf, pg_am am, pg_amop amop
where opc.opcname = 'point_ops'
and opf.oid = opc.opcfamily
and am.oid = opf.opfmethod
and amop.amopfamily = opc.opcfamily
and am.amname = 'gist'
and amop.amoplefttype = opc.opcintype;
      amopopr      | amoppurpose | amopstrategy
-------------------+-------------+--------------
 <<(point,point)   | s           |            1  строго слева
 >>(point,point)   | s           |            5  строго справа
 ~=(point,point)   | s           |            6  совпадает
 <^(point,point)   | s           |           10  строго снизу
 >^(point,point)   | s           |           11  строго сверху
 <->(point,point)  | o           |           15  расстояние
 <@(point,box)     | s           |           28  содержится в прямоугольнике
 <@(point,polygon) | s           |           48  содержится в полигоне
 <@(point,circle)  | s           |           68  содержится в окружности
(9 rows)

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

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

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

В случае точек на плоскости расстояние понимается в самом обычном смысле: значение выражения (x1,y1) <-> (x2,y2) равно корню из суммы квадратов разностей абсцисс и ординат. За расстояние от точки до ограничивающего прямоугольника принимается минимальное расстояние от точки до этого прямоугольника, или ноль, если точка находится внутри него. Это значение легко вычислить, не обходя дочерние точки, и оно гарантированно не больше расстояния до любой из дочерних точек.

Рассмотрим алгоритм поиска для приведенного выше запроса.



Поиск начинается с корневого узла. В нем имеется два ограничивающих прямоугольника. Расстояние до (1,1)-(6,3) составляет 4.0, а до (5,5)-(8,8) — 1.0.

Обход дочерних узлов происходит в порядке убывания расстояния. Таким образом, сначала спускаемся в дочерний узел для (5,5)-(8,8) и находим расстояния до точек (для наглядности покажем цифры на рисунке):



Этой информации уже достаточно, чтобы вернуть первые две точки (5,5) и (7,8). Поскольку нам известно, что расстояние до точек, находящихся внутри прямоугольника (1,1)-(6,3), составляет 4.0 или больше, то нет необходимости спускаться в первый дочерний узел.

Что, если бы нам потребовалось найти первые три точки?

postgres=# select * from points order by p <-> point '(4,7)' limit 3;
   p  
-------
 (5,5)
 (7,8)
 (8,6)
(3 rows)


Хотя все эти точки содержатся во втором дочернем узле, мы не можем вернуть (8,6), не заглянув в первый дочерний узел, поскольку там могут оказаться более близкие точки (так как 4.0 < 4.1).



Этот пример поясняет требования к функции расстояния для внутренних записей. Выбрав для второй записи меньшее расстояние (4.0 вместо реальных 4.5), мы ухудшили эффективность (алгоритм напрасно стал просматривать лишний узел), но не нарушили правильность работы.

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

R-дерево для интервалов


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

Простой пример. Будем сдавать в аренду домик в деревне и хранить в таблице интервалы бронирования:

postgres=# create table reservations(during tsrange);
CREATE TABLE
postgres=# insert into reservations(during) values
('[2016-12-30, 2017-01-09)'),
('[2017-02-23, 2017-02-27)'),
('[2017-04-29, 2017-05-02)');
INSERT 0 3
postgres=# create index on reservations using gist(during);
CREATE INDEX

Индекс может использоваться, например, для ускорения следующего запроса:

postgres=# select * from reservations where during && '[2017-01-01, 2017-04-01)';
                    during                    
-----------------------------------------------
 ["2016-12-30 00:00:00","2017-01-08 00:00:00")
 ["2017-02-23 00:00:00","2017-02-26 00:00:00")
(2 rows)

postgres=# explain (costs off) select * from reservations where during && '[2017-01-01, 2017-04-01)';
                                     QUERY PLAN                                    
------------------------------------------------------------------------------------
 Index Only Scan using reservations_during_idx on reservations
   Index Cond: (during && '["2017-01-01 00:00:00","2017-04-01 00:00:00")'::tsrange)
(2 rows)

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

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

postgres=# select amop.amopopr::regoperator, amop.amoppurpose, amop.amopstrategy
from pg_opclass opc, pg_opfamily opf, pg_am am, pg_amop amop
where opc.opcname = 'range_ops'
and opf.oid = opc.opcfamily
and am.oid = opf.opfmethod
and amop.amopfamily = opc.opcfamily
and am.amname = 'gist'
and amop.amoplefttype = opc.opcintype;
         amopopr         | amoppurpose | amopstrategy
-------------------------+-------------+--------------
 @>(anyrange,anyelement) | s           |           16  содержит элемент
 <<(anyrange,anyrange)   | s           |            1  строго слева
 &<(anyrange,anyrange)   | s           |            2  не выходит за правую границу
 &&(anyrange,anyrange)   | s           |            3  пересекается
 &>(anyrange,anyrange)   | s           |            4  не выходит за левую границу
 >>(anyrange,anyrange)   | s           |            5  строго справа
 -|-(anyrange,anyrange)  | s           |            6  прилегает
 @>(anyrange,anyrange)   | s           |            7  содержит интервал
 <@(anyrange,anyrange)   | s           |            8  содержится в интервале
 =(anyrange,anyrange)    | s           |           18  равен
(10 rows)

(Кроме равенства, которое входит и в класс операторов для метода доступа btree.)

Внутри


Внутрь можно заглянуть все тем же расширением gevel. Надо только не забыть поменять тип данных в вызове gist_print:

postgres=# select level, a from gist_print('reservations_during_idx')
as t(level int, valid bool, a tsrange);
 level |                       a                      
-------+-----------------------------------------------
     1 | ["2016-12-30 00:00:00","2017-01-09 00:00:00")
     1 | ["2017-02-23 00:00:00","2017-02-27 00:00:00")
     1 | ["2017-04-29 00:00:00","2017-05-02 00:00:00")
(3 rows)


Ограничение исключения


Индекс GiST может применяться для поддержки ограничений исключения (exclude).

Ограничение исключения гарантирует, что заданные поля любых двух строк таблицы не будут «соответствовать» друг другу в смысле некоторого оператора. Если в качестве оператора выбрать «равно», то получится в точности ограничение уникальности: заданные поля любых двух строк не равны друг другу.

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

  1. поддерживался индексным методом — свойство can_exclude (это, например, методы btree, gist или spgist, но не gin);
  2. был коммутативен, то есть должно выполняться условие: a оператор b =
    b оператор a.

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

  • для btree:
    • «равно» =

  • для gist и spgist:
    • «пересечение» &&
    • «совпадение» ~=
    • «прилегание» -|-


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

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

postgres=# alter table reservations add exclude using gist(during with &&);
ALTER TABLE

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

postgres=# insert into reservations(during) values ('[2017-06-10, 2017-06-13)');
INSERT 0 1

Но попытка вставить в таблицу пересекающийся интервал приведет к ошибке:

postgres=# insert into reservations(during) values ('[2017-05-15, 2017-06-15)');
ERROR: conflicting key value violates exclusion constraint "reservations_during_excl"
DETAIL: Key (during)=(["2017-05-15 00:00:00","2017-06-15 00:00:00")) conflicts with existing key (during)=(["2017-06-10 00:00:00","2017-06-13 00:00:00")).


Расширение btree_gist


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

postgres=# alter table reservations add house_no integer default 1;
ALTER TABLE

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

postgres=# alter table reservations drop constraint reservations_during_excl;
ALTER TABLE
postgres=# alter table reservations add exclude using gist(during with &&, house_no with =);
ERROR: data type integer has no default operator class for access method "gist"
HINT: You must specify an operator class for the index or define a default operator class for the data type.

В этом случае поможет расширение btree_gist, которое добавляет GiST-поддержку операций, характерных для B-деревьев. В конце концов, GiST может работать с любыми операторами, так почему бы не научить его работать с «больше», «меньше», «равно»?

postgres=# create extension btree_gist;
CREATE EXTENSION
postgres=# alter table reservations add exclude using gist(during with &&, house_no with =);
ALTER TABLE

Теперь мы по-прежнему не можем забронировать первый домик на те же даты:

postgres=# insert into reservations(during, house_no) values ('[2017-05-15, 2017-06-15)', 1);
ERROR: conflicting key value violates exclusion constraint "reservations_during_house_no_excl"

Зато можем забронировать второй:

postgres=# insert into reservations(during, house_no) values ('[2017-05-15, 2017-06-15)', 2);
INSERT 0 1

Но надо понимать, что хотя GiST и может как-то работать с операциями «больше», «меньше», «равно», B-дерево все равно справляется с ними лучше. Так что такой прием стоит использовать только, если индекс GiST необходим по существу — как в нашем примере.

RD-дерево для полнотекстового поиска


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


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

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

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

postgres=# set default_text_search_config = russian;
SET
postgres=# select to_tsvector('И встал Айболит, побежал Айболит. По полям, по лесам, по лугам он бежит.');
                            to_tsvector                            
--------------------------------------------------------------------
 'айбол':3,5 'беж':13 'встал':2 'лес':9 'луг':11 'побежа':4 'пол':7
(1 row)

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

Поисковый запрос представляется другим типом — tsquery. Запрос, грубо говоря, состоит из одной или нескольких лексем, соединенных логическими связками: «и» &, «или» |, «не» !. Также можно использовать скобки для уточнения приоритета операций.

postgres=# select to_tsquery('Айболит & (побежал | пошел)');
            to_tsquery            
----------------------------------
 'айбол' & ( 'побежа' | 'пошел' )
(1 row)

Для полнотекстового поиска используется один-единственный оператор соответствия @@.

postgres=# select to_tsvector('И встал Айболит, побежал Айболит.') @@ to_tsquery('Айболит & (побежал | пошел)');
 ?column?
----------
 t
(1 row)

postgres=# select to_tsvector('И встал Айболит, побежал Айболит.') @@ to_tsquery('Бармалей & (побежал | пошел)');
 ?column?
----------
 f
(1 row)

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

RD-деревья


Чтобы полнотекстовый поиск работал быстро, нужно, во-первых, хранить в таблице столбец типа tsvector (чтобы не выполнять дорогое преобразование каждый раз при поиске), и во-вторых, построить по этому полю индекс. Один из возможных методов доступа для этого — GiST.

postgres=# create table ts(doc text, doc_tsv tsvector);
CREATE TABLE
postgres=# create index on ts using gist(doc_tsv);
CREATE INDEX
postgres=# insert into ts(doc) values
  ('Во поле береза стояла'),  ('Во поле кудрявая стояла'), ('Люли, люли, стояла'),
  ('Некому березу заломати'), ('Некому кудряву заломати'), ('Люли, люли, заломати'),
  ('Я пойду погуляю'),        ('Белую березу заломаю'),    ('Люли, люли, заломаю');
INSERT 0 9
postgres=# update ts set doc_tsv = to_tsvector(doc);
UPDATE 9

Последний шаг (преобразование документа в tsvector), конечно, удобно возложить на триггер.

postgres=# select * from ts;
           doc           |            doc_tsv            
-------------------------+--------------------------------
 Во поле береза стояла   | 'берез':3 'пол':2 'стоя':4
 Во поле кудрявая стояла | 'кудряв':3 'пол':2 'стоя':4
 Люли, люли, стояла      | 'люл':1,2 'стоя':3
 Некому березу заломати  | 'берез':2 'заломат':3 'нек':1
 Некому кудряву заломати | 'заломат':3 'кудряв':2 'нек':1
 Люли, люли, заломати    | 'заломат':3 'люл':1,2
 Я пойду погуляю         | 'погуля':3 'пойд':2
 Белую березу заломаю    | 'бел':1 'берез':2 'залома':3
 Люли, люли, заломаю     | 'залома':3 'люл':1,2
(9 rows)

Как должен быть устроен индекс? Непосредственно R-дерево здесь не годится — непонятно, что такое «ограничивающий прямоугольник» для документов. Зато можно применить некоторую модификацию этого подхода для множеств — так называемое RD-дерево (RD — это Russian Doll, матрешка). Под множеством в данном случае мы понимаем множество лексем документа, но вообще множество может быть любым.

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

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



Тогда, например, для доступа по условию doc_tsv @@ to_tsquery('стояла') можно было бы спускать только в те узлы, в которых есть лексема 'стоя':



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

Такое представление иногда используется, но для других типов данных. А для полнотекстового поиска применяется другое, более компактное, решение — так называемое сигнатурное дерево. Идея его хорошо знакома всем, кто имел дело с фильтром Блума.

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

Сигнатурой документа называется побитовое «или» сигнатур всех лексем документа.

Допустим, сигнатуры наших лексем такие:

бел      1000000
берез    0001000
залома   0000010
заломат  0010000
кудряв   0000100
люл      0100000
нек      0000100
погуля   0000001
пойд     0000010
пол      0000010
стоя     0010000

Тогда сигнатуры документов получатся следующими:

Во поле береза стояла     0011010
Во поле кудрявая стояла   0010110
Люли, люли, стояла        0110000
Некому березу заломати    0011100
Некому кудряву заломати   0010100
Люли, люли, заломати      0110000
Я пойду погуляю           0000011
Белую березу заломаю      1001010
Люли, люли, заломаю       0100010


Дерево индекса можно представить так:



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

Рассмотрим то же самое условие doc_tsv @@ to_tsquery('стояла'). Вычислим сигнатуру поискового запроса точно так же, как и для документа: в нашем случае 0010000. Функция согласованности должна выдать все дочерние узлы, сигнатура которых содержит хотя бы один бит из сигнатуры запроса:



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

Кроме того, может так получиться, что и разные документы получат одинаковые сигнатуры: в нашем примере не повезло «люли, люли, стояла» и «люли, люли, заломати» (у обоих сигнатура 0110000). И если в листовой индексной записи не хранить само значение tsvector, то и сам индекс будет давать ложные срабатывания. Разумеется, в таком случае метод доступа попросит индексный механизм перепроверять результат по таблице, так что пользователь этих ложных срабатываний не увидит. Но эффективность поиска вполне может пострадать.

На самом деле сигнатура в текущей реализации занимает 124 байта вместо семи бит на наших картинках, так что вероятность коллизий существенно меньше, чем в примере. Но ведь и документов на практике индексируется гораздо больше. Чтобы как-то снизить число ложных срабатываний индексного метода, реализация идет на хитрость: индексированный tsvector сохраняется в листовой индексной записи, но только если он не занимает много места (чуть меньше 1/16 страницы, что составляет около половины килобайта для страниц 8 КБ).

Пример


Чтобы посмотреть, как индексирование работает на реальных данных, возьмем архив рассылки pgsql-hackers. В использованной в примере версии содержится 356125 писем с датой отправления, темой, автором и текстом:

fts=# select * from mail_messages order by sent limit 1;
-[ RECORD 1 ]------------------------------------------------------------------------
id         | 1572389
parent_id  | 1562808
sent       | 1997-06-24 11:31:09
subject    | Re: [HACKERS] Array bug is still there....
author     | "Thomas G. Lockhart" <thomas.lockhart@jpl.nasa.gov>
body_plain | Andrew Martin wrote:                                                    +
           | > Just run the regression tests on 6.1 and as I suspected the array bug +
           | > is still there. The regression test passes because the expected output+
           | > has been fixed to the *wrong* output.                                 +
           |                                                                         +
           | OK, I think I understand the current array behavior, which is apparently+
           | different than the behavior for v1.0x.                                  +
             ...

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

fts=# alter table mail_messages add column tsv tsvector;
ALTER TABLE
fts=# update mail_messages
set tsv = to_tsvector(subject||' '||author||' '||body_plain);
NOTICE:  word is too long to be indexed
DETAIL:  Words longer than 2047 characters are ignored.
...
UPDATE 356125
fts=# create index on mail_messages using gist(tsv);
CREATE INDEX

Некоторое количество слов, как видно, было отброшено из-за слишком большого размера. Но в итоге индекс построен и готов поддерживать поисковые запросы:

fts=# explain (analyze, costs off)
select * from mail_messages where tsv @@ to_tsquery('magic & value');
                        QUERY PLAN
----------------------------------------------------------
 Index Scan using mail_messages_tsv_idx on mail_messages
 (actual time=0.998..416.335 rows=898 loops=1)
   Index Cond: (tsv @@ to_tsquery('magic & value'::text))
   Rows Removed by Index Recheck: 7859
 Planning time: 0.203 ms
 Execution time: 416.492 ms
(5 rows)

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

Внутри


Для анализа содержимого индекса снова воспользуемся расширением gevel:

fts=# select level, a from gist_print('mail_messages_tsv_idx') as t(level int, valid bool, a gtsvector) where a is not null;
 level |               a              
-------+-------------------------------
     1 | 992 true bits, 0 false bits
     2 | 988 true bits, 4 false bits
     3 | 573 true bits, 419 false bits
     4 | 65 unique words
     4 | 107 unique words
     4 | 64 unique words
     4 | 42 unique words
...

Значения специального типа gtsvector, хранящиеся в индексных записях — это собственно сигнатура плюс, возможно, исходный tsvector. Если вектор есть, то выводится количество лексем в нем (unique words), а если нет — то число установленных (true) и сброшенных (false) битов в сигнатуре.

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

Свойства


Поглядим на свойства метода доступа gist (запросы приводились ранее):

 amname |     name      | pg_indexam_has_property
--------+---------------+-------------------------
 gist   | can_order     | f
 gist   | can_unique    | f
 gist   | can_multi_col | t
 gist   | can_exclude   | t

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

Свойства индекса:

     name      | pg_index_has_property
---------------+-----------------------
 clusterable   | t
 index_scan    | t
 bitmap_scan   | t
 backward_scan | f

И, самое интересное, свойства уровня столбца. Некоторые свойств будет постоянными:

        name        | pg_index_column_has_property
--------------------+------------------------------
 asc                | f
 desc               | f
 nulls_first        | f
 nulls_last         | f
 orderable          | f
 search_array       | f
 search_nulls       | t

(Нет поддержки сортировки; индекс нельзя использовать для поиска в массиве; неопределенные значения поддерживаются.)

А вот два оставшихся свойства, distance_orderable и returnable, будут зависеть от используемого класса операторов. Например, для точек увидим:

        name        | pg_index_column_has_property
--------------------+------------------------------
 distance_orderable | t
 returnable         | t

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

А вот интервалы:

        name        | pg_index_column_has_property
--------------------+------------------------------
 distance_orderable | f
 returnable         | t

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

И полнотекстовый поиск:

        name        | pg_index_column_has_property
--------------------+------------------------------
 distance_orderable | f
 returnable         | f

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

Другие типы данных


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

Из стандартных типов это IP-адреса inet, а остальное добавляется расширениями:
  • cube предоставляет тип данных cube для многомерных кубов. Для него, как и для геометрических типов на плоскости, определен класс операторов GiST: R-дерево с возможностью поиска ближайших соседей.
  • seg предоставляет тип данных seg для интервалов с границами, заданными с определенной точностью, и поддержку GiST-индекса для него (R-дерево).
  • intarray расширяет функциональность целочисленных массивов и добавляет для них GiST-поддержку. Реализованы два класса операторов: gist__int_ops (RD-дерево с полным представлением ключей в индексных записях) и gist__bigint_ops (сигнатурное RD-дерево). Первый класс можно использовать для небольших массивов, второй — для более серьезных объемов.
  • ltree добавляет тип данных ltree для древовидных структур и GiST-поддержку для него (RD-дерево).
  • pg_trgm добавляет специальный класс операторов gist_trgm_ops для использования триграмм в полнотекстовом поиске. Но об этом — в другой раз вместе с индексом GIN.

Продолжение следует.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/333878/


Метки:  

[Перевод] Начинаем работать с Ruby on Rails в Docker

Вторник, 01 Августа 2017 г. 10:22 + в цитатник

Docker замечательно справляется с изолированием приложений и их окружений, облегчая распространение и репликацию состояний между различными средами (dev, test, beta, prod и т. д.). Его использование позволяет избавиться от проблемы «на моей машине все работает» и помогает с легкостью масштабировать приложение по мере его роста.


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


В этой статье мы возьмем простое приложение на Rails и подготовим его для использования в Docker-контейнере («докеризуем»).


Необходимые компоненты


Наше приложение будет написано под Rails 5; базу данных возьмем PostgreSQL. Если вы хотите подключить другую СУБД, то потребуется поправить несколько файлов.


Вы можете воспользоваться заранее подготовленным шаблоном для создания приложения, которое сконфигурировано с помощью Dockerfile и config/database.yml:


$ rails new --database=postgresql --skip-bundle --template=https://gist.githubusercontent.com/cblunt/1d3b0c1829875e3889d50c27eb233ebe/raw/01456b8ad4e0da20389b0b91dfec8b272a14a635/rails-docker-pg-template.rb my-app
$ cd my-app

Конфигурация базы данных


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


Отредактируйте файл конфигурации config/database.yml


Если вы воспользовались приведенным выше шаблоном, то редактировать файл не нужно.


Добавьте в config/database.yml переменные окружения:


# config/database.yml
default: &default                                                                    
  adapter: postgresql                                                                
  encoding: unicode                                                                  
  pool: 
  host: db
  username: 
  password: 

development:
  <<: *default                                                                       
  database: my-app_development

test:
  <<: *default                                                                       
  database: my-app_test

production:
  <<: *default                                                                       
  database: my-app_production

Создание Dockerfile


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


Для экономии дискового пространства я предпочитаю использовать базовый образ alpine-linux Ruby. Alpine linux — крошечный linux-дистрибутив, идеально подходящий для использования в контейнерах. В Docker доступен базовый образ ruby:alpine, которым мы и воспользуемся.


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


Если вы воспользовались приведенным выше шаблоном, то редактировать файл не нужно.


# /path/to/app/Dockerfile
FROM ruby:2.3-alpine

# Установка часового пояса
RUN apk add --update tzdata && \
    cp /usr/share/zoneinfo/Europe/London /etc/localtime && \
    echo "Europe/London" > /etc/timezone

# Установка в контейнер runtime-зависимостей приложения
RUN apk add --update --virtual runtime-deps postgresql-client nodejs libffi-dev readline sqlite

# Соберем все во временной директории
WORKDIR /tmp
ADD Gemfile* ./

RUN apk add --virtual build-deps build-base openssl-dev postgresql-dev libc-dev linux-headers libxml2-dev libxslt-dev readline-dev && \
    bundle install --jobs=2 && \
    apk del build-deps

# Копирование кода приложения в контейнер
ENV APP_HOME /app
COPY . $APP_HOME
WORKDIR $APP_HOME

# Настройка переменных окружения для production
ENV RAILS_ENV=production \
    RACK_ENV=production

# Проброс порта 3000 
EXPOSE 3000

# Запуск по умолчанию сервера puma
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]                              

А что если я не хочу использовать PostgreSQL?


Если вы используете другую СУБД (например, MySQL), то для установки соответствующих пакетов потребуется внести изменения в Dockerfile.


Произвести поиск необходимых пакетов можно с помощью следующей команды Docker:


$ docker run --rm -it ruby:2.3-alpine apk search --update mysql | sort
...
mariadb-client-libs-10.1.22-r0
mariadb-dev-10.1.22-r0
mariadb-libs-10.1.22-r0
mysql-10.1.22-r0
mysql-bench-10.1.22-r0
...

Поскольку Dockerfile уже готов, пора запустить сборку Docker-образа для нашего приложения:


Собираем образ


$ docker build . -t my-app

Образ готов, можно начинать! Запустите контейнер следующей командой:


$ docker run --rm -it --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --publish 3000:3000 --volume ${PWD}:/app my-app

Мы передали команде docker run несколько аргументов:


  • -it — на самом деле это 2 аргумента, которые позволяют взаимодействовать с контейнером с помощью командной оболочки (например, чтобы передать комбинацию клавиш Ctrl+C);
  • --env — позволяет передать контейнеру переменные окружения. Здесь они используются для установки параметров подключения к базе данных;
  • --rm — говорит докеру удалить контейнер после завершения его работы (например, после нажатия Ctrl+C);
  • --publish — пробрасывает порт 3000 контейнера на порт 3000 хоста. Таким образом у нас появляется возможность подключиться к сервису так, как будто он запущен напрямую на хосте (например, http://localhost:3000);
  • --volume — говорит докеру подмонтировать в контейнер текущую директорию хоста. Таким образом вы получаете возможность редактировать код на хосте, но при этом он будет доступен в контейнере. Без этого вам пришлось бы после каждого изменения кода заново создавать контейнер.

Запуск контейнера базы данных


Хотя контейнер с приложением и запустился, попытка открыть ссылку localhost:3000, к сожалению, приведет к ошибке:


could not translate host name “db” to address: Name does not resolve

У нас пока нет доступного приложению PostgreSQL-сервера. Сейчас мы это починим, запустив Docker-контейнер с PostgreSQL:




Совет. Не забывайте, что в Docker один контейнер должен выполнять одну и только одну функцию.


В нашем случае будет 2 контейнера: один для приложения и один для базы данных (PostgreSQL).




Запуск нового контейнера с PostgreSQL


Для остановки (и удаления) контейнера с приложением нажмите Ctrl+C, затем запустите новый контейнер с PostgreSQL:


$ docker run -d -it --env POSTGRES_PASSWORD=superSecret123 --env DB_NAME=my-app_development --name mydbcontainer postgres:9.6

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


Использование однозадачных (Single–Task) контейнеров


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


Они идеальны для разовых задач, таких как команды rails (например, bin/rails db:setup).


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


Выполнение задачи rails db:migrate с использованием контейнера


Для запуска копии контейнера с приложением выполните следующую команду. Затем запустите в контейнере bin/rails db:setup и выключите его.


Обратите внимание: вам потребуется настроить переменные окружения для соединения с базой данных (они вставляются в config/database.yml, который вы ранее редактировали).


Опция --link позволит подключиться к контейнеру с PostgreSQL (mydbcontainer), используя имя хоста db:


$ docker run --rm --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --link mydbcontainer:db --volume ${PWD}:/app my-app bin/rails db:create db:migrate

Флаг --rm удалит контейнер после завершения его работы.


После выполнения этой команды в контейнере mydbcontainer будет настроенная под нужны приложения база данных. Наконец-то мы сможем его запустить!


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


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


$ docker run --rm -it --env RAILS_ENV=development --env POSTGRES_USER=postgres --env POSTGRES_PASSWORD=superSecret123 --publish 3000:3000 --volume ${PWD}:/app --link mydbcontainer:db my-app

=> Puma starting in single mode...
=>  * Version 3.8.2 (ruby 2.4.1-p111), codename: Sassy Salamander
=>  * Min threads: 5, max threads: 5
=>  * Environment: development
=>  * Listening on tcp://0.0.0.0:3000
=>  Use Ctrl-C to stop

Откройте в браузере страницу localhost:3000, где вы должны увидеть наше приложение, работающее полностью из-под Docker!


Следующие шаги


Docker — это очень удобный инструмент разработчика. Со временем вы можете перенести в него все компоненты своего приложения (БД, redis, рабочие процессы sidekiq, cron и т. д.).


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


Ссылки:


  1. Оригинал: Rails on Docker: Getting Started with Docker and Ruby on Rails.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334084/


Метки:  

В разрезе: новостной агрегатор на Android с бэкендом. Система сборки

Вторник, 01 Августа 2017 г. 10:02 + в цитатник
Вводная часть (со ссылками на все статьи)

Лет 10-15 назад, когда программы состояли из исходников и небольшого количества двоичных файлов с работой по сборке итоговых программ отлично справлялись всевозможные «?make». Однако сейчас современные программы и подходы к разработке сильно изменились – это:
множество различных файлов (не считаю исходников) – стили, шаблоны, ресурсы, конфигурации, скрипты, бинарные данные и.т.д;
  • предпроцессоры;
  • системы проверки стиля исходников или всего проекта (lint, checkstyle и т.д.);
  • методики разработки, основанные на тестах, с их запуском при сборке;
  • различного типа стенды;
  • системы развёртывания на базе облачных технологий и т.д. и.т.п.


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

Мой путь в использовании систем сборки был непонятное_количество_*make -> ant -> maven -> gradle (то обстоятельство, что android Studio под капотом использует gradle меня сильно порадовало).

Gradle меня привлёк:
  • своей простой моделью (из которой правда можно вырастить монстра, соизмеримого с самим создаваемым продуктом);
  • гибкостью (как в части настройки самих скриптов, так и организации их распределения в рамках больших проектов);
  • постоянным развитием (как и со всеми остальными вещами в разработке — тут надо постоянно изучать что-то новое);
  • лёгкостью адаптации (знание groovy и gradle DSL обязательно);
  • наличием системы plugin’ов, разрабатываемых сообществом — это и разные предпроцессоры, генераторы кода, системы доставки и публикации и прочее, прочее (см. login.gradle.org)


Ознакомиться с возможностями Gradle можно на сайте разработчиков в разделе документации (можно узнать всё!). Для тех, кто хочет сравнить gradle и maven есть интересное видео от JUG.

В моём случае скрипты для сборки выглядят таким образом:
image,
где:
  • build_scripts/build-tasks.gradle — все задачи для сборки с указанием их зависимостей;
  • build_scripts/dependencies.gradle — описания зависимостей и способов публикации;
  • build.gradle — основной скрипт, определяющий зависимые модули, библиотеки и включающий другие скрипты сборки;
  • settings.gradle — перечень зависимых модулей и настройки самого скрипта (можно переопределить ч/з аргументы запуска gradle).

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

Tips


Из интересных вещей/советов, которыми я хотел бы поделиться, при настройке gradle в моём проекте есть следующие:
  • Вынос версий артефактов (шарится по блокам модулей было скучным занятием)


    Объявляем блок с зависимостями:
    // project dependencies
    ext {
    COMMONS_POOL_VER='2.4.2'
    DROPWIZARD_CORE_VER='1.1.0'
    DROPWIZARD_METRICS_VER='3.2.2'
    DROPWIZARD_METRICS_INFLUXDB_VER='0.9.3'
    JSOUP_VER='1.10.2'
    STORM_VER='1.0.3'
    ...
    GROOVY_VER='2.4.7'
    // test
    TEST_JUNIT_VER='4.12'
    TEST_MOCKITO_VER='2.7.9'
    TEST_ASSERTJ_VER='3.6.2'
    }
    

    И используем его в проекте:
    project(':crawler_scripts') {
    	javaProject(it)
    	javaLogLibrary(it)
    	javaTestLibrary(it)
    
    	dependencies {
    		testCompile "org.codehaus.groovy:groovy:${GROOVY_VER}"
    		testCompile "edu.uci.ics:crawler4j:${CRAWLER4J_VER}"
    		testCompile "org.jsoup:jsoup:${JSOUP_VER}"
    		testCompile "joda-time:joda-time:${JODATIME_VER}"
    		testCompile "org.apache.commons:commons-lang3:${COMMONS_LANG_VER}"
    		testCompile "commons-io:commons-io:${COMMONS_IO_VER}"
    	}
    }
    

  • Вынос настроек во внешний файл
    Создаём или уже имеем файл с конфигурацией:
    ---
    # presented - for test/development only - use artifact from ""/provision/artifacts" directory
    storyline_components:
      crawler_scripts:
        version: "0.5"
      crawler:
        version: "0.6"
      server_storm:
        version: "presented"
      server_web:
        version: "0.1"
    

    И используем его в проекте:
    import com.fasterxml.jackson.databind.ObjectMapper
    import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
    
    buildscript {
    	repositories {
    		jcenter()
    	}
    	dependencies {
    		// reading YAML
    		classpath "com.fasterxml.jackson.core:jackson-databind:2.8.6"
    		classpath "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.8.6"
    	}
    }
    ....
    def loadArtifactVersions(type) {
    	Map result = new HashMap()
    	def name = "${projectDir}/deployment/${type}/hieradata/version.yaml"
    	println "Reading artifact versions from ${name}"
    	if (new File(name).exists()) {
    		ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
    		result = mapper.readValue(new FileInputStream(name), HashMap.class);
    	}
    	return  result['storyline_components'];
    }
    

  • Использование шаблонов
    Это моя любимая часть — позволяет сформировать необходимые конфигурационные файлы из шаблонов.
    Формируем шаблоны:
    version: '2'
    services:
    ...
      server_storm:
        domainname: story-line.ru
        hostname: server_storm
        build: ./server_storm
        depends_on:
            - zookeeper
            - elasticsearch
            - mongodb
        links:
            - zookeeper
            - elasticsearch
            - mongodb
        ports:
         - "${server_storm_ui_host_port}:8082"
         - "${server_storm_logviewer_host_port}:8083"
         - "${server_storm_nimbus_host_port}:6627"
         - "${server_storm_monit_host_port}:3000"
         - "${server_storm_drpc_host_port}:3772"
        volumes:
         - ${logs_dir}:/data/logs
         - ${data_dir}:/data/db
    ....
    

    И используем его в проекте:
    // выполнить копирование скриптов для подготовки сервера
    task copyTemplates (type: Copy, dependsOn: ['createStandDir']){
    	description "выполнить копирование шаблонов"
    	from "${projectDir}/deployment/docker_templates"
    	into project.ext.stand.deploy_dir
    	expand(project.ext.stand)
    	filteringCharset = 'UTF-8'
    }

    В итоге получаете конфигурационный файл, данные которого заполнены значениями переменных, получившими значения в ходе работы скрипта.Однако необходимо отметить, что если вам потребуется более сложная логика с переменными (например скрытие при отсутствии значений) — вам потребуется другой движок шаблонов в системе сборке. Я эту ситуацию обошел используя формат YAML по ходу проекта.
  • Использование списков и замыканий groovy для специфичной обработки конкретных модулей
    Это фактически обычное использование groovy в скрипте сборки, но мне это помогло решить пару нетривиальных задач.

    Объявляем или получаем переменные множественного значения:
    ext {
    	// образы, создаваемые docker'ом
    	docker_machines = ['elasticsearch', 'zookeeper', 'mongodb', 'crawler', 'server_storm', 'server_web']
    	// образы, создаваемые docker'ом для которых необходимо копировать артефакты
    	docker_machines_w_artifacts = ['crawler', 'server_storm', 'server_web']
    }
    

    И используем их в проекте:
    // выполнить копирование шаблонов для docker с подстановкой значений
    docker_machines.each { machine ->
    	task "copyProvisionScripts_${machine}" (type: Copy, dependsOn: ['createStandDir']){
    ...
    	}
    }
    

  • Интеграция с репозитариями maven — больно громоздкое описание для включения в статью (и сама работа достаточно бесполезная с учётом наличия примеров в документации)
  • Возможность задачи блоков зависимостей для модулей, копирование и вставка которых отсутствует для каждого модуля

    Объявляем блоки:
    def javaTestLibrary(project) {
        project.dependencies {
    		testCompile "org.apache.commons:commons-lang3:${COMMONS_LANG_VER}"
    		testCompile "commons-io:commons-io:${COMMONS_IO_VER}"
    		testCompile "junit:junit:${TEST_JUNIT_VER}"
    		testCompile "org.mockito:mockito-core:${TEST_MOCKITO_VER}"
    		testCompile  "org.assertj:assertj-core:${TEST_ASSERTJ_VER}"
        }
    }
    

    И используем их в проекте:
    project(':token') {
    	javaProject(it)
    	javaLogLibrary(it)
    	javaTestLibrary(it)
    }
    



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

Спасибо за внимание!
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334592/


Метки:  

[Перевод] ZFS — лучшая файловая система (пока)

Вторник, 01 Августа 2017 г. 09:48 + в цитатник
ZFS должна быть классной, но меня немного бесит, что она словно застряла в прошлом — даже до того, как её признали крутой и лучшей файловой системой. Она негибкая, ей не хватает современной интеграции с флеш-памятью и она не поддерживается напрямую большинством операционных систем. Но я храню все свои ценные данные на ZFS, поскольку именно она обеспечивает наилучший уровень защиты для условий SOHO (малый офис/домашний офис). И вот почему.


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

Революция ZFS. Около 2006 года


В своих статьях о FreeNAS я настойчиво повторял, что «ZFS — самая лучшая файловая система», но если вы посмотрите мои сообщения в социальных медиа, то станет ясно, что мне она на самом деле не совсем нравится. Я пришёл к выводу, что такое противоречие требует объяснения и контекста, так что рискнём потревожить фанатов ZFS и сделаем это.

Когда ZFS впервые появилась в 2005 году, она была абсолютно своевременной, но она застряла там до сих пор. Разработчики ZFS сделали много правильных вещей, объединив лучшие функции диспетчера томов с файловой системой «зеттабайтного масштаба» в Solaris 10:

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

Когда появилась ZFS, это была революционная система, по сравнению со старыми диспетчерами томов и файловыми системами. И Sun открыла б'oльшую часть исходного кода ZFS, позволив портировать её на другие операционные системы. Как любимая игрушка всей индустрии, ZFS быстро появилась на Linux и FreeBSD, и даже Apple начала внедрять её как часть файловой системы следующего поколения в Mac OS X! Будущее казалось таким светлым!

Контрольные суммы для пользовательских данных необходимы, иначе вы неизбежно потеряете данные: «Почему в больших дисках требуется проверка целостности данных» и «Первая директива систем хранения: не терять данные»

С 2007 по 2010-й: ZFS пошла под откос


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

Первые тучи появились в 2007 году, когда NetApp подала иск к Sun на основании того, что ZFS нарушает их патенты на WAFL. Sun ответила встречным иском в том же году — и юридические тяжбы затянулись. Хотя в ZFS определённо не было кода NetApp, но механизм копирования при записи в снапшоты был похож на WAFL, и некоторые из нас в индустрии обеспокоились, что иск NetApp повлияет на доступность открытых исходников ZFS. Этих рисков оказалось достаточно для Apple, чтобы отказаться от поддержки ZFS в Mac OS X 10.6 “Snow Leopard” прямо перед выпуском этой ОС.

Вот отличный блог о ZFS и Apple от Адама Левенталя, который работал над этим проектом в компании: ZFS: Apple’s New Filesystem That Wasn’t

Тогда Sun переживала трудные времена, и Oracle воспользовалась моментом для покупки компании. Это посеяло новые сомнения о будущем ZFS, поскольку Oracle известна как не большой любитель широкой общественной поддержки свободных проектов. А лицензия CDDL, которую Oracle применила к коду ZFS, признана несовместимой с GPLv2, которая используется в Linux, что делает невозможным использование ZFS в самой популярной в мире ОС для серверов.

Хотя проект OpenSolaris продолжился и после приобретения Oracle, а ZFS включили во FreeBSD, но это было в значительной степени за пределами корпоративного сектора. Конечно, NexentaStor и GreenBytes помогли продвинуть ZFS в корпоративном секторе, но недостаток поддержки серверов Sun со стороны Oracle тоже начал влиять на ситуацию.

Какие проблемы у ZFS сейчас?


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

Многие продолжают скептически относиться к дедупликации, которая требует много дорогой памяти. И я действительно имею в виду дорогой: практически каждый ZFS FAQ однозначно требует наличия памяти только ECC и минимум 8 ГБ. По моему собственному опыту с FreeNAS, для активного маленького сервера с ZFS подойдёт 32 ГБ, а это стоит $200-300 даже по сегодняшним ценам.

И ZFS так и по-настоящему не приспособился к флеш-памяти, которая сейчас используется повсеместно. Хотя флеш можно использовать для кэшей ZIL и L2ARC, это сомнительное преимущество для систем с достаточным количеством RAM, и у ZFS нет настоящей функции гибридного хранилища данных. Смехотворно, что в документации ZFS повсеместно упоминаются несколько гигабайт флеш-памяти SLC, когда на рынке уже есть многотерабайтные диски 3D NAND. И никто не говорит о NVMe, хотя это стандарт для высокопроизводительых ПК.

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

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

Кроме третьего способа, у вас нет возможности уменьшить пул ZFS. Хуже того, вы не можете изменить тип защиты данных без пересборки всего пула, в том числе добавить второй и третий диски чётности. FreeNAS добросовестно тратит огромное количество времени, пытаясь отговорить новичков от использования RAID-Z1[1], и жалуется, если они всё равно выбирают такую схему.

Всё это может показаться мелкими, незначительными придирками, но в совокупности они субъективно отправляют ZFS в средние века, после использования Drobo, Synology или современных облачных систем хранения. С ZFS вам нужно «купить диски, много памяти, создать RAID-массив и никогда его больше трогать», что не совсем соответствует современному использованию систем хранения[2].

Какие варианты?


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

В Linux несколько приличных диспетчеров томов и файловых систем, а большинство используют LVM или MD и ext4. Спецов по файловым системам очень порадовала Btrfs, которая сочетает в себе функции диспетчера томов и файловой системы в стиле ZFS, но с дополнительной гибкостью за пределами того, на чём шлёпнулась ReiserFS. И Btrfs действительно могла бы стать «ZFS для Linux», но не так давно разработка споткнулась, после ужасного прошлогоднего бага с потерей данных с рейдах RAID 5 и 6, и больше о них почти ничего не слышно. Но я по-прежнему думаю, что через пять лет буду рекомендовать пользователям Linux использовать Btrfs, особенно с её мощным потенциалом для применения в контейнерах[3].

Для Windows компания Microsoft тоже собирается выкатить собственную файловую систему нового поколения ReFS с использованием деревьев B+ (похоже на Btrfs), с сумасшедшим масштабированием и функциями стойкости и защиты данных[4]. В сочетании со Storage Spaces, у Microsoft будет жизнеспособная система хранения следующего поколения для Windows Server, которая может даже использовать SSD и 3D-XPoint как уровень или кэш.

И есть ещё Apple, которая по слухам несколько раз меняла систему хранения, до того как остановиться на APFS, которая вышла в этом году в macOS High Sierra. APFS во многом похожа на Btrfs и ReFS, хотя реализована совершенно иначе, с большей ориентацией на пользователя. Уступая в некоторых сферах (пользовательские данные не проверяются контрольной суммой и не поддерживается сжатие), APFS — именно та система, которая нужна для iOS и macOS. И APFS — это последний гвоздь в гроб идеи «ZFS на Mac OS X».

В каждой из трёх основных ОС теперь есть файловая система нового поколения (и диспетчер томов). В Linux есть Btrfs, в Windows — ReFS и Storage Spaces, а в macOS есть APFS. FreeBSD вроде бы сохранила приверженность ZFS, но это незначительная часть рынка. И каждая система корпоративного уровня уже продвинулась намного дальше того, что может делать ZFS и системы корпоративного уровня на базе ZFS от Sun, Nexenta и iXsystems.

Но ZFS по-прежнему намного превосходит старые файловые системы для домашнего пользователя. Из-за отсутствия проверки целостности, избыточности и восстановления после ошибок NTFS (Windows), HFS+ (macOS) и ext3/4 (Linux) абсолютно не подходят для долговременного хранения данных. И даже ReFS и APFS из-за отсутствия проверки целостности не подходят там, где потеря данных неприемлема.

Позиция автора: используйте ZFS (пока)


Грустно это признавать, но на 2017 год ZFS — лучшая файловая система для долговременного широкомасштабного хранения данных. Хотя иногда и сложно с ней работать (кроме FreeBSD, Solaris и специализированных устройств), но надёжность и проверенность делают ZFS единственным заслуживающим доверия инструментом для хранения данных за пределами корпоративных систем хранения. В конце концов, надёжное хранение данных — это единственное, что действительно должна делать файловая система. Все мои важные данные сразу идут в ZFS, от фотографий до музыки, от фильмов до офисных файлов. Ещё нескоро я доверюсь чему-нибудь кроме ZFS!

Сноски


1. Для современных больших дисков предпочтительнее RAID-Z2 и RAID-Z3 с большей избыточностью.^
2. Странно, хотя множественные пулы и съёмные диски отлично работают на ZFS, почти никто не говорит о таком варианте использования. Всегда речь идёт об одном пуле под названием “tank”, который включает в себя все диски в системе.^
3. Одна вещь, которой по-настоящему не хватает в Btrfs — это поддержки флеш, и особенно гибридных систем хранения. Но лично я бы предпочёл, чтобы они сначала реализовали поддержку RAID-6.^
4. Хотя контрольные суммы для данных в ReFS по-прежнему отключены по умолчанию.^
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334596/


Про Reflect API доступным языком

Вторник, 01 Августа 2017 г. 09:28 + в цитатник


Всем привет! Недавно услышал, как одни молодые фронтендеры пытались объяснить другим молодым фронтендерам, что такое Reflect в JavaScript. В итоге кто-то сказал, что это такая же штука, как прокси. Ситуация напомнила мне анекдот:

Встречаются два майнера:
— Ты что-нибудь понимаешь в этом?
— Ну объяснить смогу.
— Это понятно, но ты что-нибудь понимаешь в этом?

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

Сначала дадим определение, что такое рефлексия в программировании:
Reflection/Reflect API — это API, который предоставляет возможность проводить реверс-инжиниринг классов, интерфейсов, функций, методов и модулей.

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

Например, Reflection API есть в Java. Он используется для просмотра информации о классах, интерфейсах, методах, полях, конструкторах и аннотациях во время выполнения java программ. К примеру, с помощью Reflection в Java можно использовать ООП паттерн — Public Morozov.

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

В JavaScript Reflect — это встроенный объект, который предоставляет методы для перехватывания JavaScript операций. По сути, это неймспейс (как и Math). Reflect содержит в себе набор функций, которые называются точно так же, как и методы для Proxy.

Некоторые из этих методов — те же, что и соответствующие им методы класса Object или Function. JavaScript растет и превращается в большой и сложный ЯП. В язык приходят различные вещи из других языков. На сегодня Reflect API умеет не так много, как в других ЯП. Тем не менее, есть предложения по расширению, которые еще не вошли в стандарт, но уже используются. Например, Reflection Metadata.

Можно сказать, что неймспейс Reflect в JS — это результат рефакторинга кода. Мы уже пользовались ранее возможностями Reflect API, просто все эти возможности были вшиты в базовый класс Object.

Reflect Metadata / Metadata Reflection


Это API создано для получения информации об объектах в рантайме. Это proposal, который пока не является стандартом. Сейчас активно используется полифил. На сегодняшний день активно применяется в Angular. С помощью этого API реализованы Inject и декораторы (анотаторы).

Собственно ради Angular в TypeScript был добавлен расширенный синтаксис декораторов. Одной из интересных особенностей декораторов является возможность получать информацию о типе декорируемого свойства или параметра. Чтобы это заработало, нужно подключить библиотеку reflect-metadata, которая расширяет стандартный объект Reflect и включить опцию emitDecoratorMetadata к конфиге TS. После этого для свойств, которые имеют хотя бы один декоратор, можно вызвать Reflect.getMetadata с ключем «design:type».

В чем различие Reflect от Proxy?


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

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

Use Cases


Ну и рассмотрим способы применения Reflect API. Некоторые примеры уже давно известны, просто для этих целей мы привыкли использовать методы из класса Object. Но было бы правильнее, по логике, использовать их из пакета Reflect (пакеты — терминология из Java).

Автогенерируемые поля объекта


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

const emptyObj = () =>
 new Proxy({},
   {
     get: (target, key, receiver) => (
           Reflect.has(target, key) ||
           Reflect.set(target, key, emptyObj()),
           Reflect.get(target, key, receiver)
     )
   }
 )
;
const path = emptyObj();

path.to.virtual.node.in.empty.object = 123;

console.log(path.to.virtual.node.in.empty.object); // 123


Все круто, но такой объект нельзя сериализовать в JSON, получим ошибку. Добавим магический метод сериализации — toJSON

console.clear();
const emptyObj = () =>
 new Proxy({},
   {
     get: (target, key, receiver) => (
        key == 'toJSON'
          ? () => target
          : (
              Reflect.has(target, key) ||
              Reflect.set(target, key, emptyObj()),
              Reflect.get(target, key, receiver)
            )
     )
   }
 )
;
const path = emptyObj();
path.to.virtual.node.in.empty.object = 123;

console.log(JSON.stringify(path));
// {"to":{"virtual":{"node":{"in":{"empty":{"object":123}}}}}}


Динамический вызов конструктора


Имеем:
var obj = new F(...args)


Но хотим уметь динамически вызывать конструктор и создавать объект. Для этого есть Reflect.construct:
var obj = Reflect.construct(F, args)


Может понадобиться для использования в фабриках (ООП гайз поймут). Пример:
// Old method
function Greeting(name) { this.name = name }
Greeting.prototype.greet = function() { return `Hello ${this.name}` }

function greetingFactory(name) {
   var instance = Object.create(Greeting.prototype);
   Greeting.call(instance, name);
   return instance;
}

var obj = greetingFactory('Tuturu');
obj.greet();


Как такое пишется в 2017 году:
class Greeting {
   constructor(name) { this.name = name }
   greet() { return `Hello ${this.name}` }
}

const greetingFactory = name => Reflect.construct(Greeting, [name]);

const obj = greetingFactory('Tuturu');
obj.greet();


Повторяем поведение jQuery


Следующая строка показывает как можно сделать jQuery в 2 строки:
const $ = document.querySelector.bind(document);
Element.prototype.on = Element.prototype.addEventListener;


Удобно, если нужно что-то быстро наваять без зависимостей, а писать длинные нативные конструкции лень. Но в этой реализации есть минус — выбрасывает исключение при работе с null:
console.log( $('some').innerHTML );
error TypeError: Cannot read property 'innerHTML' of null


Используя Proxy и Reflect можем переписать этот пример:
const $ = selector =>
  new Proxy(
    document.querySelector(selector)||Element,
    { get: (target, key) => Reflect.get(target, key) }
   )
;


Теперь при попытке обращения к null свойствам просто будем получать undefined:
console.log( $('some').innerHTML ); // undefined


Так почему же надо использовать Reflect?


Reflect API более удобен при обработке ошибок. К примеру, всем знакома инструкция:
Object.defineProperty(obj, name, desc)

В случае неудачи будет выброшено исключение. А вот Reflect не генерит исключений на все подряд, а умеет возвращать булев результат:
try {
   Object.defineProperty(obj, name, desc);
   // property defined successfully
} catch (e) {
   // possible failure (and might accidentally catch the wrong exception)
}
/* --- OR --- */
if (Reflect.defineProperty(obj, name, desc)) {
   // success
} else {
   // failure
}


Это позволяет обрабатывать ошибки через условия, а не try-catch. Пример применения Reflect API с обработкой ошибки:
try {
   var foo = Object.freeze({bar: 1});
   delete foo.bar;
} catch (e) {}


А теперь можно писать так:
var foo = Object.freeze({bar: 1});
if (Reflect.deleteProperty(foo, 'bar')) {
   console.log('ok');
} else {
   console.log('error');
}


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

Некоторые записи выходят короче


Без лишних слов:
Function.prototype.apply.call(func, obj, args)
/* --- OR --- */
Reflect.apply.call(func, obj, args)


Разница в поведении


Пример без слов:
Object.getPrototypeOf(1); // undefined
Reflect.getPrototypeOf(1); // TypeError


Вроде бы все понятно. Делаем выводы, что лучше. Reflect API более логичный.

Работа с объектами с пустым прототипом


Дано:
const myObject = Object.create(null);
myObject.foo = 123;

myObject.hasOwnProperty === undefined; // true

// Поэтому приходится писать так:
Object.prototype.hasOwnProperty.call( myObject, 'foo' ); // true


Как видите, мы уже не имеем методов рефлексии, например, hasOwnProperty. Поэтомы мы либо пользуемся старым способом, обращаясь к прототипу базового класса, либо обращаемся к Reflect API:
Reflect.ownKeys(myObject).includes('foo') // true


Выводы


Reflect API — это результат рефакторинга. В этом неймспейсе содержатся функции рефлексии, которые раньше были зашиты в базовые классы Object, Function… Изменено поведение и обработка ошибок. В будущем этот неймспейс будет расширяться другими рефлективными инструментами. Так же Reflect API можно считать неотъемлемой частью при работе с Proxy (как видно из примеров выше).
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334546/


Метки:  

Комплексное руководство по онлайн маркетингу. День 67. Ретаргетинг и с чем его едят

Вторник, 01 Августа 2017 г. 06:00 + в цитатник
Так сложилось, что между первым и вторым днём из обещанного цикла об онлайн-маркетинге образовалась дыра в 66 дней. Но оставим её на совести невероятного скачка биткоина, из-за которого у меня от волнения потели ладошки и еще более невероятного аукцион щедрости от Apple, который решил устроить обмен 15 дюймовых Macbook Pro 2012 года на модели 2015/2016 и даже 2017 годов. И несмотря на то, что прямо сейчас я активно занимаюсь подготовкой к запуску трёх новых онлайн-проектов, обещаю, что следующие публикации будут выходить намного чаще.

А пока — давайте оставим лирическое отступление, и перейдем под кат, где плотно поговорим о ретаргетинге. Вернее о том, как повальное большинство интернет-предпринимателей теряет деньги на ровном месте!
image

Я на 100 процентов уверен, что практически каждый из вас как минимум раз в жизни сталкивался с пикселем ретаргетинга; активные пользователи Интернета, взаимодействуют с ними по несколько раз в день. Возможно, вы замечали, как после посещения определенного сайта у вас в соцсетях, на каких-либо форумах, других сайтах, или даже прямиком в «Google» или «Яндексе» - повсюду появляются объявления, непосредственно связаные с сайтом, продуктом или услугой, которую вы видели всего пару дней, а может быть и вовсе пару минут назад. Безусловно, аудитория этого сайта склонна считать, что подобные техники (абсолютно белый и легальный инструмент) — дело рук дьявола, которое невроетятно вредит пользователю. Однако цифры говорят об обратном. Подумайте сами, неужели пользователи настолько глупы, чтобы с завидной регулярностью совершать покупки, которые им вредят?

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

Но давайте обо всём по порядку!

Что такое ретаргетинг?


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

С технической точки зрения, это выглядит как-то так:
image

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

Но зачем мне это?


Что достигается с помощью этого инструмента? Все очень просто: если рассматривать среднестатистического пользователя, то человек, который еще совсем недавно взаимодействовал с вашим веб-сайтом или посадочной страницей, гораздо более склонен к покупке. В идеале вы можете построить для него так называемую (будь неладно это выражение) воронку продаж,
image
благодаря которой у вас появится возможность провести пользователя по целой последовательности страниц, постоянно открывая для него новые “порции” информации, чьей целью является борьба с возражениями/знакомство с продуктом или услугой/предложение апселла, о которых я упоминал в прошлой статье, или что-то еще.

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

Но что же происходит на практике?


Недавно я проанализировал СТО лендингов, которые находились на первых позициях в контекстной рекламе от Google по запросам: установка окон в Москве, установка видеонаблюдения, ремонт ноутбуков, одежда для беременных, ремонт квартир в Москве и организация мероприятий в Москве. Совершенно нерепрезентативная и маленькая выборка (как статистик вам говорю), тем не менее, я был настолько поражен, что решил остановиться, чтобы не расстроиться еще сильнее. Из вышеуказанных лендингов, лишь на 12 установлен пиксель ретаргетинга ВК и на 10 — Tag manager от Гугл (будем считать, что в каждом из тэг менеджеров есть пиксель ВК, хотя это не так). Потенциально лишь 22% сайтов в высококонкурентных нишах используют возможности ретаргетинга.

Его не было обнаружено здесь:
image

Ни здесь:

image

И ни здесь:

image

Более того, а здесь даже ЛЕНДИНГА не было обнаружено, не говоря об аналитике:

image

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

Прежде всего необходимо перейти в рекламный кабинет. Для этого нужно перейти в пункт Реклама на вашей странице, или создать рекламный кабинет, если таковой отсутствует, по адресу vk.com/ads/targeting

image

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

image

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

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

image

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

image

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

image

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

image

Все, что вам остается, так это задать ПОНЯТНОЕ название своей аудитории, выбрать источник (в данном случае остановимся на пикселе ретаргетинга с сайта, о файле — поговорим позже) и задать набор правил.

Что касается правил, то тут есть две основные группы настроек:
  • первая фокусируется на местоположении (все посетители сайта, или посетители отдельных страниц)
  • вторая — на периодичности (за все время, за последние 3/7/14/30/90/180 дней)


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

Играясь с настройками данных параметров (полями url содержит, совпадает или регулярные выражения, а также датами последних заходов на сайт) вы открываете для себя огромное поле действий, которое при определенном уровне логики и креативности позволит построить вам невероятные “воронки” продаж.

До встречи в следующем выпуске!

P.S. Помимо пикселя ВК стоит установить пиксель Фейсбука, скрипт Яндекс Метрики и Гугл Аналитикс (но об этом позже), и, возможно, счетчик МайТаргета

Маркетинг 101. Комплексное руководство о маркетинге


Общие концепции




Социальные сети




Разное


Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334588/


Метки:  

Microservices и Модель Актера (Actor Model)

Вторник, 01 Августа 2017 г. 01:49 + в цитатник



Доклад посвящен:
  • Пользовательскому интерфейсу ориентированному на задачи (Task Based UI)
  • CQRS (Command/Query Responsibility Segregation)
  • Микросервисы
  • Закон Конвея и его влияние на примере организации команд в Magento
  • Fine-grained сервисы vs Coarse-grained сервисы
  • Синхронность vs Асинхронность
  • Модель Актера (Actor Model)

Вторая часть доклада находится под хабракатом.



Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334586/


Как настроить Travis CI для проекта .NET Core + PostgreSQL

Понедельник, 31 Июля 2017 г. 23:41 + в цитатник

Я расскажу о том, как настроить автоматический запуск модульных тестов в сервисе Travis CI для .NET Core проекта, в котором используется PostgreSQL.


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



У меня есть хобби-проект — инструмент для версионной миграции БД на .NET Core. Он умеет работать с несколькими СУБД, в том числе, с PostgreSQL. В проекте есть некоторое количество тестов (xUnit), которым для работы тоже нужен PostgreSQL.


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


  • было неясно, как скрестить Travis CI и .NET Core;
  • непонятно, как настроить PostgreSQL для тестов.

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


Что такое Travis CI


Travis CI — это continuous integration сервис для проектов на Github. Когда вы коммитите что-то в репозиторий, Travis CI может автоматически выполнять разные полезные действия. Например, он может запускать модульные тесты и линтеры кода. Я буду называть эти полезные действия словом "сборка" ("build").


Чтобы настроить Travis CI для своего репозитория, нужно указать адрес репозитория в веб-интерфейсе Travis CI и положить в корень проекта файл .travis.yml с настройками сборки.


Настройка Travis CI


Первое, что нужно сделать — залогиниться на сайте https://travis-ci.org, используя свой GitHub аккаунт. После этого вы увидите список всех своих репозиториев. Нажмите на переключатель напротив репозитория, для которого нужно включить интеграцию с Travis:



Далее перейдите в настройки выбранного репозитория. Здесь вы можете настроить, в каких случаях нужно запускать сборку. Я указал, что сборку нужно запускать при каждой операции Push в репозиторий, а также при создании или изменении Pull request. Кроме того, я указал, что сборку нужно запускать только если в корне репозитория есть конфигурационный файл .travis.yml.



Пример .travis.yml для .NET Core


Следующий шаг — добавление в репозиторий файла .travis.yml с настройками сборки. Для сборки проекта на .NET Core файл .travis.yml будет выглядеть примерно так:


language: csharp  
sudo: required  
dist: trusty  
mono: none
dotnet: 2.0.0-preview2-006497
before_script:
  - dotnet restore
script:  
  - dotnet test ./ThinkingHome.Migrator.Tests -c Release -f netcoreapp2.0

Давайте разберемся, что здесь написано:


  • mono: none — этот параметр задает версию Mono, которую нужно использовать для сборки. Т.к. мы собираем проект для .NET Core, выключаем Mono, чтобы не тратить время на инициализацию.
  • dotnet: 2.0.0-preview2-006497 — здесь мы задаем нужную нам версию .NET Core. Обратите внимание, нужно указывать версию SDK, а не версию Runtime.
  • before_script: — команды, которые нужно выполнить до начала сборки. У нас здесь пока только одна команда dotnet restore (установить необходимые пакеты из NuGet). Здесь можно написать несколько команд, каждую на отдельной строке.
  • script: — основные команды сборки. В нашем случае, это запуск тестов, которые находятся в проекте ThinkingHome.Migrator.Tests. При запуске будет использована конфигурация Release и целевой фреймфорк netcoreapp2.0. Опять же, если нужно выполнить несколько команд, пишем каждую на отдельной строке.

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


git add .travis.yml  
git commit -m "Add travis config file"  
git push

Через несколько секунд видим, что Travis увидел наши изменения и запустил сборку. Вы можете посмотреть подробную информацию о выполнении сборки, в том числе всё, что было выведено в консоль. Видим, что был установлен .NET Core и запущены тесты.



Также видим, что тесты упали, т.к. они обращаются к БД, которая им недоступна.




Подключение PostgreSQL


Давайте подключим PostgreSQL в нашу сборку. Для этого добавим в .travis.yml раздел services с записью postgresql, а также добавим в раздел before_script команды для создания БД для тестов с помощью утилиты psql.


services:
  - postgresql
before_script:
  - psql -c "CREATE DATABASE migrations;" -U postgres
  ...

По умолчанию доступна БД postgres, к которой можно подключиться от имени пользователя postgres без пароля. Чтобы не зависеть от этих настроек по умолчанию, создадим для тестов отдельного пользователя и БД. Если нужно что-то сделать с новой БД, не забываем указывать её название в параметре -d.


Полный текст файла .travis.yml приведен ниже:


language: csharp  
sudo: required  
mono: none
dotnet: 2.0.0-preview2-006497
dist: trusty  
services:
  - postgresql
before_script:
  - psql -c "CREATE DATABASE migrations;" -U postgres
  - psql -c "CREATE USER migrator WITH PASSWORD '123';" -U postgres  
  - psql -c 'CREATE SCHEMA "Moo" AUTHORIZATION migrator;' -U postgres -d migrations  
  - dotnet restore
script:  
  - dotnet test ./ThinkingHome.Migrator.Tests -c Release -f netcoreapp2.0

Коммитим измененный файл и пушим на удаленный сервер. Тада!!! На этот раз тесты прошли успешно!



Заключение


Отлично! Мы разобрались, как настроить Travis CI для нашего проекта на .NET Core, использующего PostgreSQL. Теперь можем добавить в readme нашего проекта бирку, показывающую результат последнего билда с помощью сервиса Shields.io.


[![Travis](https://img.shields.io/travis/thinking-home/migrator.svg)](https://travis-ci.org/thinking-home/migrator)


Спасибо за внимание!

Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/334576/


Метки:  

Поиск сообщений в rss_rss_hh_new
Страницы: 1437 ... 1074 1073 [1072] 1071 1070 ..
.. 1 Календарь