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

Блог компании онлайн-кинотеатр ivi

Профессия СТО

29.12.2020 10:06:55 | Автор: admin

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

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

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

А в чем отличие CTO от CIO?

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

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

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

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

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

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

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

Есть вопросы, которые касаются драматической перестройки архитектуры как с точки зрения бизнес-логики, так и со стороны технических решений таких задач 3-5 в каждом квартале. В них я сижу по уши - настраиваю себе систему уведомлений по тикетам, делаю специальные синки, если нужно, создаю временные микро-, а где-то и макро-команды. Бывают задачи, которые ломают флоу компании. Нам надо было за месяц запустить спортивные трансляции, мы не делали этого раньше. Есть месяц, через месяц футболисты начинают пинать мяч, уже все оплачено, контракт стартует. Хочешь-не хочешь, надо успеть. У всех команд свои дедлайны, свои бэклоги с протухшим техдолгом, нехваткой ресурсов QA А тебе надо запуститься через месяц. И если тут ты говоришь: Ну, ребята, давайте договоримся, - это не работает. Наступило военное положение. Надо стараться, чтобы оно не наступало каждые три минуты, но если оно наступает - а пару раз в году такое случается - крокодилы начинают низенько, но летать.

Также у нас создана специальная система синков и демонстраций, чтобы можно было в любой момент времени подключиться к информационному потоку и понять, что происходит в компании здесь я уже выступаю в роли CIO. Например, по пятницам у нас есть так называемый Product Owner Sync. Примерно 20 команд из 26 рассказывают о самом важном, что произошло у них за неделю - мы научились укладывать эту встречу в 40 минут . Кроме того, важная потребность любого бизнеса - знать и уметь быстро находить информацию о предыдущих факапах и неудачах. Если кто-то из самоорганизующихся команд бежит в ту сторону, где уже похоронено несколько десятков, сотен или тысяч человекочасов, нужно вовремя сказать: Ребята, там рыбы нет, мы туда ходили много раз, вот эти могилы.

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

История с планированием и история с целеполаганием очень интересная. Пару лет назад я понял, что надо сделать процесс целеполагания внутри компании прозрачным, чтобы и самому понимать лучше. По сути это кусок тайм-менеджмента СТО. Я послушал доклад Егора Толстого про OKR в Avito, написал ему: Приди, пожалуйста, к нам в гости и расскажи правду. Егор, надо отдать ему должное, сказал: Да, конечно, приду. Он пришёл, рассказал правду и мы начали внедрять эту историю, которая мне помогает стратегически думать и это половина ответа на твой вопрос. А другая половина - ты убегаешь от всех людей, выключаешь мессенджеры и пытаешься сформировать свое мнение относительно того, что происходит вокруг. Потом начинаешь стучать этими мыслями во всех своих ребят. Это один паттерн. Другой паттерн, когда ты говоришь: Друзья, давайте, каждый из вас подумает, и мы пока не будем делиться мыслями, а обменяемся ими через недельку. В итоге все рассказывают, что у них болит. Ты думаешь: Блин, а я об этой хрени даже не думал. СТО - это не гений, который знает, куда вести компанию. Это человек, который в состоянии оценить те или иные решения. Часть придумывает он сам, но большую часть придумывает его команда. Это оркестратор, это кубернетес командного управления.

Есть ли СТО, у которых всё хорошо с work-life balance?

Я не очень верю в то, что человек говорит: Когда я выхожу с работы, я всё забываю, я обнулился. Я не знаю людей, у которых это так работает. Значит, им не интересно то, чем они занимаются. Да, можно сместить куда-то акцент, например, завести детей дети оттягивают на себя ресурс сами. Это прекрасные стейкхолдеры и продактоунеры твоего времени. Мне кажется, что у всех примерно одинаковая проблема: число задач всегда превышает число твоих возможностей. Так или иначе, СТО должен быть стратегом, мечтателем и смотреть вперед. Но чтобы смотреть вперед, нужно хорошо понимать, где ты находишься сейчас, и любить то, что ты делаешь, чтобы толкать это вперед и заряжать людей, которые рядом с тобой, тем, что интересно. Любое направление, особенно то, что касается информационных технологий, это довольно интересная история. Везде есть свой рок-н-ролл. Даже в такой прекрасной области знания как 1С, который почему-то в среде разработчиков считается странным направлением, есть своя романтика и драйв. Думаю, что для того, чтобы найти баланс между работой и чем-то личным, ты должен любить и то, и другое. А дальше оно всё естественным образом сформируется.

Любовь к делу понятно, первое, без этого фундамента дальше что-то строить будет сложно. Но каким образом СТО наращивает экспертизу?

Власть никто не даёт. Если кто-то выдал тебе власть, то, скорее всего, ты будешь хреновым управленцем. Власть берут сами. У меня всегда было шило в заднице. Я в институте стал старостой, всегда был активным, всегда мне было что-то нужно. Я понимал, что люди сами ничего не организуют, сейчас я их организую. Те, кто спрашивает: Ой, а как мне расти в сторону СТО? или Как мне стать тимлидом? хуже растут в управлении. Если у тебя возник этот вопрос, дружище, у меня для тебя плохие новости ты уже проиграл. Бывает обратная история - приходит человек с горящими глазами и говорит: У нас CI - говно! Смотрите, я придумал вот это, давайте? И все такие: О, тема! И вот ты возглавляешь этот путь хрен знает, куда. Ты достиг какой-то вехи, чего-то добился на пути управления людьми и ресурсами и далее тебе естественным образом захочется чего-то ещё. Я не очень верю в историю, что если получить степень МВА, станешь шикарным управленцем - чувак, который будет просто наглым, может обойти тебя за счёт харизмы. Если ты не понимаешь, как тебе расти, значит, первую проблему управления ты не решил. Как ты будешь рассказывать другим людям, как расти? Как только ты становишься лидом или техническим директором, тебе нужно будет так или иначе курировать в том числе процесс непрерывного развития твоей команды.

Куда пойдёт хороший СТО, чтобы заполучить экспертизу, к каким людям? Или пойдет гуглить: Как сделать стриминговый сервис спортивных трансляций?

Я стучусь к разным техническим директорам, ко мне стучатся люди, спрашивают совета. Есть коммьюнити, абсолютно не злое, очень классное, открытое, оно в первую очередь про рациональность. Ты можешь обратиться чуть ли не к конкурентам сплошь и рядом мы обсуждаем подобные вещи. Это инженерные практики, какие-то элементы наукоемкого производства. Например, мы не могли нормально подружить openstack с сетью, но нашлись люди, которые помогли. Кто-то тебе советует нормальных консалтеров - у нас были сложные вопросы с Postgres и Олег Бунин помог мне найти ребят. Ты понимаешь, что тебе намного проще обратиться за консультацией - пусть она будет стоить дорого, зато ты очень быстро получишь ответ. И ты потихонечку обрастаешь такими контактами. Есть куча экспертов, и все они открыты. Я снобов ещё не видел, меня ещё ни разу никто не послал.

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

Два раза в неделю синк с топ-менеджментом, раз в неделю Product Owner Sync со всеми командами, где-то порядка 7-8 синхронизационных встреч с самыми важными командами и направлениями, начиная с эксплуатации и заканчивая разработкой. Раз в месяц или два раза в месяц у нас проходят Вопросы без правил, когда сотрудники или участники команд задают вопросы, от которых их сильно бомбит, и ты ходишь, собираешь ответы по другим департаментам чтобы разработчики понимали, что мы боремся с безысходностью, когда даже спросить не у кого. Например, ты сделал фичу, но А/В тест проиграл, её не раскатили, а ты её пилил полгода, и ты такой: Блин, ну почему? Тебе не объяснили, а тебя бомбит. Раз в квартал проходит OKR кухня, когда мы рассказываем, какие objective мы поставили себе и какие key result у нас есть по командам, оркестрация всего этого и подготовка нашими value-стримами, стейкхолдерами. Ещё мы периодически устраиваем различные лекции. Плюс история про годовое планирование, бюджетирование. История с деньгами очень тесно связана с жизнью СТО. Когда я был молодой, а деревья были зеленые, я думал, что технический директор думает про технологии - весело, классно. На самом деле часть твоей работы - это гребаные гугл-таблицы, в которых тебе нужно понять, а где тебе взять бюджет. Например, Сбербанк опять начал в железных бочках сжигать деньги на зарплаты людям, поэтому ты уже три месяца не можешь найти админа. Ты понимаешь, что рынок задрали, у тебя жёсткие рамки, а человек нужен. Надо либо выращивать, либо искать бюджет. И так или иначе тебе приходится оркестрировать историю с деньгами, если она не решается на локальном уровне команды.

О том, где ищут СТО, сколько это стоит и что происходит когда он приходит в компанию - читайте во второй части.

Подробнее..

Профессия СТО часть 2

13.01.2021 14:06:06 | Автор: admin

И: Что происходит, когда новый СТО приходит в компанию?

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

Интересный вопрос: а кто собеседует СТО? СЕО и его подчиненные. СТО - это такой мостик между теми, кто дает деньги, и теми, кто занимается разработкой, внедрением, имплементацией. Тебе надо сначала сформулировать свою картину мира, какие задачи и цели перед тобой стоят. Со 100%-й вероятностью то, что тебе говорит СЕО, окажется не так. Потому что то, какие задачи перед тобой ставят люди, которые тебя нанимают, скорее всего, это не совсем то, что им нужно - если бы они знали, что им нужно, им не нужен бы был СТО. Поэтому сначала требуется аудит и понимание того, что происходит. Для меня это прежде всего аудит людей, их возможностей, их потребностей. У меня был очень серьезный вызов - мне в подчинение достался человек, который был существенно старше меня и обладал - и до сих пор обладает - компетенцией, которой я не обладаю настолько глубоко. То есть я в институте проходил, как выглядит семиуровневая модель сети, но ниже истории с транспортным уровнем никуда не опускался. А тут есть человек, матёрый и опытный, и пришёл шпендель, который моложе его и не обладает компетенцией, чтобы им руководить, как ему могло показаться. Мой челлендж был понять, можем ли мы работать вместе и можем ли совместно приносить компании пользу. И мы, как мне кажется, завоёвывали взаимное уважение друг перед другом. Очень аккуратно присматривались, общались, пытались понять, на сколько мы в принципе говорим на одном языке. Нам это удалось, мы работаем вместе с 2012 года и тьфу-тьфу-тьфу вроде у нас всё хорошо.

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

А как выглядит собеседование СТО?

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

А как тебя собеседуют?

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

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

Поскольку я предпочитаю работать подолгу на одном месте, у меня есть довольно интересное мнение, которое касается и разработчиков, и СТО в первую очередь. Люди, которые прыгают из одной компании в другую, проработав по году, не отвечают за свои слова. Это значит, что те архитектурные решения, которые они сделали, проверяет кто-то другой. И они не получают того самого опыта, который нужен для жизни долгоиграющего сервиса. Ты устроился работать в компании, проработал там два месяца, написал какой-то сервис или сделал какую-нибудь фичу, а когда эта фича через 7-8 месяцев перешла в суровую эксплуатацию, ты увольняешься. Но кто будет отвечать за то, что эту хрень написал ты? У СТО ещё более длительный период. Ты принимаешь такие решения, за которые компания будет расплачиваться через несколько лет.

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

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

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

Вилки зарплат СТО примерно такие. Я не слышал о максимуме, но могу привести несколько примеров предложений, которые приходили мне. Я знаю, что сейчас зарплаты СТО начинаются примерно от 250 тысяч. Это мы говорим про компании не очень большого масштаба. В средней компании зарплата СТО сейчас, как мне кажется, около 450 тысяч. У гигантов побольше. Опять-таки, я смотрю по рынку по России. Если мы говорим про западные компании, история может отличаться, там роль СТО несколько более дорогостоящая. Но сейчас хорошие ценники болтаются около 700-800 тысяч. А дальше уже идут звёздные истории. Я знаю СТО с зарплатой в миллион, я знаю СТО с зарплатой полмиллиона, я знаю СТО с зарплатой 0,7 миллиона. И это всё довольно крупные проекты, которые на слуху.

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

Так совпадает, что люди выбиваются в сторону СТО примерно в районе кризиса среднего возраста или чуть-чуть пораньше - хотя я сильно до 30-ти начал заниматься управлением. Но по-хорошему, это просто жизненный вопрос: А что ты хочешь? Кто-то сильно упарывается по баблу. Идут в говнопроекты, где понятно, что то, что ты будешь разрабатывать, будешь разрабатывать в стол, и человек понимает, что оно никогда не полетит. Я знаю людей, которые в разные проекты ходят за деньгами. И когда они с помощью бонусных программ получают определенное value, они начинают не работать. Это один из путей. Второй из путей - когда ты переходишь в категорию бизнес-ангелов или инвесторов, людей, которые своим опытом и компетенцией помогают молодым компаниям пробиваться с технологической точки зрения. Тоже вполне себе интересная история. Этим славится определенное количество людей. То есть либо ты уходишь и делаешь свой стартап, либо ты просто помогаешь команде - за долю или ещё за что-то - которая тебе симпатична. Есть люди, которые продолжают переться от того, что они делают, и находить себе задачи и проекты, в которые они влюбляются. Я пока не разочаровался в том, чем я занимаюсь, и мне нравится то, что я делаю. Мне нравится заводить эти шестереночки и находить применение своим умениям. Плюс, есть два стандартных направления: вертикальный рост и горизонтальный рост. Ты можешь перепрыгнуть в компанию с большим штатом, с другими масштабами, ты можешь вырастить свою компанию - вариантов масса. А можешь пойти работать разработчиком, потому что, вы удивитесь, но в разных направлениях в большинстве случаев ценник заплат СТО не сильно отличается от зарплат разработчика или лида.

А что с опционами?

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

Подробнее..

Путь IVI от монолита к микросервисам

08.04.2021 16:21:10 | Автор: admin

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

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

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

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

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

Тут мы столкнулись с другой проблемой, которую, в том числе, позволяют решать микросервисы, правда мы этим не воспользовались. Название этой проблеме монорепозиторий. С ростом команды разработка в одном репозитории становилась проблемой. Росло количество тестов, и нам пришлось усложнять CI, чтобы распараллелить запуск тестов различных независимых частей репозитория. Но даже это не сильно спасало. Дело в том, что из-за активной разработки скапливалась очередь задач на слияние веток, и разрабатываемая тобой ветка могла быть слита только через пол дня. Если повезёт и кто-то другой не объединит раньше тебя ветку, которая ломает твои тесты.

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

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

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

И ещё одной проблемой стала сложность добавления нового сервиса в production. Средства CD позволяли быстро обновлять существующие сервисы, но это не касалось первоначальной установки. Она подразумевала необходимость установить новые машины в ДЦ или создать виртуалки, установить всё необходимое ПО, настроить CD. И это был небыстрый процесс, а новые доработки обычно требовались довольно срочно. Да, в будущем будут появляться различные средства для решения этой проблемы, но в то время их ещё не было. Хотя даже когда у нас начали появляться средства виртуализации, контейнеризации, оркестрации и эта проблема стала исчезать, на смену пришла другая сложность эксплуатации. Уже нельзя справиться с десятками сервисов без автоматизации доставки новых версий приложений. Сложность эксплуатации также возрастает в связи с повышением требований к мониторингу и управлению большим количеством сервисов. Решение таких эксплуатационных проблем требует множества навыков и инструментов.

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

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

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

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

При этом и наша компания разрасталась, менялась её структура, появлялись команды, развивавшие конкретные направления. И тут мы осознали ещё один недостаток монолитов и преимущество микросервисов разграничение ответственности. Для каждого микросервиса у нас появились свои ответственные команды. Чего нельзя сказать о разросшихся в размерах сервисах: за них отвечали все и одновременно никто. Стали происходить диалоги вида: Кто отвечает за сервис X? Команда A. У меня вопрос по функциональности F. А, за этим тебе надо идти в команду B..

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

Другой проблемой стала сложность поддержки актуального стека технологий. Уже давно закончилась поддержка второго Python, но один из наших сервисов до сих пор на нём работает. А другой сервис мы полгода переводили на третий Python. Да и в целом любой рефакторинг сервисов с большой кодовой базой это очень сложная задача, которая требует больших усилий для организации плавного перехода на новые технологии, или может потребовать feature freeze на длительный период. К тому же после написания всего необходимого кода очень долгое время занимает тестирование таких изменений. Тогда как микросервисы переводились на Python 3 за пару дней и очень быстро тестировались.

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

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

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

Подробнее..

Автоматическая документация для Flask с использованием OpenAPI

15.02.2021 18:22:10 | Автор: admin
image alt


Техническая документация, как известно, крайне важная часть любого проекта. До недавнего времени мы прекрасно жили с таким генератором документаций как Sphinx. Но наступил момент переходить на технологии с бОльшим набором возможностей, поэтому мы приняли решение переписать нашу документацию на более современный стандарт: OpenAPI Specification. Эта статья является скромным гайдом по такому переезду. Она будет интересна Python-разработчикам, особенно тем, которые используют Flask. После ее прочтения вы узнаете, как создать статическую OpenAPI документацию для Flask приложения и развернуть ее в GitLab Pages.




apispec + marshmallow


В качестве веб-фреймворка у нас используется Flask. Документацию для API, созданного с помощью него, мы и хотим создать. Спецификация по стандарту OpenAPI описывается форматом YAML(или JSON). Чтобы преобразовать докстринги нашего API в необходимый формат, будем использовать такой инструмент, как apispec, и его плагины. Например, MarshmallowPlugin, с помощью которого (и самой библиотеки marshmallow) можно удобно за счет возможности наследования и переиспользования описать входные и выходные данные эндпоинтов в виде python классов, а также провалидировать их.

Используя библиотеку marshmallow, создадим класс, описывающий параметры API:

from marshmallow import Schema, fieldsclass InputSchema(Schema):   number = fields.Int(description="Число", required=True, example=5)   power = fields.Int(description="Степень", required=True, example=2)

Аналогично сделаем для выходных параметров:

class OutputSchema(Schema):   result = fields.Int(description="Результат", required=True, example=25)

Для группировки запросов в OpenAPI используются теги. Создадим тег и добавим его в объект APISpec:

def create_tags(spec):   """ Создаем теги.   :param spec: объект APISpec для сохранения тегов   """   tags = [{'name': 'math', 'description': 'Математические функции'}]   for tag in tags:       print(f"Добавляем тег: {tag['name']}")       spec.tag(tag)

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

Пример:

from flask import Blueprint, current_app, json, requestblueprint_power = Blueprint(name="power", import_name=__name__)@blueprint_power.route('/power')def power():   """   ---   get:     summary: Возводит число в степень     parameters:       - in: query         schema: InputSchema     responses:       '200':         description: Результат возведения в степень         content:           application/json:             schema: OutputSchema       '400':         description: Не передан обязательный параметр         content:           application/json:             schema: ErrorSchema     tags:       - math   """   args = request.args   number = args.get('number')   if number is None:       return current_app.response_class(           response=json.dumps(               {'error': 'Не передан параметр number'}           ),           status=400,           mimetype='application/json'       )   power = args.get('power')   if power is None:       return current_app.response_class(           response=json.dumps(               {'error': 'Не передан параметр power'}           ),           status=400,           mimetype='application/json'       )   return current_app.response_class(       response=json.dumps(           {'response': int(number)**int(power)}       ),       status=200,       mimetype='application/json'   )

Эта функция пример реализации метода GET в нашем API.

Блок summary. Краткое описание функции. Для более подробного описания можно добавить блок description.

Блок parameters. Описание параметров запроса. У параметра указывается, откуда он берется:

  • path, для /power/{number}
  • query, для /power?number=5
  • header, для X-MyHeader: Value
  • cookie, для параметров переданных в cookie файле

и schema, в которую передается python класс, описывающий данный параметр.

Блок responses. Описание вариантов ответа команды и их структура.

Блок tags. Описание тегов, которые используются для логической группировки эндпоинтов.

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

После того, как мы описали методы API, можем загрузить их описание в объект APISpec:

def load_docstrings(spec, app):   """ Загружаем описание API.   :param spec: объект APISpec, куда загружаем описание функций   :param app: экземпляр Flask приложения, откуда берем описание функций   """   for fn_name in app.view_functions:       if fn_name == 'static':           continue       print(f'Загружаем описание для функции: {fn_name}')       view_fn = app.view_functions[fn_name]       spec.path(view=view_fn)

Создаем метод get_apispec, который будет возвращать объект APISpec, в нем добавляем общую информацию о проекте и вызываем описанные ранее методы load_docstrings и create_tags:

from apispec import APISpecfrom apispec.ext.marshmallow import MarshmallowPluginfrom apispec_webframeworks.flask import FlaskPlugindef get_apispec(app):   """ Формируем объект APISpec.   :param app: объект Flask приложения   """   spec = APISpec(       title="My App",       version="1.0.0",       openapi_version="3.0.3",       plugins=[FlaskPlugin(), MarshmallowPlugin()],   )   spec.components.schema("Input", schema=InputSchema)   spec.components.schema("Output", schema=OutputSchema)   spec.components.schema("Error", schema=ErrorSchema)   create_tags(spec)   load_docstrings(spec, app)   return spec


image alt


Swagger UI


Swagger UI позволяет создать интерактивную страницу с документацией.

Создадим эндпоинт, который будет возвращать спецификацию в json формате, и вызываем в нем get_apispec:

@app.route('/swagger')def create_swagger_spec():   return json.dumps(get_apispec(app).to_dict())

Теперь, когда мы получили json спецификацию, нам нужно сформировать из неё html документ. Для этого воспользуемся пакетом flask_swagger_ui, с помощью которого можно встроить интерактивную страницу с документацией на базе Swagger UI в наше Flask приложение:

from flask_swagger_ui import get_swaggerui_blueprintSWAGGER_URL = '/docs'API_URL = '/swagger'swagger_ui_blueprint = get_swaggerui_blueprint(   SWAGGER_URL,   API_URL,   config={       'app_name': 'My App'   })


Таким образом, мы сделали эндпоинт /docs, при обращении по которому получаем документацию следующего вида:

image alt


image alt

GitLab Pages + ReDoc



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

Использование GitLab позволяет сгенерировать такую статическую страницу с документацией в CI/CD процессах.

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

Для этого сохраним APISpec в YAML файл:

DOCS_FILENAME = 'docs.yaml'def write_yaml_file(spec: APISpec):   """ Экспортируем объект APISpec в YAML файл.   :param spec: объект APISpec   """   with open(DOCS_FILENAME, 'w') as file:       file.write(spec.to_yaml())   print(f'Сохранили документацию в {DOCS_FILENAME})

Теперь, когда мы получили YAML файл по спецификации OpenAPI, нужно сформировать HTML документ. Для этого будем использовать ReDoc, так как он позволяет сгенерировать документ в gitlab-ci с красивой и удобной структурой. Публиковать его будем с помощью GitLab Pages.

Добавим следующие строки в файл gitlab-ci.yml:

pages: stage: docs image: alpine:latest script:   - apk add --update nodejs npm   - npm install -g redoc-cli   - redoc-cli bundle -o public/index.html docs.yaml artifacts:   paths:     - public

Стоит отметить, что index.html нужно сохранять в папку public, так как она зарезервирована GitLabом.

Теперь, если мы запушим изменения в репозиторий, по адресу namespace.gitlab.com/project появится документация:

image alt


Также путь до документации можно посмотреть в Settings/Pages

Пример документации с использованием ReDoc: ivi-ru.github.io/hydra

Заключение


Таким образом, мы научились собирать OpenAPI документацию с использованием ReDoc и хостить ее на GitLab Pages. В эту статью не попало еще несколько возможностей этих инструментов, например, валидация параметров с помощью marshmallow. Но основной ее целью было показать непосредственно процесс создания документации.

Полезные ссылки


Подробнее..

Переброска данных между идентичными объектами метаданных базы 1С через подмену УИДов в базе SQL

10.12.2020 18:18:39 | Автор: admin

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




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


А. Перенос удалившихся документов после обновления конфигурации в новый объект метаданных с помощью правил обмена (данный способ рекомендует использовать 1С);


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


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


Разберем подробно вариант "В".


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


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

  3. Теперь выгрузим конфигурацию в XML-файлы, чтобы удобно было находить все интересующие нас УИДы:



  4. Открываем файл старого документа (ivi_ТестовыйДокументУКФ.xml), ищем и фиксируем УИДы его самого и всех его реквизитов и табличных частей:
    УИД документа:

    УИД реквизита:

    Табличная часть также имеет свой УИД:


  5. Далее делаем тоже самое для нового документа.

  6. В итоге получается два списка, для старого и нового объектов:


  7. Теперь, зная УИДы,, отправляемся в базу данных для их замены.
    https://its.1c.ru/db/metod8dev/content/1798/hdoc статья на ИТС с подробным описанием структуры хранения данных в БД.
    Нас интересует таблица Params, в которой расположены соответствия между объектами конфигурации 1С и таблицами базы MS SQL. Данные соответствий хранятся в текстовом формате, упакованном алгоритмом Deflate:

  8. Выгружаем из таблицы Params двоичные данные файла с именем DBNames:

    Через SQL Management studio данные выгружаются в формате HEX, где каждый символ кодируется двумя шестнадцатеричными знаками, так как выгружается в текстовом виде. Для дальнейшей работы с данными их необходимо преобразовать из формата HEX в формат BIN. Подойдет любой конвертер HEX to BIN. Я использовал конвертер https://tomeko.net/online_tools/hex_to_file.php?lang=en
    В итоге получили двоичный файл, по длине совпадающий с длиной поля в БД, 955126 байт.

  9. Раскодируем полученный файл. Для раскодировки использовал обработку: https://infostart.ru/public/618906/
    Распакованный DBNames выглядит следующим образом (слева УИД 1С, далее префикс и номер таблицы SQL):


  10. Наша задача заменить УИДы 1С между старым и новым документами, не затрагивая при этом имена таблиц SQL.
    На рисунке выше УИДы уже заменены.

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

  12. С помощью SQL-запроса помещаем новый файл в DBNames:
    UPDATE [ukf_real_test].[dbo].[Params] SET [BinaryData] = (
    SELECT *
    FROM OPENROWSET(BULK N'C:\DBNames-FM.dfl', SINGLE_BLOB) tt) where [FileName] = 'DBNames'.
    Следующим запросом устанавливаем поле с длиной двоичных данных в правильное значение, чтобы не возникло внутренней ошибки в 1С при обращении к ним:
    UPDATE [ukf_real_test].[dbo].[Params] SET [DataSize] = 954234 where [FileName] = 'DBNames';

  13. Откроем конфигуратор 1С. Необходимо, чтобы 1С реструктуризировал данные измененного документа. Для этого необходимо внести любое изменение в состав или значения реквизитов и сохранить, обновив конфигурацию базы данных.

  14. После реструктуризации запускаем 1С в режиме предприятия.
    Все данные из старого документа перешли в новый:

    Состояние документов и ссылки рабочие.
    База 1С обновляется, реструктуризируется без ошибок.
    При загрузке новой конфигурации старый документ, в котором уже нет объектов, удаляется, а все документы остаются в новом.

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

Подробнее..

Как мы в IVI используем массивы в ClickHouse для подсчета продуктовых метрик

25.02.2021 14:18:30 | Автор: admin

IVI кросс-платформенный сервис, а значит, мы должны анализировать метрики всюду: на вебе, телевизорах и мобильных приложениях. Продукт непрерывно развивается, чтобы стать максимально эффективным, удобным и повысить ценность и привлекательность подписки. Перед тем, как внедрить какую-то новую фичу, мы проводим a/b-тесты и исследуем, на сколько востребованным окажется нововведение и как оно повлияет на конверсию или смотрение. Одновременно у нас может проверяться до 70-ти гипотез, от которых непосредственно зависят планы по развитию продукта.

Для того, чтобы правильно оценить успешность или неуспешность теста, требовалось технологичное решение. Тут рассказывали о том, как мы перешли на ClickHouse (а также о его проблемах на январь 2018). Новая схема ETL позволила нам иметь хранилище, толерантное к дубликатам. При ошибке в коде мы всегда можем откатить consumer offset в kafka и обработать часть данных снова, не прилагая лишних усилий для движения данных. Хотим рассказать о том, как мы в IVI используем ClickHouse, чтобы посчитать метрики для решения разных продуктовых задач и понять, что мы действительно делаем продукт лучше, а не придумываем фичи, которыми никто не будет пользоваться.

О массивах и махинациях с монетизацией контента.

Сперва введем ряд обозначений, которые будем использовать. На IVI есть несколько моделей монетизации. AVOD большой каталог бесплатных фильмов и сериалов, для просмотра которых придётся посмотреть рекламу. SVOD контент по подписке, расширенный каталог без рекламы. TVOD/EST контент, который доступен за отдельную плату и не входит в SVOD. EST покупка навсегда, TVOD аренда фильма, нужно начать просмотр в течение 30 дней, а закончить за 48 часов. Почему так дорого? У меня есть подписка, а я еще должен платить за отдельные фильмы? Ну совсем! Да этому фильму уже 20 лет, а я должен за него заплатить?! 600 рублей за аренду?! - подобное я слышу от друзей, таксистов, врачей по ДМС, преподавателей в университете и даже от родителей. Давайте разбираться.

Есть фильм, а есть правообладатель. Последний решает, в каком формате станут продавать контент. Заключается контракт, в котором описано, по какой модели будет доступен фильм, и если в контракте указано Доступна только TVOD-модель (как было, например, для онлайн-премьеры Человека-невидимки или Эммы), то условия должны быть выполнены. Но времена меняются, контракты перезаключаются, а контент, который был доступен только по модели TVOD/EST, может перетечь в подписку (т. е. в SVOD). И естественно, нельзя не поделиться этой информацией с пользователями.

В результате изменения модели монетизации контента перед нами встает задача объяснить бизнесу, как влияет коммуникация о переходе контента из TVOD/EST в SVOD на выручку и отток. Любая коммуникация с пользователем это ресурсы: человеко-часы, деньги на смс и прочее. Соответственно, нужно убедиться в том, что эти затраты оправданы (ведь экономика должна быть экономной). Мы стараемся проводить большинство коммуникаций в виде a/b-тестирования. Во-первых, чтобы убедиться в их полезности, а во-вторых, чтобы понимать, какие работают, а какие нет.

Переформулируем задачу для себя: провести a/b-тест, связанный с коммуникацией о переходе контента в SVOD из TVOD/EST, чтобы оценить изменение метрик. Выберем для себя ряд показателей, которые будут ключевыми в принятии решения:

1) количество зашедших на карточку контента;

2) количество смотрящих;

3) количество оформивших подписку SVOD;

4) количество купивших TVOD/EST;

5) выручка SVOD;

6) выручка TVOD/EST;

7) конверсия в оформление подписки после захода на карточку контента;

8) конверсия в покупку TVOD/EST после захода на карточку контента.

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

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

У каждого массива в БД своя функция, например, массив, в котором хранятся a/b-тесты, массив с url ресурса, массив с событиями и их индексами в сессии и т.д. Вот маленькая шпаргалка, то, что необходимо понять перед составлением сложных мозгодробильных запросов:

arrayElement( "Вытащить в массиве элемент номер

details.int_value, В массиве таком-то

indexOf( найти номер элемента в массиве

details.name, В массиве таком-то

id' элемента такого-то

)

) in (1,2)

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

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

джойним на покупки, чтобы посчитать выручку;

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

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

создаем флаги наличия просмотра или покупки в рассматриваемых цепочках;

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

считаем события в необходимой агрегации.

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

Когда массивы излишни

Давайте отдельно поговорим о кейсе, когда нет смысла использовать массивы.

На IVI есть классная фича вход по коду (см. видео). Если вы авторизованы через мобильное приложение, то все, что вам надо для авторизации на ТВ ввести код с телефона в приложении IVI в своём Smart TV. Для пользователей, у которых нет чуда техники magic mouse, это по-настоящему полезная штука.

Вход по кодуВход по коду

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

Задача: оценить, как часто люди используют флоу входа по коду на Smart TV. Это нужно, чтобы понять, достаточно ли мы уведомляем людей о новых фичах. Ведь важно не только иметь классный функционал, но и понимать, сколько человек о нём знают и пользуются.

Так выглядит стандартная схема авторизации/регистрации на Smart TV:

Нужно проанализировать воронку:

  1. количество пользователей, использующих Вход по коду ко всем авторизовавшимся;

  2. количество пользователей, авторизованных по почте ко всем авторизовавшимся;

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

Так мы оценим, какой % людей использует функцию входа по коду. Делаем поправку на то, что в данном случае мы оцениваем только кросс-платформенных пользователей, которые проявляют активность и на Smart TV, и на телефоне.

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

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

Пару слов о проблеме выбора.

Если вы дочитали до этого места, то вот ещё кое-что интересное.

Как лучше показывать акционные предложения на телевизоре? Не стоит множить кнопки на экране, ставя перед пользователем проблему выбора. Мы решили провести небольшой эксперимент, в рамках которого запустили продуктовую коммуникацию, разработав два дизайна одного и того же предложения. На варианте 1 есть только одна кликабельная кнопка, ДА (рис. 1). На варианте 2 кнопок две ДА и Не сегодня. Кроме того, в каждом варианте есть аппаратный back и крестик в правом верхнем углу, их тоже учитываем. Сделав обычный select from посчитали CTR (количество кликов на кнопку/количество показов экрана) и поняли, что лучшее, что есть в выборе его отсутствие. Вариант 1 оказался заметно продуктивнее.

P.S. Благодаря эксперименту мы также узнали о том, что крестик носит скорее эстетический характер, а большая часть пользователей предпочитает аппаратный back: в среднем 7 из 10 человек нажимают на back вместо того, чтобы навести magic mouse на Х.

Рис.1

Рис.2

Подробнее..

Подсказки по написанию тестов в приложениях на Go

23.04.2021 14:18:12 | Автор: admin

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

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

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

package yourpackage import (    "context"     "github.com/go-redis/redis/v8") func CheckLen(ctx context.Context, client *redis.Client, key string) bool {    val, err := client.Get(ctx, key).Result()    if err != nil {    return false    }    return len(val) < 10  }

Тесты для неё могут выглядеть примерно так:

package yourpackage import (    "context"    "testing"     "github.com/go-redis/redis/v8") func TestCheckLen(t *testing.T) {    ctx := context.Background()    rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})    err := rdb.Set(ctx, "some_key", "value", 0).Err()    if err != nil {    t.Fatalf("redis return error: %s", err)    }     got := CheckLen(ctx, rdb, "some_key")    if !got {    t.Errorf("CheckLen return %v; want true", got)    }}

Но как проверить ситуацию, когда Redis возвращает ошибку? Или что делать, если мы не хотим добавлять Redis в наш CI? То есть как нам замокать вызов Redis? И ответ на эти вопросы используйте интерфейсы!

Перепишем наш код с использованием интерфейсов:

package yourpackage import (    "context"     "github.com/go-redis/redis/v8") type Storage interface {    Set(ctx context.Context, key string, v interface{}) error    Get(ctx context.Context, key string) (string, error)} type RedisStorage struct {    Redis *redis.Client} func (rs *RedisStorage) Set(ctx context.Context, key string, v interface{}) error {    return rs.Redis.Set(ctx, key, v, 0).Err()} func (rs *RedisStorage) Get(ctx context.Context, key string) (string, error) {    return rs.Redis.Get(ctx, key).Result()} func CheckLen(ctx context.Context, storage Storage, key string) bool {    val, err := storage.Get(ctx, key)    if err != nil {    return false    }    return len(val) < 10}

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

package yourpackage import (    "context"    "testing") type testRedis struct{} func (t *testRedis) Get(ctx context.Context, key string) (string, error) {    return "value", nil}func (t *testRedis) Set(ctx context.Context, key string, v interface{}) error {    return nil} func TestCheckLen(t *testing.T) {   ctx := context.Background()    storage := &testRedis{}     got := CheckLen(ctx, storage, "some_key")    if !got {    t.Errorf("CheckLen return %v; want true", got)    }}

Используем генераторы моков

Понятное дело, что для каждого случая писать свой мок немного избыточно. Можно попробовать написать универсальный мок. А можно попробовать его сгенерировать на основе интерфейса. Существует множество генераторов моков. Нам нравится https://github.com/vektra/mockery.

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

mockery --recursive=true --inpackage --name=Storage

И дальше используем его в тестах следующим образом:

package yourpackageimport (    "context"    "testing"     mock "github.com/stretchr/testify/mock") func TestCheckLen(t *testing.T) {    ctx := context.Background()     storage := new(MockStorage)    storage.On("Get", mock.Anything, "some_key").Return("value", nil)     got := CheckLen(ctx, storage, "some_key")    if !got {    t.Errorf("CheckLen return %v; want true", got)    }

Перехватываем логирование

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

package yourpackage import (    log "github.com/sirupsen/logrus") func Minus(a, b int) int {    log.Infof("Minus(%v, %v)", a, b)    return a - b} func Plus(a, b int) int {    log.Infof("Plus(%v, %v)", a, b)    return a + b} func Mul(a, b int) int {    log.Infof("Mul(%v, %v)", a, b)    return a + b // тут ошибка}

И тесты к этому коду:

package yourpackage import "testing" func TestPlus(t *testing.T) {    a, b, expected := 3, 2, 5    got := Plus(a, b)    if got != expected {    t.Errorf("Plus(%v, %v) return %v; want %v", a, b, got, expected)    }} func TestMinus(t *testing.T) {    a, b, expected := 3, 2, 1    got := Minus(a, b)    if got != expected {    t.Errorf("Minus(%v, %v) return %v; want %v", a, b, got, expected)    }} func TestMul(t *testing.T) {    a, b, expected := 3, 2, 6    got := Mul(a, b)    if got != expected {    t.Errorf("Mul(%v, %v) return %v; want %v", a, b, got, expected)    }}

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

time="2021-03-22T22:09:54+03:00" level=info msg="Plus(3, 2)"time="2021-03-22T22:09:54+03:00" level=info msg="Minus(3, 2)"time="2021-03-22T22:09:54+03:00" level=info msg="Mul(3, 2)"--- FAIL: TestMul (0.00s)yourpackage_test.go:55: Mul(3, 2) return 5; want 6FAILFAILgotest2/yourpackage 0.002sFAIL

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

package yourpackage import (    "io"    "testing"     "github.com/sirupsen/logrus") type logCapturer struct {    *testing.T    origOut io.Writer} func (tl logCapturer) Write(p []byte) (n int, err error) {    tl.Logf((string)(p))    return len(p), nil} func (tl logCapturer) Release() {    logrus.SetOutput(tl.origOut)} func CaptureLog(t *testing.T) *logCapturer {    lc := logCapturer{T: t, origOut: logrus.StandardLogger().Out}    if !testing.Verbose() {    logrus.SetOutput(lc)    }    return &lc} func TestPlus(t *testing.T) {    defer CaptureLog(t).Release()    a, b, expected := 3, 2, 5    got := Plus(a, b)    if got != expected {    t.Errorf("Plus(%v, %v) return %v; want %v", a, b, got, expected)    }} func TestMinus(t *testing.T) {    defer CaptureLog(t).Release()    a, b, expected := 3, 2, 5    got := Minus(a, b)    if got != expected {    t.Errorf("Minus(%v, %v) return %v; want %v", a, b, got, expected)    }} func TestMul(t *testing.T) {    defer CaptureLog(t).Release()    a, b, expected := 3, 2, 5    got := Mul(a, b)    if got != expected {    t.Errorf("Mul(%v, %v) return %v; want %v", a, b, got, expected)    }}

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

--- FAIL: TestMul (0.00s)yourpackage_test.go:16: time="2021-03-22T22:10:52+03:00" level=info msg="Mul(3, 2)"yourpackage_test.go:55: Mul(3, 2) return 5; want 6FAILFAILgotest2/yourpackage 0.002sFAIL

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

Считаем покрытие правильно

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

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

$ go tool cover -helpUsage of 'go tool cover':Given a coverage profile produced by 'go test':    go test -coverprofile=c.out...Display coverage percentages to stdout for each function:    go tool cover -func=c.out

Пробуем:

$ go test -coverprofile=c.out ./...ok  gotestcover/minus   0.001s  coverage: 100.0% of statements?   gotestcover/mul [no test files]ok  gotestcover/plus    0.001s  coverage: 100.0% of statements

Уже из этого вывода видно, что у нас два пакета покрыты на 100 % и для одного пакета нет тестовых файлов. Получим отчёт о покрытии:

$ go tool cover -func=c.outgotestcover/minus/minus.go:4:   Minus       100.0%gotestcover/plus/plus.go:4: Plus        100.0%total:                      (statements)100.0%

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

go test -coverpkg=./... -coverprofile=c.out ./

И теперь отчёт выдаёт ожидаемый процент покрытия тестами:

$ go tool cover -func=c.outgotestcover/minus/minus.go:4:   Minus       100.0%gotestcover/mul/mul.go:4:   Mul         0.0%gotestcover/plus/plus.go:4: Plus        100.0%total:                      (statements)66.7%

Считаем покрытие при тестировании приложения как черного ящика

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

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

func TestRunMain(t *testing.T) {    main()}

Запускаем его, потом интеграционные тесты, и завершаем наш тест. Звучит просто, но есть несколько нюансов. Зачастую надо сделать так, чтобы этот тест не запускался со всеми остальными тестами. Он особый, и для него должна быть отдельная логика запуска. Ещё функция main не должна приводить к выходу с ненулевым кодом возврата. И надо реализовать способ выхода из main по сигналу, не завершая при этом сам тест. То есть в целом надо реализовать для нашего web-сервиса graceful shutdown, что несложно сделать, и это в целом полезно. Давайте на примере реализуем небольшой web-сервис, протестируем его с помощью curl, и посчитаем покрытие тестами.

Сервис наш будет выглядеть следующим образом (взято с https://gobyexample.com/http-servers):

package main import (    "context"    "fmt"    "net/http"    "os"    "os/signal"    "time") func hello(w http.ResponseWriter, req *http.Request) {    fmt.Fprintf(w, "hello\n")} func headers(w http.ResponseWriter, req *http.Request) {    for name, headers := range req.Header {    for _, h := range headers {    fmt.Fprintf(w, "%v: %v\n", name, h)    }    }} func main() {    http.HandleFunc("/hello", hello)    http.HandleFunc("/headers", headers)     // Приложим некоторые усилия, чтобы приложение завершилось с нулевым кодом выхода    // Это важно для тестов, и в целом приятно    server := &http.Server{Addr: ":8090", Handler: nil}    // Запускаем приложение в отдельной горутине    go func() {    server.ListenAndServe()    }()     // А в текущей ждём сигнала об остановке приложения    quit := make(chan os.Signal, 1)    signal.Notify(quit, os.Interrupt)    <-quit    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)    defer cancel()    server.Shutdown(ctx)}

И тест к нему:

// +build testrunmain package main import "testing" func TestRunMain(t *testing.T) {    main()}

Комментарий +build testrunmain говорит о том, что тест будет запускаться только в случае, если передан соответствующий tag. Запускаем наш тест:

$ go test -v -tags testrunmain -coverpkg=./... -coverprofile=c.out  ./...=== RUN   TestRunMain

Тестируем с помощью curl:

$ curl 127.0.0.1:8090/hellohello

И завершаем наше тестирование, нажав Ctrl+C:

$ go test -v -tags testrunmain -coverpkg=./... -coverprofile=c.out  ./...=== RUN   TestRunMain^C--- PASS: TestRunMain (100.92s)PASScoverage: 80.0% of statements in ./...ok  gobintest   100.926s    coverage: 80.0% of statements in ./

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

$ go tool cover -func=c.outgobintest/main.go:12:   hello       100.0%gobintest/main.go:16:   headers     0.0%gobintest/main.go:24:   main        100.0%total:              (statements)80.0%

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

Хотите узнать больше о тестировании в Go? Вот ещё несколько интересных статей на хабре: один, два, три.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru