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

Поиск сообщений в 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 ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

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

Руководство по взаимопониманию между заказчиком и подрядчиком

Вторник, 12 Сентября 2017 г. 13:26 + в цитатник
zarutskiy_k сегодня в 13:26 Управление

Руководство по взаимопониманию между заказчиком и подрядчиком

    image


    Складывается ощущение, что о клиентоориентированности и стремлении к модели «Win-Win» не заявляет только ленивый. Но притом «негласная война» продолжается: исполнители хают «наглых» клиентов, осуждая их в своих уютных бложиках, а заказчики собираются в пятницу вечером в баре, чтобы выдавить слезу разочарования и пожаловаться друзьям на «непрофессионализм» очередных горе-разработчиков / -маркетологов. Каждый тянет одеяло на себя, пытаясь сделать «Win-Lose» в свою пользу. А в итоге получается «Lose-Lose». Хотя очевидно, что 95% рынка с каждой стороны вполне адекватны, у всех есть сильные стороны, достойные уважения.


    Как перестать вести эту бессмысленную «борьбу»? Разберем конкретные тезисы без воды и нудной философии.


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

    Парадигма и позиции


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



    Даже старик Google знает, что нужно не отнимать, а понимать.


    Пример:


    Клиент: «Попрошу-ка я у них скидку».


    Подрядчик: «Попробуем раздуть смету».


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


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


    Это как принцип маятника: качаешь его в одну сторону — сразу приходит ответка. Так и перекидывается ответственность. Люди создают никому не нужные границы, стараясь отстаивать свой «лагерь» (разумеется, никто не отменял жестокой реальности: рынок определяет условия, невозможно постоянно «уступать», во всем нужна умеренность).


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

    Типичная позиция «эгоистов»:


    • Главное — поменьше сделать и побольше заработать / поменьше заплатить и побольше получить.

    Очевидно ведь, что нужно как минимум одной ногой быть всегда в «лагере» / «шкуре» другой стороны. Не буду объяснять банальные вещи, идем дальше.


    Взаимоизучение


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


    Проверьте себя.


    Через что лежит путь к сердцу заказчика


    Цитаты:


    • «Платил им 4 года, последний год уже только ради интереса… Так вот проверено колешьком — КИДАЛОВО… У меня этот проект находиться в пределах операционной погрешности, если бы он был единственным, то разорился бы давно»

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


    • «Работал 1 год с этой компанией, ощущения жуткие. Выгружали базу только 4 недели, что так — не ясно, хотя работы на час максимум. Просили, звонили — жуть… В итоге, пояснил ген директору, что проще сделать самим, в итоге еле ноги унесли...»

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


    • «Не получился сайт, о котором я мечтал. Чуть-чуть не то, не в обиду, ребята!»

    Ребята может и не обидятся, но осадочек с двух сторон остался. Не выяснили / не зафиксировали «мечты» или пообещали «золотые горы». Классика.


    • «…в итоге прошло 3,5 мес. сайт не готов, сам сайт некрасивый, совершенно обычный сайт, который никак не доделают, такой сайт легко можно сделать в 4 раза!!! дешевле, обещают сделать и уже тянется и тянется, на письма часто не отвечают, менеджер **** общается плохо, много раз пишешь исправить, то что должны были исправить давным давно, каждый раз ответ «завтра завтра» и ничего… А к лету должен был быть раскрученный сайт, теперь мы останемся без клиентов к летнему сезону! Месяц назад сам директор обещал доделать сайт через десять дней, прошел месяц и НИ ОДНОГО сдвига!»

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


    • «Шарлатаны! Наши меня по какой-то базе, скорее холодный обзвон. Предложили продвинуть сайт за 2 месяца, занимаюсь установкой окон. Через 2 месяца ни звонков ни продаж. Звоню выяснять в чем дело, говорят поисковики поменяли там какие-то фильтры и теперь мой сайт в **** и они тут не причем Ругаться не стал, ушел в ***-***** сказали что мой сайт забанили поисковики»

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


    А как завоевать сердце подрядчика?


    Ради справедливости приведем пример и другой точки зрения:


    • «Один клиент «купил» у другого сайт (оба мои знакомые и бывшие партнеры друг друга) и поручил нам помочь с переоформлением домена — подготовить письма регистраторам, проинструктировать их, ничего сложного, в общем. Только из-за занятости и тёрок между знакомыми процесс затянулся на год (!). Продавец домена, не горит желанием его передавать. На созваны между ними, нашими сотрудниками потрачено в 20 раз больше оплаченного времени. А сегодня был апогей, продавец пишет нашему менеджеру: предлагаю сделку, я еду к регистратору, а вы мне правки на сайт вносите. Лол, короче»

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


    А еще есть и другие стороны — партнеры, подрядчики, сотрудники:


    • «Я работала в этой организации-отвратительно! мы клиентов как только они проплатилт сатвили в чёрный список, если как говорил ***** *****-они ,, надоедали звонками,, а почему надоедали?? а потому что ничего они не делали… коллектив менялся через каждые 3 месяца-зарплату не выдавали!!! кстати этой фирмы юридически уже нет… он все деньги ., очищал,, разводя их по многим счетам...»

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


    • «Лохотрон. Написал им текстов на штуку рублей, причем писал в срок, несмотря на температуру под 39!!! Одобрили, все хорошо, говорят. Потом тянули с оплатой. То у них бухгалтерия переучет или чет такое делает, то деваха-менеджер болеет, то еще какая-нибудь лажа, которая меня, в принципе, интересовать не должна».

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


    Ожидания


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


    Подтекст


    Есть лексика — это всего лишь набор букв. А есть смысл — он может быть гораздо шире и сложнее букв. И его не всегда выносят «на блюдечке». Учитесь читать между строк. Иногда оппоненты неосознанно подталкивают нас к очень важным выводам:


    • «Нам нужен сайт, которому будут завидовать все конкуренты» = «Не спрашивайте меня сразу про бюджет, это для меня не первостепенный фактор».
    • «Мы ориентированы на задачи другого типа» = «Давайте не будем терять наше и ваше время — не мучайте нас уговорами „поработать“. Попробуйте лучше пообщаться с другими исполнителями».
    • «Нам нужно максимально дешево» = «Не ввязывайтесь в эту борьбу, если не выигрываете ценой».

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


    Но всегда учитывайте, что кто-то извлекает из слов неправильные смыслы. Либо правильные, но такие, которые вы не хотели бы «палить».


    • «Будем очень рады сотрудничеству с вами» = «Помогите, у нас острая нехватка работы».
    • «Мне нужно посоветоваться» = «Замучаем правками и будем просить сделать еще одну бесплатную итерацию».
    • «Очень хочу поработать у вас» = «Больше никуда не берут, ищу средства для оплаты кредита, ну и просто новую строчку в резюме».

    Как этого избежать? Думайте, перед тем как что-то озвучивать, на 3 шага вперед.


    Разберем на примере конкретного диалога:



    1. Клиент сразу показал, что некомпетентен в вопросах выбора каналов трафика. Это нормально.
    2. Ожидал узнать цену, ответ (объективный, разумеется) по вопросу необходимости SEO, ну и заодно получить какое-то впечатление по адекватности заказчика.
    3. Итог: загрузили заполнением брифа (угадайте, насколько он был заполнен), «впарили» все, по поводу чего были сомнения, и в догонку накинули таргет (настройка — в подарок, а рекламный бюджет — тоже?).

    Явно нужно обращаться к ЛОР’у — проблемы со слухом, стороны не слышат друг друга.


    Критерии


    Оценивая, мы часто неправильно расставляем приоритеты.


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


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


    • Модель работы (изучите мат. часть на примере категорий в рейтингах; например, если нужны пляски с менеджерами, то чистой воды «продакшны» вам не подойдут) — многие проекты проваливаются, потому что отсутствует понимание этой разницы.
    • Средняя оценка по работам за последний год.
    • Как устроены процессы изнутри (если правильно попросить, заказчику все покажут и расскажут в офисе).
    • Объективно слабые стороны (важно, чтобы исполнитель их сам честно называл).

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


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


    Погрешности


    Учитесь отличать их от ошибок. Даже при автоматизированном измерении показателей на фабричном производстве иногда допускаются отклонения в ± 2%. Что уж говорить о человеческом факторе у нас. Просрочка на 5 минут, лишний пиксель, недостаток 0,5 часов в отчете — при больших масштабах работ подобные цифры практически не имеют значения. Если обе стороны будут заострять внимание на мелочах, то потеряют в действительно важных факторах.


    «Нет» стереотипам


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


    «А сколько у вас стоит эта услуга?»


    «Ну это зависит от ваших требований»


    «Нам как всем, хотя бы примерную стоимость назовите»


    «Вы хотите на шаблоне или индивидуальный дизайн?»


    — [ Мысленно про себя: «Откуда мне знать, я же не профессионал. Сколько меня еще будут здесь мучить?» ] «Не знаю, пусть будет индивидуальный»


    «А с корзиной + онлайн-оплатой будем делать или просто каталог + форма для обратной связи?»


    «Уф, все понятно. Мы вам перезвоним»


    Нет повести печальнее на свете, чем повесть о расстроенном клиенте.


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


    Поэтому call-to-action следующий: не слушайте шаблонные советы (даже вышеозвученные мной :) ). В продажах, управлении проектами и пр. на общие вопросы аля «Как?» можно ответить, только перечислив 100 500 подпунктов «Если / В случае…». В этом-то и прелесть нашей сферы — здесь всегда много альтернативных подходов.
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/337724/


    Метки:  

    [Из песочницы] Как понять, что ваша предсказательная модель бесполезна

    Вторник, 12 Сентября 2017 г. 13:21 + в цитатник
    cointegrated сегодня в 13:21 Разработка

    Как понять, что ваша предсказательная модель бесполезна

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


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


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


    Какие модели и для чего?


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


    Я не рассматриваю:


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

    Я буду говорить об алгоритмах обучения с учителем, т.е. таких, которые "увидели" много примеров пар (X,Y), и теперь могут для любого X сделать оценку Y. X — это информация, известная на момент прогноза (например, заполненная клиентом заявка на кредит). Y — это заранее неизвестная информация, необходимая для принятия решения (например, будет ли клиент своевременно платить по кредиту). Само решение может приниматься каким-то простым алгоритмом вне модели (например, одобрять кредит, если предсказанная вероятность неплатежа не выше 15%).


    Аналитик, создающий модель, как правило, опирается на метрики качества прогноза, например, среднюю ошибку (MAE) или долю верных ответов (accuracy). Такие метрики позволяют быстро понять, какая из двух моделей предсказывает точнее (и нужно ли, скажем, заменять логистическую регрессию на нейросеть). Но на главный вопрос, а насколько модель полезна, они дают ответ далеко не всегда. Например, модель для кредитного скоринга очень легко может достигнуть точности 98% на данных, в которых 98% клиентов "хорошие", называя "хорошими" всех подряд.


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


    Проблемы


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


    1. Модель вообще не нужна


    Мы как-то строили модель, предсказывающую крепость пива после дображивания (на самом деле, это было не пиво и вообще не алкоголь, но суть похожа). Задача ставилась так: понять, как параметры, задаваемые в начале брожения, влияют на крепость финального пива, и научиться лучше управлять ей. Задача казалась весёлой и перспективной, и мы потратили не одну сотню человеко-часов на неё, прежде чем выяснили, что на самом-то деле финальная крепость не так уж и важна заказчику. Например, когда пиво получается 7.6% вместо требуемых 8%, он просто смешивает его с более крепким, чтобы добиться нужного градуса. То есть, даже если бы мы построили идеальную модель, это принесло бы прибыли примерно нисколько.


    Эта ситуация звучит довольно глупо, но на самом деле случается сплошь и рядом. Руководители инициируют machine learning проекты, "потому что интересно", или просто чтобы быть в тренде. Осознание, что это не очень-то и нужно, может прийти далеко не сразу, а потом долго отвергаться. Мало кому приятно признаваться, что время было потрачено впустую. К счастью, есть относительно простой способ избегать таких провалов: перед началом любого проекта оценивать эффект от идеальной предсказательной модели. Если бы вам предложили оракула, который в точности знает будущее наперёд, сколько бы были бы готовы за него заплатить? Если потери от брака и так составляют небольшую сумму, то, возможно, строить сложную систему для минимизации доли брака нет необходимости.


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


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


    2. Модель откровенно слабая


    Даже если идеальная модель способна принести большую пользу, не факт, что вам удастся к ней приблизиться. В X может просто не быть информации, релевантной для предсказания Y. Конечно, вы редко можете быть до конца уверены, что вытащили из X все полезные признаки. Наибольшее улучшение прогноза обычно приносит feature engineering, который может длиться месяцами. Но можно работать итеративно: брейншторм — создание признаков — обучение прототипа модели — тестирование прототипа.


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


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


    3. Модель невозможно внедрить


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


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


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


    4. Модель очень трудно внедрить


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


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


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


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


    Разработчикам и админам продуктивной системы полезно с начала проекта задуматься о том, в какой среде будет работать модель. В их силах сделать так, чтобы код data scientist'a мог выполняться в ней с минимумом изменений. Если вы внедряете предсказательные модели регулярно, стоит один раз создать (или поискать у внешних провайдеров) платформу, обеспечивающую управление нагрузкой, отказоустойчивость, и передачу данных. На ней любую новую модель можно запустить в виде сервиса. Если же сделать так невозможно или нерентабельно, полезно будет заранее обсудить с разработчиком модели имеющиеся ограничения. Быть может, вы избавите его и себя от долгих часов ненужной работы.


    5. При внедрении происходят глупые ошибки


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


    Такие нарушения бизнес-логики чаще всего происходят не в самой модели, а или при подготовке признаков, или, чаще всего, при применении прогноза. Если они менее очевидны, чем ошибка в знаке, то их можно не находить очень долго. Всё это время модель будет работать хуже, чем ожидалось, без видимых причин. Стандартный способ предупредить это — делать юнит-тесты для любого кусочка стратегии принятия решений. Кроме этого, нужно тестировать всю систему принятия решений (модель + её окружение) целиком (да-да, я повторяю это уже три раздела подряд). Но это не спасёт от всех бед: проблема может быть не в коде, а в данных. Чтобы смягчить такие риски, серьёзные нововведения можно запускать не на всём потоке данных (в нашем случае, заявок на кредиты), а на небольшой, но репрезентативной его доле (например, на случайно выбранных 10% заявок). A/B тесты— это вообще полезно. А если ваши модели отвечают за действительно важные решения, такие тесты могут идти в большом количестве и подолгу.


    6. Модель нестабильная


    Бывает, что модель прошла все тесты, и была внедрена без единой ошибки. Вы смотрите на первые решения, которые она приняла, и они кажутся вам осмысленными. Они не идеальны — 17 партия пива получилась слабоватой, а 14 и 23 — очень крепкими, но в целом всё неплохо. Проходит неделя-другая, вы продолжаете смотреть на результаты A/B теста, и понимаете, что слишком крепких партий чересчур много. Обсуждаете это с заказчиком, и он объясняет, что недавно заменил резервуары для кипячения сусла, и это могло повысить уровень растворения хмеля. Ваш внутренний математик возмущается "Да как же так! Вы мне дали обучающую выборку, не репрезентативную генеральной совокупности! Это обман!". Но вы берёте себя в руки, и замечаете, что в обучающей выборке (последние три года) средняя концентрация растворенного хмеля не была стабильной. Да, сейчас она выше, чем когда-либо, но резкие скачки и падения были и раньше. Но вашу модель они ничему не научили.


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


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


    image


    генерация картинки в python
    # coding: utf-8
    # настраиваем всё, что нужно настроить
    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib import rc
    rc('font', family='Verdana')
    from sklearn.ensemble import GradientBoostingRegressor
    from sklearn.neural_network import MLPRegressor
    from sklearn.linear_model import LinearRegression
    
    # генерируем данные
    np.random.seed(2)
    n = 15
    x_all = np.random.randint(20,size=(n,1))
    y_all = x_all.ravel() + 10 * 0.1 + np.random.normal(size=n)
    fltr = ((x_all<=15)&(x_all>=5))
    x_train = x_all[fltr.ravel(),:]
    y_train = y_all[fltr.ravel()]
    x_new = x_all[~fltr.ravel(),:]
    y_new = y_all[~fltr.ravel()]
    x_plot = np.linspace(0, 20)
    
    # обучаем модели
    m1 = GradientBoostingRegressor(
        n_estimators=10, 
        max_depth = 3,
        random_state=42
        ).fit(x_train, y_train)
    m2 = MLPRegressor(
        hidden_layer_sizes=(10), 
        activation = 'logistic',
        random_state = 42, 
        learning_rate_init = 1e-1, 
        solver = 'lbfgs',
        alpha = 0.1
        ).fit(x_train, y_train)
    m3 = LinearRegression().fit(x_train, y_train)
    
    # Отрисовываем графики
    plt.figure(figsize=(12,4))
    title = {1:'Обучающая выборка и прогнозы на ней', 
        2:'Точки, не вошедшие в обучающую выборку'}
    for i in [1,2]:
        plt.subplot(1,2,i)
        plt.scatter(x_train.ravel(), y_train, lw=0, s=40)
        plt.xlim([0, 20])
        plt.ylim([0, 25])
        plt.plot(x_plot, m1.predict(x_plot[:,np.newaxis]), color = 'red')
        plt.plot(x_plot, m2.predict(x_plot[:,np.newaxis]), color = 'green')
        plt.plot(x_plot, m3.predict(x_plot[:,np.newaxis]), color = 'orange')
        plt.xlabel('x')
        plt.ylabel('y')
        plt.title(title[i])
        if i == 1:
            plt.legend(['Бустинг', 'Нейронка', 'Линейная модель'], 
                loc = 'upper left')
        if i == 2:
            plt.scatter(x_new.ravel(), y_new, lw=0, s=40, color = 'black')
    plt.show()

    Некоторые более простые модели (в том числе линейные) экстраполируют лучше. Но как понять, что они вам нужны? На помощь приходит кросс-валидация (перекрёстная проверка), но не классическая, в которой все данные перемешаны случайным образом, а что-нибудь типа TimeSeriesSplit из sklearn. В ней модель обучается на всех данных до момента t, а тестируется на данных после этого момента, и так для нескольких разных t. Хорошее качество на таких тестах даёт надежду, что модель может прогнозировать будущее, даже если оно несколько отличается от прошлого.


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


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


    7. Обучающая выборка действительно не репрезентативна


    Бывает, что источником нерепрезентативности выборки являются не изменения во времени, а особенности процесса, породившего данные. У банка, где я работал, раньше существовала политика: нельзя выдавать кредиты людям, у которых платежи по текущим долгам превышают 40% дохода. С одной стороны, это разумно, ибо высокая кредитная нагрузка часто приводит к банкротству, особенно в кризисные времена. С другой стороны, и доход, и платежи по кредитам мы можем оценивать лишь приближённо. Возможно, у части наших несложившихся клиентов дела на самом деле были куда лучше. Да и в любом случае, специалист, который зарабатывает 200 тысяч в месяц, и 100 из них отдаёт в счёт ипотеки, может быть перспективным клиентом. Отказать такому в кредитной карте — потеря прибыли. Можно было бы надеяться, что модель будет хорошо ранжировать клиентов даже с очень высокой кредитной нагрузкой… Но это не точно, ведь в обучающей выборке нет ни одного такого!


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


    Если бы подобного потока чистых данных не было, то убедить менеджмент, что модель нормально ранжирует людей с нагрузкой больше 40%, было бы сложно. Наверное, я бы обучил её на выборке с нагрузкой 0-20%, и показал бы, что на тестовых данных с нагрузкой 20-40% модель способна принять адекватные решения. Но узенькая струйка нефильтрованных данных всё-таки очень полезна, и, если цена ошибки не очень высока, лучше её иметь. Подобный совет даёт и Мартин Цинкевич, ML-разработчик из Гугла, в своём руководстве по машинному обучению. Например, при фильтрации электронной почты 0.1% писем, отмеченных алгоритмом как спам, можно всё-таки показывать пользователю. Это позволит отследить и исправить ошибки алгоритма.


    8. Прогноз используется неэффективно


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


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


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


    Заключение


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


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

    Высоких вам ROC-AUC и Эр-квадратов!

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

    https://habrahabr.ru/post/337722/


    Метки:  

    10 лет Computer Science клубу

    Вторник, 12 Сентября 2017 г. 13:05 + в цитатник
    avsmal сегодня в 13:05 Разработка

    10 лет Computer Science клубу



      В этом году Computer Science клубу в Санкт-Петербурге исполняется 10 лет. С 2007 года в клубе проходят открытые лекции и курсы, где любой желающий может познакомиться с классическими результатами, современным положением дел и открытыми задачами в различных областях computer science. Вход на все лекции свободный, регистрация не требуется. Слайды и видеозаписи всех прошедших лекций доступны с сайта клуба.


      Поздравить клуб с юбилеем приедут сотрудники следующих организаций: Академический университет, Математический институт Стеклова в Санкт-Петербурге, Санкт-Петербургский государственный университет, Яндекс, JetBrains, Montpellier University, Northwestern University, Toyota Technological Institute at Chicago, University of Bergen, University of California at San Diego, Yahoo Research. Они прочитают мини-курсы по следующим темам.


      Теоретические:


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

      Прикладные:


      • биоинформатика;
      • язык программирования Kotlin;
      • эффективное использование С++;
      • машинное обучение и рекомендательные системы;
      • искусственный интеллект и беспилотные автомобили;
      • нейронные сети и др.

      (подробнее на сайте клуба).

      Откроет осенний семестр в клубе академик РАН Юрий Владимирович Матиясевич. Он прочтёт две лекции, посвящённые алгоритму Тарского.

      Присоединяйтесь к клубу: сайт клуба, группа ВК, группа FB.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/337720/


      Метки:  

      Книга «Аудит безопасности информационных систем»

      Вторник, 12 Сентября 2017 г. 12:45 + в цитатник

      Метки:  

      Как перейти на gRPC, сохранив REST

      Вторник, 12 Сентября 2017 г. 12:05 + в цитатник
      divan0 сегодня в 12:05 Разработка

      Как перейти на gRPC, сохранив REST

      • Tutorial

      Многие знакомы с gRPC — открытым RPC-фреймворком от Google, который поддерживает 10 языков и активно используется внутри Google, Netflix, Kubernetes, Docker и многими другими. Если вы пишете микросервисы, gRPC предоставляет массу преимуществ перед традиционным подходом REST+JSON, но на существующих проектах часто переход не так просто осуществить из-за наличия уже использующихся REST-клиентов, которые невозможно обновить за раз. Нередко общаясь на тему gRPC можно услышать "да, мы у нас в компании тоже смотрим на gRPC, но всё никак не попробуем".


      Что ж, этой проблеме есть хорошее решение под названием grpc-rest-gateway, которое занимается именно этим — автогенерацией REST-gRPC прокси с поддержкой всех основных преимуществ gRPC плюс поддержка Swagger. В этой статье я покажу на примере как это выглядит и работает, и, надеюсь, это поможет и вам перейти на gRPC, не теряя существующие REST-клиенты.



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


      • бекенд (Go/Java/C++/node.js/whatever) и фронтенд (JS/iOS/Kotlin/Java/etc) общаются с помощью REST API
      • микросервисы (на разных языках) общаются между собой также через REST-подобный API (протокол HTTP и JSON для сериализации)

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


      Чем плох REST?


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


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


      Далее, при REST подходе, у вас есть чересчур много сущностей, которые несут смысл — метод HTTP (GET/POST/PUT/DELETE), URI запроса (/users, /user/1), тело запроса ({id: 1}) плюс заголовки (X-User-ID: 1). Всё это добавляет излишнюю сложность и возможность неверной интерпретации, что превращается в большую проблему по мере того, как API начинает использоваться между различными сервисами, которые пишут различные команды и синхронизация всех этих сущностей начинает занимать значительную часть времени команд.


      Это приводит нас к следующей проблеме — сложности декларативного описания интерфейсов API и описания типов данных. OpenAPI Specification (известное как Swagger), RAML и API Blueprint частично решают эту проблему, но делают это ценой добавления другой сложности. Кто-то пишет YAML файлы ручками для каждого нового запроса, кто-то использует web-фрейморки с автогенерацией, раздувая код описаниями параметров и типов запроса, и поддержка swagger-спецификации в синхронизации с реальной реализацией API всё равно лежит на плечах ответственных разработчиков, что отнимает время от решения, собственно, задач, которые эти API должны решать.


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


      gRPC


      gRPC решает эти проблемы кодогенерацией и декларативным языком описания типов и RPC-методов. По-умолчанию используется Google Protobuf 3 в качестве IDL, и HTTP/2 для транспорта. Кодогенераторы есть по 10 языков — Go, Java, C++, Python, Ruby, Node.js, C#, PHP, Android.Java, Objective-C. Есть также пока неофициальные реализации для Rust, Swift и прочих.


      В gRPC у вас есть только одно место, где вы определяете, как будут именоваться поля, как называться запросы, что принимать и что возвращать. Это описывается в .proto файле. Например:


      syntax = "proto3";
      
      package library;
      
      service LibraryService {
        rpc AddBook(AddBookRequest) returns (AddBookResponse)
      }
      
      message AddBookRequest {
        message Author {
          string name = 1;
          string surname = 2;
        }
        string isbn = 1;
        repeated Author authors = 2;
      }
      
      message AddBookResponse {
        int64 id = 1;
      }

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


      Если вы когда-либо разруливали конфликты в названиях полей вроде UserID vs user_id, вам понравится работать с gRPC.


      Но я не буду сильно подробно останавливаться на принципах работы с gRPC, и перейду к вопросу, что же делать, если вы хотите использовать gRPC, но у вас есть клиенты, которые всё ещё должны работать через REST API, и их не просто будет перевести/переписать на gRPC. Это особенно актуально, учитывая, что официальной поддержки gRPC в браузере пока нет (JS только Node.js официально), и реализация для Swift также пока не в списке официальных.


      GRPC REST Gateway


      Проект grpc-gateway, как и почти всё в grpc-экосистеме, реализован в виде плагина для protoc-компилятора. Он позволяет добавить аннотации к rpc-определениям в protobuf-файле, который будут описывать REST-аналог этого метода. Например:


      import "google/api/annotations.proto";
      ...
      service LibraryService {
        rpc AddBook(AddBookRequest) returns (AddBookResponse) {
          option (google.api.http) = {
            post: "/v1/book"
            body: "*"
          };
        }
      }

      После запуска protoc с указанным плагином, вы получите автосгенерированный код, который будет прозрачно перенаправлять POST HTTP запросы на указанный URI на реальный grpc-сервер и также прозрачно конвертировать и отправлять ответ.


      Тоесть формально, это API Proxy, который запущен, как отдельный сервис и делает прозрачную конвертацию REST HTTP запросов в gRPC коммуникацию между сервисами.


      Пример использования


      Давайте, продолжим пример выше — скажем, наш сервис работы с книгами, должен уметь работать со старым iOS-фронтендом, который пока умеет работать только по REST HTTP. Другие сервисы вы уже перевели на gRPC и наслаждаетесь меньшим количеством головной боли при росте или изменениях ваших API. Добавив выше указанные аннотации, создаём новый сервис — например rest_proxy и в нём автогенерируем код обратного прокси:


      protoc -I/usr/local/include -I. \
        -I$GOPATH/src \
        -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
        --grpc-gateway_out=logtostderr=true:. \
        library.proto

      Код самого сервиса может выглядеть вот как-нибудь так:


      import (
          "github.com/myuser/rest-proxy/library"
      )
      
      var main() {
          gw := runtime.NewServeMux(muxOpt)
          opts := []grpc.DialOption{grpc.WithInsecure()}
      
          err := library.RegisterLibraryServiceHandlerFromEndpoint(ctx, gw, "library-service.dns.name", opts)
          if err != nil {
              log.Fatal(err)
          }
      
          mux := http.NewServeMux()
          mux.Handle("/", gw)
          log.Fatal(http.ListenAndServe(":80", mux))
      }

      Этот код запустит наш прокси на 80-м порту, и будет направлять все запросы на gRPC сервер, доступный по library-service.dns.name. RegisterLibraryServiceHandlerFromEndpoint это автоматически сгенерированный метод, который делает всю магию.


      Очевидно, что этот прокси может служить входной точкой для всех остальных ваших сервисов на gRPC, которым нужен fallback в виде REST API — просто подключаете остальные автосгенерированные пакаджи и регистрируете их на тот же gw-объект:


          err = users.RegisterUsersServiceHandlerFromEndpoint(ctx, gw, "users-service.dns.name", opts)
          if err != nil {
              log.Fatal(err)
          }

      и так далее.


      Преимущества


      Автосгенерированный прокси поддерживает автоматический реконнект к сервису, с экспоненциальной backoff-задержкой, как и в обычных grpc-сервисах. Аналогично, поддержка TLS есть из коробки, таймаутов и всё, что доступно в grpc-сервисах, доступно и в прокси.


      Middlewares


      Отдельно хочется написать про возможность использования т.н. middlewares — обработчиков запросов, которые автоматически должны срабатывать до или после запроса. Типичный пример — ваши HTTP запросы содержат специальный заголовок, который вы хотите передать дальше в grpc-сервисы.


      Для примера, я возьму пример со стандартным JWT токеном, которые вы хотите расшифровывать и передавать значение поля UserID grpc-сервисам. Делается это также просто, как и обычные http-middlewares:


      func checkJWT(h http.Handler) http.Handler {
          return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
              bearer := r.Header.Get("Authorization")
              ...
              // parse and extract value from token
              ...
              ctx = context.WithValue(ctx, "UserID", claims.UserID)
              h.ServeHTTP(w, r.WithContext(ctx))
          })
      }

      и заворачиваем наш mux-объект в эту middleware-функцию:


          mux.Handle("/", checkJWT(gw))

      Теперь на стороне сервисов (все gRPC-методы в Go реализации принимают первым параметром context), вы просто достаёте это значение из контекста:


      func (s *LIbrary) AddBook(ctx context.Context, req *library.AddBookRequest) (*library.AddBookResponse, error) {
      userID := ctx.Value("UserID").(int64)
      ...
      }

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


      Разумеется, ничего не ограничивает ваш rest-proxy от реализации дополнительного функционала. Это обычный http-сервер, в конце-концов. Вы можете пробросить какие-то HTTP запросы на другой legacy REST сервис:


          legacyProxy := httputil.NewSingleHostReverseProxy(legacyUrl)
          mux.Handle("/v0/old_endpoint", legacyProxy)
      

      Swagger UI


      Отдельной вишенкой в подходе с grpc-gateway есть автоматическая генерация swagger.json файла. Его можно затем использовать с онлайн UI, а можно и отдавать напрямую из нашего же сервиса.


      С помощью небольших манипуляций со SwaggerUI и go-bindata, можно добавить ещё один endpoint
      к нашему сервису, который будет отдавать красивый и, что самое важное, актуальный и автосгенерированный UI для REST API.


      Генерируем swagger.json


      protoc -I/usr/local/include -I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --swagger_out=logtostderr=true:swagger-ui/ path/to/library.proto

      Создаем handler-ы, которые будут отдавать статику и генерировать index.html (в примере статика добавляется прямо в код с помощью go-bindata):


          mux.HandleFunc("/swagger/index.html", SwaggerHandler)
          mux.Handle("/swagger/", http.StripPrefix("/swagger/", http.FileServer(assetFS())))
          ...
      
      // init indexTemplate at start
      func SwaggerHandler(w http.ResponseWriter, r *http.Request) {
          indexTemplate.Execute(w, nil)
      }

      и вы получаете Swagger UI, подобный этому, с актуальной информацией и возможностью тут же тестировать:


      Проблемы


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


      В Go для сериализации в JSON используются так называемые "тэги структур" — мета информация для полей. В encoding/json есть такой тэг omitempty — он означает, что если значение равно нулю (нулевому значению для этого типа), то его не нужно добавлять в результирующий JSON. Плагин grpc-gateway для Go именно этот тег и добавляет к структурам, что приводит иногда к неверному поведению.


      Например, у вас есть переменная типа bool в структуре, и вы отдаёте эту структуру в ответе — оба значения true и false одинаково важны в ответе, и фронтенд ожидает это поле получить. Ответ же, сгенерированный grpc-gateway будет содержать это поле, только если значение равно true, в противном случае оно просто будет пропущено (omitempty).


      К счастью, это легко решается с помощью опций конфигурации:


          customMarshaller := &runtime.JSONPb{
              OrigName:     true,
              EmitDefaults: true, // disable 'omitempty'
          }
          muxOpt := runtime.WithMarshalerOption(runtime.MIMEWildcard, customMarshaller)
          gw := runtime.NewServeMux(muxOpt)

      Ещё одним моментом, которым хотелось бы поделиться, можно назвать неочевидная семантика работы с самим protoc-компилятором. Команды вызова очень длинные, трудночитаемые, и, что самое важное, логика того, откуда берется protobuf и куда генерируется вывод (+какие директории создаются) — очень неочевидна. Например, вы хотите использовать proto-файл из другого проекта и сгенерировать каким-нибудь плагином код, положив его в текущий проект в папку swagger-ui/. Мне пришлось минут 15 перепробовать массу вариантов вызова protoc, прежде чем стало понятно, как заставить генератор работать именно так. Но, снова же, ничего нерешаемого.


      Заключение


      gRPC может ускорить продуктивность и эффективность работы с микросервис архитектурой в разы, но часто помехой становится требование обратной совместимости и поддержки REST API. grpc-gateway предоставляет простой и эффективный способ решения этой проблемы, автоматически генерируя обратный прокси сервер, транслирующий REST/JSON запросы в gRPC вызовы. Проект очень активно развивается и используется в продакшене во многих компаниях.


      Ссылки


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

      https://habrahabr.ru/post/337716/


      Метки:  

      Платформа для сбора донатов за две недели – итоги антихакатона

      Вторник, 12 Сентября 2017 г. 11:06 + в цитатник
      dimskiy сегодня в 11:06 Управление

      Платформа для сбора донатов за две недели – итоги антихакатона


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


        Под катом немного метаний между ChromeApps и Electron, впечатления от пробы Yandex SpeechKit и вообще о разработке в формате антихакатона.


        Одним дождливым летним вечером мы с товарищами решили поучаствовать в антихакатоне Яндекс.Денег. И повод был подходящий, ведь уже давно хотелось создать приложение, связанное со стрим-сервисами и процессом отправки донатов. Наша команда состояла из четырех человек: дизайнера Наумова Александра, ответственного за мобильное приложение Джавида Халилова, главного по ПК-клиентам Александра Кобрина и меня, в качестве бэкенд-разработчика и идейного вдохновителя.
        Получилось бодро – через две недели появился минимально жизнеспособный продукт «ЯСтрим».

        Зачем кому-то платить за стрим


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


        Под стримингом в этой статье понимается именно игровой стриминг. То есть когда некто транслирует в сеть свой игровой процесс и заодно все это комментирует. У популярных стримеров есть своя аудитория зрителей, которые следят за выходом новых «эпизодов» и порой присылают комментарии, послания или деньги. Разумеется, всё на добровольной основе и без какой-либо практической цели для зрителей – сугубо just for fun.

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


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


        1. позволяет пользователю с минимумом усилий перевести некую сумму стримеру;


        2. поддерживает передачу сообщения (текстового или голосового) от пользователя стримеру в процессе перевода денег;


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


        4. умеет читать пользовательские сообщения вслух в прямом эфире и содержит настройки по «запикиванию» мата и других не подходящих стримеру слов;


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

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


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


        [https://youtu.be/G3eCmfFvnQs](https://youtu.be/G3eCmfFvnQs)

        Так стример видит интерфейс ЯСтрима.


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


        Строим счастье для стримера


        Так как хотелось охватить рынок побольше, а заодно изучить что-то новое на антихакатоне, решение должно было стать кроссплатформенным – с поддержкой всех стриминговых платформ и сопутствующего ПО. В качестве основы изначально выбрали Chrome Apps, который казался почти идеальным инструментом для отладки и разработки API (особенно понравилась отладка приложений в Postman, который тоже написан под Chrome). Тогда десктопных клиентов можно было бы написать на Chrome Apps сразу под множество платформ.


        Вдохновившись лучшим, на мой взгляд, инструментом для разработки Web API – Postman в версии для Chrome – я решил использовать в качестве основы Google Chrome Apps. Написанные под эту платформу приложения работают в Google Chrome и в других браузерах на движке chromium, что позволило бы сделать легкое кроссплатформенное решение.


        Вот несколько мотивирующих факторов:


        • Google Chrome и другие браузеры на движке Chromium составляют большую часть рынка, поэтому решение стало бы «всеядным»;


        • разработка на этой платформе представляет собой типичный web frontend с некоторыми нюансами, которые помещались в книге Marc Rochkind на 240 страницах.

        Что касается бэкенда, то выбор для меня однозначен – ASP.NET. После некоторого опыта конфигурирования сервера на Ubuntu стоит один раз познакомиться с приятным UI Azure, и пути назад не останется. Последний гвоздь в наши сомнения вбила бесплатная подписка Azure на месяц, что в условиях антихакатона – самое оно.



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


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

        Когда мы определились с основными компонентами продукта (приложение для десктопа, веб-форма и мобильное приложение), наш фронт-разработчик внезапно выяснил, что Google прекращает поддержку Chrome Apps на Windows, Mac и Linux. Неловкая пауза, разочарование, негодование, принятие.


        Король умер, да здравствует король Electron


        В качестве альтернативы отлично подошел фреймворк Electron с открытым кодом. Конечно, насторожила новизна платформы, но растущая популярность компенсировала опасения. Что касается мобильного приложения, то скажем так: у меня в команде по мобильной части был только Android-разработчик. Архитектура для ЯСтрима выбрана клиент-серверная, с Microsoft SQL в качестве бэкенд-базы.


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


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


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


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

        Далее наступил этап разработки WEB API нашего сервиса, где основной задачей стало обеспечение безопасности стримеров: что если кто-то изменит настройки донатов, поменяет логотип или затеет еще что-то недоброе.


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


        • При каждом переводе в поле label вносится специальная метка, а идентификатор операции (если его удается получить) попадает в данные о донате. Это было необходимо для удобной работы с API Яндекс.Денег на стороне стримера и проверки существования перевода в платежной системе.


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


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


        • Отдельно пришлось повозиться с загрузкой фотографий, так как какого-либо опыта не было, а для простоты работы с API нужно было сделать небольшой фотохостинг.

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


        Волнительный этап сборки


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



        Пример того, что мы ловили при попытках сборки.


        Наша команда перекопала вдоль и поперек Stackoverflow и GitHub Issues, так что осталось только открывать новый топик и искать знающих людей. Так мы познакомились с разработчиком Electron и @akashnimare, который имеет подходящий опыт и был готов им поделиться. Кроме того, открыли для себя российское комьюнити Electron, куда стоит зайти любому, кто собирается использовать эту платформу.


        С хранением файлов тоже вышла заминка, из которой и родился совет уделять больше внимания фундаментам приложения. Нужно было и нам не лениться, а с самого начала подключать библиотеку electron-storage или electron-storage-json, чтобы не переписывать код, когда выяснится, что local-storage из main недоступен.


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


        const tts = new ya.speechkit.Tts({
                apikey: 'ffffffff-2222-4444-0000-1111111111',
                emotion: 'good',
                speed: 1.2
            })
        ...
            tts.speak(donate.text_data, { speaker: 'zahar' })

        Достаточно указать персональный ключ API, выбрать голос (zahar), скорость, эмоциональный окрас (good) – и готово.


        За плечами литры энергетиков и 3 недели плотной разработки


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


        Если наш опыт показался вам интересным – с удовольствием отвечу на вопросы в комментариях.


        Страница проекта «ЯСтрим».

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

        https://habrahabr.ru/post/337652/


        [Из песочницы] История 13 места на Highload Cup 2017

        Вторник, 12 Сентября 2017 г. 10:59 + в цитатник
        reatfly сегодня в 10:59 Разработка

        История 13 места на Highload Cup 2017

        Image


        11 августа компания Mail.Ru Объявила об очередном конкурсе HighloadCup для системных программистов backend-разработчиков.


        Вкратце задача стояла следующим образом: докер, 4 ядра, 4Гб памяти, 10Гб HDD, набор api, и нужно ответить на запросы за наименьшее количество времени. Язык и стек технологий неограничен. В качестве тестирующей системы выступал яндекс-танк с движком phantom.


        О том, как в таких условиях добраться до 13 места в финале, и будет эта статья.


        Постановка задачи


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


        Api представляло из себя 3 get-запроса на просто вернуть записи из базы, 2 get-запроса на агрегирование данных, 3 post-запроса на модификацию данных, и 3 post-запроса на добавление данных.


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


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

        Запросы были поделены на 3 части: сначала get-запросы по исходным данным, потом серия post-запросов на добавление/изменение данных, и последняя самая мощная серия get-запросов по модифицированным данным. Вторая фаза была очень важна, т.к. неправильно добавив или поменяв данные, можно было получить много ошибок в третьей фазе, и как результат — большой штраф. На данном этапе последняя стадия содержала линейное увеличение числа запросов от 100 до 2000rps.


        Еще одним условием было то, что один запрос — одно соединение, т.е. никаких keep-alive, но в какой-то момент от этого отказались, и все get-запросы шли с keep-alive, post-запросы были каждый на новое соединение.


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


        Поехали


        Т.к. о highload'e я знал чуть менее, чем ничего, и я до последнего надеялся, что писать свой web-сервер не придется, то первым шагом к решению задачи стал поиск подходящего web-сервера. Мой взгляд ухватился за proxygen. В целом, сервер должен был быть хорошим — кто еще знает о хайлоаде столько, сколько facebook?


        Исходный код содержит несколько примеров, как использовать этот сервер.


          HTTPServerOptions options;
          options.threads = static_cast(FLAGS_threads);
          options.idleTimeout = std::chrono::milliseconds(60000);
          options.shutdownOn = {SIGINT, SIGTERM};
          options.enableContentCompression = false;
          options.handlerFactories = RequestHandlerChain()
              .addThen()
              .build();
        
          HTTPServer server(std::move(options));
          server.bind(IPs);
        
          // Start HTTPServer mainloop in a separate thread
          std::thread t([&] () {
            server.start();
          });

        И на каждое принятое соединение вызывается метод фабрики


        class EchoHandlerFactory : public RequestHandlerFactory {
         public:
          // ...
          RequestHandler* onRequest(RequestHandler*, HTTPMessage*) noexcept override {
            return new EchoHandler(stats_.get());
          }
          // ...
        
         private:
          folly::ThreadLocalPtr stats_;
        };

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


        Сам EchoHandler должен реализовать интерфейс proxygen::RequestHandler:


        class EchoHandler : public proxygen::RequestHandler {
         public:
          void onRequest(std::unique_ptr headers)
              noexcept override;
        
          void onBody(std::unique_ptr body) noexcept override;
        
          void onEOM() noexcept override;
        
          void onUpgrade(proxygen::UpgradeProtocol proto) noexcept override;
        
          void requestComplete() noexcept override;
        
          void onError(proxygen::ProxygenError err) noexcept override;
        };

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


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


        struct Location {
          std::string place;
          std::string country;
          std::string city;
          uint32_t distance = 0;
        
          std::string Serialize(uint32_t id) const {
            std::stringstream data;
            data <<
              "{" <<
                  "\"id\":" << id << "," <<
                  "\"place\":\"" << place << "\"," <<
                  "\"country\":\"" << country << "\"," <<
                  "\"city\":\"" << city << "\"," <<
                  "\"distance\":" << distance <<
              "}";
            return std::move(data.str());
          }
        };

        Первоначальный вариант "базы данных" был такой:


        template 
        class InMemoryStorage {
        public:
          typedef std::unordered_map Map;
          InMemoryStorage();
        
          bool Add(uint32_t id, T&& data, T** pointer);
          T* Get(uint32_t id);
        private:
          std::vector> buckets_;
          std::vector bucket_indexes_;
          std::vector bucket_mutexes_;
        };
        
        template 
        InMemoryStorage::InMemoryStorage()
            : buckets_(BUCKETS_COUNT), bucket_indexes_(BUCKETS_COUNT),
              bucket_mutexes_(BUCKETS_COUNT) {
        }
        
        template 
        bool InMemoryStorage::Add(uint32_t id, T&& data, T** pointer) {
          int bucket_id = id % BUCKETS_COUNT;
          std::lock_guard lock(bucket_mutexes_[bucket_id]);
        
          Map& bucket_index = bucket_indexes_[bucket_id];
          auto it = bucket_index.find(id);
          if (it != bucket_index.end()) {
            return false;
          }
        
          buckets_[bucket_id].emplace_front(data);
          bucket_index.emplace(id, &buckets_[bucket_id].front());
          if (pointer)
            *pointer = &buckets_[bucket_id].front();
          return true;
        }
        
        template 
        T* InMemoryStorage::Get(uint32_t id) {
          int bucket_id = id % BUCKETS_COUNT;
          std::lock_guard lock(bucket_mutexes_[bucket_id]);
        
          Map& bucket = bucket_indexes_[bucket_id];
          auto it = bucket.find(id);
          if (it != bucket.end()) {
            return it->second;
          }
          return nullptr;
        }

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


        Т.к. были запросы на выборку данных, и нужно было быстро искать все места, в которых бывал пользователь, и все отзывы, оставленные для мест, то нужны были индексы users -> visits и locations -> visits.


        Для индекса был написан следующий код, с той же идеологией:


        template
        class MultiIndex {
         public:
          MultiIndex() : buckets_(BUCKETS_COUNT), bucket_mutexes_(BUCKETS_COUNT) {
          }
        
          void Add(uint32_t id, T* pointer) {
            int bucket_id = id % BUCKETS_COUNT;
            std::lock_guard lock(bucket_mutexes_[bucket_id]);
            buckets_[bucket_id].insert(std::make_pair(id, pointer));
          }
        
          void Replace(uint32_t old_id, uint32_t new_id, T* val) {
            int bucket_id = old_id % BUCKETS_COUNT;
            {   
              std::lock_guard lock(bucket_mutexes_[bucket_id]);
              auto range = buckets_[bucket_id].equal_range(old_id);
              auto it = range.first;
              while (it != range.second) {
                if (it->second == val) {
                  buckets_[bucket_id].erase(it);
                  break;
                }
                ++it;
              }   
            }   
            bucket_id = new_id % BUCKETS_COUNT;
            std::lock_guard lock(bucket_mutexes_[bucket_id]);
            buckets_[bucket_id].insert(std::make_pair(new_id, val));
          }
        
          std::vector GetValues(uint32_t id) {
            int bucket_id = id % BUCKETS_COUNT;
            std::lock_guard lock(bucket_mutexes_[bucket_id]);
            auto range = buckets_[bucket_id].equal_range(id);
            auto it = range.first;
            std::vector result;
            while (it != range.second) {
              result.push_back(it->second);
              ++it;
            }   
            return std::move(result);
          }
         private:
           std::vector> buckets_;
           std::vector bucket_mutexes_;
        };

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


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


        Highload, начало


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


        first attempt


        graph


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


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


        void onEOM() noexcept override {
          proxygen::ResponseBuilder(downstream_)
            .status(200, "OK")
            .header("Content-Type", "application/json")
            .body("{}")
            .sendWithEOM();
        }

        Вот так выглядел график 3-ей фазы.


        proxygen fail


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


        Следующим подопытным стал Crow. Сразу скажу, мне он очень понравился, и если вдруг в будущем мне потребуется плюсовый http-сервер, то это будет именно он. header-based сервер, я его просто добавил в свой проект вместо proxygen, и немного переписал обработчики запросов, чтобы они начали работать с новым сервером.


        Использовать его очень просто:


        crow::SimpleApp app;
        CROW_ROUTE(app, "/users/").methods("GET"_method, "POST"_method) (
          [](const crow::request& req, crow::response& res, uint32_t id) {
            if (req.method == crow::HTTPMethod::GET) {
              get_handlers::GetUsers(req, res, id);
            } else {
              post_handlers::UpdateUsers(req, res, id);
            }
          });
        
        app.bindaddr("0.0.0.0").port(80).multithreaded().run();

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


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


        Crow win


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


        Crow first result


        100 секунд — уже что-то, и можно начинать заниматься оптимизацией логики, а не поисками сервера.


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


        • убирается лишний write()
        • данных всего 200мб, памяти хватает с запасом

        Еще одной проблемой, о которую было сломано много копий в чатике в телеграмме и мной лично, это фильтр пользователей по возрасту. По условию задачи, возраст пользователей хранился в unix timestamp, а в запросах он приходил в виде полных лет: fromAge=30&toAge=70. Как года привести к секундам? Учитывать високосный год или нет? А если пользователь родился 29 февраля?


        Итогом стал код, который решал все эти проблемы одним махом:


        static time_t t = g_generate_time;   // get time now
        static struct tm now = (*localtime(&t));
        if (search_flags & QueryFlags::FROM_AGE) {
          tm from_age_tm = now;
          from_age_tm.tm_year -= from_age;
          time_t from_age_t = mktime(&from_age_tm);
          if (user->birth_date > from_age_t) {
            continue;
          }
        }

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


        В этот момент были сделаны еще два наблюдения:


        • индексы данных всегда шли по-порядку от 1 без пропусков
        • размеры индексов были примерно по 1М для Users и Locations, и 10М для Visits.

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


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


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


        I want to ride my bicycle...


        В мире нет ничего более беспомощного, безответственного и безнравственного, чем начинать писать свой web-сервер. И я знал, что довольно скоро я в это окунусь.


        И вот этот момент настал.
        При написании своего сервера было сделано несколько допущений:


        • танк стреляет через loopback, и значит потери отсутствуют
        • пакеты от танка очень маленькие, значит их можно вычитать за один read()
        • мои ответы тоже небольшие, поэтому они будут записаны за один вызов write(), и вызов на запись не будет заблокирован
        • от танка идут только корректные get- и post-запросы

        Вообще, можно было по-честному поддержать read() и write() в несколько кусков, но текущий вариант работал, поэтому это осталось "на потом".


        После серии экспериментов я остановился на следующей архитектуре: блокирующий аccept() в главном потоке, добавление нового сокета в epoll, и std::thread::hardware_concurrency() потоков слушают один epollfd и обрабатывают данные.


        unsigned int thread_nums = std::thread::hardware_concurrency();
        for (unsigned int i = 0; i < thread_nums; ++i) {
          threads.push_back(std::thread([epollfd, max_events]() {
            epoll_event events[max_events];
            int nfds = 0;
            while (true) {
              nfds = epoll_wait(epollfd, events, max_events, 0);
              // ...
        
              for (int n = 0; n < nfds; ++n) {
                // ...
              }
            }
          }));
        }
        
        while (true) {
          int sock = accept(listener, NULL, NULL);
          // ...
          struct epoll_event ev;
          ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
          ev.data.fd = sock;
          if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &ev) == -1) {
            perror("epoll_ctl: conn_sock");
            exit(EXIT_FAILURE);
          }
        }

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


        Тут я сделал одну ошибку: я использовал std::thread::hardware_concurrency() для определения количества доступных потоков, но это было плохой идеей, потому что этот вызов возвращал 10 (количество ядер на сервере), а в докере было доступно только 4 ядра. Впрочем, на результат это мало повлияло.


        Для парсинга http я использовал http-parser (Crow также использует его), для разбора урла — libyuarel, и для декодирования параметров в query-запросе — qs_parse. qs_parse также используется в Crow, он умеет разбирать урл, но так получилось, что я его использовал только для декодирования параметров.


        Переписав таким образом свою реализацию, я сумел скинуть еще 10 секунд, и теперь мой результат составлял 40 секунд.


        MOAR HIGHLOAD


        До конца соревнования оставалось полторы недели, и организаторы решили, что 200Мб данных и 2000rps — это мало, и увеличили размер данных и нагрузки в 5 раз: данные стали занимать 1Гб в распакованном виде, а интенсивность обстрела выросла до 10000rps на 3-ей фазе.


        Моя реализация, которая хранила ответы целиком, перестала влезать по памяти, делать много вызовов write(), чтобы писать ответ по частям, тоже казалось плохой идеей, и я переписал свое решение на использование writev(): не нужно было дублировать данные при хранении и запись происходила с помощью одного системного вызова (тут тоже добавлено улучшение для финала: writev может записать за раз 1024 элемента массива iovec, а моя реализация для /users//locations была очень iovec-затратной, и я добавил возможность разбить запись данных на 2 куска).


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


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


        За оставшуюся неделю я сделал следующие оптимизации:


        Заменил цепочки вызовов вида


        DBInstance::GetDbInstance()->GetLocations()

        на указатель


        g_location_storage

        Потом я подумал, что честный http-парсер для get-запросов не нужен, и для get-запроса можно сразу брать урл и больше ни о чем не заботиться. Благо, фиксированные запросы танка это позволяли. Также тут примечателен момент, что можно портить буфер (записывать \0 в конец урла, например). Таким же образом работает libyuarel.


        HttpData http_data;
        if (buf[0] == 'G') {
          char* url_start = buf + 4;
          http_data.url = url_start;
          char* it = url_start;
          int url_len = 0;
          while (*it++ != ' ') {
            ++url_len;
          }
          http_data.url_length = url_len;
          http_data.method = HTTP_GET;
        } else {
          http_parser_init(parser.get(), HTTP_REQUEST);
          parser->data = &http_data;
          int nparsed = http_parser_execute(
              parser.get(), &settings, buf, readed);
          if (nparsed != readed) {
            close(sock);
            continue;
          }
          http_data.method = parser->method;
        }
        Route(http_data);

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


        const char* header = "HTTP/1.1 200 OK\r\n"
                             "S: b\r\n"
                             "C: k\r\n"
                             "B: a\r\n"
                             "Content-Length: ";

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


        Все эти изменения привели к тому, что лучшее время в песочнице до 197 секунд, и это было 16 место на момент закрытия, а в финале — 188 секунд, и 13 место.


        final


        Код решения полностью расположен здесь: https://github.com/evgsid/highload_solution


        Код решения на момент финала: https://github.com/evgsid/highload_solution/tree/final


        Magic pill


        А теперь давайте немного поговорим о магии.


        В песочнице особенно выделялись первые 6 мест в рейтинге: у них время было ~140 сек, а у следующих ~ 190 и дальше время плавно увеличивалось.


        Было очевидно, что первые 6 человек нашли какую-то волшебную пилюлю.
        Я попробовал в качестве эксперимента sendfile и mmap, чтобы исключить копирование из userspace -> kernelspace, но тесты не показали никакого прироста в производительности.


        И вот последние минуты перед финалом, прием решений закрывается, и лидеры делятся волшебной пилюлей: BUSY WAIT.
        При прочих равных, решение, которое давало 180 секунд с epoll(x, y, z, -1) при использовании epoll(x, y, z, 0) давало сразу же 150 секунд и меньше. Конечно, это не продакшн-решение, но очень сильно уменьшало задержки.


        Хорошую статью по этому поводу можно найти тут: How to achieve low latency with 10Gbps Ethernet.


        Мое решение, лучший результат которого был 188 в финале, при использовании busy wait сразу же показало 136 секунд, что было бы 4-м временем в финале, и 8-ое место в песочнице на момент написания этой статьи.


        Вот так выглядит график лучшего решения:


        ideal


        Disclaimer

        На самом деле, busy wait нужно использовать очень осторожно. Мое решение, когда главный поток принимает соединения, а 4 потока только обрабатывают данные из сокетов, при использовании busy wait стало сильно проседать на post-фазе, потому что accept'у не хватало процессорного времени, и мое решение сильно тормозило. Уменьшение количества обрабатыающих потоков до 3 полностью решило эту проблему.


        Заключение


        Тамада хороший, и конкурсы интересные.


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


        С нетерпением жду следующего конкурса!

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

        https://habrahabr.ru/post/337710/


        Метки:  

        Блиц. Как попасть в Яндекс, минуя первое собеседование

        Вторник, 12 Сентября 2017 г. 10:30 + в цитатник
        nkmakarov сегодня в 10:30 Разработка

        Блиц. Как попасть в Яндекс, минуя первое собеседование

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



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


          Квалификацию можно пройти с 18 по 24 сентября включительно. В этом раунде вам нужно будет написать программы для решения шести задач. Можете использовать Java, C++, C# или Python. На всё про всё у вас будет четыре часа. В решающем раунде будут соревноваться те, кто справится как минимум с четырьмя квалификационными задачами. Финал пройдёт одновременно для всех участников — 30 сентября, с 12:00 до 16:00 по московскому времени. Итоги будут подведены 4 октября. Чтобы всем желающим было понятно, с чем они столкнутся на Блице, мы решили разобрать пару похожих задач на Хабре.


          1. Robust Execution Manager


          Первая задача связана с одной из систем обработки данных, разработанной в Яндексе, — Robust Execution Manager, REM. Он позволяет строить сложные процессы, включающие множество разных операций, и устанавливать зависимости между ними. Так, решение многих задач связано с обработкой пользовательских логов, содержащих информацию о заданных запросах, показанных документах, совершённых кликах и так далее. Поэтому вычисление поискового фактора CTR (click through rate, отношение числа кликов по документу к количеству его показов) зависит от задач обработки пользовательских логов, а от решения задачи вычисления CTR зависит задача доставки значений этого фактора в поисковый runtime. Таким образом, REM позволяет выстраивать цепочки обработки данных, в которых поставщик данных не обязан знать о том, кто эти данные в дальнейшем будет использовать.


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


          1.1. Постановка задачи


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


          1.2. Решение


          Рассмотрим для начала решение задачи в случае, когда вершины из ответа не нужно упорядочивать. Тогда задача решается достаточно просто: запустим из выделенной вершины любой алгоритм обхода графа (например, обход в глубину), и запишем в ответ все посещённые вершины. Действительно, если задачи с номерами $j_1$, $j_2$, ..., $j_k$ зависят по данным от задачи $i$, то после перезапуска задачи $i$ необходимо перезапустить все эти задачи, а также те задачи, что зависят уже от них. Кроме того, каждую вершину нужно вывести ровно один раз. Оба этих условия обеспечиваются стандартными методами обхода графов.


          Так может выглядеть решение для данной задачи
          #include 
          #include 
          
          using Graph = std::vector>;
          
          void DFS(const Graph& graph,
                   const int vertice,
                   std::vector& used,
                   std::vector& result)
          {
              used[vertice] = true;
              result.push_back(vertice);
          
              for (size_t idx = 0; idx < graph[vertice].size(); ++idx) {
                  const int adjacent_vertice = graph[vertice][idx];
                  if (!used[adjacent_vertice]) {
                      DFS(graph, adjacent_vertice, used, result);
                  }
              }
          }
          
          void Solve(const Graph& graph,
                     const int from,
                     std::vector& result)
          {
              std::vector used(graph.size(), false);
              DFS(graph, from, used, result);
          }
          
          int main() {
              int vertices, edges;
              std::cin >> vertices >> edges;
          
              Graph graph(vertices + 1);
          
              for (int edge = 0; edge < edges; ++edge) {
                  int from, to;
                  std::cin >> from >> to;
                  graph[from].push_back(to);
              }
          
              int reset_vertice;
              std::cin >> reset_vertice;
          
              std::vector result;
          
              Solve(graph, reset_vertice, result);
          
              for (std::vector::const_iterator it = result.begin();
                   it != result.end();
                   ++it)
              {
                  std::cout << *it << " ";
              }
              std::cout << std::endl;
          
              return 0;
          }

          Теперь мы знаем, как сформировать множество вершин, составляющих ответ, но не знаем, как его упорядочить. Рассмотрим пример: пусть от подзадачи $A$ зависят подзадачи $B$ и $C$. В таком случае нам неважно, в каком порядке запускать подзадачи $B$ и $C$. Но что, если между ними существует явная или неявная (через другие подзадачи) зависимость?



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


          Используем для решения возникшей проблемы топологическую сортировку вершин, входящих в ответ, то есть, введём некоторый порядок на этом множестве вершин, например: $v_1$, $v_2$,...,$v_k$, и при этом будет верно, что, если $j > i$, то $v_j$ не зависит прямо или косвенно от $v_i$. Известно, что такое упорядочение возможно, если граф не содержит циклов. Топологическая сортировка вершин достигается при помощи обхода в глубину: когда для некоторой вершины посещены все её потомки, она добавляется в начало списка. Итоговый список будет искомой сортировкой вершин графа.


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


          Так будет выглядеть полное решение нашей задачи
          #include 
          #include 
          
          using Graph = std::vector>;
          
          void DFS(const Graph& graph,
                   const int vertice,
                   std::vector& used,
                   std::vector& result)
          {
              used[vertice] = true;
              for (size_t idx = 0; idx < graph[vertice].size(); ++idx) {
                  const int adjacent_vertice = graph[vertice][idx];
                  if (!used[adjacent_vertice]) {
                      DFS(graph, adjacent_vertice, used, result);
                  }
              }
              result.push_back(vertice);
          }
          
          void Solve(const Graph& graph,
                     const int from,
                     std::vector& result)
          {
              std::vector used(graph.size(), false);
              DFS(graph, from, used, result);
          }
          
          int main() {
              int vertices, edges;
              std::cin >> vertices >> edges;
          
              Graph graph(vertices + 1);
          
              for (int edge = 0; edge < edges; ++edge) {
                  int from, to;
                  std::cin >> from >> to;
                  graph[from].push_back(to);
              }
          
              int reset_vertice;
              std::cin >> reset_vertice;
          
              std::vector result;
          
              Solve(graph, reset_vertice, result);
          
              for (std::vector::const_reverse_iterator it = result.rbegin();
                   it != result.rend();
                   ++it)
              {
                  std::cout << *it << " ";
              }
              std::cout << std::endl;
          
              return 0;
          }

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


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


          2.1. Постановка задачи


          Необходимо вычислить, каково количество различных бинарных деревьев поиска, в вершинах которых записаны числа от 1 до $N$. Например, если $N = 3$, существует ровно пять различных деревьев, изображённых на картинке ниже:



          Если $N = 5$, то количество деревьев равняется 42. Именно поэтому задача очень нравится нашим разработчикам, среди которых немало ценителей творчества Дугласа Адамса.


          2.2. Решение с использованием динамического программирования


          Эта задача является достаточно простой для тех, кто знаком с методами динамического программирования. Обозначим за $T_k$ количество различных бинарных деревьев поиска, образованных числами $1$, $2$, ..., $K$. Предположим, что мы знаем $T_0$, $T_1$, $T_2$, ..., $T_N$ и хотим вычислить $T_{N+1}$.


          Ясно, что в корне любого дерева будет находиться некоторое число из множества ${1,...,N+1.}$ Обозначим это число за $ t$. Тогда в силу свойств дерева поиска левое поддерево содержит числа $1,...,t-1$, а правое — ${t+1,...,N+1}$. Количество различных возможных способов сформировать левое поддерево тогда равняется $T_{t-1}$, а правое — $T_{N - t + 1}$. Поэтому общее количество деревьев, составленных из чисел $1$, $2$, ..., $N + 1$ и имеющих в корне значение $t$, равняется произведению $T_{i-1}\cdot T_{N - t + 1}$. Для того, чтобы найти общее количество деревьев, необходимо просуммировать это произведение для всех возможных значений $t$:


          $T_{N+1} = \sum_{t=1}^{N+1} T_{i-1} \cdot T_{N - t + 1} = \sum_{t=0}^{N} T_{i} \cdot T_{N - t}$


          Приведем пример реализации, для разнообразия на языке программирования Python
          N = int(raw_input())
          
          result = [1, 1]
          
          for count in xrange(2, N + 1):
              result.append(0)
              for root in xrange(1, count + 1):
                  result[-1] += result[root - 1] * result[count - root]
          
          print result[N]

          2.3. Связь с числами Каталана


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


          $T_{N+1} = C_{N+1} = \frac{C_{2N + 2}^{N+1} }{N + 2}$


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




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

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

          https://habrahabr.ru/post/337690/


          Метки:  

          VMware объявила о «конце» vCenter Server для Windows

          Вторник, 12 Сентября 2017 г. 10:21 + в цитатник
          1cloud сегодня в 10:21 Администрирование

          VMware объявила о «конце» vCenter Server для Windows

            За последний месяц компания VMware остановила поддержку сразу нескольких продуктов из своего арсенала. Одним из них стал vCenter Server для Windows. На смену ему придет vCenter Server Appliance (vCSA).


            / Flickr / Alexas_Fotos / CC

            Модуль VMware vCenter Server Appliance (vCSA) был представлен с выпуском vSphere 5.0 в 2011 году. Изначально он заметно уступал по функциональности и масштабируемости vCenter Server для Windows, который появился в vSphere 3.0. Шли годы, и vCSA на базе Linux избавлялся от своих недостатков, наращивая возможности.

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

            В компании и раньше говорили о том, что vCSA станет основой экосистемы будущего. Поэтому последнее решение не вызывает удивления. Этот шаг укладывается в стратегию VMware по упрощению администрирования дата-центров и управления жизненным циклом IT. С этой точки зрения у сред vSphere должна быть одна централизованная платформа управления. vCSA поддерживает весь стек vCenter Server, операционную систему Photon OS и базы данных (vPostgres).

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

            К миграции на vCSA призывает Linux-эксперт Стюарт Бёрнс (Stuart Burns). Он называет решение VMware масштабируемым, экономически эффективным и изначально подготовленным к развертыванию. Процесс перехода с помощью специального сервиса Migration Tool подробно описан в блоге VMware.

            Мнение сообщества


            Мнения касательно решения VMware прекратить поддержку клиента для Windows разделились. Пользователи Reddit в обсуждении темы выразили недовольство веб-клиентом, который из-за своих особенностей не поддерживает некоторый функционал. Несколько участников дискуссии высказались в пользу того, что «большой список теоретических преимуществ vCSA не подкреплен практически никакими реальными преимуществами», а «выход на пенсию Windows-версии приносит пользу только разработчикам VMware, а не клиентам».

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

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

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

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


            P.P.S. О чем мы пишем на Хабре:

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

            https://habrahabr.ru/post/337694/


            Метки:  

            Анатомия аналитики от Google

            Вторник, 12 Сентября 2017 г. 09:47 + в цитатник
            xoxol_89 сегодня в 09:47 Разработка

            Анатомия аналитики от Google

            • Tutorial

            Всем привет!
            Мы — разработчики (гордо звучит, не правда ли?), и мы активно пилим новые фичи, правим баги и стараемся сделать наш продукт лучше. Но чтобы понять, а как именно пользователь использует наш продукт, какие фишки продукта ему по душе, а какие — не очень, мы используем аналитику. Есть много разных средств, но в этой статье я бы хотел поговорить именно об аналитике от Google, которая активно развивается и меняется. Старого часового по имени Google Analytics сменяет новый боец — Google Analytics for Firebase (в девичестве — Firebase Analytics).
            Уже даже в названиях вы можете уловить этот ветер перемен. А ветер перемен всегда порождает некоторый информационный вакуум, в который попадают разного рода слухи, далеко не всегда достоверные при этом.
            Поэтому давайте попробуем разобраться подробно, а что сейчас с этой аналитикой, чем пользоваться-то в итоге. И как вообще дальше жить.
            Если про Google Analytics информации довольно много, и она систематизирована (чего только стоит этот ресурс, идеальная справка), то у Google Analytics for Firebase типичная болезнь молодого и активно развивающегося продукта — информации мало, она разрознена и иногда даже противоречива. И я в свое время потратил немало сил и времени, чтобы разобраться, что к чему.
            Собственно главная цель данной статьи — это систематизация знаний и нынешнего состояния Google Analytics for Firebase. Некоторая «дорожная карта» Google Analytics for Firebase.
            Уверен, данная «карта» сэкономит вам прилично времени и нервов =)


            Самый главный миф. Google Analytics всё


            Начну все-таки с самого горячего.
            Мне кажется, что данный слух идет с самого появления Firebase Analytics. И с одной стороны, это логично, зачем «Гуглу» два средства аналитики. Но Google Analytics (будем именовать GA) и Google Analytics for Firebase (по старинке назовем FA) — это две аналитики с разными концепциями и подходами, про которые мы поговорим чуть ниже.
            GA никуда не денется и не пропадет (по крайней мере сейчас), а также не будет кем-то поглощен. Это как информация от представителей москвовского офиса Гугла, так и инсайды от самих разработчиков.
            Фанаты GA могут спать спокойно… пока что. Но кто знает, что будет дальше. Поэтому я настоятельно рекомендую продолжить чтение =)


            GA vs FA. Общая концепция


            FA — это аналитика с совершенно другой концепцией и философией. Она является event based и предназначена исключительно для мобилки. Тогда как GA — screen-based и сначала была для веба, а уж потом ее допилили для мобилки.
            GA структурирована вокруг иерархичных событий с одним значением, FA — больше о записи одного события с большим количеством параметров (пар «ключ-значение»).
            Эти аналитики очень разные. И поэтому они не могут быть взаимозаменяемыми.
            Миграции с одной на другую не предусматривается. Но «Гугл» работает над определенной совместимостью этих аналитик, о чем мы тоже поговорим чуть позже.


            GA vs FA. События


            Коль уж мы затронули тему событий. В плане осмысления «события» GA и FA действительно очень разные. И это особенно заметно на примере.
            Допустим, ваше приложение — это игра. По окончании игры вы хотите послать статистику, как в итоге сыграл пользователь. И вы хотите узнать у пользователя общий счет, количество убитых врагов и количество пройденных раундов.
            В GA все это будет выглядеть примерно вот так:


            // total score
            mTracker = googleAnalytics.newTracker(R.xml.tracker_global_config);
            HitBuilders.EventBuilder builder = new HitBuilders.EventBuilder()
                    .setCategory("gameOver")
                    .setAction("totalScore")
                    .setLabel("")
                    .setValue(gameStats.getTotalScore());
            mTracker.send(builder.build());
            // enemies beaten
            mTracker = googleAnalytics.newTracker(R.xml.tracker_global_config);
            HitBuilders.EventBuilder builder = new HitBuilders.EventBuilder()
                    .setCategory("gameOver")
                    .setAction("enemiesBeaten")
                    .setLabel("")
                    .setValue(gameStats.getEnemiesBeaten());
            mTracker.send(builder.build());
            // roundsSurvived
            mTracker = googleAnalytics.newTracker(R.xml.tracker_global_config);
            HitBuilders.EventBuilder builder = new HitBuilders.EventBuilder()
                    .setCategory("gameOver")
                    .setAction("roundsSurvived")
                    .setLabel("")
                    .setValue(gameStats.getRoundsSurvived());
            mTracker.send(builder.build());

            В GA каждое событие по сути представляет собой иерархию параметров:
            category -> action -> label -> value
            И в самой консоли вы могли наблюдать данную иерархию параметров. Собственно при придумывании событий, которые вы бы хотели отслеживать, вы должны были руководствоваться данной парадигмой. Также в консоли можно строить различные фильтры по данным параметрам.
            Но в GA в плане событий есть небольшой минус. Если вы хотите навешать событию дополнительные параметры, помимо вышеназванных, вот тут приходится танцевать вокруг "category" -> "action" -> "label" -> "value", придумывать новые формулировки и прочее. Неудобно. По крайней мере так было раньше.


            А теперь посмотрим, как можно данную статистику обыграть с FA:


            Bundle params = new Bundle();
            params.putLong("totalScore", gameStats.getTotalScore());
            params.putLong("enemiesBeaten", gameStats.getEnemiesBeaten());
            params.putLong("roundsSurvived", gameStats.getRoundSurvived());
            mFirebaseAnalytics.logEvent("game_over", params);

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


            GA vs FA. Консоль


            Второе, чем сильно отличаются аналитики, это — консоль.
            Вот как выглядит консоль в GA (картинка кликабельна):


            «События» спрятаны глубоко во вкладке «Поведение» слева. Но в стандартном отчете сразу идет разбивка на Category, Action, Label (рисунок кликабельный):


            Вот так выглядит консоль FA (рисунок кликабельный):


            Первое, что вы видите, это — «Сводка». И я бы сразу обратил внимание на карточку User engagement (рисунок кликабельный):


            Наконец-то в FA-консоль добавили нормальный просмотр экранов. До мая мы жили без этого. То есть событие «user engagement» отсылалось, но в консоли его никак нельзя было посмотреть. Это было ужасно. И это, возможно, одна из причин, почему никто не хотел переходить на FA.
            Как еще можно заметить, вкладка Events идет сразу за Dashboard, что еще раз подтверждает — FA заточена на работу с событиями. К консоли мы также вернемся чуть позже, а сейчас я предлагаю погрузиться в эту обширную тему «Событий» в FA.


            События FA


            Давайте сразу взглянем на код:


            Bundle bundle = new Bundle();
            bundle.putString(FirebaseAnalytics.Param.ITEM_ID, id);
            bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, name);
            bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "image");
            mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);

            Вы можете отправлять до 500 различных типов событий в своем приложении, включая предустановленные (FirebaseAnalytics.Event.SELECT_CONTENT — это предустановленное, но вы можете задавать и свои типы). Общее количество отправляемых событий не лимитировано (источник).
            К каждому событию можно прикреплять до 25 параметров (то, что идет в Bundle). Параметры также есть предопределенные, но никто не запрещает вам задавать кастомные параметры. Описано здесь.
            Типы событий и параметров — это обычные String.
            Названия событий и параметров чувствительны к регистру. Одинаковые события должны совпадать по типу и параметрам.
            Кроме того, есть события, которые отправляются по умолчанию. Весь список автоматически отправляемых событий с описанием приведен по данной ссылке. Как вы можете заметить, там много действительно интересных событий, которые раньше нам не представлялось возможным получить. Круто!
            Также по приведенной выше ссылке вы можете прочитать, какие предопределенные события и параметры можно выбрать для определенных событий.


            События FA. «Гладко было на бумаге, да забыли про овраги»


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

            «Но где же все мои параметры?» — спросите вы. А нет их на консоли, вот так вот.
            Дело в том, что все красивые графики и прочее строятся, только если вы используете предопределенные названия. Используете свое, «кастомное», ничегошеньки не увидите. Только «количество событий» да «количество пользователей».
            И до I/O 17 это было прям страшной болью. Графики можно было строить, играя, например, с параметром Value, как в этой статье. Но это, конечно, все не то.


            И тут, конечно же, пора бы вспомнить про GA, где все для людей, строй всякие там фильтры по чему угодно и сколько душе угодно.
            Но и тут маленькая засада. Стандартные отчеты — да, стройте без проблем. Но в большинстве случаев нам нужны и кастомные отчеты. Например, добавить Secondary dimension, чтобы отсортировать события по моделям устройств. И вот тут всплывает страшное слово «Sampling».
            В зависимости от отчета алгоритм сэмплирования в GA различается. То, как конкретно считается семпл для каждого отчёта, «Гугл» не раскрывает, но в целом все практики уже известны. Обычно это hi-based-сэмплирование или cookie-based-сэмплирование. В первом случае берется рандомная выборка из всех записей (событий, просмотров и т.д.), во втором — рандомная выборка по всем пользователям (размеченным кукам или gaid/idfa, если это мобильное приложение).
            Поэтому нельзя достоверно говорить об ошибке по каждому полю.
            По практике говорят, что при выборке больше 5% ошибка в абсолютных числах в отчетах по событиям составляла меньше 2,5%.
            За предоставление информации о сэмплировании хочу выразить благодарность Александру Сергееву из «Яндекса».


            События FA. Продолжение


            Да уж. Все непросто с этими «Событиями». И на самом деле FA идет навстречу пожеланиям простого люда.
            Во-первых, никакого сэмплинга в FA нет. Там доступны все данные.
            И это очень круто, так как стоимость Google Analytics 360 (платной версии GA без сэмплинга) весьма немаленькая. А в FA вы можете ваши данные выгрузить в BigQuery и там делать с ними все что угодно.
            Во-вторых, после I/O 17 появилась возможность строить отчеты и по кастомным параметрам.
            Вам прямо на экране конкретного события предлагается зарегистрировать кастомные параметры (рисунок кликабельный):


            Но учтите, что всего для данного приложения вы можете зарегистрировать до 50 таких параметров (10 текстовых и 40 числовых). Пробовал лайфхак для обхода данного ограничения: регистрировал для разных событий кастомные параметры с одинаковыми именами. Не помогло, все равно делается «плюс один».
            Кроме того, если вы ожидаете увидеть сразу готовые отчеты, спешу вас разочаровать. Отчеты строятся накопительным образом. Допустим, есть у вас «event_1» с кастомным параметром «custom_1», для которого вы хотите построить отчет. В консоли вы настроили, чтобы строился данный отчет в момент времени X. Так вот в отчет попадут все события «event_1», которые придут после момента времени X. А все «event_1» до момента X, увы, не будут обработаны. Так что будьте внимательны.
            То есть вроде бы лучше стало, но не сильно. Что еще обидно, вы не можете эти отчеты как-то совмещать друг с другом. Но, пожалуй, мы слишком многого хотим от консоли. Если уж вы хотите делать с данными все что угодно, то добро пожаловать в удивительный мир BigQuery. Давайте немного приоткроем эту завесу таинства данных.


            BigQuery


            BigQuery — это вообще немного другая галактика.
            С BigQuery можно было работать и через GA, но только если у вас premium-режим. В FA же вам прямо во вкладке Events предлагается установить связь (рисунок кликабельный):


            Google говорит: «Мы дарим вам машину, но за бензин платите вы». С тарифными планами можно ознакомиться здесь, а еще лучше здесь. Но поверьте, чтобы просто попробовать, вам вполне достаточно будет бесплатных лимитов тарифа Blaze. Да и даже при работе с боевыми продуктами, судя по отзывам товарищей, плата весьма условной получается.
            Итак, начнем знакомство. Вот так выглядит консоль BigQuery (рисунок кликабельный):


            В левом меню представлен список доступных данных. Например, TestStep — это мой тестовый проект с одним приложением в составе. А bigquery-public-data и Public Datasets — это, как можно догадаться, публичные данные, с которыми вы можете поэкспериментировать и на которых можете потренироваться в написании запросов.
            Справа же вы видите список запросов, как успешных, так и не очень.
            Теперь взглянем на данные тестового приложения за 14 марта 2017 года (таблица app_events_20170314, рисунок кликабельный):


            В таблицу я перебросил все данные за сутки (52 события). Общий состав таблицы представлен перед вами. Как видно, тут каждое событие описывается максимально полно, включая все properties, о которых речь будет чуть ниже.
            Давайте посмотрим на превью данных (вкладка Preview, рисунок кликабельный):


            Табличный вид с ходу малоинформативен. Намного более понятная форма — это JSON (рисунок кликабельный):


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


            5 событий в BigQuery
            [
              {
                "user_dim": {
                  "user_id": null,
                  "first_open_timestamp_micros": "1488878151620000",
                  "user_properties": [
                    {
                      "key": "first_open_time",
                      "value": {
                        "value": {
                          "string_value": null,
                          "int_value": "1488880800000",
                          "float_value": null,
                          "double_value": null
                        },
                        "set_timestamp_usec": "1488878151620000",
                        "index": null
                      }
                    }
                  ],
                  "device_info": {
                    "device_category": "mobile",
                    "mobile_brand_name": null,
                    "mobile_model_name": null,
                    "mobile_marketing_name": null,
                    "device_model": "507SH",
                    "platform_version": "6.0.1",
                    "device_id": null,
                    "resettable_device_id": null,
                    "user_default_language": "ru-ru",
                    "device_time_zone_offset_seconds": "10800",
                    "limited_ad_tracking": "false"
                  },
                  "geo_info": {
                    "continent": "Europe",
                    "country": "Russia",
                    "region": "Moscow",
                    "city": "Moscow"
                  },
                  "app_info": {
                    "app_version": "1.0",
                    "app_instance_id": "d0c587de4d5804ddc1d34f8d54b981f9",
                    "app_store": "manual_install",
                    "app_platform": "ANDROID",
                    "app_id": "com.example.matsyuk.testfirebase"
                  },
                  "traffic_source": null,
                  "bundle_info": {
                    "bundle_sequence_id": "65",
                    "server_timestamp_offset_micros": "-496748"
                  },
                  "ltv_info": null
                },
                "event_dim": [
                  {
                    "date": "20170314",
                    "name": "user_engagement",
                    "params": [
                      {
                        "key": "firebase_screen_class",
                        "value": {
                          "string_value": "SecondActivity",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_event_origin",
                        "value": {
                          "string_value": "auto",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_screen_id",
                        "value": {
                          "string_value": null,
                          "int_value": "1109587836504693342",
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "engagement_time_msec",
                        "value": {
                          "string_value": null,
                          "int_value": "4424",
                          "float_value": null,
                          "double_value": null
                        }
                      }
                    ],
                    "timestamp_micros": "1489478210462000",
                    "previous_timestamp_micros": "1489478205970000",
                    "value_in_usd": null
                  }
                ]
              },
              {
                "user_dim": {
                  "user_id": null,
                  "first_open_timestamp_micros": "1488878151620000",
                  "user_properties": [
                    {
                      "key": "first_open_time",
                      "value": {
                        "value": {
                          "string_value": null,
                          "int_value": "1488880800000",
                          "float_value": null,
                          "double_value": null
                        },
                        "set_timestamp_usec": "1488878151620000",
                        "index": null
                      }
                    }
                  ],
                  "device_info": {
                    "device_category": "mobile",
                    "mobile_brand_name": null,
                    "mobile_model_name": null,
                    "mobile_marketing_name": null,
                    "device_model": "507SH",
                    "platform_version": "6.0.1",
                    "device_id": null,
                    "resettable_device_id": null,
                    "user_default_language": "ru-ru",
                    "device_time_zone_offset_seconds": "10800",
                    "limited_ad_tracking": "false"
                  },
                  "geo_info": {
                    "continent": "Europe",
                    "country": "Russia",
                    "region": "Moscow",
                    "city": "Moscow"
                  },
                  "app_info": {
                    "app_version": "1.0",
                    "app_instance_id": "d0c587de4d5804ddc1d34f8d54b981f9",
                    "app_store": "manual_install",
                    "app_platform": "ANDROID",
                    "app_id": "com.example.matsyuk.testfirebase"
                  },
                  "traffic_source": null,
                  "bundle_info": {
                    "bundle_sequence_id": "64",
                    "server_timestamp_offset_micros": "-515257"
                  },
                  "ltv_info": null
                },
                "event_dim": [
                  {
                    "date": "20170314",
                    "name": "user_engagement",
                    "params": [
                      {
                        "key": "firebase_screen_class",
                        "value": {
                          "string_value": "MainActivity",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_event_origin",
                        "value": {
                          "string_value": "auto",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_screen_id",
                        "value": {
                          "string_value": null,
                          "int_value": "1109587836504693341",
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "engagement_time_msec",
                        "value": {
                          "string_value": null,
                          "int_value": "17278",
                          "float_value": null,
                          "double_value": null
                        }
                      }
                    ],
                    "timestamp_micros": "1489478205970000",
                    "previous_timestamp_micros": "1489153178047000",
                    "value_in_usd": null
                  }
                ]
              },
              {
                "user_dim": {
                  "user_id": null,
                  "first_open_timestamp_micros": "1488878151620000",
                  "user_properties": [
                    {
                      "key": "first_open_time",
                      "value": {
                        "value": {
                          "string_value": null,
                          "int_value": "1488880800000",
                          "float_value": null,
                          "double_value": null
                        },
                        "set_timestamp_usec": "1488878151620000",
                        "index": null
                      }
                    }
                  ],
                  "device_info": {
                    "device_category": "mobile",
                    "mobile_brand_name": null,
                    "mobile_model_name": null,
                    "mobile_marketing_name": null,
                    "device_model": "507SH",
                    "platform_version": "6.0.1",
                    "device_id": null,
                    "resettable_device_id": null,
                    "user_default_language": "ru-ru",
                    "device_time_zone_offset_seconds": "10800",
                    "limited_ad_tracking": "false"
                  },
                  "geo_info": {
                    "continent": "Europe",
                    "country": "Russia",
                    "region": "Moscow",
                    "city": "Moscow"
                  },
                  "app_info": {
                    "app_version": "1.0",
                    "app_instance_id": "d0c587de4d5804ddc1d34f8d54b981f9",
                    "app_store": "manual_install",
                    "app_platform": "ANDROID",
                    "app_id": "com.example.matsyuk.testfirebase"
                  },
                  "traffic_source": null,
                  "bundle_info": {
                    "bundle_sequence_id": "63",
                    "server_timestamp_offset_micros": "-500210"
                  },
                  "ltv_info": null
                },
                "event_dim": [
                  {
                    "date": "20170314",
                    "name": "ga_event",
                    "params": [
                      {
                        "key": "label",
                        "value": {
                          "string_value": "label1",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_screen_class",
                        "value": {
                          "string_value": "MainActivity",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "action",
                        "value": {
                          "string_value": "action1",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_event_origin",
                        "value": {
                          "string_value": "app",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "value",
                        "value": {
                          "string_value": null,
                          "int_value": "1",
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "category",
                        "value": {
                          "string_value": "category1",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_screen_id",
                        "value": {
                          "string_value": null,
                          "int_value": "1109587836504693341",
                          "float_value": null,
                          "double_value": null
                        }
                      }
                    ],
                    "timestamp_micros": "1489478204880000",
                    "previous_timestamp_micros": "1489137436229000",
                    "value_in_usd": null
                  }
                ]
              },
              {
                "user_dim": {
                  "user_id": null,
                  "first_open_timestamp_micros": "1488878151620000",
                  "user_properties": [
                    {
                      "key": "first_open_time",
                      "value": {
                        "value": {
                          "string_value": null,
                          "int_value": "1488880800000",
                          "float_value": null,
                          "double_value": null
                        },
                        "set_timestamp_usec": "1488878151620000",
                        "index": null
                      }
                    }
                  ],
                  "device_info": {
                    "device_category": "mobile",
                    "mobile_brand_name": null,
                    "mobile_model_name": null,
                    "mobile_marketing_name": null,
                    "device_model": "507SH",
                    "platform_version": "6.0.1",
                    "device_id": null,
                    "resettable_device_id": null,
                    "user_default_language": "ru-ru",
                    "device_time_zone_offset_seconds": "10800",
                    "limited_ad_tracking": "false"
                  },
                  "geo_info": {
                    "continent": "Europe",
                    "country": "Russia",
                    "region": "Moscow",
                    "city": "Moscow"
                  },
                  "app_info": {
                    "app_version": "1.0",
                    "app_instance_id": "d0c587de4d5804ddc1d34f8d54b981f9",
                    "app_store": "manual_install",
                    "app_platform": "ANDROID",
                    "app_id": "com.example.matsyuk.testfirebase"
                  },
                  "traffic_source": null,
                  "bundle_info": {
                    "bundle_sequence_id": "62",
                    "server_timestamp_offset_micros": "-499813"
                  },
                  "ltv_info": null
                },
                "event_dim": [
                  {
                    "date": "20170314",
                    "name": "select_content",
                    "params": [
                      {
                        "key": "firebase_screen_class",
                        "value": {
                          "string_value": "MainActivity",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "content_type",
                        "value": {
                          "string_value": "image",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "item_name",
                        "value": {
                          "string_value": "name1",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_event_origin",
                        "value": {
                          "string_value": "app",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_screen_id",
                        "value": {
                          "string_value": null,
                          "int_value": "1109587836504693341",
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "item_id",
                        "value": {
                          "string_value": "1",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      }
                    ],
                    "timestamp_micros": "1489478204208000",
                    "previous_timestamp_micros": "1489137435605000",
                    "value_in_usd": null
                  }
                ]
              },
              {
                "user_dim": {
                  "user_id": null,
                  "first_open_timestamp_micros": "1488878151620000",
                  "user_properties": [
                    {
                      "key": "first_open_time",
                      "value": {
                        "value": {
                          "string_value": null,
                          "int_value": "1488880800000",
                          "float_value": null,
                          "double_value": null
                        },
                        "set_timestamp_usec": "1488878151620000",
                        "index": null
                      }
                    }
                  ],
                  "device_info": {
                    "device_category": "mobile",
                    "mobile_brand_name": null,
                    "mobile_model_name": null,
                    "mobile_marketing_name": null,
                    "device_model": "507SH",
                    "platform_version": "6.0.1",
                    "device_id": null,
                    "resettable_device_id": null,
                    "user_default_language": "ru-ru",
                    "device_time_zone_offset_seconds": "10800",
                    "limited_ad_tracking": "false"
                  },
                  "geo_info": {
                    "continent": "Europe",
                    "country": "Russia",
                    "region": "Moscow",
                    "city": "Moscow"
                  },
                  "app_info": {
                    "app_version": "1.0",
                    "app_instance_id": "d0c587de4d5804ddc1d34f8d54b981f9",
                    "app_store": "manual_install",
                    "app_platform": "ANDROID",
                    "app_id": "com.example.matsyuk.testfirebase"
                  },
                  "traffic_source": null,
                  "bundle_info": {
                    "bundle_sequence_id": "61",
                    "server_timestamp_offset_micros": "-537470"
                  },
                  "ltv_info": null
                },
                "event_dim": [
                  {
                    "date": "20170314",
                    "name": "session_start",
                    "params": [
                      {
                        "key": "firebase_screen_class",
                        "value": {
                          "string_value": "MainActivity",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_event_origin",
                        "value": {
                          "string_value": "auto",
                          "int_value": null,
                          "float_value": null,
                          "double_value": null
                        }
                      },
                      {
                        "key": "firebase_screen_id",
                        "value": {
                          "string_value": null,
                          "int_value": "1109587836504693341",
                          "float_value": null,
                          "double_value": null
                        }
                      }
                    ],
                    "timestamp_micros": "1489478198696000",
                    "previous_timestamp_micros": "1489137330069000",
                    "value_in_usd": null
                  }
                ]
              }
            ]

            Красота, да и только!
            Теперь более подробно рассмотрим Queries. Выберем первый (рисунок кликабельный):


            И перед нами откроется следующий экран (рисунок кликабельный):


            Запрос наш довольно произвольный. Обратите внимание на вкладку Results. Собственно, в ней вы и увидите результаты вашего запроса.
            Если открыть вкладку Explanation, то вы увидите более подробный процесс прохождения запроса (рисунок кликабельный):


            Ну и самая интересная вкладка — Job information (рисунок кликабельный):


            Обратите внимание на Bytes Processed, Bytes Billed и Bites Tier. В ходе запроса было обработано 26,4 KB, но платите вы по нижней границе для Bites Tier = 1, то есть платите как за 10 MB. Однако, судя по документации, 1 TB в месяц для вас будет бесплатным, а каждый последующий будет стоить $5. Вполне вам хватит наиграться и напробоваться. Ну и важное дополнение — платите вы только за успешные запросы!


            Даже очень краткий обзор по BigQuery получается немаленьким. Это очень мощный и функциональный инструмент, с помощью которого вы можете анализировать данные как угодно. Но за 5 минут в BigQuery вы точно не разберетесь, в отличие от обычной консоли в GA или FA. Поэтому очень круто, если в вашей команде или компании есть человек, который в этом разбирается, и который может получить какие угодно результаты.
            Если этим человеком хотите стать вы, то начать можете со вступительного видео от «Гугла», где, кстати, рассказывается и про расчет стоимости. Также есть неплохие статьи — раз и два. Далее советую вам копать в сторону официальной доки и книги по BigQuery (целая книга, Карл!).
            Будет здорово, если кто-то уже хорошо покопал в эту сторону и может поделиться советами и опытом =)
            Отмечу также, что существуют UI-обертки над BigQuery типа Data Studio, позволяющие загружать туда данные и удобно их визуализировать. Data Studio пока в бете, но в будущем обещает стать очень удобным инструментом.


            User properties


            Мы, по сути, продолжаем тему событий, так как user properties — ее неотъемлемая часть.
            User properties (по-русски «Свойства пользователя») — это признаки, с помощью которых вы можете описывать различные сегменты вашей пользовательской базы, такие как язык, географическая локация и т.д. Их еще называют sticky params, так как они прикрепляются к каждому событию.
            Изначально к каждому событию прикрепляются только properties по-умолчанию. А если в коде вы вызываете подобный код:


            mFirebaseAnalytics.setUserProperty("license_property", mLicenseType);

            то к каждому последующему вашему событию будет прикрепляться property «license_property» с заданным заранее значением (значение «mLicenseType»). И даже после перезапуска приложения, телефона и прочее данное property будет прикрепляться. То есть property является еще и persistence.
            При этом вы должны предварительно зарегистрировать ваше property в консоли (рисунок кликабельный):


            Все подробно расписано здесь и в api.
            Отмечу, что для конкретного приложения вы можете отправлять до 25 properties (без учета properties, которые отправляются по умолчанию). Список properties, отправляемых по умолчанию, здесь.
            Собственно в консоли вы можете фильтровать все, что угодно, по properies и «аудитории» (про «аудиторию» скажем чуть ниже). Например, события (рисунки кликабельные):



            Аналогом setUserProperty(...) в GA являются методы setCustomDimension(...) и setCustomMetric(...). Единственное, данные dimension и metric не являются sticky и persistence, и вам будет необходимо к каждому событию каждую сессию вручную прикреплять их.


            События. FA + другая аналитика


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


            Кратко представлю их, чтобы вы имели представление:


            1. Просто отдельно слать разные аналитики. В коде вы, скорее всего, создадите некий универсальный фасад, который и будете использовать везде.
              Минусы, я думаю, понятны. Больше трафика и кода.
            2. Google Tag Manager.
              Данный менеджер подключается через консоль и там же настраивается. Суть в том, что в коде вы вообще ничего не делаете дополнительно (за исключением build.gradle и добавления конфигурационных файлов), просто шлете FA-события — и все. А уже на своей стороне Google Tag Manager трансформирует FA-события по заданным вами правилам в события аналитик, которые вам нужны (GA, AppsFlyer и прочие партнеры Google Tag Manager). Там же вы можете настраивать все возможные триггеры, по которым события FA будут попадать в другие аналитики (например, для какой-то аналитики нужны только строго определенные события).
              Звучит прям очень круто и гибко. Сам, к сожалению, не пробовал, так как нужно определенное время, чтобы погрузиться и разобраться, что есть что там. Если у кого есть опыт, пишите, будет очень круто более подробно расписать Google Tag Manager. Пока же кину статью, с которой можно попробовать начать.
              Но есть минусы. Первое — необходимо время, чтобы настроить все тэги для всех событий, да и вообще просто разобраться. Второе — вы не можете использовать FA и Google Tag Manager для отправки в GA ecommerce data.
            3. BigQuery.
              Это относится, конечно же, к GA и FA, когда вам необходимо совместить данные. Но выгрузить данные с GA в BigQuery вы сможете, только если у вас Google Analytics 360.

            Особенности подключения FA


            Чтобы настроить на своем проекте аналитику, вам нужно четко следовать данной документации. Есть уже встроенный в Android Studio плагин, который делает за вас половину работы. Если у вас все в первый раз, то процесс займет не более 15 минут. Хотите API? А вот и оно, довольно короткое и вроде понятное.
            После настойки FA через Android Studio Assistant у себя в проекте вы заметите появление нового файла google-services.json. Это специальный файл, в котором прописаны все идентификаторы и пути, которые необходимы для работы в вашем приложении различных гугловских сервисов, в данном случае — для работы FA.
            Также в ваш корневой build.gradle добавилась такая строчка:


            dependencies {
                classpath 'com.google.gms:google-services:3.0.0'
                // ...
            }

            Собственно google-services — это специальный плагин, который распарсивает google-services.json, преобразуя его в обычные строчки, используемые FA. А также google-services добавляет все необходимые зависимости для используемых гугловских сервисов (в нашем случае только для FA). Правда, для этого нужно в app/build.gradle в самом конце добавить:


            apply plugin: 'com.google.gms.google-services'

            В google-services.json находится вся необходимая информация для подключения вашего проекта к Firebase, причем не только к аналитике, но и ко всем инстументам.


            Вот как выглядит примерный google-services.json:
            {
              "project_info": {
                "project_number": "887654601522",
                "firebase_url": "https://fir-test3-4bab3.firebaseio.com",
                "project_id": "fir-test3-4bab3",
                "storage_bucket": "fir-test3-4bab3.appspot.com"
              },
              "client": [
                {
                  "client_info": {
                    "mobilesdk_app_id": "1:887654601522:android:9c6c1c11f784b956",
                    "android_client_info": {
                      "package_name": "com.example.matsyuk.firebasetest3"
                    }
                  },
                  "oauth_client": [
                    {
                      "client_id": "887654601522-o8rolth1g5mq5qq650844chk07mib2un.apps.googleusercontent.com",
                      "client_type": 1,
                      "android_info": {
                        "package_name": "com.example.matsyuk.firebasetest3",
                        "certificate_hash": "82f13b732dec32c5ebd4498c3a7acf4bda23a846"
                      }
                    },
                    {
                      "client_id": "887654601522-4riqkg424gb236q6mqehksn03u4hoqqg.apps.googleusercontent.com",
                      "client_type": 3
                    }
                  ],
                  "api_key": [
                    {
                      "current_key": "AIzaSyAYRPNTcgxWP7qUzI__kx9gSwxnIgc3iBo"
                    }
                  ],
                  "services": {
                    "analytics_service": {
                      "status": 1
                    },
                    "appinvite_service": {
                      "status": 2,
                      "other_platform_oauth_client": [
                        {
                          "client_id": "887654601522-4riqkg424gb236q6mqehksn03u4hoqqg.apps.googleusercontent.com",
                          "client_type": 3
                        }
                      ]
                    },
                    "ads_service": {
                      "status": 2
                    }
                  }
                }
              ],
              "configuration_version": "1"
            }

            С помощью плагина google-services данный json преобразуется в набор строчек, сгенерированныч в файл your_project\app\build\generated\res\google-services\debug\values\values.xml:


            
            
                887654601522-4riqkg424gb236q6mqehksn03u4hoqqg.apps.googleusercontent.com
                https://fir-test3-4bab3.firebaseio.com
                887654601522
                AIzaSyAYRPNTcgxWP7qUzI__kx9gSwxnIgc3iBo
                1:887654601522:android:9c6c1c11f784b956
                AIzaSyAYRPNTcgxWP7qUzI__kx9gSwxnIgc3iBo
                fir-test3-4bab3.appspot.com
            

            Обратите внимание на такие строчки, как firebase_database_url, google_storage_bucket и т.д. Захотите подключить еще один инструмент от Firebase, в проекте у вас уже все будет готово для этого.
            Подробнее про плагин и устройство google-services.json написано здесь.


            А теперь рассмотрим жизненный пример. Есть у нас приложение Example с applicationId, равное, допустим, com.fa.example. И есть у нас в продукте flavors:


            productFlavors {
                dev {
                    applicationId "com.fa.example.dev"
                }
                qa {
                    applicationId "com.fa.example.qa"
                }
                prod {
                    // applicationId "com.fa.example"
                }
            }

            Далее мы хотим зарегистрировать проект в FA через Android Studio Assistant. Делаем все по инструкции и получаем в консоли проект Example, в котором три приложения:
            image


            И если вы посмотрите в проекте app/google-services.json, то в нем будет информация о ваших трех приложениях (трех flavors с разными applicationId). То есть для каждого flavor аналитика будет собираться отдельно.
            Также отмечу, что вы можете самостоятельно скачать google-services.json с любого приложения вашего проекта. Но все google-services.json вашего проекта будут одинаковы и будут содержать информацию о всех приложениях в проекте.


            Далее такая ситуация. Ваш проект Example настроен с FA. Но вам вдруг понадобилось добавить в проект еще один flavor с другим именем пакета. И для этого flavor вы также хотите собирать аналитику отдельно. Тогда вам необходимо сделать следующее:


            1. Добавить новый flavor в build.gradle.
            2. Зарегистрировать новое приложение в проекте в консоли:
              image


            3. Скачать новый google-services.json (в котором будет информация о четырех приложениях в проекте) и подставить его вместо старого.


              Теперь такой пример. Допустим, есть у вас в проекте buildTypes, и выглядят они в build.gradle-файле следующим образом:


              buildTypes {
                  release {
              
                  }
                  ultra_debug {
                      applicationIdSuffix ".ultra_debug"
                  }
                  debug {
                      applicationIdSuffix ".debug"
                  }
              }

              То есть для сборок ultra_debug и debug вы добавляете суффикс к имени пакета. Таким образом, у вас в проекте три вышеназванных buildTypes и три flavors:


              productFlavors {
                  dev {
                      applicationId "com.fa.example.dev"
                  }
                  qa {
                      applicationId "com.fa.example.qa"
                  }
                  prod {
                      // applicationId "com.fa.example"
                  }
              }

              Вы запускаете Android Studio Assistant для подключения к проекту FA. Как вы думаете, сколько будет зарегистрировано в консоли приложений и с какими именами пакетов?
              Не догадаетесь =) Появятся в консоли такие приложения:


              com.fa.example.debug
              com.fa.example.dev.debug
              com.fa.example.qa.debug

              Почему именно только с суффиксом «debug», осталось для меня загадкой. Так что имейте в виду данный баг.



            Ну и заключительный пример.
            У вас в проекте все также те самые три flavors. И вы хотите добавить в проекте еще один flavor (например, custom), но для него нет необходимости в другом applicationId, и при этом данный flavor тоже желательно отдельно от всех остальных просматривать с точки зрения аналитики:


            productFlavors {
                dev {
                    applicationId "com.fa.example.dev"
                }
                qa {
                    applicationId "com.fa.example.qa"
                }
                prod {
                    // applicationId "com.fa.example"
                }
                custom {
                    // applicationId "com.fa.example"
                } 
            }

            Ситуация усложняется еще тем, что в консоли в проект вы не можете добавлять приложения с одинаковым applicationId. Как тогда быть? Делаем следующее:


            1. Регистрируем новый проект (не приложение в составе проекта Example, а именно отдельный проект) в консоли.
            2. Регистрируем в новом проекте приложение com.fa.example.
            3. Скачиваем с нового проекта google-services.json.
            4. Подставляем новый google-services.json следующим образом (выделено красным).
              image

            То есть в вашем андроидовском проекте будут лежать уже два google-services.json. При сборке google-services plugin сначала смотрит в папку конкретного flavor. Если в этой папке есть google-services.json, то плагин берет его. Если нет, то тогда берется google-services.json с app папки. Довольно удобно и гибко получается в итоге.


            Вроде бы жизнь разработчика стала проще. Зарегистрировал проект в консоли, скачал google-services.json, закинул в app/ (ну это все в случае без flavors и прочего), и все, больше ни о чем не думаешь. Но иногда бывает необходимость на лету переключить канал аналитики. И если в GA вы могли задавать в коде id, то в FA пока что такая возможность отсутствует. И были у меня надежды сначала на такую конструкцию (взято с SO):


            FirebaseOptions options = new FirebaseOptions.Builder()
                   .setApplicationId("bla-bla") // Required for Analytics.
                   .setApiKey("bla-bla") // Required for Auth.
                   .setDatabaseUrl("bla-bla") // Required for RTDB.
                   .build();
            FirebaseApp.initializeApp(this /* Context */, options, "secondary");

            Но выдается ошибка «Missing google_app_id. Firebase Analytics disabled». Команда Firebase знает про это и постепенно работает над данной проблемой.
            Более подробно про все описанные примеры вы можете прочитать в вышеназванной статье про google-services-плагин и здесь.


            Отправка данных


            В GA есть метод setLocalDispatcher(...). С помощью него мы можем задавать интервал периодической отправки данных. Хорошо, что FA заботится о нас и нашем трафике и не дает нам возможность самим регулировать данный параметр. Но в GA с помощью метода setLocalDispatcher(-1) мы можем отменить автоматическую отправку событий, а с методом dispatchLocalHits() вручную отправлять накопившиеся события. Это очень удобно, когда, например, мы не хотим отправлять события до принятия соглашения и т.д.
            У FA подобной возможности накопления и отправки событий нет, придется все руками делать.
            Зато хотя бы есть метод setAnalyticsCollectionEnabled(boolean enabled), с помощью которого мы можем включать и отключать аналитику. Например, если мы не хотим отправлять аналитику до принятия пользователем нужного соглашения, то в манифесте прописываем:



            А потом, когда нужно, в коде вызовем:


            setAnalyticsCollectionEnabled(true);

            Также можно отключить аналитику на постоянной основе. То есть даже вызов setAnalyticsCollectionEnabled(true) не поможет. Для этого в манифесте прописываем:



            Информация взята из данной статьи.


            Режим реального времени и отладка в FA


            В FA события с реальных устройств приходят в консоль лишь спустя сутки. И в начале не было возможности просмотреть действия пользователя в реальном времени. Чтобы увидеть первые данные, приходилось ждать целые сутки. Сейчас же вы можете воспользоваться вкладкой StreamView/DebugView (рисунок кликабельный):


            На картинке выше представлен StreamView, на котором вы можете наблюдать, как себя ведут пользователи в данный момент времени. Также вы можете выбрать режим Snapshot (кнопка User snapshot справа снизу), и вам покажутся действия случайно выбранного пользователя (рисунок кликабельный):


            Подобным образом выглядит и DebugView. Наконец-то отлаживаться можно в режиме реального времени. Вы будете видеть все events и properties, которые посылаются вашим приложением, включая и events c properties по умолчанию. Как можно представить, до DebugView процесс отладки был воистину ужасным.
            О StreamView и DebugView хорошо расписано здесь.


            Понятие «сессии» в FA


            Что в GA, что в FA, все мы видим такие слова, как «сессия», «количество событий за сессию» и т.д. И, наверное, может сложиться впечатление, что сессия = время жизни процесса. Но это не так. Сессия — это просто временной промежуток, в течение которого ваше приложение активно (находится в foreground). В API FA есть такие методы:


            setMinimumSessionDuration (long milliseconds); // default 10 sec
            setSessionTimeoutDuration (long milliseconds); // default 30 min

            То есть если вы запустили приложение и убили его менее чем за minimumSessionDuration, то сессия даже не начнется. Если же запущенное приложение находится в foreground более minimumSessionDuration, то сессия стартует.
            Если ваше приложение было выгружено системой, но оно успело подняться до истечения sessionTimeoutDuration, то это все будет одна сессия. Если вы запустили приложение, что-то поделали там, потом вышли из него (то есть приложение не в foreground), и только через sessionTimeoutDuration+ зашли обратно (при этом приложение не было убито, к примеру), то первая сессия завершится и стартует вторая.


            Еще немного об FA-консоли


            Audiences


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


            Создание новой «аудитории» (рисунок кликабельный):


            Допустим, вам нужна аудитория «Мужчины из России, которые прошли регистрацию». Тогда при создании «аудитории» вы выбираете properties «country» = «Russia» и «sex» = «male» и event «reg_comleted» (это уже ваш кастомный event) = «true».


            Funnels


            На этой вкладке у вас есть возможность строить разные воронки (рисунок кликабельный):


            Очень нравится маркетологам это делать =)
            Отмечу, что подобный функционал есть и у GA.


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


            FA. Выводы


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


            Плюсы:


            1. FA — это активно развивающийся продукт. Примерно с февраля я наблюдаю за его развитием и должен отметить, что команда разработки старается максимально реализовывать первоочередные потребности пользователей.
            2. FA events + BigQuery. Это прямо главное преимущество FA. У вас есть доступ ко всем событиям вашего приложения практически бесплатно. И если в вашей команде есть спец по BigQuery, то вам чертовски повезло. Кроме того, сами «события» в FA намного более гибкие и удобные в использовании.
            3. Минимализм. В консоли, по сути, только самое необходимое. Акцент делается на «события». В GA же все-таки много всего намешано, и далеко не все нам нужно.
            4. Интеграция с другими проектами Firebase. Будь то сбор крашей или RemoteConfig. Продукты действительно дополняют друг друга, и это открывает новые возможности.

            Минусы:


            1. Ребятам еще много работать, особенно в консоли. Но мы верим в них =)
            2. Разбросанность информации. Это то, о чем я говорил в самом начале статьи. Каждый вопрос или уточнение нужно искать и ресерчить. Отсутствие упорядоченности тоже сбивает вначале. Но данная статья в принципе призвана устранить данный недостаток.

            Меня часто спрашивают, так стоит ли использовать FA или нет. Может, вполне достаточно GA? Или сразу обе аналитики не достойны места в вашем продукте?
            Однозначного ответа нет. Все очень зависит от потребностей ваших аналитиков и маркетологов. А также зависит от способностей ваших аналитиков осилить BigQuery. Все-таки мы, разработчики, — это «пехотинцы продукта», особенно в части аналитики. Что нам скажут, то мы и будем делать. Но лично я бы смотрел в сторону связки FA + BigQuery. Уж очень она крутая, и вы никак не ограничены возможностями консоли.


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


            P.S. Большое спасибо хочу сказать Тимуру Ахметгарееву за помощь и за то, никогда не бросал в беде =)


            P.P.S. И еще добавлю. 16 сентября 2017 совместно с независимым сообществом разработчиков MOSDROID мы организовываем вечернюю встречу для всех, кто заинтересован в разработке под Android. По традиции, мы подготовили для вас несколько докладов. Ждём всех желающих. Зарегистрироваться на встречу можно здесь.

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

            https://habrahabr.ru/post/334292/


            [Перевод] Пишем оператора для Kubernetes на Golang

            Вторник, 12 Сентября 2017 г. 09:32 + в цитатник
            shurup сегодня в 09:32 Разработка

            Пишем оператора для Kubernetes на Golang

            • Перевод
            Прим. перев.: Операторы (operators) — это вспомогательное ПО для Kubernetes, призванное автоматизировать выполнение рутинных действий над объектами кластера при определённых событиях. Мы уже писали об операторах в этой статье, где рассказывали об основополагающих идеях и принципах их работы. Но если тот материал был скорее взглядом со стороны эксплуатации готовых компонентов для Kubernetes, то предлагаемый теперь перевод новой статьи — это уже видение разработчика/DevOps-инженера, озадаченного реализацией нового оператора.



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

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

            Достичь желаемого можно назначением пользователю группы, у которой есть RoleBinding к конкретным Namespace и ClusterRole с правом на редактирование. YAML-представление будет выглядеть так:

            ---
            kind: RoleBinding
            apiVersion: rbac.authorization.k8s.io/v1beta1
            metadata:
              name: kubernetes-team-1
              namespace: team-1
            subjects:
            - kind: Group
              name: kubernetes-team-1
              apiGroup: rbac.authorization.k8s.io
            roleRef:
              kind: ClusterRole
              name: edit
            apiGroup: rbac.authorization.k8s.io

            (rolebinding.yaml, в raw)

            Создавать такой RoleBinding можно и вручную, но после преодоления отметки в сотню пространств имён это становится утомительным занятием. Как раз здесь помогают операторы Kubernetes — они позволяют автоматизировать создание ресурсов Kubernetes, основываясь на изменениях в ресурсах. В нашем случае мы хотим создавать RoleBinding при создании Namespace.

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

            (Прим. перев.: здесь и далее комментарии в коде переведены на русский язык. Кроме того, отступы исправлены на пробелы вместо [рекомендуемых в Go] табов исключительно с целью лучшей читаемости в рамках вёрстки Хабры. После каждого листинга приведены ссылки на оригинал на GitHub, где сохранены англоязычные комментарии и табы.)

            func main() {
              // Устанавливаем вывод логов в консольный STDOUT
              log.SetOutput(os.Stdout)
            
              sigs := make(chan os.Signal, 1) // Создаем канал для получения сигналов ОС
              stop := make(chan struct{})     // Создаем канал для получения стоп-сигнала
            
              // Регистрируем получение SIGTERM в канале sigs
              signal.Notify(sigs, os.Interrupt, syscall.SIGTERM, syscall.SIGINT) 
            
              // Goroutines могут сами добавлять себя в WaitGroup,
             // чтобы завершения их выполнения дожидались
              wg := &sync.WaitGroup{} 
            
              runOutsideCluster := flag.Bool("run-outside-cluster", false, "Set this flag when running outside of the cluster.")
              flag.Parse()
              // Создаем clientset для взаимодействия с кластером Kubernetes
              clientset, err := newClientSet(*runOutsideCluster)
            
              if err != nil {
                panic(err.Error())
              }
            
              controller.NewNamespaceController(clientset).Run(stop, wg)
            
              <-sigs // Ждем сигналов (до получения сигнала более ничего не происходит)
              log.Printf("Shutting down...")
            
              close(stop) // Говорим goroutines остановиться
              wg.Wait()   // Ожидаем, что все остановлено
            }

            (main.go, в raw)

            Мы делаем следующее:

            1. Настраиваем обработчика конкретных сигналов операционной системы, чтобы вызвать корректное (graceful) завершение работы оператора.
            2. Используем WaitGroup, чтобы корректно остановить все функции Go перед завершением работы приложения.
            3. Предоставляем доступ к кластеру созданием clientset.
            4. Запускаем NamespaceController, в котором будет расположена вся наша логика.

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

            // NamespaceController следит через Kubernetes API за изменениями
            // в пространствах имен и создает RoleBinding для конкретного namespace.
            type NamespaceController struct {
              namespaceInformer cache.SharedIndexInformer
              kclient           *kubernetes.Clientset
            }
            
            // NewNamespaceController создает новый NewNamespaceController
            func NewNamespaceController(kclient *kubernetes.Clientset) *NamespaceController {
              namespaceWatcher := &NamespaceController{}
            
              // Создаем информер для слежения за Namespaces
              namespaceInformer := cache.NewSharedIndexInformer(
                &cache.ListWatch{
                  ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
                    return kclient.Core().Namespaces().List(options)
                  },
                  WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
                    return kclient.Core().Namespaces().Watch(options)
                  },
                },
                &v1.Namespace{},
                3*time.Minute,
                cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
              )
            
              namespaceInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
                AddFunc: namespaceWatcher.createRoleBinding,
              })
            
              namespaceWatcher.kclient = kclient
              namespaceWatcher.namespaceInformer = namespaceInformer
            
              return namespaceWatcher
            }

            (controller.go, в raw)

            Здесь мы настраиваем SharedIndexInformer, который будет эффективно (используя кэш) ожидать изменений в пространствах имён (подробнее об informers читайте в статье «Как на самом деле работает планировщик Kubernetes?» — прим. перев.). После этого мы подключаем EventHandler к информеру, благодаря чему при добавлении пространства имён (Namespace) вызывается функция createRoleBinding.

            Следующий шаг — определить эту функцию createRoleBinding:

            func (c *NamespaceController) createRoleBinding(obj interface{}) {
              namespaceObj := obj.(*v1.Namespace)
              namespaceName := namespaceObj.Name
            
              roleBinding := &v1beta1.RoleBinding{
                TypeMeta: metav1.TypeMeta{
                  Kind:       "RoleBinding",
                  APIVersion: "rbac.authorization.k8s.io/v1beta1",
                },
                ObjectMeta: metav1.ObjectMeta{
                  Name:      fmt.Sprintf("ad-kubernetes-%s", namespaceName),
                  Namespace: namespaceName,
                },
                Subjects: []v1beta1.Subject{
                  v1beta1.Subject{
                    Kind: "Group",
                    Name: fmt.Sprintf("ad-kubernetes-%s", namespaceName),
                  },
                },
                RoleRef: v1beta1.RoleRef{
                  APIGroup: "rbac.authorization.k8s.io",
                    Kind:     "ClusterRole",
                    Name:     "edit",
                },
              }
            
              _, err := c.kclient.Rbac().RoleBindings(namespaceName).Create(roleBinding)
            
              if err != nil {
                log.Println(fmt.Sprintf("Failed to create Role Binding: %s", err.Error()))
              } else {
                log.Println(fmt.Sprintf("Created AD RoleBinding for Namespace: %s", roleBinding.Name))
              }
            }

            (controller.go, в raw)

            Мы получаем пространство имён как obj и преобразуем его в объект Namespace. Затем определяем RoleBinding, основываясь на упомянутом в начале YAML-файле, используя предоставленный объект Namespace и создавая RoleBinding. Наконец, логируем, успешно ли прошло создание.

            Последняя функция, которую необходимо определить, — Run:

            // Run запускает процесс ожидания изменений в пространствах имён
            // и действия в соответствии с этими изменениями.
            func (c *NamespaceController) Run(stopCh <-chan struct{}, wg *sync.WaitGroup) {
              // Когда эта функция завершена, пометим функцию go как выполненную
              defer wg.Done()
            
              // Инкрементируем wait group, т.к. собираемся вызвать функцию go
              wg.Add(1)
            
              // Вызываем функцию go
              go c.namespaceInformer.Run(stopCh)
            
              // Ожидаем получения стоп-сигнала
              <-stopCh
            }

            (controller.go, в raw)

            Здесь мы говорим WaitGroup, что запустим функцию go и затем вызываем namespaceInformer, который был предварительно определён. Когда поступит сигнал остановки, он завершит функцию go, сообщит WaitGroup, что больше не выполняется, и эта функция завершит свою работу.

            Информацию о сборке и запуске этого оператора в кластере Kubernetes можно найти в репозитории на GitHub.

            На этом оператор, который создаёт RoleBinding при появлении Namespace в кластере Kubernetes, готов.
            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/337698/


            Метки:  

            Нам нечего скрывать – все честно в HPE 3PAR Data Reduction Guarantee

            Вторник, 12 Сентября 2017 г. 09:24 + в цитатник
            vlad_msk_ru сегодня в 09:24 Администрирование

            Нам нечего скрывать – все честно в HPE 3PAR Data Reduction Guarantee

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

              А в информационных технологиях такого контролирующего органа, как Центробанк, нет, поэтому беспредел продолжается. Одна из самых горячих тем – это экономия дискового пространства при использовании флеш-памяти. Да, SSD стоят уже не так дорого (особенно после скидок), но для того, чтобы сравняться по стоимости хранения с традиционными дисками, приходится учитывать возможности всех технологий сжатия данных. И вот тут фантазия отделов маркетинга становится буйной. Судя по сохранившимся ссылкам на форуме, эффективность технологий сжатия данных у одного из вендоров составила 933:1 (правда, в следующей версии операционной системы, новой и улучшенной, упала до 4:1 – но ведь прогресс не остановить?). Бесконечность – не предел. Но реальная жизнь предприятий, использующих системы хранения, отличается от мультипликационных фильмов. Мы здесь не в игрушки играем!



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

              • Методика «вали все в кучу». Мне кажется, что для оценки эффективности сжатия нужно учитывать только те технологии, которые в реальности уменьшают количество записываемых системой хранения битов и байтов – если использовать универсальный подход, то компрессию и дедупликацию. Не все со мной согласятся. Те производители, массивы которых не поддерживают компрессию и/или дедупликацию, используют хитрости для того, чтобы прилично выглядеть в маркетинговых брошюрах и применяют методику «вали все в кучу». Экономия при использовании thin provisioning (которая впервые в мире появилась в массивах 3PAR) и снепшотов, почти не занимающих места на диске, безусловно достигается. Но к сжатию данных это не относится. Да и показателями манипулировать слишком просто – сделал 1000 снепшотов одного тома, данные не менял, места они не занимают. Назвал это дедупликацией и достиг экономии 933:1 (почему не 1000:1? Спросите у соответствующего вендора). По аналогии – создайте том с thin provisioning размером 1ТБ и запишите на него один единственный текстовый файл со списком вендоров-производителей массивов all-flash. Посчитайте виртуальную экономию :)

                Как бороться: попробуйте получить от вендора показатели сжатия данных без учета thin provisioning и снепшотов. Если какой-то производитель не предоставляет эту информацию – переходите к другому.
              • Методика «…до 10 раз». Это напоминает мне еще один рекламный трюк прошлого: «применяйте это средство, и вы получите результат уже через некоторое время!». Никто не утверждал, что это время наступит до того, как покупатель потратит все имеющиеся деньги в ожидании эффекта, так что формально придраться не к чему. Вот и изобретательные маркетологи производителей систем хранения пишут – «вы можете сэкономить до 10 раз». До 10 раз может быть любым числом от 1 до 10. Безусловно, чудо действительно произойдет уже через некоторое время у отдельных заказчиков. Но не факт, что вы войдете в их число.

                Как бороться: требуйте точную оценку коэффициента сжатия/дедупликации, которую реально получить в ваших условиях. Если вендор отказывается или предлагает провести исследование используемой в настоящее время системы хранения с остановкой работы ваших бизнес- приложений – переходите к следующему.
              • Методика «наживка и крючок». Некоторые производители рекламируют большие коэффициенты сжатия – 5:1 и больше, но гарантируют ли они достижение этих цифр? Во многих случаях нет. Вы поверили на слово и купили. А потом уже деваться некуда: если результат не будет достигнут, время, деньги и работу по внедрению никто не компенсирует.

                Как бороться: требуйте гарантии экономии дискового пространства в договоре. Предусмотрите действия, которые будут предприняты со стороны поставщика, если эти условия не будут выполняться. Если вендор отказывается – позвоните коммерческому представителю Hewlett Packard Enterprise и обсудите HPE 3PAR Data Reduction Guarantee.

              Все честно


              Я уверен, что технологии HPE 3PAR Adaptive Data Reduction – это лучшее из того, что есть в настоящее время на рынке в плане экономии емкости флеш-накопителей. Вы получаете превосходные показатели стоимости владения, и кроме того, чем меньше вы перезаписываете SSD, тем дольше он останется работоспособным. Кроме того, все должно быть честно с гарантиями, поэтому Data Reduction Guarantee:

              1. не учитывает экономию от снепшотов и thin provisioning
              2. предлагает учитывать реальные средние показатели, которые достигаются при эксплуатации 3PAR в разных профилях нагрузки.

              Рабочая нагрузка Коэффициент экономии емкости
              VDI 2,5 – 6,0
              Виртуализация серверов 1,5 – 2,5
              СУБД 2,0 – 3,5

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

              В программе нет никаких секретных условий, прописанных мелким шрифтом. Листовка находится в открытом доступе на сайте HPE. Если приобретение массива для вас актуально в ближайшее время — распечатайте ее и обратитесь к вашему поставщику.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/337636/


              Метки:  

              [Перевод] Moving Java forward faster

              Вторник, 12 Сентября 2017 г. 09:24 + в цитатник
              alek_sys сегодня в 09:24 Разработка

              Moving Java forward faster

              • Перевод

              От переводчика


              Недавно в мире Java случилось пара важных событий: Марк Рейнхольд опубликовал письмо, в котором предложил перейти на полугодовой график релизов Java, а за этим последовало сообщение от Oracle, где они предлагают ряд серьезных измненеий в работе над OpenJDK:


              • Начиная с JDK 9 GA, Oracle будет выпускать OpenJDK билды под GPL лицензией
              • Java SE перейдет на постоянный график релизов (письмо Марка)
              • Oracle внесет ранее коммерческие фичи (как Java Flight Recorder) в OpenJDK
              • Oracle будет сотрудничать с другими разработчиками OpenJDK чтобы обеспечить современню, полную и доступную среду

              Несмотря на то, что коммерческая версия Oracle JDK останется, целью компании станет сделать ее полностью совместимой и взаимозаменяемой с OpenJDK (до конца 2018).


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


              Moving Java forward faster


              На протяжении более чем 20 лет, платформа Java SE и JDK двигались вперед большими, нерегулярными и непредсказуемыми шагами. Каждый feature-release определялся какой-то основной фичей и график релиза менялся — иногда и не один раз! — чтобы подстроится под график разработки этой фичи.


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


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


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


              Снова поезд


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


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


              Модель "поезда" с двухгодичным графиком выглядела неплохо в теории, но доказала свою неработоспособность на практике. Нам потребовалось дополнительные 8 месяцев для Java 8 чтобы закрыть критические проблемы безопасности и закончить проект "Лямбда", и это было предпочтительнее, чем откладывать лямбды на два года. Изначально мы даже планировали Java 9 как 2.5 годичный релиз, чтобы включить туда Project Jigsaw, чтобы не задерживать его на 18 месяцев до следующего "поезда". Но в конец-концов нам потребовался еще один год на доработку, и Java 9 выйдет только в этом месяце, через 3.5 года после Java 8.


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


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


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


              Предложение


              Воодушевившись примером релизного графика других платформ и операционных систем, я предлагаю, после релиза Java 9, перейти на строгий, регулярный график релизов с новым feature-релизом каждые шесть месяцев, update-релизом каждый квартал и LTS (long term support) релизом каждые три года.


              • Feature-релиз может содержать любые фичи, включая не только новые или обновленные API, но так же фичи языка и JVM. Новые фичи будут внесены в релиз тогда на финальных стадиях, так что текущий релиз должен быть feature-complete в любой момент времени. Feature-релизы будут выпускаться дважды в год, в марте и сентябре, начиная с марта 2018 года.
              • Update-релиз будет строго ограничен исправлениями проблем с безопасностью, регрессионных багов, и багов в новых фичах. Каждый feature-релиз полчит два update-релиза. Update-релизы будут выпускаться ежеквартально, в январе, апреле, июле и октябре.
              • Каждые три года, начиная с сентября 2017, feature-релиз будет становится LTS релизом. Обновления для LTS релизов будут доступны как минимум три года, а возможно и дольше, в зависимости от поставщика.

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


              Разработчики, которые предпочитают инновации и новые фичи смогут использовать новые feature-релизы или последние доступные update-релизы. Они смогут упаковывать приложения в Докер контейнеры, или любые другие типы контейнеров, указывая точную версию Java, которая им необходима. С таким подходом, т.к. новый релиз Java и приложение могут быть легко протестированы на совместимость с помощью современных CI/CD pipeline-ов, переход на новый релиз будет простым.


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


              Чтобы ориентироваться в новых релизах со строгим графиком и чтобы легко понять, когда был выпущен тот или иной релиз, версия релиза будет выглядеть как $YEAR.$MONTH. То есть, следующий мартовский релиз будет 18.3, а сентябрьский LTS будет 18.9.


              Последствия


              Это предложение, если будет принято, потребует серьезных изменений в том, как контрибьюторы в OpenJDK Community производят непосредственно JDK. Я изложил ряд мыслей на этот счет. Этот процесс будет гораздо проще, если мы сможем уменьшить накладные расходы, создаваемые Java Community Process, которые курирует развитие платформы Java SE; мои коллеги Брайн Гетз и Джордж Саб уже подняли этот вопрос на обсуждения с JCP Executive Committee.


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

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

              https://habrahabr.ru/post/337704/


              Метки:  

              [Перевод] PostgreSQL: материализованные представления и FWD

              Вторник, 12 Сентября 2017 г. 09:00 + в цитатник
              olemskoi сегодня в 09:00 Разработка

              PostgreSQL: материализованные представления и FWD

              • Перевод


              Вы наверняка знаете, что в Postgres есть материализованные представления (materialized views) и обертки сторонних данных (foreign data wrappers, FWD). Материализованные представления позволяют материализовывать запросы и обновлять их по требованию. Обертки сторонних данных предоставляют функциональность загрузки данных из внешних источников, таких как, например, NoSQL-хранилища или другие серверы Postgres.


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


              Давайте подтвердим это практикой! Для начала создадим стороннюю таблицу (foreign table):


              CREATE DATABASE fdw_test;
              \connect fdw_test;
              CREATE TABLE world (greeting TEXT);
              \connect test
              
              CREATE EXTENSION postgres_fdw;
              CREATE SERVER postgres_fdw_test FOREIGN DATA WRAPPER postgres_fdw
              OPTIONS (host 'localhost', dbname 'fdw_test');
              
              CREATE USER MAPPING FOR PUBLIC SERVER postgres_fdw_test
              OPTIONS (password '');
              
              CREATE FOREIGN TABLE other_world (greeting TEXT)
              SERVER postgres_fdw_test
              OPTIONS (table_name 'world');
              
              \det
                        List of foreign tables
               Schema |    Table    |      Server
              --------+-------------+-------------------
               public | other_world | postgres_fdw_test
              

              заполним ее данными:


              INSERT INTO other_world
              SELECT *
              FROM generate_series(1, 100000);

              и создадим материализованное представление на основе сторонней таблицы:


              CREATE MATERIALIZED VIEW mat_view (first_letter, count) AS
                      SELECT left(greeting, 1), COUNT(*)
                      FROM other_world
                      GROUP BY left(greeting, 1);

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


              \timing
              
              SELECT left(greeting, 1) AS first_letter, COUNT(*)
              FROM other_world
              GROUP BY left(greeting, 1);
               first_letter | count
              --------------+-------
               1            | 11112
               2            | 11111
               3            | 11111
               4            | 11111
               5            | 11111
               6            | 11111
               7            | 11111
               8            | 11111
               9            | 11111
              
              Time: 354.571 ms
              
              SELECT * FROM mat_view;
               first_letter | count
              --------------+-------
               1            | 11112
               2            | 11111
               3            | 11111
               4            | 11111
               5            | 11111
               6            | 11111
               7            | 11111
               8            | 11111
               9            | 11111
              
              Time: 0.783 ms

              Материализованное представление оказалось намного быстрее, однако не все так радужно, поскольку его обновление (refresh) занимает практически столько же времени, сколько и выборка из сторонней таблицы:


              REFRESH MATERIALIZED VIEW mat_view;
              Time: 364.889 ms

              Вышеприведенные команды выполнялись в Postgres 9.6. Однако уже в десятой версии появилось вот такое улучшение:


              Выполнение агрегатных функций на серверах FWD, когда это возможно (Jeevan Chalke, Ashutosh Bapat).

              Благодаря ему удается уменьшить объем передаваемых от FWD-сервера данных, а также снять с запрашивающего сервера нагрузку по агрегированию. Эта оптимизация реализована в обертке сторонних данных postgres_fdw, которая также умеет выполнять соединения (join) на сторонних серверах (используя расширения).


              В Postgres 10 агрегаты сторонних таблиц выполняются быстрее, чем в 9.6, но все же пока медленнее, чем выборки из материализованных представлений:


              SELECT left(greeting, 1) AS first_letter, COUNT(*)
              FROM other_world
              GROUP BY left(greeting, 1);
               first_letter | count
              --------------+-------
               1            | 11112
               2            | 11111
               3            | 11111
               4            | 11111
               5            | 11111
               6            | 11111
               7            | 11111
               8            | 11111
               9            | 11111
              
              Time: 55.052 ms

              Использовать агрегаты в материализованных представлениях совсем не обязательно — можно просто скопировать стороннюю таблицу целиком и обновлять соответствующую вьюшку по необходимости (но логическая репликация в Postgres 10 подходит для этого еще лучше):


              CREATE MATERIALIZED VIEW mat_view2  AS
                      SELECT *
                      FROM other_world;

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


              \o /dev/null
              
              SELECT *
              FROM other_world;
              Time: 317.428 ms
              
              SELECT * FROM mat_view2;
              Time: 34.861 ms

              В заключение отмечу, что материализованные представления и обертки сторонних данных отлично работают вместе. С помощью материализованных представлений можно создавать локальные копии (кэши) внешних таблиц целиком или агрегированных данных (выборок) из этих таблиц. Обновить такой кэш очень просто: refresh materialized view. При этом в Postgres 10 появились улучшения, которые ускоряют запросы с агрегатными функциями к сторонним таблицам.


              Ссылки:


              1. Оригинал: Materialized Views and Foreign Data Wrappers.
              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/337632/


              Метки:  

              От Торонто до Томска: подведение итогов и планирование будущих семинаров по микроэлектронике в России

              Вторник, 12 Сентября 2017 г. 08:59 + в цитатник
              YuriPanchul сегодня в 08:59 Разработка

              От Торонто до Томска: подведение итогов и планирование будущих семинаров по микроэлектронике в России

                Видите японского робота-собачку слева от девушки Ирины? Этот робот из компьютерного музея у офиса Гугла управлялся встроенным процессором MIPS R4000. Дальний потомок этого процессора, MIPS microAptiv UP, станет темой нескольких докладов на семинаре по обмену опытом преподования электроники, который пройдет в Томске на следущей неделе. Ядро MIPS microAptiv UP, в своем бесплатном варианте, MIPSfpga, стало удобной морской свинкой для студенческих экспериментов по микроархитектуре процессорных ядер и созданию систем на кристалле.

                Важная часть экспериментов с MIPSfpga случилась в России, на Украине и в Казахстане в течении двух прошлых лет — про это рассказала на конференции в Торонто Сара Харрис, профессор Университета Лас-Вегаса. В семинарах на эту и смежные темы проектирования чипов (Nanometer ASIC) приняли участие МГУ, МФТИ, МИФИ, МИЭТ, ИТМО и другие ведущие университеты.

                Сейчас эксперимент продолжается: в этом месяце, помимо конференции в Томске, в Россию приезжает Роберт Оуэн, известный консультант по университетским образовательным программам в области микроэлектроники. За последние 23 года Роберт Оуэн посетил сотни университетов в Европе, Азии, Америке и даже Африке, как представитель Texas Instruments, ARM, Xilinx и Imagination. Он помогал университетам поставить программы в области DSP, микроконтроллеров, встроенных процессоров и систем на кристалле.

                Вся эта деятельность — не абстрактное образование ради образования. На технологиях разработки микросхем с использованием языков описания аппаратуры Verilog и VHDL и использовании синтезируемых ядер стоит бизнес таких российских компаний как ЭЛВИС-НеоТек и Байкал Электроникс, о которых расскажет в лекции на этой неделе никто иной как Чубайс.



                Про эти и другие мероприятия из прошлого и будущего подробнее под катом.

                Сара Харрис, соавтор популярного учебника «Цифровая схемотехника и архитектура компьютера»:



                Слайд Сары Харрис на конференции в Торонто про семинары в России, на Украине и в Казахстане:



                Отрывок из статьи группы европейцев, американцев и русских украинцев об этих семинарах (вот она, евроинтеграция!):



                Русский перевод этой статьи есть на Хабре

                Теперь про семинар в Томске. Объявленная цель семинара: «В последние годы российские компании научились писать программы мирового качества, но как достигнуть микросхем мирового качества, чтобы сегодняшние студенты через несколько лет проектировали росийские чипы на уровне передовых западных компаний, таких как Intel, Samsung и Apple? Мы приглашаем вас на два мероприятия в Томске, которые организованы для толчка в этом направлении:

                1. Симпозиум-совещание для преподавателей по обмену опытом преподавания электроники. На него приедут с докладами представители ведущих российских университетов, включая МГУ, МФТИ, МИЭТ, российских и международных компаний, включая МЦСТ, Imagination Technologies и National Instruments.

                2. Школа-семинар для студентов, аспирантов и всех интересующихся по введению в языки описания цифровых схем Verilog и VHDL, архитектуру и микроархитектуры процессоров, прототипирование систем на кристалле с использованием ПЛИС/FPGA и их связь с массовыми микроконтроллерами и встроенными процессорами. Для студентов и аспирантов из Томска и Новосибирска эта школа бесплатна.

                Оба мероприятия состоятся 18-22 сентября. Таблица с программой симпозиума, и контактная информация.

                История создания: идея семинара в Томске зародилась прошлой зимой за этим столом, который территориально находится между офисами Интела и AMD, у сотрудника Университета Калифорнии Санта-Круз (отделение в Silicon Valley) Чарльза, сотрудника Университета Аризоны (и бывшего сотрудника Моторолы) Анатолия, сотрудника Imagination Technologies Юрия и сотрудника AMD Тимура:



                Собрание некоторых докладчиков в Москве этим летом:



                Следущее мероприятие — приезд в Москву Роберта Оуэна. Роберт в России уже восьмой раз (что немудрено — он уже 23 года повсюду ездит, сначала с миссиями от Texas Instruments, потом Xilinx, ARM и Imagination). Вот Роберт в одном из российских университетов в один из предыдущих приездов:



                А вот информация про новую встречу:



                А вот Чубайс на фоне слайда с ЭЛВИС-НеоТек и Байкал Электроникс, проектов, для которых нужны специалисты со знанием проектирования микросхем. Чубайс будет говорить об этих проектах в четверг 14 сентября:



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



                Сегодня утром я также узнал про активиста, который хочет организовать конференцию по проектированию на Verilog и FPGA в Киргизии. Это развитие в правильном направлении — все народы евразийского культурного пространства еще со средневековья были сообразительными, дали миру неэвклидовых геометров Омара Хайама и Николая Лобачевского (что показывает способность к абстрактным математическим конструкциям, очень полезную в микроархитектуре), но из-за цепочки исторических случайностей, не очень известны в мире как проектировщики high-end микросхем. В XXI веке этот тренд есть шанс переломить, и образовательные семинары, в комбинации с кооперацией между университетами и корпорациями, служат именно этим целям.
                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/337702/


                Как, а точнее где мы будем работать через 10 лет

                Вторник, 12 Сентября 2017 г. 08:48 + в цитатник
                Maslukhin сегодня в 08:48 Администрирование

                Как, а точнее где мы будем работать через 10 лет

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



                  Прогноз Gartner от 2016 г.


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

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



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

                  Что такое Smart Workspace


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

                  Как мне кажется, лучше всего идеи Smart Workspace сформулировала компании Intel и Sogeti (еще одна крупная консалтинговая фирма), предположившие, что в основе офиса будущего лежат 4 принципа:
                  • пользовательский дизайн;
                  • доступность в любое время и в любом месте;
                  • думай глобально, выполняй локально;
                  • предвидение и инновационность.

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

                  Итак, доступность в любое время в любом месте подразумевает полную или частичную мобильность сотрудника и возможность подключить его к работе в любой точке мира. Прежде всего это скажется (и во многих передовых компаниях сказывается уже сейчас) на устройстве офисного пространства. Понятия “рабочий стол Васи Иванова” и “график работы с 9 до 18-00” постепенно отмирают, потому что больше ни у кого нет времени ждать, когда Вася доберётся до своего стола и скоординирует свои рабочие часы с другими сотрудниками. Все мы знаем, как работают в Гугл, лёжа в гамаках и самостоятельно устанавливая режим нахождения в офисе. Или вот хороший пример с Lego — недавно компания внедрила в своих офисах в Лондоне и Сингапуре систему офисного зонирования и сотрудники постоянно выбирают для себя комфортное пространство для работы: традиционные рабочие столы, тихая библиотека или open space, где люди общаются между собой и фоном играет музыка.


                  Эта и заглавная картинка из еще одного исследования от крупнейшего коммерческого агентства по недвижимости CBRE, посвященного гипотетическому офису 2040. Помимо картинок там есть забавная табличка отличий офиса 1990 г., 2015 г. и 2040 г. и даже приведен один день из жизни такого будущего работника с весьма интересной схемой семьи.

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

                  Забавный факт: часть компаний уже вынуждена работать так, несмотря на то, что удаленная работа на все 100% не показала свою эффективность. Мне нравится пример San Jose Sharks Sports & Entertainment, когда рекрутеры хоккейной команды вынуждены мотаться по любительским командам по всей стране, постоянно обмениваться информацией с тренерами и загружать большие объемы видеоданных. У этих ребят просто физически не может быть офиса, при этом они каждый день на работе и подключены к основным своим системам с помощью виртуального десктопа Citrix Xendesktop. Все, что им нужно — это кресло и быстрый интернет. А на стороне штаб-квартиры админ просто объединяет необходимые агентам ресурсы в виртуальной машине на гипервизоре и не тратит ресурсы на поддержание зоопарка техники.

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

                  Есть и третий сорт идей, совмещающих инновации как во времени, так и в пространстве, например: офис-робомобиль. Ещё в 2015 году креативное агентство IDEO представило концепт Automobility.



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

                  Пара слов о безопасности


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

                  Еще один забавный факт: согласно исследованию, проведенному в 2014 году, от 22 до 43% пользователей с удовольствием установит себе любое приложение, если им заплатить в диапазоне от 1 цента до 10 долларов. Если вспомнить, как хорошо прошелся по компаниям Петя и WannaCry, понятно, что компании, дорожащие своими данными, не пустят себе на сервера домашние устройства даже самого ценного сотрудника. И тут, видимо, на помощь придут тонкие клиенты.

                  В качестве примера такой работы можно привести учебу на факультете Инжиниринга Индустриальных Систем (Industrial Systems Engineering department) Техасского университета A&M. В ряде случаев, студенты должны работать с высокопроизводительными приложениями, установленными только на компьютерах, размещенных в лаборатории университета (к примеру, Matlab с последующим запуском расчетов на кластере университета). Понятно, что в таком случае возможны ситуации, когда все машины заняты и поход в лабораторию оказывался безрезультатным.

                  В качестве решения можно было бы разрешить студентам устанавливать приложения на свои машины и уже с них подключаться к университетскому серверу (что не всегда возможно, т.к. часть программного обеспечения, к примеру, используемый в институте SOLIDWORKS, который работает только под Windows), однако администрация пошла по другому, более разумному пути — предоставила доступ к виртуальным десктопам, к которых студенты могут подключиться с любых устройств.
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/337620/


                  Практика формирования требований в ИТ проектах от А до Я. Часть 6. Поведение системы. Совершенстваоние требований

                  Вторник, 12 Сентября 2017 г. 08:30 + в цитатник
                  ARadzishevskiy сегодня в 08:30 Разработка

                  Практика формирования требований в ИТ проектах от А до Я. Часть 6. Поведение системы. Совершенстваоние требований

                  • Tutorial
                  С частью 5 можно ознакомиться, перейдя по ссылке

                  IX Определение поведения системы.


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



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

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

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

                  На рисунке 9.1 изображен процесс формализации требований к целевой системе, дополненный подпроцессом определения ее поведения.



                  Рисунок 9.1 — модель процесса определения поведения системы

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


                  Продолжим проектирование контура «Учета заданий» и разберем в качестве примера — поведение системы при выставлении Заданий исполнителям см. на Рисунке 9.2
                  Напомню, что диаграммы последовательности читаются слева направо, сверху вниз.



                  Рис. 9.2 – Диаграмма Последовательности Формирование Заданий

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

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

                  Идем дальше, как было подчеркнуто ранее — сверху вниз, слева направо. ШКЗ связывает Шаблоны заданий, определяя состав и последовательность операций для исполнителей. Поэтому аналогично тому, как по ШКЗ мы создали Карту заданий, по Шаблонам заданий формируем — Задания (операции), объединенные в только что созданную Карту заданий. Но поскольку в одной Карте может быть множество Заданий, мы применяем на диаграмме — прием моделирования цикла (фрейм со стереотипом loop). Подобным образом на диаграммах данного вида можно показать: условные переходы, предусловия и прочие условные конструкции.

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

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

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

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

                  2. Анализируем изменение состояний объектов при моделировании поведения системы


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

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

                  Ниже на Рисунке 9.3 приведен пример моделирования переходов состояния объекта «Задание». На диаграммах данного типа всегда есть элемент, определяющий порождение объекта и указывающий начало его Жизненного цикла (далее ЖЦ). В нашем случае, при создании нового Задания критическим атрибутом, определяющим его следующее состояние, будет исполнитель. Если он задан, то задача может быть предложена для выполнения, если нет, то она и дальше будет оставаться в начальном состоянии. В данном примере не рассматриваются правила заполнения остальных атрибутов объекта (которые также могут иметь место), так как они в конкретном случае для нас не существенны.



                  Рис. 9.3 – Диаграмма Состояния Заданий

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

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

                  • «Закрыта», если отчет принят и подтвержден,
                  • «Отложена», если принято решение о приостановке задачи
                  • ввернуться в состояние «В работе», если отчет о выполнении не принят и задание требует доработки.

                  На диаграмме, всегда должен быть определен конец ЖЦ объекта.

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



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

                  3. Избегаем излишней «заадминистрированности» системы


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

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

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

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

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

                  1. Формируется Карта заданий и Задания по шаблону;
                  2. Производится извещение исполнителей и ответственных о назначенном для них задании.

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

                  X Совершенствуем требования


                  Я, в своей практике, использую прием формирования требований в несколько проходов. То есть, сначала я набрасываю черновой вариант, не стараясь получить сразу качественные формулировки. В описаниях могут повторяться слова, выражения могут быть «корявыми» и т.п. Главное, изложить основные мысли, и сформировать связанную структуру – каркас спецификаций. Позже, я вычитываю спецификации еще раз и стараюсь «причесать» текст так, чтобы он хорошо воспринимался на слух. Иногда описания меняются до неузнаваемости. Так я прохожу по спецификациям 4-5 раз (а иногда и больше), пока они не превратятся в стройную и легко воспринимаемую структуру. Кстати, таким же образом писалась эта публикация.

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

                  1. Проверяем трассируемость требований


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

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

                  2. Работаем над тестируемостью


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

                  С точки зрения тестируемости следует обратить внимание на то, что требование должно содержать в себе точный проверочный критерий. То есть, оно должно позволить однозначно ответить на вопрос: “удовлетворяет ли требование пожеланиям заказчика или нет”.

                  Формируя требования, старайтесь составлять их таким образом, чтобы на основании их было легко и удобно разрабатывать приемочные тесты. Критерии должны позволять проверить как полноту, так точность реализации. Подобные приемы очень эффективно используются в методологиях гибкого проектирования Scrum, XP [10]. Используйте, например, формирование разъяснений в тексте требований, отображающих правила заполнения данных с указанием вариантов, являющихся граничными для выбора алгоритма обработки. Например, можно сформировать таблицу, в одной из колонок которой, перечисляются значения, граничные для выбора алгоритма поведения, а в другой указания того как должна повести себя система в этом случае. Например:



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

                  • Ясность (недвусмысленность, определенность, однозначность);
                  • Осуществимость (реализуемость исполнителями);
                  • Полнота (текст требования не нуждается в дополнительной детализации для исполнителей);
                  • Единичность (требование должно описывать одну и только одну “вещь”);
                  • Непротиворечивость (требование не должно противоречить другим требованиям);


                  3. Согласуем требования


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

                  Я сам неоднократно читал подобные рекомендации и большей частью пропускал их мимо ушей, пока это не коснулось меня лично. Как-то все проходило достаточно гладко, и иногда брали проекты с недостаточно проработанными требованиями, и дорабатывали их уже в ходе разработки, и все раз за разом сходило с рук. Но рано или поздно наступает момент, когда расслабишься, постепенно притупится осторожность, обязательно находится партнер или заказчик, который на основании плохоформализованных, но очень бюджетных договоренностей об одном, потребует от Вас совершенно другого и будет Вам выкручивать руки и вводить Вас в отчаяние. А не дай бог, он окажется государственной структурой — Вы «попали»… Лучше до этого не доводить.

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

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

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

                  Более детально механизм изменения требований описан в главе «Решаем проблему изменений требований».

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

                  4. Определяем риски процесса разработки требований


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

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

                  В общем случае, риски, связанные с требованиями, обусловлены в основном отклонением одной из его характеристик от нормы. Это:

                  • Непротиворечивость требований между собой;
                  • Однозначность толкования;
                  • Определение критериев Тестируемости;
                  • Согласование с заказчиком и заинтересованными сторонами;

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



                  Следующую часть мы посвятим специфицированию требований.

                  Список литературы
                  1. Якобсон А., Буч Г., Рамбо Дж. – «Унифицированный процесс разработки ПО» (2004)
                  2. Дэвид А. Марк и Клемент МакГоуэн – «Методология структурного анализа и проектирования SADT»
                  3. Коберн — «Современные методы описания функциональных требований» (2002)
                  4. Леффингуэлл Дин, Уидрих Дон — «Принципы работы с требованиями к ПО» (2002)
                  5. Карл И. Вигерс – «Разработка требований к программному обеспечению» (2002)
                  6. Элизабет Халл, Кен Джексон, Джереми Дик — «Разработка и управление требованиями практическое руководство пользователей» (2005)
                  7. Скотт Амблер – «Гибкие технологии: экстремальное программирование и унифицированный процесс разработки» (2005)
                  8. Гринфилд Джек, Кейн Шорт — «Фабрики разработки программ» (2007)
                  9. Алистэр Коуберн — «Каждому проекту своя методология»
                  10. Вольфсон Борис- «Гибкие методологии разработки»
                  11. Лешек А. — «Анализ требований и проектирование систем»
                  12. Фримен Эрик, Фримен Элизабет — «Паттерны проектирования» (2011)
                  13. Эванс Эрик — «Предметно-ориентированное Проектирование» (2011)
                  14. ГОСТ 34.602-89 «Информационная технология. Комплекс стандартов на автоматизированные системы. Техническое задание на создание автоматизированной системы»
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/337678/


                  [Перевод] Это не статья — просто пища для размышлений о том, как её писать

                  Вторник, 12 Сентября 2017 г. 05:37 + в цитатник
                  Ares_ekb сегодня в 05:37 Разное

                  Это не статья — просто пища для размышлений о том, как её писать

                  • Перевод
                  image

                  Под катом перевод статьи Carsten Sorensen «This is not an article — just some food for thoughts on how to write one». В ней рассказывается на что нужно обращать внимание при написании научных статей. Главным образом, статья ориентирована на людей, занимающихся наукой. Впрочем, и авторы популярных статей тоже могут узнать что-то полезные.

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

                  Аннотация


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

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

                  1. Введение


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

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

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

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

                  Другие авторы писали на эту и связанные темы. Robert Day (1977, 1991) написал статью о том, как писать статьи, а также очень хорошую книгу, в которой излагаются важные аспекты того как написать статью и затем опубликовать её, плюс рассматривается множество других связанных вопросов. Я не пытаюсь конкурировать с ним или многими другими, перечисленными ниже. Данная статья может рассматриваться как руководство «на скорую руку» или аперитив для дальнейших исследований. Beer (1992) собрал более 60 коротких статей, предоставляющих практическую помощь по ряду вопросов от написания первого черновика статьи до выступлений. Я также нашел пару статей по этой теме: относительно короткая статья Naur (1992); статья, содержащая подробные примеры того, как представлять результаты Gopen и Swan (1990); статья Snyder (1991) направлена на потенциальных участников OOPSLA; Pugh (1991) и Wegman (1986) фокусируются на том, как писать расширенные тезисы. Klein (1989), Krathwohl (1988) и Witten (1990) писали о том, как писать предложения. Книга Lester Sr. и Jr. (2004), посвященная написанию исследовательских статей, новее, чем книга Day, но также и менее занимательна. Однако там где ей не хватает юмора описание компенсируется богатыми деталями. Bj"ork и R"ais"anen (2003) предлагают полный курс академического письма. Weston (1987) дает очень вдохновляющую основу для построения аргументации, а классика Strunk и White (1979) научит вас большинству из того, что стоит знать о стиле в английском языке. The Economist (2003) выпустил отличное руководство по стилю написания, а Truss (2003) написала очень популярную книгу о пунктуации «Eats, Shoots & Leaves». Stephen King (2000) написал книгу о ремесле письма не в научном контексте, а с авторской точки зрения. Восьмистраничное руководство «Guide to punctuation, mechanics, and manuscript form» содержится в (Guralnik, 1970), а Cook (1985) предоставляет действующее и практическое руководство по написанию. Несколько книг предлагают помощь начинающим писателям, которые занимаются научной работой, например, книги Dunleavy (2003), Holliday (2001) и Philips & Pugh (2000). Olk & Griffith (2004) обсуждают, как знания создаются и распространяются среди ученых. Fuller (2005) в основном рассматривает разницу между научными сотрудниками и учеными, а Frankfurt доходит до некоторой крайности в своей маленькой философской экспозиции о бреде сивой кобылы. В MIT группа студентов-информатиков, возможно, вдохновленная этой работой, даже написала генератор, который автоматически подготовит для вас научную публикацию (http://pdos.csail.mit.edu/scigen/).

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

                  Данная статья структурирована как серия уроков, которые я выучил. В следующем разделе оговаривается, что вам нужно основание для того, чтобы высказаться. В разделе 3 подчеркивается важность простоты. В разделе 4 излагаются пять основных вопросов, которые следует задавать себе при написании статьи. В разделе 5 предлагается начинать с подражания, а в разделе 6 утверждается, что вы должны получить право на отклонение от нормы. В разделе 7 мы остановимся на процессе написания статьи. В разделе 8 сделаем акцент на читаемости, а в разделе 9 — на взаимосвязи между написанием и рецензированием.

                  2. Вам нужна уважительная причина для заявления своей позиции


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

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

                  (Этот список вдохновлен выступлением Джонатана Либенау на панельной сессии «Meet the Editors» на консорциуме «The Joint European and American Doctoral Consortium», Копенгаген, Дания. Декабрь 1990 года.)

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

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

                  Рисунок 1. Примеры архетипов статей, чтение которых не приносит мне радости.

                  3. Не усложняйте


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

                  Только одна идея на статью


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

                  Кладите вашу шею только на одну гильотину


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

                  4. Основные вопросы, которые необходимо задать себе


                  При документировании в статье интересного аспекта ваших исследований есть множество вопросов, которые вы можете себе задать. Большинство из них относятся к теме, которую вы описываете. Однако есть, по крайней мере, следующие пять общих вопросов, о которых полезно подумать: (1) Какова проблемная область? (2) В чем заключается проблема? (3) Каков исследовательский подход? (4) Что сделали другие? и (5) Каковы результаты работы?

                  Прежде чем мы углубимся в эти пять вопросов, давайте немного осветим их с помощью забавного примера того, как на них ответили во введении к статье «Трассировка лучей для желатина марки Jell-O» («Ray Tracing Jell-O Brand Gelatin») (Heckbert, 1987) (рисунок 2).

                  «Трассировка лучей зарекомендовала себя в последние годы как самый общий алгоритм синтеза изображений [10]. Исследователи изучили расчеты пересечения лучей с поверхностями для ряда поверхностных примитивов. К ним относятся шахматные доски [Whitted 80]; хромированные шары [Whitted 80]; манипуляторы [Barr 82]; синие абстрактные вещи [Hanrahan 82]; больше стеклянных шаров [Watterberg 83]; мандрилы [Watterberg 83]; больше мандрилов [Sweeney 83]; зеленые фрактальные холмы [Kajiya 83]; больше стеклянных шаров [SEDIC 83]; водные бесформенные вещи [Kaw 83]; больше хромированных шаров [Heckbert 83]; бильярдные шары [Porter 84]; больше стеклянных шаров [Kajiya 86].

                  К сожалению, никто не делал трассировку лучей для продуктов питания. До настоящего времени наиболее реалистичными продуктами были классические изображения апельсина и клубники Блинна, но они были созданы с помощью алгоритма построчного сканирования [2]. Проект „Dessert Realism Project“ в Pixar затрагивает эту проблему. В этой статье представлена новая технология трассировки лучей для ограниченного класса десертов, в частности, желатина марки Jell-O. Мы полагаем, что этот метод может применяться к другим маркам желатина и, возможно, к пудингу.

                  Данная статья делится на три части: метод моделирования статического Jell-O, моделирование движения Jell-O с использованием впечатляющей математики и вычисления пересечения лучей с Jell-O.»

                  Рисунок 2. Введение из статьи «Трассировка лучей для желатина марки Jell-O» (Heckbert, 1987).

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

                  Какова проблемная область?


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

                  «Трассировка лучей зарекомендовала себя в последние годы как самый общий алгоритм синтеза изображений [10]. Исследователи изучили расчеты пересечения лучей с поверхностями для ряда поверхностных примитивов.»

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

                  «К сожалению, никто не делал трассировку лучей для продуктов питания. До настоящего времени наиболее реалистичными продуктами были классические изображения апельсина и клубники Блинна, но они были созданы с помощью алгоритма построчного сканирования [2]. Проект „Dessert Realism Project“ в Pixar затрагивает эту проблему.»

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

                  В чем заключается проблема?


                  Вам самим очень важно получить четкое представление о том, какой исследовательский вопрос вы хотите рассмотреть в статье. Это важно по нескольким причинам. Во-первых, чем более четкое понимание у вас есть, тем лучше вы можете достичь достаточной глубины своей аргументации. Во-вторых, когда читатель прочитал вашу статью, он должен, по крайней мере, связать её с одной важной идеей, которую вы аргументировали. В качестве очень хорошего примера рассмотрим статью Брукса «Серебряной пули нет — сущность и акциденция в программной инженерии» (Brooks, 1987) (см. также рисунок 4). Когда вы прочтете эту статью, вы свяжете её с идеей: ни одна технология разработки программного обеспечения сама по себе не может решить проблему кризиса в области разработки программного обеспечения. Что является простым, но, как видно из числа ссылок на эту статью, также и мощным утверждением. Если у читателя будет небольшая путаница в отношении сути статьи, статья в лучшем случае будет иметь незначительное влияние. Один из основных критериев успеха в деле написания статей заключается в том, чтобы заставить специалистов в вашей области прочитать и процитировать ваши статьи. Если они не помнят, что они только что прочитали, мало шансов, что они будут использовать статью или рекомендовать ее другим.

                  Каков метод исследования?


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


                  Рисунок 3. Простая характеристика взаимосвязи между типом метода исследования и типом результата. Адаптировано из (Sorensen, 1993).

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

                  Вы могли бы также контекстуализировать свои исследования немного больше, чем просто охарактеризовать тип подхода и тип результата. На эту тему есть множество источников знаний, поэтому я остановлюсь лишь на нескольких. Ives, Hamilton и Davis (1980) представляют таксономию исследований ИС. Wynekoop и Conger (1990) характеризуют исследование CASE, объединяя исследовательские цель и подход. Dahlbom и Mathiassen (1993) предоставляют инструментарий на философской основе для проектирования систем и, наоборот, также являются основой для обсуждения исследований в области проектирования систем.

                  Как только контекст на месте, вы должны уточнить исследовательский подход, особенно если вы сообщаете об эмпирическом исследовании. Здесь раздолье для инструментариев, характеризующих разные подходы, и я остановлюсь только на нескольких источниках, которые я нахожу ценными. «The Information Systems Research Challenge» Гарвардской бизнес-школы делит исследовательские подходы на три аналитические категории: (1) экспериментальные исследования (Benbasat, 1989), (2) качественные исследования (Cash and Lawrence, 1989) и (3) обзоры (Kraemer, 1991). Mumford и другие (1985) обсуждают исследовательские подходы для информационных систем в ряде статей.

                  Что сделали другие?


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

                  Каковы результаты исследования?


                  Связав ваши исследования с тем, что сделали другие, вы остаетесь со сложной задачей — четко заявить, в чем заключается ваш вклад. При этом простой вариант соотнесения вашей работы с существующими исследованиями может перейти в трудный вариант описания новых и интересных результатов вашей работы. Если вы делаете что-то впервые, то ваша задача может быть проще. Ну, при написании статьи, по крайней мере. Если вы выбрали «левое поле», используя термин из бейсбола, вы можете получить проблемы позже, пытаясь опубликовать ваши результаты. Я не рассматриваю эту проблему в данной статье. Заинтересованные читатели могут обратиться, например, к Kuhn (1969), который может поведать много интересного об этом.

                  Где нужно отвечать на эти пять вопросов?


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

                  5. Подражательство может окупиться


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

                  Давайте рассмотрим пример того, как быть подражателем и получить очень интересный результат, сильно отличающийся от того, что было скопировано (см. рисунок 4). Известная статья Брукса «Серебряной пули нет — сущность и акциденция в программной инженерии» (Brooks Jr., 1987) начинается со сравнения проектов по разработке программного обеспечения с оборотнями и использует идею убийства оборотней с помощью серебряных пуль в качестве литературного приема обрамления.

                  Читатель сразу понимает точку зрения Брукса о том, что мы не должны надеяться на единственную технологию, действующую как серебряная пуля. Dahlbom & Mathiassen (1994) не интересно заявлять что-либо об оборотнях. Тем не менее, они заинтересованы в том, чтобы захватить интерес читателя и в том, чтобы обратить внимание на проблему, с которой они сталкиваются так, что ни один читатель не будет сомневаться в том, какова их точка зрения. Что они делают? Они заимствуют трюк Брукса и заменяют оборотней часами. На рисунке 4 воспроизводятся большие выдержки из введений к статьям Брукса (левая колонка) и Dahlbom & Mathiassen (правая колонка). Как вы можете видеть, основная идея использования истории часов и часовщиков в качестве литературного приема обрамления для точного определения проблем, связанных с компьютерами и компьютерными специалистами, очень похожа на то, что делает Брукс в своей статье.

                  Статьи «Трассировка лучей для желатина бренда Jell-O» (Heckbert, 1987) (см. рисунок 2) и «Интерфейсное устройство, основанное на жестах носом: расширение виртуальных реальностей» (Henry et al., 1993) — обе отличные и очень забавные парафразы архетипных статей по компьютерным наукам. Объемом они всего лишь несколько страниц, но очень забавным способом отражают и исследуют стандартный формат статьи.

                  Серебряной пули нет — сущность и акциденция в программной инженерии
                  (Brooks Jr., 1987)
                  Будущее вычислений
                  (Dahlbom and Mathiassen, 1994)
                  1. Введение.

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

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

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

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

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

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

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

                  Рисунок 4. Выдержки из введений к статьям «No Silver Bullet: Essence and Accidents of Software Engineering» Brooks Jr. (1987) и «The Future of Computing» Dahlbom & Mathiassen (1994). Очевидно, обе статьи начинаются с очень мощных литературных приемов обрамления.

                  6. Заработайте свое право отклоняться от нормы


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

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

                  Вы легко сможете найти очень хорошие статьи, которые не следуют нормальной структуре. Просто взятие двух первых работ (Brooks Jr., 1987) и (Weinberg, 1982) из коллекции любимых статей по разработке программного обеспечения DeMarco и Lister (1990), показывает, что хорошие статьи не обязательно должны следовать самому распространенному рецепту. С другой стороны, мы, вероятно, не все Бруксы или Вайнберги в своей области. Если вы уже давно работаете в некоторой области, и вы очень уважаемый исследователь, специалисты будут читать вашу статью независимо от того, как она структурирована, исходя из предположения, что у вас, по-видимому, есть что-то интересное для представления, и что вы, вероятно, сделаете это в элегантной и подкрепленной аргументами манере. Остальным из нас, скорее всего, будет лучше полагаться на результаты наших исследований, как основного «рекламного аргумента», и представлять эти результаты таким образом, чтобы читателям было как можно проще их прочитать.

                  7. Как избежать написания нескольких статей в одной


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

                  Что бы вы подумали, если бы я предположил вам начать писать статью с пары разделов, затем проложить свой путь к размышлению о введении и заключении? А перед самой отправкой статьи на конференцию прочитать, что вы написали, и посмотреть, какой заголовок будет уместен. Словом, предложил бы восходящий (bottom-up) процесс. По понятным причинам я не знаю вашего ответа на этот вопрос, но раскрою вам свой ответ: это, вероятно, не очень хороший способ писать статью. Как обсуждалось ранее, одним из основных качеств статьи является то, что она передает одну четкую идею. Применяя восходящий процесс, вы делаете так, что вам очень трудно удерживать устойчивый курс. Существует реальная опасность того, что результатом будет несколько статей в одной из-за отсутствия четкого критерия релевантности того, что должно быть включено в статью, и что следует опустить. Одна из основных характеристик научных статей — это относительно ограниченное количество страниц.

                  Как вы, возможно, догадались, я сторонник нисходящего (top-down) процесса написания статей. Начните с названия. Найдите действительно хорошее название, которое отражает основную идею статьи. Вы можете пойти на юмористическое название или, возможно, более трезвое и описательное. В любом случае, оно должно отражать основную идею статьи. Будучи осторожным при разработке названия, вы заставляете себя думать о том, какие из бесконечного количества возможных идей для статьи вы на самом деле собираетесь реализовать. После того, как заголовок зафиксирован, займитесь аннотацией. Думайте о ней как о миниатюрной версии статьи. Используйте её для размышления об аргументации. Следующая остановка — введение. Здесь у вас есть немного больше места для описания статьи и ее контекста. Теперь вы готовы приступить к работе над разделами. Поскольку вы заставили себя задуматься о том, какую статью вы хотите написать, в отличие от всех других вариантов, у вас будет гораздо более четкое представление о том, чем наполнить разделы. Словом, научная статья должна, на мой взгляд, не обязательно рассматриваться как нечто выходящее из головы автора — она проектируется.

                  Ну, это вас спровоцировало? Надеюсь, что так, потому что я очень люблю провоцировать. К счастью, всё, конечно, немного сложнее. Любые аргументы, которые вы могли бы выдвинуть против моей позиции типа «всё это о написании статей с использованием нисходящего процесса просто невозможно, потому что невозможно обозреть все аспекты аргументации, и я мог бы стать мудрее в процессе написания и т.д., и т.п.» вполне обоснованы. Я совершенно согласен с тем, что люди не могут и не будут вести себя полностью рационально. Статьи не могут быть произведены полностью рациональным и нисходящим образом — но это не повод для того, чтобы не пытаться! Parnas и Clements (1986) написали статью об этом в области проектирования программного обеспечения. Они как-раз начинают, представляя исчерпывающий набор аргументов о том почему мы никогда не сможем рационально проектировать компьютерные системы. После чего они настаивают, что, в любом случае, к этому нужно стремиться. Название этой статьи: «Рациональный процесс проектирования: как и почему подделывать его» («A Rational Design Process: How and Why to Fake It»). Почему бы не подделывать рациональный процесс при написании статей? Стремитесь следовать нисходящему процессу и когда вы терпите неудачу, вы знаете, что все в порядке, потому что невозможно быть полностью рациональным. Если в середине написания статьи вы думаете о лучшем названии, то измените его. Не используйте в качестве аргумента, чтобы не думать о первоначальном варианте названия, риск того, что оно может быть изменено на более позднем этапе.

                  8. Шлифуйте упаковку и содержимое


                  Предположим, что вы потратили много времени на создание первого черновика статьи. К чему тогда стоит приложить больше усилий по улучшению? Ну, это, конечно, очень гипотетический и, возможно, довольно глупый вопрос. Моё первоначальное предположение, тем не менее, было бы следующее: улучшайте «упаковку», т.е. название, аннотацию, введение и заключение — в этом порядке. Довольно сложно побудить людей прочитать основное содержание вашей статьи. Если заголовок вводит в заблуждение, и если в аннотации и введении не удается заложить сеттинг интересным образом, скорее всего, читатель не продвинется дальше, если только он не очень увлечен этой темой, не назначенный рецензент (в этом случае вы все равно в беде) или не близкий друг.

                  То, как вы представляете результаты своих исследований, очень важно. Важно приложить усилия для шлифовки статьи, чтобы она была удобочитаемой. Аннотация Дональда Кнута к статье «Ошибки TeX» («The Errors of TeX») (см. рисунок 5) — очень хороший пример того, как в очень немногих и хорошо выбранных словах сформулировать аннотацию.

                  «Настоящая статья представляет собой тематическое исследование качественной оценки программы. Автор отслеживал все изменения, внесенные в TeX, в течение десяти лет, включая изменения, внесенные, когда программа была впервые отлажена в 1978 году. Журнал этих ошибок, насчитывающий более 850 элементов, приведен в приложении к данной статье. Ошибки были отнесены к пятнадцати категориям для целей анализа, и некоторые из примечательных ошибок рассматриваются детально. История проекта TeX может дать ценные уроки о подготовке хорошо портируемого программного обеспечения и сопровождении программ, стремящихся к высоким стандартам надежности.»

                  Рисунок 5. Аннотация к статье «Ошибки TeX» Кнута (Knuth, 1989).

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

                  Есть несколько источников, на которые следует обратить внимание, если вы хотите улучшить стиль своего письма. Weston (1991) написал очень хорошую книгу, которая настоятельно рекомендуется, о том, как выстроить аргументацию. Классика Strunk и White «Элементы стиля» (Strunk Jr. and White, 1979) и «Строка за строкой» Кука (Cook, 1985) обе являются ценными источниками знаний о том, как писать на английском языке. Книга Day (Day, 1991) обязательна для молодых исследователей, поскольку она дает короткие и точные предписания о том, как структурировать статьи.

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

                  9. Написание и рецензирование — две стороны одной монеты


                  Существует тесная взаимосвязь между написанием и рецензированием статьи, как утверждается в подзаголовке работы Gopen и Swan (1990): «Если читатель должен понять, что имел в виду писатель, писатель должен понять, что нужно читателю».

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

                  В рамки данной статьи не входит подробное описание процесса публикации: прочитайте, например, статьи Smith (1990) и Parberry (1990) для очень хороших введений, а также очень интересные дебаты в журнале «The Behavioral and Brain Sciences», начатые работой Peters & Ceci (1982), которая была прокомментирована в (BBS, 1982) и (BBS, 1985).

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

                  Заключение


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

                  • Вам нужна уважительная причина для заявления своей позиции.
                  • Не усложняйте, имея только одну идею на статью, и кладя вашу шею только на одну гильотину.
                  • Основные вопросы, которые необходимо задать себе
                    1. Какова проблемная область?
                    2. В чем заключается проблема?
                    3. Каков исследовательский подход?
                    4. Что сделали другие? и
                    5. Каковы результаты работы?
                  • Подражательство может окупиться.
                  • Заработайте свое право отклоняться от нормы.
                  • Подделывайте нисходящий (top-down) процесс написания статьи.
                  • Шлифуйте упаковку и содержимое.
                  • Написание и рецензирование — две стороны одной монеты.

                  Существует только одна жизнеспособная стратегия изучения мастерства написания статей, и это пробы и ошибки. Работа, таким образом, не была нацелена на решение какой-либо из главных проблем. Цель заключалась в том, чтобы с помощью моего собственного опыта сфокусироваться на нескольких простых уроках, которые могут стать основой для дальнейшей дискуссии и работы над этой темой. В качестве заключительного замечания я, поэтому, хотел бы призвать вас как читателя искать дополнительную информацию об этой очень важной теме, в книгах и статьях, которые в более структурированном и тщательном виде представляют важные аспекты написания статей. Я надеюсь, что я обострил ваш аппетит, и… да прибудет с вами сила, исследования — это огромное удовольствие и тяжкий труд! (Но не всегда в таком порядке).

                  Задание


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

                  Онлайн ресурсы (чтобы было проще избежать библиотеки)


                  Strunk & White, The Elements of Style (full text)
                  Common Errors in English
                  A Handbook for Technical Writers
                  Grammar, Punctuation and Spelling
                  Guide to Grammar and Writing
                  Grammar Help
                  Guide to Grammar and Style
                  Ranking of IS Journals
                  Ranking of IS Journals
                  IS Journals
                  IS Journals

                  Благодарности тоже важны


                  Исследовательские сообщества — это слабосвязанные сети, основанные на высоких этических стандартах. Поэтому очень важно, чтобы признавался вклад в статью как в форме идей и предложений со стороны специалистов, так и в виде финансирования исследований. Надеюсь, поэтому, что я не забыл упомянуть кого-либо по ошибке! Длинные благодарности опущены. Все ошибки, просчеты и т.п. полностью на моей собственной ответственности. Это исследование частично спонсировалось Центром когнитивной информатики, финансируемым Датским советом технических исследований. Выражаю благодарность моему научному руководителю Lars Mathiassen за то, что он научил меня некоторым секретам мастерства (просто сообщите мне, если Вы хотите, чтобы Ваше имя было удалено после прочтения этих страниц). Спасибо Kristin Braa и Tone Bratteteig за приглашение обсудить эту тему на научном семинаре — это большой риск. Также выражаю благодарность John Venable за ценные предложения и за улучшение языка. Большое спасибо Henning Boje Andersen, Bo Dahlbom, John Paulin Hansen, Pertti J"arvinen, Christian S. Jensen, Leif Lovborg, Lars Mathiassen, Kjeld Schmidt, Diane Sonnenwald, анонимному рецензенту IRIS и всем участникам семинара IRIS 17 за конструктивные комментарии и предложения. Курс для соискателей, проводимый Heinz K. Klein, по издательской игре и его панельная сессия на ICIS Doctoral Consortium, 1991 год, в Копенгагене, о представлении главного редактора, о том, как публиковать статьи, вдохновили меня на личную борьбу при написании статей. Все ошибки, просчеты и т.п. полностью на моей собственной ответственности.

                  Библиографические ссылки определяют контекст


                  BBS (1982): Open Peer Commentary: Commentary on Peters & Ceci (1982): Journal review process. The Behavioral and Brain Sciences, vol. 5, no. 2, pp. 196–255.

                  BBS (1985): Continuing Commentary: Commentary on Douglas P. Peters & Stephen J. Ceci (1982): Peer-review of practices of psychological journals: The fate of published articles submitted again. The Behavioral and Brain Sciences, vol. 8, no. 4, pp. 743–750.

                  Beer, David F., ed. (1992): Writing & Speaking in the Technology Professions — A Practical Guide. New York: IEEE Press.

                  Benbasat, Izak, ed. (1989): The Information Systems Research Challenge: Experimental Research Methods, vol. 2. Boston Massachusetts: Harward Business School Research Colloquium Harward Business School.

                  Bj"ork, L. A. & C. R"ais"anen (2003): Academic writing: A university writing course. Lund: Studentlitteratur.

                  Cash, James I. and Paul R. Lawrence, ed. (1989): The Information Systems research Challenge: Qualitative Research Methods, vol. 1. Boston Massachusetts: Harward Business School Research Colloquium Harward Business School.

                  Cook, Claire Kehrwald (1985): Line By Line — How to Improve Your Own Writing. Boston, USA: Modern Language Association.

                  Dahlbom, Bo and Lars Mathiassen (1993): Computers in Context — The Philosophy and Practice of Systems Design. Cambridge, Massachusetts: Blackwell Publishers.

                  Dahlbom, Bo and Lars Mathiassen (1994): The Future of Computing. In 17th Information systems Research seminar In Scandinavian at Sy"ote Conference Centre, Finland, August 6–9, Sy"ote, Finland, ed. Penti Kerola, Antti Juustila, and Janne J"arvinen. Oulu University, vol. Vol. I, pp. 2–16.

                  Day, Robert A. (1977): How to Write a Scientific Paper. IEEE Transactions on Proffessional Communication, vol. PC-20, no. 1, pp. 32–37.

                  Day, Robert A. (1991): How to Write & Publish a Scientific Paper. Cambridge: Cambridge University Press.

                  DeMarco, Tom and Timothy Lister, ed. (1990): Software State-Of-The-Art: Selected Papers. New York: Dorset House Publishing.

                  Dunleavy, P. (2003): Authoring a PhD Thesis: How to Plan, Draft, Write and Finish a Doctoral Dissertation. Basingstoke, UK: Palgrave Macmillan.

                  Frankfurt, H. G. (2005): On Bullshit. Princeton University Press.

                  Fuller, S. (2005): The Intellectual. Icon Books.

                  Gopen, George D. and Judith A. Swan (1990): The Science of Scientific Writing — If the reader is to grasp what the writer means, the writer must understand what the reader needs. American Scientist, vol. 78, November-December, pp. 550–558.

                  Guralnik, David B., ed. (1970): Webster’s New World Dictionary of the American Language. Second College Edition. New York: The World Publishing Company.

                  Heckbert, Paul S. (1987): Ray Tracing Jell-O Brand Gelatin. Communications of the ACM, vol. 21, no. 4, pp. 73–74.

                  Henry, Tyson R., Andrey K. Yeatts, Scott E. Hudson, Brad A. Myers, and Steven Feiner (1993): A Nose Gesture Interface Device: Extending Virtual Realities. Presence, vol. 1, no. 2, pp. 258–261.
                  Holliday, A. (2001): Doing and Writing Qualitative Research. Sage.

                  Ives, Blake, Scott Hamilton, and Gordon B. Davis (1980): A Framework for research in Computer-Based Management Information Systems. Management Science, vol. 26, no. 6, pp. 910- 934.
                  King, S. (2000): On Writing: A Memoir of the Craft. London: New English Library.

                  Klein, Heinz K. (1989): The Prospectus and Dissertation Workplan in Information Systems Research. Manuscript. School of School of Management, SUNY Binghamton, NY 13902-6000: pp. 8 pages.
                  Kraemer, Kenneth L., ed. (1991): The Information Systems Research Challenge: Survey Research Methods, vol. 3. Boston Massachusetts: Harward Business School Research Colloquium. Harward Business School.

                  Krathwohl, David R. (1988): How to Prepare a Research Proposal — Guidelines for Funding and Dissertations in the Social and Behavioral Sciences. Third Edition, Syracuse, New York: Syracuse University Press.

                  Kuhn, Thomas S. (1969): The Structure of Scientific Revolutions, vol. 2. International Encyclopedia of Unified Science.

                  Lester, J. D. S. & J. D. J. Lester (2004): Writing Research Papers: A Complete Guide. Longman.
                  Mumford, Enid, Rudi Hirschheim, Guy Fitzgerald, and Trevor Wood-Harper, ed. (1985): Research Methods in Information Systems. Proceedings of the IFIP WG 8.2 Colloquium at Manchester Business School 1-3 September 1984. Amsterdam: North-Holland.

                  Naur, Peter (1992): Writing Reports — A Guide. In Computing: A Human Activity. New York: ACM Press, pp. 254–258.

                  Olk, P. & T. L. Griffith (2004): Creating and Disseminating Knowledge Among Organizational Scholars: The Role of Special Issues. Organization Science, vol. 15, no. 1, pp. 120-129.

                  Parberry, Ian (1990): A Guide for New Referees in Theoretical Computer Science. Unpublished manuscript Department of Computer Sciences, University of North Texas, P.O. Box 3886, Denton, TX 76203-3886, U.S.A.

                  Parnas, D. L. and P. C. Clements (1986): A Rational Design Process: How and Why to Fake it. IEEE Trans. Software Eng., vol. SE-12, no. 2, pp. 251-257.

                  Peters, Douglas P. and Stephen J. Ceci (1982): Peer-review practices of psychological journals: The fate of published articles, submitted again. The Behavioral and Brain Sciences, vol. 5, no. 2, pp. 187–195.

                  Phillips, E. M. & D. S. Pugh (2000): How to Get a PhD: A Handbook for Students and Their Supervisors. UK: Open University Press.

                  Pugh, William (1991): Advice to Authors of Extended Abstracts. Department of Computer Science and Institute for Advanced Computer Studies, University of Maryland, College Park.

                  Smith, Alan Jay (1990): The Task of the Referee. IEEE Computer, vol. 23, no. 4, pp. 65–71.

                  Snyder, Alan (1991): How to get Your paper Accepted at OOPSLA. In OOPSLA ‘91, pp. 359–363.

                  Sorensen, Carsten (1993): Introducing CASE Tools into Software Organizations. Ph.D. Dissertation Department of Mathematics and Computer Science, Aalborg University, [R-93- 2011].

                  Strunk Jr., William and E.B. White (1979): The Elements of Style. New York: Macmillan Publishing Co. Inc.

                  The Economist (2003): Style Guide: The Bestselling Guide to English Usage. London: The Economist.

                  Truss, L. (2003): Eats, Shoots & Leaves: The Zero Tolerance Approach to Punctuation. Profile Books.

                  Tufte, Edward R. (1983): The Visual Display of Quantitative Information. Cheshire, Connecticut: Graphics Press.

                  Tufte, Edward R. (1990): Envisioning Information. Cheshire, Connecticut: Graphics Press.

                  Wegman, Mark N. (1986): What it’s like to be a POPL Referee or How to write an extended abstract so that it is more likely to be accepted. SIGPLAN Notices, vol. 21, no. 5, pp. 91–95.

                  Weston, Anthony (1991): A Rulebook for Arguments. Indianapolis: Hackett Publishing Company.

                  Witten, Ian (1990): How to get a Research Grant. Research Report Department of Computer Science, The University of Calgary, [90/385/09].

                  Wynekoop, Judy L. and Sue A. Conger (1990): A Review of Computer-Aided Software Engineering Research Methods. In The Information Systems Research Arena of the 90’s: Challenges, Perceptions, and Alternative Approaches, Lund, Sweden, ed. H-E Nissen, H.K. Klein, and R. Hirschheim. IFIP TC 8 WG 8.2, vol. 1, pp. 129-154
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/337656/


                  Метки:  

                  Искусственный интеллект для ритейла: продажники уровня Скайнет, отзывы уровня Бог

                  Понедельник, 11 Сентября 2017 г. 23:28 + в цитатник

                  Метки:  

                  Как оптимально рассчитать объем «железа»: сайзинг-модель ЕФС

                  Понедельник, 11 Сентября 2017 г. 21:56 + в цитатник
                  EFS_programm сегодня в 21:56 Администрирование

                  Как оптимально рассчитать объем «железа»: сайзинг-модель ЕФС

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




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

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

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

                    Что такое сайзинг-модель в ЕФС?


                    Сайзинг-модель представляет собой шаблон из нескольких разделов. Рассмотрим каждый из них подробнее.

                    Раздел 1. Входные данные


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

                    В обоих случаях мы получаем от заказчика оценку:

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

                    Также получаем количество запросов через MQ (Messages Queue) интеграции с внешними системами.

                    Внутренняя сеть
                    Наименование
                    Комментарий
                    Пиковое количество бизнес-операций за час
                    Берется из бизнес-требований либо рассчитывается: операций за сутки/10
                    Среднее количество бизнес-операций за сутки
                    Берется из бизнес-требований либо рассчитывается: операций за час*10
                    Пиковое количество запросов через MQ в секунду (прямая интеграция с внешними системами)
                    Сумма по взаимодействиям точка-точка через MQ

                    Внешняя сеть
                    Наименование
                    Комментарий
                    Пиковое количество бизнес-операций за час
                    Берется из бизнес-требований либо рассчитывается: операций за сутки/10
                    Среднее количество бизнес-операций за сутки
                    Берется из бизнес-требований либо рассчитывается: операций за час*10


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

                    1. В России 12 часовых поясов, отделения и клиенты распределены по всем часовым поясам, время работы отделений – 10–12 часов в сутки.
                    2. Наибольшее количество пользователей и операций сосредоточено в часовом поясе Москвы, этот пояс определяет пиковую нагрузку.
                    3. График распределения нагрузки по часам имеет два пика — утром и после обеда, в целом одинаков для всех часовых поясов с учетом временного сдвига.


                    Параметры модели



                    Посмотреть схему крупнее.

                    Рисунок 1. Упрощенная архитектура ЕФС
                    Архитектура ЕФС основана на классической трехзвенной архитектуре

                    1. Презентационный слой. Чаще всего – просто nginx со статикой, в специальных случаях добавляется сервер приложений.
                    2. Сервера приложений с бизнес-логикой.
                    3. Базы данных.

                    Раздел 2. Параметры модели


                    Параметры разбиты на три группы:

                    • параметры, зависящие от реализуемого функционала.

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

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

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

                    • фиксированные параметры.

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



                    Расчетные коэффициенты

                    В таблице приведено описание коэффициентов, для себя выработали значения. Если интересно, задавайте вопросы в комментариях – поделимся экспертными оценками.



                    Раздел 3. Модель расчета


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

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

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

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

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



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

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



                    • Расчет количества web-серверов.


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

                    Количество Web-серверов внутренней сети



                    • Количество Web-серверов во внешней сети




                    • Мобильный шлюз внешней сети



                    • Презентационная логика во внешней сети




                    • Презентационная логика во внутренней сети




                    • Расчет серверов приложений для бизнес-логики



                    • MQ (аудит + логирование + интеграция точка-точка)



                    • Распределенный внутренний кеш



                    • Расчет СУБД


                    • Количество CPU БД




                    • Количество RAM БД


                    • Размер БД



                    На выходе сайзинг-модели мы получаем оценку «железа» для промышленной среды.
                    В мировой практике выделяют три основные модели сайзинга:
                    • Пользовательская модель

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

                    • Транзакционная модель

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

                    • Тестовая модель

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

                    В программе ЕФС у нас комбо из «Транзакционной модели» и «Тестовой модели». Так как мы обладаем информацией о количестве пиковой нагрузки бизнес-операций, но также руководствуемся показателями, полученными из проведённого нагрузочного тестирования. Но есть особые случаи, когда в ЕФС внедряется новый функционал, которого не было в других системах, и нам на вход поступают предполагаемые данные, тогда мы действуем по аналогии с «Пользовательской моделью».

                    Давайте пообщаемся, а какую модель используете вы?
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/337676/


                    Метки:  

                    Поиск сообщений в rss_rss_hh_new
                    Страницы: 1437 ... 1139 1138 [1137] 1136 1135 ..
                    .. 1 Календарь