«Рамблер Касса» как пример высокопроизводительного проекта на .Net

Программный комитет еще не принял решения по этому докладу
Алексей Нигметзянов (Дочерняя IT‑компания ПАО «Татнефть»)Алексей Нигметзянов

«рамблер касса» как пример высокопроизводительного проекта на .Net д.паньшин
View more presentations from Ontico.

Дмитрий Паньшин, руководитель группы разработки Объединенной Компании "Афиша-Рамблер":

– Здравствуйте! Меня зовут Дмитрий Паньшин. Я сегодня расскажу, как мы строили, разрабатывали наше приложение "Рамблер Касса". Если честно, я не знал, что это "Microsoft free" конференция. Это очень похоже на "fat free" – как будто это какой-нибудь фастфуд.

Какая задача перед нами стояла? Надо было разработать сервис, который позволяет пользователю покупать билет в кино. Есть виджет "Билеты". Вы нажимаете на кнопку, и появляется окно, в котором вы можете выбрать кинотеатр, время, места, способ оплаты и так далее.

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

Как это распространяется? В виде виджета. Вы представляете, что такое лайк от "Facebook" или твит от "Twitter"? Инструмент похожий. С одной стороны, javascript плюс HTML-разметка. С другой стороны, значение логики, связанной с отображением данных велико. 

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

С другой стороны, кнопка и все, что видит пользователь – вершина айсберга. Мы решили довольно много задач. Это и связь с кинотеатрами, и получение актуальных данных (они не всегда правильно работают), и кросс-доменные коммуникации, и большое количество javascript- и HTML-кода. Это позволяет нам "вклиниваться" в работу сайта и отображать виджет, чтобы ничего не разъехалось и работало правильно. Есть и платежные системы, которые позволяют пользователю оплачивать билеты.

Мы выбрали технологию .Net. Давайте посмотрим, что это такое.

Знаете Highscalability.com? Это сайт, который посвящен приложениям и продуктам с высокими нагрузками. 

Он используется существенно реже, чем MySQL, PHP, Python, JAVA. Но он применяется, и те решения, которые на нем построены, достаточно успешны.

Для тех, кто пишет на .Net, я постараюсь дать некоторые рекомендации и описать подводные камни.

А кто не пишет на .Net – устроим холивар. На самом деле, возможно, когда вы будете строить какую-то новую систему, к вам придут разработчики и скажут: "Давайте напишем на .Net". Вы в этот момент задумаетесь: а может, правда, написать?

Какие нефункциональные требованияперед нами стояли?

Количество просмотров составляло около 15-ти миллионов в день. Каждый раз на странице должна была показываться кнопка. Пустая кнопка должна была на 85% показываться за 50 миллисекунд, видимая кнопка – за 70 миллисекунд. Дальше количество пользователей должно существенно уменьшается, однако было необходимо, чтобы данный процесс протекал в разумных пределах.

Любая крупная веб-система состоит из составных систем. Это веб-сервер, базы данных, способ отправки задач на выполнение в background worker, невидимые пользователю задачи (забрать расписание, отправить письмо и т.д.) и кеш.

Платформа Microsoft IIS. Веб-сервер – это по умолчанию IIS. Это отличный application-сервер. Он работает стабильно и достаточно быстро.

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

В плане масштабированности и отказоустойчивости в нашем распоряжении два стандартных варианта "из коробки". Это стандартный Network Load Balancer (Microsoft). Это балансер уровня TCP. Мы получаем возможность балансировать запросы пользователей по IP (и еще по каким-либо другим, не очень важным для нас параметрам).

Но если нам надо, например, отправить всех авторизованных пользователей на один сервер, а неавторизованных - на другой, то такой возможности у нас на данном уровне нет. Для решения этой проблемы у Microsoft есть Application Request Routing. Это модуль для IIS, который позволяет балансировать запросы между серверами, но это уже уровень HTTP, т.е. самый верхний.

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

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

Данная схема, конечно, работает существенно лучше. NGINX – это наше все.

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

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

С точки зрения баз данных, использование MySQL на Windows-платформе стало своего рода стандартом. Это хороший промышленный SQL-сервер, который обладает большой функциональностью, устойчивостью, стабильностью и т.п. Он очень производительный. Из последних нововведений стоит отметить  "geo special data" "из коробки". В одном из наших проектов мы активно используем данную функциональность.

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

Кластер строится следующим образом. У нас есть две ноды базы данных и сетевое хранилище данных, где хранятся все данные. На самом деле это SAN (Serial Attached Network). Это должно быть быстрое хранилище (например, какое-нибудь FC-хранилище или нечто подобное).

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

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

Подобное решение используется на "Афише". Совсем недавно ребята доставили на сайт новую полку и "перераспределили" луны. "Перераспределение" происходило совершенно незаметно для приложений и пользователей. Не было ни деградации, ни других негативных эффектов. Поэтому если у вас есть деньги, используйте MS SQL Cluster.

Другое решение – это MS SQL Mirroring. Оно работает следующим образом. У вас есть два сервера: один - условно активный, другой - пассивный. Когда вы пишете в базу данных, то пишете в первый сервер, при этом запись автоматически передается на второй. Решение хорошее, однако в процессе записи могут случаться задержки, т.к. для передачи записи на второй сервер требуется некоторое время.

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

С точки зрения данных, это работа ASP.NET Cache. Это in-memory кэш, который работает только в рамках данного процесса.

Проблем в IT две: инвалидация кэша и именование переменных. Если использовать ASP.NET Cache, то при наличии 10 серверов инвалидация будет практически невозможна. На одном сервере вы поменяли данные, "выбросили" часть информации из кэша, а на другом они остались. Вам придется их каким-либо образом "выбросить". Это не очень прозрачный механизм.

С другой стороны, есть некоторый стандарт – Memcache. Это OutProc-кэш-сервер, который позволяет делать запросы. Но сравнивать их, конечно, проблематично, т.к. один существенно более производительный, а у другого нет проблем с инвалидацией данных.

С точки зрения презентационного кэша (страничного), есть NGINX и ASP.NET Output Cache. В IIS, если вы используете Output Cache и дополнительно используете, например, кэш данных, если у вас приложение обрабатывает достаточно большое количество ресурсов, все три сущности начинают конкурировать за ресурс в виде памяти.

У вас 10 гигабайт, 16 гигабайт памяти – они начинают претендовать на данный ресурс. Если память кончается, ASP.NET Cache сообщает: "Выбрось все страницы из кэша". Или наоборот, начинает выбрасывать кэш данных. Решение проблемы достаточно простое: надо "вытащить" Output Cache наружу. В частности, это может быть NGINX.

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

Out-of-process-cache - достаточно распространенный кэш. Если вы меняете данные на одном сервере, то они инвалидируются везде.

Также имеется одна особенность, связанная с регионами. Регион – это область, где вы можете оставить набор ключей и сказать: "Ключи находятся в данной области". У кинотеатров есть фотографии, счетчики и т.д. Они все содержатся в регионе. В определенный момент мы что-то меняем и приказываем: "Выбрось все ключи". Мы отдаем команду: "Clear region", – и ключи исчезают. Соответственно, данные обновляются синхронно.

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

Локальный кэш также находится в нашем распоряжении. Локальный кеш – это "золотая пуля". Когда мы сравнивали производительность Memcache и AppFabric, то заметили, что Memcache существенно быстрее (в 2 – 3 раза). Особенно это заметно на атомарных типах данных.

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

Конечно, распределение может быть совсем разным. Но у AppFabric Cache есть некоторые особенности. Web Edition – самый дешевый вариант. Вы не сможете поставить туда кэш-кластер, вам нужен Standard. Если вы хотите, чтобы у вас был реальный кластер и при выходе из строя одного из серверов не происходила потеря данных, то вам нужно использовать Enterprise Server. Соответственно, это еще в два раза дороже.

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

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

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

Перейдем к вопросу об очередях. Для решения данной задачи достаточно хорошо подходит MSMQ. В частности, когда надо поставить в очередь определенную задачу или данные, которые мы готовы потерять, мы используем MSNQ. Почему? Она позволяет работать быстро и асинхронно. Например, подтверждения доставки не трубуется. Мы положили - и забыли.

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

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

Почему MSMQ достаточно быстрый? Он работает следующим образом. На каждом из серверов (на отдающих серверах) стоит локальная служба MSMQ. Например, это отправщик. Он оправляет сообщение, сообщая локальному сервису: "Возьми новое тебе сообщение". MSMQ принимает его и отвечает: "Я его доставлю". На самом деле он может его и не доставить (он всего лишь подтвердил намерение его доставить). Процесс происходит очень быстро, т.к. протекает на одном сервере. Далее сообщение пересылается на другой сервер, куда попросил клиент. С этого сервера, соответственно, получатель осуществляет его прием.

Что происходит, если второй сервер не доступен? Ничего особенного, т.к. MSMQ на первом сервере оставляет данные у себя и складирует их. Когда работа второго сервера восстановлена, требуемые данные направляются ему. Процедура происходит достаточно быстро и просто.

С точки зрения обработки данных, требуемого application-сервера нет. Либо мы должны своими руками создавать WCF и запускать его через Task Scheduler Windows. Либо придется делать наоборот: через Windows Service, в котором будет Timer. Если у нас 10 задач, которые мы должны сделать, должно быть 10 Windows Service + Timer.

Мы решили создать свой Task Server. Это Windows Service, у которого имеется набор задач. Он запустил одну задачу, вторую, третью... В общем используется расширенный Windows Service + Timer (с доменами и прочим).

Когда вы видите рекламу автомобиля, то думаете: "Я сейчас пойду и куплю ее за 800 тысяч, как написано в рекламе". Вы приходите в салон и понимаете, что на самом деле хотите не ручную, а автоматическую коробку передач. На улице еще зима, и вам пока нужны зимние колеса. Вы покупаете ее в кредит и вам нужно еще оформить КАСКО. Это уже совсем другие деньги.

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

Что такой NAS? Он нужен, чтобы хранить статический контент, конфигурацию IIS и другие параметры конфигурации. Также требуется резервное копирование. Естественно, оно должно быть отказоустойчивым и в достаточной степени масштабируемой. Мы используем Microsoft Distributed File System. Когда у нас несколько серверов, и мы на них настраиваем общую файловую систему, то запись производится в один сервер, а он реплицируется на все остальные. В итоге все достаточно хорошо работает.

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

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

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

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

Для тестирования бизнес-процессов (например, в плане доступности сайтов, сервисов, почты и т.п.) используются Web Services + Remstats. Это внутренняя разработка, которая "дергает" страничку. Если она отдает код "500", мы должны что-то сделать. Если это какой-либо другой код, мы показываем предупреждение, отправляем SMS и т.д.

В последнее время мы тестируем SCOM. Это решение от "Microsoft" - бывший MOM. Возможно, мы перейдем на него. Он нам пока кажется существенно лучше, чем WhatsUp.

У нас есть балансеры в виде NGINX, application-сервера, на которых распределяются запросы, базы данных, сетевое хранилище, домен-контроллеры и сервисные машины, которые в действительности находятся на виртуальных машинах.

В одном случае домен-контроллеры не требуют ни процессора, ни памяти. В другом случае AppFabric Cache требует большое количество памяти, но процессор ему не нужен. В третьем случае имеются сервисы (Background worker), которые требуют и того, и другого, и третьего. Соответственно, они все хорошо уживаются на виртуальных машинах. Есть сервис, который тестирует снаружи, есть сервис, который тестирует внутри.

И все-таки почему мы используем .Net? На это имееттся несколько причин. У нас есть достаточный опыт создания крупных проектов уровня "Афиши". Это "Eda.ru" и "Mir.travel". Кстати, на "Mir.travel" коллеги используют geo special data для хранения всего дерева географии и осуществления необходимых выборок.

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

Кроме того, работаю на .Net, мы получаем удовольствие.

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

В частности это решение, которое я здесь вам демонстрирую. Перед запуском мы тестировали нагрузку. Мы получили 2000 rps в секунду на один application. Это очень легко масштабируется. До 10-ти тысяч – это без проблем. Мы тестировали до 10-ти тысяч rps в секунду – оно держало, все хорошо. Но и этот результат ничего не значит.

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

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

Спасибо за внимание!

Вопросы и ответы

Реплика из зала: Вы говорили про 15 миллион в день, 2000 миллисекунд - отдача. Расскажите подробнее о пиковых нагрузках.

Дмитрий Паньшин: Реальная нагрузка была порядка 1000, к в секунду. Сейчас это два application-сервера.

Реплика из зала: 1000 к? То есть миллион запросов в секунду?

Дмитрий Паньшин: Нет. Просто тысяча - один К.

Реплика из зала Тысяча запросов в секунду. 200 миллисекунд – это время у пользователя или время отдачи сервера?

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

Реплика из зала: Сколько серверов (а в идеале, какова стоимость лицензий) потребовалось на это дело?

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

Реплика из зала: Но сколько хотя бы серверов?

Дмитрий Паньшин: Application – это два фронта. Это и есть application-сервера, которые… А также два NGINX, которые стоят, балансируют – но это так, в нагрузочку.

Реплика из зала: Когда Вы рисовали картинку по поводу отказоустойчивости еще на IIS, вы, по-моему, перепутали ОВ и HR чуть-чуть. У вас там нарисована картинка для outbalansing, а слайд называется "высокая доступность". Там распределяются пользователи по кластерам, а не выбирается, какой из них "упа"л и "приклеивается "на второй. Это так, мелочь.

Дмитрий Паньшин: Может быть, может быть. Спасибо.

Реплика из зала: .Net, "Microsoft" – NGINX как-то не очень в эту среду вписывается. Наверное, у нас, как у многих, нет опыта работы с NGINX. Сколько инвестиций, времени, разработчиков нужно, чтобы "прикрутить" NGINX?

Дмитрий Паньшин: Если честно, нам это досталось бесплатно, потому что компания – "Афиша-Рамблер", а в "Рамблере" все написано (большинство написано) на .NIX технологиях. Игорь Сысоев долгое время работал в "Рамблер", соответственно, там везде стоит NGINX. Опыт настройки, эксплуатации – тоже бесплатно.

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

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

Реплика из зала: А на .NIX запущен? Не под Windows.

Дмитрий Паньшин: Да.

Реплика из зала: Спасибо за доклад. Вы рассматривали вариант использования Mono и полностью переноса системы на .NIX решения?

Дмитрий Паньшин: Нет.

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

Дмитрий Паньшин: Это сложный вопрос. У нас есть большой опыт работы уже с Windows-серверами, с Windows-платформой, и она нас устраивает. Если честно, я не вижу смысла их заменять. Если вы мне объясните, то отлично.

Реплика из зала: Разница в цене.

Дмитрий Паньшин: Может быть. Но там еще цена ее поддержки, развития и так далее. Цена в том, что вы не можете использовать .Net 4.0.

Реплика из зала: Нет, .Net 4.0 уже можно использовать – Mono 2.10 отлично держит все.

Дмитрий Паньшин: Я не уверен, что там все поддерживается.

Реплика из зала: Какой объем логов в сумме в записях? Или сколько гигабайт сервера "падает" в сутки?

Дмитрий Паньшин: Если возникло достаточно большое количество ошибок, то за секунду может...

Реплика из зала: А вы только ошибки логируете?

Дмитрий Паньшин: Нет. Я имею в виду, что это зависит от времени. И там может…

Реплика из зала: Порядок. Нормальный честный стабильный продакшн. Миллион, 10 миллионов, 100 миллионов в сутки?

Дмитрий Паньшин: Порядка гигабайта логов в сутки.

Реплика из зала: Гигабайт их все-таки нормально складывается в базу и там при этом еще…

Дмитрий Паньшин: Да.

Реплика из зала: А за какой промежуток времени хранится информация в базе данных?

Дмитрий Паньшин: Зависит от логов. Есть логи, которые мы храним неделю, некоторые храним месяц. Далее мы их агрегируем, складываем в базу и удаляем. Некоторые вещи мы храним дольше – таких данных меньше. Некоторые вещи мы храним всегда.

Реплика из зала: Как вы это "деплоите"? Вы останавливаете или у вас high available?

Дмитрий Паньшин: Все достаточно просто. Если мы говорим про два application-сервера, то один application-сервер мы условно "опускаем" и говорим NGINX: "Не отправляй к нам запросы". NGINX ждет, мы "деплоим". После этого мы, условно так скажу, "прогреваем", чтобы кэш наполнился, какой-то набор запросов дает на application, а потом его поднимаем. "Опускаем" второй, обновляем, запускаем.

Реплика из зала: Что делать, если SQL-скрипты нужно "накатить" на базу данных? Если изменилась база данных...

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

Реплика из зала: Я услышал хорошее слово "Mono". Не пробовали вообще портировать? Хоть запускать, не знаю, хоть как-то.

Дмитрий Паньшин: Нет.

Реплика из зала: А если вне проекта, в личном использовании?

Дмитрий Паньшин: Не знаю зачем, но, может быть, стоит попробовать.