MCP
Показаны сообщения с ярлыком программирование. Показать все сообщения
Показаны сообщения с ярлыком программирование. Показать все сообщения

вторник, 9 апреля 2019 г.

Странная реализация HttpClient в .NET Core

Представьте что у вас есть приложение. Не очень маленькое, но и не гигантское, работает помаленьку развивается, и тут вы совершенно случайно замечаете в мониторинге, что один из сервисов жрёт 25% CPU. Хм... вроде многовато, но мало ли что. Может боты набежали на сайт и смотрят все странички, или пользователи ходят. Да и 25% немного, запас огромный (400% максимум). Но на всякий случай сервис перепускается и загрузка падает до 1%. Вы думаете — какой-то глюк, но через некоторое время загрузка процессора растёт опять.

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

Когда я в очередной раз сдался, я не выдержал, и подцепился дебаггером к боевому серверу напрямую. И... ничего не понял, всё выглядит нормально. Отцепился — загрузка 0%. Тут у меня уже задёргался глаз. Через некоторое время повторил — да, подключение дебаггера явно лечит проблему, как и перезапуск приложения. Но это ведь не выход и не решение. А вдруг это какая-то проблема у нас, и если в этом приложении данную проблему можно замаскировать, то она может выстрелить где-то ещё. Так что пришлось разбираться дальше.

Неожиданно удалось нагуглить данный тред на GitHub'е. Проблема у Microsoft'овской библиотеки и связана она с HttpClient. Утверждалось, что нельзя его создавать на каждый HTTP запрос, а надо использовать статичный. Правда в документации есть пример использования:

В котором его заворачивают в using. Я попробовал покопать в данном направлении и выяснил первую забавную вещь: использование статичного HttpClient в .NET Core быстрее раз в 5, чем создавать его на каждый запрос. Но тут нюансы с самим временем запроса, но смысл в том, что уже есть причина использовать статичную версию. При этом под "большой" .NET разницы нет никакой.

В общем, выложил новую версию приложения, но проблема осталась. (может я не туда копаю?). Попробовал переключиться с .NET Core 2.1 на 2.0 — проблема ушла. Т.е. проблема есть только под core, под Linux и версия должна быть больше чем 2.0 (более свежие не проверял). Читаем документацию и видим, как разработчики Microsoft, светясь от гордости, рекламируют новую реализацию Http Client Factory, через которую и идут все запросы (не может Microsoft просто написать библиотеку, делающие HTTP запросы, внутри обязана быть магия!). В общем теперь всё managed, в 10 раз лучше и в 50 раз быстрее. Ага, и сделано как раз в 2.1

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

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

Получается совершенно странная ситуация: Disposable-класс, который надо использовать синглтоном. При том, что все предыдущие реинкарнации подобных классов были честными Disposable-объектами. Но тут решили сделать по-другому. Зачем? Я пока не понял. Но свинью подложили знатную. Представьте, у вас есть базовая библиотека, написанная под большой .NET, вы её мигрируете на Core, пользуетесь под Windows, всё нормально, а потом, спустя длительное время замечаете, что приложение жрёт много процессора. Вам нужно будет пройти квест, подобный моему, чтобы найти проблему, переписать библиотеку и весь остальной код, обновить всё что нужно и всё из-за странного архитектурного решения разработчиков из Microsoft.

вторник, 27 февраля 2018 г.

Хранение данных в памяти

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

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

Почему не стоит полагаться на кеш SQL-сервера?

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

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

Что это даёт?


  • Резко упрощается внешняя логика пользователя. Во многих случаях можно отказаться от джойнов, если нет дополнительной фильтрации. Например, классический джойн с данных с пользователем (например, вывести автора данных) можно заменить на выборку данных и доставание всех пользователей по ID. Это очёнь дешёво
  • Можно использовать очень тупые алгоритмы, и это всё равно будет очень быстро. Фуллскан 10000 записей почти незаметная вещь, а если надо будет ещё быстрее — всегда можно будет прикрутить "индекс" в виде словарика
  • Можно избавиться от отдельного кеша для тяжёлых данных, ибо и так всё в памяти
  • Тупо быстрее из-за отсутствия запросов к внешнему SQL-серверу
  • Легко хранить данные, которые тяжело забирать из SQL (объект с зависимыми детишками, слабо-структурированные документы, JSON/XML поля)
  • При этом, если приложение написано с использованием LINQ, то местами можно вообще не заметить разницы между базой и памятью (если грамотно спланировать архитектуру приложения)

 А в память влезет?

А вот это как раз главный вопрос, который всех останавливает и которого все боятся. И из-за этого, всё так и останавливается на уровне идей. Но давайте прикинем необходимое количество памяти.
  • База данных в 20ГБ содержит где-то 20ГБ данных (логично, да?), в памяти это будет занимать примерно столько же. Найти подходящий сервер — не так уж и сложно. Естественно, базы в 100МБ вообще влезут в память без проблем
  • Очень часто в "больших" базах большой объём занимают всякие полумёртвые данные — журналы, результаты импорта, файлы, подписанные данные, акксесс логи... Эти данные нужны очень редко, их можно не хранить в памяти, тем самым кардинально снизив объём "реальных" данных
  • Многие данные нужны только в небольшом количестве. Например, у вас есть 10000 пользователей в системе, но активны только 1000, тут можно использовать какой-нить LRU кеш и не держать в памяти все объекты, а только активные. Опять же, очень сокращает необходимый объём памяти
  • Ну и для реально огромных баз данных можно уж держать в памяти только специально выделенные объекты (например, справочники). Хотя, с такими объёмами у вас будет проблем побольше чем просто держать в памяти

Как реализовать?

Поскольку я ещё не делал это в полном виде, то могу только предположить следующие варианты:
  • Собственный кеш класса, ратающего с сущностью (e.g. UserManager), он сам решает, что и как кешировать. Проблемы в куче аналогичного кода в разных классах и сложность с инвалидацией. Плюсы: в каждом конкретном случае можно использовать самые эффективные варианты
  • Мемоизация и автомемоизация методов. Плюсы: очень упрощается код, минусы: сложно инвалидировать и оптимально использовать данные. 
  • Обёртка над ORM (или использование встроенных средств типа Second Level Cache), которая сделает всё сама. Проблемы: сложно в реализации и конфигурировании. Плюсы: полная прозрачность в использовании со стороны кода

Краткий итог

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

понедельник, 27 ноября 2017 г.

Ненавистный .NET

Последнее время совсем не пишу в блог, как-то нет подходящих тем, могу только сообщить, что такой ненависти к Microsoft я давно не испытывал. Попытка поработать с .NET Core 2.0 сразу же привела к идиотским ошибкам, типа 2 entry point у приложения. При этом второй генерируется самостоятельно (!), другими словами, у Microsoft новые отличнейшие идеи, как всё должно работать по их мнению, вместо того, чтобы просто сделать рабочий продукт.

Если кончится мат и появятся слова, постараюсь написать что-то более членораздельное. Но, блин, у Microsoft был отличнейший .NET, приложения на котором просто работали... Теперь не так, они могут падать по совершенно различным причинам, а Dll Hell уже перешёл все границы. Ну, как, как можно так портить жизнь разработчикам за их же деньги...

пятница, 24 февраля 2017 г.

Немного про оптимизацию

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

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

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

Давайте приведу пример. Есть у нас список пользователей в системе, и нужен классический CRUD с аццким уклоном в R, ибо пользователи достаются на каждый запрос а меняются всё-таки пореже. Логичный способ улучшить производительность — кеширование. Но чтобы удобно всё кешировалось, работа с пользователями должа быть сосредоточена в каком-нибудь UserManager. И тут сразу вылезает проблема классических приложений, которые, доставая данные, джойнят данные с пользователями (автор, ответственный, владелец). Это просто и удобно, но очень сильно рушит идею кеширования — в нём теряется множество смысла, ибо всё равно идёт запрос в базу.

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

Что с этим делать? Я уже расписал:

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

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

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

суббота, 31 декабря 2016 г.

Итоги моего 2016 года

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

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

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

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

В свободное от работы время приобщаюсь к Github'у и Nuget'у. В феврале сделал небольшую, но очень быструю библиотеку для копирования объектов: DeepCloner, потом разошёлся и сделал одну из самых быстрых (или самую быструю) библиотеку для вычисления CRC32: Crc32.NET. Вообще, началось с того, что мне понадобилась реализация CRC32C, а т.к. автор что-то медленно правил свою (версию для плюсов он уже обновил, а вот .NET никак не может, хотя спустя полгода таки принял мой pull request). Ну и в общем, раз я взялся за CRC32C, то надо было поглядеть на обычный CRC32. На нюгете и гитхабе проекты оказались неушстрыми и полузаброшенными. Пришлось сделать свой.
Ну и в конце-концов, я допилил свой архиватор до рабочего состояния, называется он красиво: Blazer. В прицнипе, нужно его повылизывать и попилить, но в целом там уже достаточно клёвых фич, типа сжатия с шаблоном. Ну и большинство фич сосредоточено в библиотеке, а не в консольном экзешнике.

Ну и ещё научился находить плавающие баги в коде, сидя в гостинице в Москве с ноутбуком. Хотя фактически несколько часов искал их с помощью головы. Оригинальные впечатления.

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

четверг, 15 декабря 2016 г.

Краткий анализ .NET Core/Standard/Framework

Тут в очередной раз разбирался с тем, что в Microsoft нагородили с .NET, пока окончательно не понял, но в целом, чтобы не пересказывать основные статьи про всё это дело, просто сообщу, то что я понял:

  • Есть .NET Core, это специальный кроссплатформенный фреймворк
  • Есть .NET Framework, это классический .NET, работающий под винду
  • Есть .NET Standard, это набор API, который гарантированно будет работать в .NET Core и .NET Framework (ну и всяких ксамаринах)
  • Код, изначально написанный на .NET Framework, не будет работать под .NET Core
  • Если есть желание писать под .NET Standard, чтобы работало под большим .NET Framework, то лучше не делать так. Ибо работать будет, но плохо.
  • Потому что вся магия в том, что API похожее, но разное. 
  • Другими словами, версия под .NET Framework будет использовать очень похожий, но отдельный набор API. Если он весь скрыт внутри. то ничего страшного, никто не увидит. Если же вылезает наружу, могут быть неприятные последствия.
  • Ещё раз уточню, что если написано, что Standard 1.2=Framework 4.5.1, то это не значит, что код, написанный под 4.5.1 будет работать на уровне 1.2. Это просто значит, что если написать код, под 1.2, то его с помощью бубна можно заставить работать на 4.5.1. При этом набор API в 1.2 особо нигде не расписан, например, там нет криптографии. Странно, но вот так.
  • Судя по всему, самая популярная версия .NET Standard — 1.3, что в ней такого особого, пока не разобрался
Т.е. в целом ситуация складывается странная. Если хочется разрабатывать универсальные вещи, то лучше тупо не думать, а разрабатывать под .NET Core, забив на .NET Framework полностью. Потому что поставить .NET Core не сложно, а существущий код под .NET Framework всё равно придётся переделывать, так что, смысла во взрослом фреймворке на мой взгляд немного.

PS: Возможно, после дальнейшего знакомства со всем этим делом, моё мнение изменится, тогда обновлю пост. Но пока есть ощущение ужасной неразберихи и путаницы в API, чего стоит только набор версий фреймворка 4.5, 4.5.1, 4.6, 4.6.1, 4.6.2 — надо долго гуглить, чтобы найти отличия, но при этом для каждого из них, есть своя версия .NET Standard, но, поскольку, скоро будет .NET Standard 2.0, использовать .NET Standard 1.5 и 1.6, не рекомендуется из-за проблем с совместимостью. Ещё раз, стандартная версия фреймворка, сделанная для совместимости, будет несовместима сама с собой. А нам с этим жить...

воскресенье, 28 августа 2016 г.

Моя месть node_modules

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

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

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

В общем, нарисовал я проект с гениальным называнием nmisf (node modules in single file). Не буду переписывать его документацию, просто скажу, что вам надо один раз запустить создание бандла и папка node_modules превратится в один файл, в котором будут только нужные файлы, да ещё и без дублей (а с учётом зависимостей зависимостей зависимостей, это актуально). Ну и рядышком ещё один файл для индекса. В итоге, два файла вместо толпы. Что положительно сказывается на времени копирования, ну и количестве хлама на диске.

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

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

воскресенье, 7 августа 2016 г.

Мегафичи моего архиватора

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

Полное шифрование без раскрытия информации

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

Управляющие блоки 

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

Восстановление данных (не сделано)

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


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

воскресенье, 29 мая 2016 г.

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

Я тут давно начал "писать" архиватор. Ну, т.е. как начал, сделал самое интересное, а допинать до конца терпения не хватило. Точнее почти хватило, но меня не устроили результаты, а, как известно, первая заповедь перфекциониста-прокрастинатора: лучше сделать хорошо, но никогда, чем плохо, но сейчас!

В общем, я полтора года не трогал свой архиватор, а тут решил, что всё-таки стоит пожертвовать 0.2% степени сжатия и 30МБ/c скорости, и выбрать простую и дубовую реализацию, так что. архиватор почти дописан, осталось сделать последний рывок и вычистить все баги, и добавить лоска. Впрочем, про сам мой алгоритм расскажу как-нибудь в другой раз.
Для начала расскажу про одну небольшую проблему, из-за которой, я начал писать архиватор, а не тупо взял готовый. Проблема весьма специфическая, но если вы в неё встрянете, то будет плохо, очень плохо.

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

Но ситуация может быть ещё хуже, архиваторы могут тупо проглатывать команду Flush (передаю привает реализации GZip в .NET, да она проглатывает Flush)! Что это значит? Это значит, что в некоторых задачах вы в принципе не можете использовать данную реализацию.

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

В качестве тестовых данных, дамп википедии в 100 Мб (один из стандартных шаблонов для сжатия). По оси X - размер блока, который флушится, по оси Y - степень сжатия.
Что видно? ЧТо на блоке в 16 байт накладные расходны на заголовок превысили все ожидания (только GZip рулит за счёт мелкого заголовка). В дальнейшем, всё становится лучше, но до блока в 16Кб счастья особого нет. Т.е. сжимается, что уже неплохо, но могло быть и лучше.

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

Результаты те же, только более явно выражены. И эта явность как раз и намекет, что 64Кб — это таки нашё всё. А лучше больше. Тот же график, но у него подрезано начала, чтобы был лучше виден масштаб:

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

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

Правда на блок уходит 8 лишних байт... и это меня бесит... Ждите следующий пост через пару лет...

воскресенье, 17 апреля 2016 г.

ZeroMQ vs NetMQ

Последнее время не спеша изучаю ZeroMQ, а так как пишу в основном на .NET, то решил сравнить производительность двух реализаций, "официальной" clrzmq4 (в nuget'е пакет называется ZeroMQ, и все примеры с сайта на нём), и альтернативный вариант NetMQ, написанный полностью на .NET и совместимый с ZeroMQ.

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

Для самого простого варианта я взял Publisher/Subscribe модель и начал закидывать сообщения по-кругу, и смотреть на результат. Результат оказался слегка неожиданным, но красноречивым. Не буду делать графики, ибо и без них всё понятно. Просто некоторые пункты:

  • clrzmq4/NetMQ одинаково быстры (около миллиона сообщений в секунду) при работе с массивами байт
  • Два паблишера на одного сабскрайбера (странный вариант, ну да ладно), гораздо быстрее работают у NetMQ 
  • При попытке работы со строками clrzmq4 начинает резко сливать производительность (в 3 раза). Судя по-всему, не очень удачная работа с маршаллингом
  • С clrzmq4 надо быть очень аккуратным, пропущенный Dispose даст прирост в скорости, но может вызвать неприятные ошибки
В общем, тестировать производительность дальше неинтересно, не думаю, что там есть какая-то волшебная разница на разных паттернах, с учётом того, что у NetMQ гораздо приятнее API, рекомендую рассматривать в качестве реализации API сразу же его и не париться с выбором.
NetMQ — быстрее и удобнее.

среда, 20 января 2016 г.

Не верьте бенчмаркам. Все врут!

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

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

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

Взял простенький массив:
В этом массиве есть числа, даты, строки разных видов, энамы и нуллабельные поля. Т.е. полный фарш. Набрал всяких сериализаторов и пошёл сериализовать. Результаты следующие:
.net json serializers benchmarks
Как видно, майкрософтовский в полной жопе, за ним идёт сериализатор с говорящим названием fastJSON (понятие не имею, что это, может где-то он действительно быстр), И затем... ServiceStack! На 66% медленнее JSON.NET Вот и хвалёные тесты.

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

PS: Моим бенчмаркам тоже не верьте. 

четверг, 31 декабря 2015 г.

Про велосипеды

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

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

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

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

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

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

воскресенье, 15 ноября 2015 г.

Чем мне не нравятся async/await

Тут на днях у нас вышел спор с коллегами по поводу async/await в Microsoft .NET. Мне эта технология не очень нравится, несмотря на её круть, но сформулировать причины сходу весьма непросто. В этом посте попробую объяснить, в чём же проблемы (естественно, это моё мнение, и оно может не совпадать с генеральной линией партии).

1. Это не про потоки, это про асинхронность в одном

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

2. Неявная точка асинхронности

Когда мы вызываем асинхронный метод, мы не знаем, когда он реально перейдёт в асинхронность. Другими словами, возьмём стандартный подход: 
Task.Factory.StartNew(DoSomething);

Сразу после вызова данного метода, мы можем продолжать работу. А теперь глянем на async/await:
var t = DoSomething();
..........
await t;

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

Вся эта неявность приводит к следующей проблеме:

3. Легко завалить производительность, при этом всё будет работать

Как я уже указал, данная технология про асинхронность. Соответственно всё должно быть асинхронно, всегда и везде. А что будет, если мы где-то забудем асинхронность? Или не мы, а тот кто написал библиотеку для нас. Может быть он не очень хороший программист и допустил маааленькую ошибку и сделал операцию синхронной. Как вы думаете, что будет? Да ничего фатального! Просто пользователь будет ждать. Например, один await не в том месте, привёл к тому, что HTTP-proxy на async/await стала обрабатывать все запросы от пользователей по очереди. И это работало год. Заметили случайно, при генерации отчётов сайт переставал открываться. А всему виной — ошибка в коде, про который все забыли и который с точки зрения логики, работал замечательно.

Другие примеры, чтобы понять масштаб проблемы: делаем запрос по HTTP, и перед запросом решили порезолвить DNS, синхронно (ну не нашли асинхронный метод). Можем получить 2 секунды тормозов на ровном месте. Использовали бы явную асинхронность вызовом метода в отдельном потоке — проблем не было бы. Ещё вариант — логгер, давно написанный, отлаженный, пишет в файлик, асинхронной версии не имеет, никому не нужна. Всё работает годами. Потом кто-то на том же интерфейсе решил запилить логгирование в базу, результат — пока один пишет, все ждут (у нас ведь синхронная версия). Провал производительности.

Хотя про то, что всё будет работать, я погорячился. Когда писал этот пост выяснил, что легко получить Deadlock из-за повторного входа и контекстов синхронизации. Не буду пересказывать статью, там на третьем примере всё достаточно просто и понятно рассказывается. 

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

4. async/await заразные


Чтобы заиспользовать его, методу надо указать что он async. Если метод async, то вызывающий его метод должен использовать await и самому стать тоже async.
Т.е. весь код резко начинает обрастать async'ами, как снежный ком. Вырваться из данной западни можно двумя способами: async void (не используйте никогда!) или явным хаком с вызовом Wait у таска, возвращаемого async методом (что может привести к некоторым проблемам с контекстом синхронизации).
В общем, для нормального использования async'ов, у нас должна быть вся библиотека, написанная на них, которая использует другие библиотеки, написанные тоже на них. Надо писать сразу весь код в этой концепции, добавление небольшого куска не даёт никакого профита. А раз это концепция, то хотелось бы явно выделять её. Например, я вызываю метод DoSomething(), в зависимости от того, async он или нет, при вызове будут абсолютно разные вещи. Но без поддержки IDE я никогда не у знаю об этом. Если я добавлю к сигнатуре async, позднее, код скомпилируется и будет работать как-то, но о проблемах я узнаю по факту, а не во время компиляции.

И это косвенно подводит нас к очередной проблеме

5. Reflection и кодогенерация

Reflection должен знать про то, что методы бывают async, т.к. от этого зависит логика их работы (банально проброс эксепшена о котором я уже упомянул вначале). Оверрайдить данные методы кодогенрацией (моё любимое занятие) тоже надо аккуратно, ибо просто для понимания, вызов метода:
public static async Task DoSomething() { Console.WriteLine("A"); await Task.Delay(1000); }

По факту превратится в что-то подобное:
 А реальная логика будет запрятана далеко, и будет выглядеть как-то так:
Согласитесь, не самый простой для понимания пример.
А так как все эти async/await сделаны на уровне языка, а не MSIL, то среда ничего не знает про это, и тем же рефлекшеном достать это сложно. Что в связи с неявной точкой асинхронности может резко менять логику.

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

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

Выводы

На мой взгляд, данная технология похожа на внутренности Perl'а. Для тех, кто не знает этот чудесный язык, поясню: смотришь примеры, видишь как всё просто и круто. Начинаешь углубляться в язык, чтобы понять как это работает и с какого-то момента становится очень страшно. Ты понимаешь огромную магию внутри всего этого. Ты понимаешь, что неверное движение может всё поломать, а работает только благодаря умным людям, которые написали кишки и программистам, которые не суют нос, куда не надо. И лишь спустя длительное время ты становишься таким умным человеком, и тебе становится всё понятно. Тебе спокойно. Так вот, я с async/await не стал таким умным человеком, но мне страшно, я уже могу всё сломать и я боюсь что кто-то из моей команды тоже может всё сломать, и не поймёт этого. И я не пойму. В результате всё будет работать не так как надо, а мы не будет знать, в чём проблема. А чтобы узнать, не достаточно статей "Async и await для чайников". Надо действительно погружаться в технологию. А оно мне не надо. Я не пишу приложения под Windows 10, а больше смысла-то и  нет, хотя Microsoft толкает её в новые библиотеки, вполне можно прожить и без неё, без этого странного волшебства и магии.

понедельник, 7 сентября 2015 г.

knockProxy

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

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

Прямой доступ

Неплохой вариант, простой для использования, но есть нюансы. Первый, нельзя добраться до машин за NAT'ом, но это можно решить пробросом портов на шлюзе. Второй, более существенный, открывать доступ снаружи даже к одному сервису весьма неприятно в плане безопасности. Неверная настройка сервиса или security issue в нём, и твой компьютер уже как бы не твой. Ну или взять RDP в домене — можно не спеша перебирать логины и пароли всего домена, и если повезёт с одним каким-нить забытым тестовым логином, внутрь уже залезает злоумышленник. Так что этот способ отпадает

VPN

Отличный способ, постоянно использую. Положительных качеств вагон и маленькая тележка: работа с NAT'ом, видна вся сеть, сжатие и шифрование трафика при желании. Но есть один недостаток: сложная настройка. Например, самый простой для системы PPTP не работает на домашнем интернете от Билайна (ибо у них тоже PPTP, а PPTP over PPTP это слишком сложно). А универсальный OpenVPN требует телодвижений по настройки как на клиенте, так и на сервере. Одного логина с паролем не хватит. Так что хотелось бы что-то более простое.

IPSec

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

Отдельная программа для связи

Например, что-то типа TeamViewer. Платно и неспортивно.

Свой велосипед

Моё любимое занятие. Ибо точно знаешь что программа умеет, что нет, и какая область применения.
Итак, был написан свой велосипед knockProxy, на node.js, лежит на ГитХабе. Я думал следующим образом: самый удобный вариант, это прямой доступ, но напрямую доступ давать нельзя. Так будем его выдавать после ввода логина и пароля (отдельного, чтобы сузить область возможной атаки). Бонусом, дадим несколько логинов для разных пользователей.

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

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

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

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

Для тулзени выбрал лицензию MIT, для работы нужен node, и какой-нить сервер с мордой наружу (нужен прямой открытый TCP порт). Веб-часть может при необходимости жить через proxy, хотя больше любит напрямую. Работает и под *NIX и под Windows. Наверное и под маком может жить. Так что универсальность полнейшая.

В общем, прошу любить и пользоваться.

воскресенье, 30 августа 2015 г.

Проблемы REST API или Яндекс тоже лажает

Вчера заметил лажу Яндекса в реализации REST API и решил немного высказаться про идеологов правильного использования REST.

Под данными идеологами я понимаю людей, которые месяцами выбирают HTTP-метод (PUT или PATCH? Сложный выбор!), вылизывают урлы (так, /user/add/group или /group/add/user, или POST /, а остальное данными?), при этом по факту никому их API никогда не нужно, а если и нужно то используется через врапперы и библиотеки.

Красивые урлы и семантика, конечно, важна, но проблема в том, что REST работает через HTTP со своими заморочками и ограничениями. Например, в GET нельзя закидывать body, и надо пытаться засунуть всё в URL, а если не влезает, то сделать POST, получить ID запроса, и сделать GET. Идиотизм. зато семантично.

Или моё любимое: по стандарту GET-ответы кешируются, если не сказано иное (хотя хрому наплевать на это). Звучит разумно, например, мы получаем текущую фазу луны, никаких параметров, она всегда одинаковая, хотя... стоп... Давайте что-нить попроще, например количество входящих сообщений пользователя... блин... Пол пользователя! Во! Главное, чтобы он к врачу не сходил. В общем, если умерить сарказм, то получается, что GET-запросы для API практически никогда не надо кешировать, ибо хоть они и получают данные, но те данные имеют тенденцию меняться в самый непредсказуемый момент.

Решений для убирания кешироания несколько:
  • С сервера посылать запрет кеширования. Работает, но несемантично
  • К каждому запросу подсовывать рандомную строку. Работает, но выглядит ужасно и по факту засираем кеш браузера
  • Переделать API так, чтобы в нём участвовала рандомная строка. Например, дать количество сообщений пользователя на определённую дату. Пофиг, что кроме текущей мы ничего не можем сделать, зато прикрутили параметр
  • Делать POST, а потом GET - очень глупо, медленно, неудобно, но семантично
В общем, вместо того, чтобы тупо сделать всё на POST'ах, мы активно боремся с инфраструктурой. И подобные проблемы периодически возникают то тут, то там. И борьба с ними идёт в основном ради красоты.

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

Собственно, а что с Яндексом? Дело в том, что недавно они написали умную статью, о том как работать с HTTP, типа учитесь как правильно. Ок, поучился. Захожу на internet.yandex.ru и вижу картину следующего вида:
Естественно, со временем всё было в порядке. Просто я обновил данную страницу в браузере, а запрос о времени, сделанный по всем правилам Яндекса по урлу http://yandex.ru/internet/api/v0/datetime/ не имел никаких параметров кеширования. Что привело к тому, что браузер честно отдал ответ, который он запомнил 6 часов назад.
Как видите, Яндекс умеет делать красиво, рассказывать всем как правильно, только выкатывает в итоге нерабочий сервис.

UPD: Увидел ссылку, которая описывает архитектурные проблемы REST чуть в другом ключе, но пересекается с данным постом и я с ней в целом согласен. Также, могу сказать, что в одном из проектов я использовал JSON-pure API, и с ним не было никаких проблем. Данный код до сих пор бродит по разным проектам и не вызывает нареканий.

четверг, 23 апреля 2015 г.

Ещё одна проблема HttpWebReqest

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

Для удобства распишу по условным пунктам. Для начала диспозиция:
  • Сервер возвращает куки в Http-заголовке Set-Cookie
  • Если надо установить две куки, сервер передаёт два заголовка (не очень логично, но ок)
  • HttpWebResponse имеет пропертю Headers, и по имени заголовка можно получить значение
Тут начинаются проблемы, ибо куки две, а заголовок один. Как вы думаете что вернётся? Не буду томить, скажу, что вернётся в этом методе содержимое двух кук через запятую. Очень, блин удобно. Считаем, что куки у нас разломаны в этом виде.

Но! У Headers можно взять значения через .GetValues(), вернётся честный массив из двух элементов. И вроде бы всё хорошо, и пост можно жакончить, но тут приходит сервер, и выдаёт нам:

Set-Cookie: ABC=123; expires=Fri, 31 Dec 2010 23:59:59 GMT; path=/;


Вы заметили, что между пятницей и 31-ым числом есть запятая? HttpWebResponse тоже заметил, и вместо этой куки честно вернул нам две, обе разломанные. Всё, приехали.

Но обойти это надо, поэтому можно сделать следующие вещи:
  • Вручную распарсить значения кук, зная, что запятая, по стандарту, запрещённый символ. Т.е. встретится она может только в expires
  • Взять у HttpWebResponse пропертю Cookies, с уже обработанными куками
Вроде второй вариант самый правильный и логичный, за исключением случаев, когда вам хочется посмотреть на изначальные данные от сервера, а не обработанные странным кодом. Но чтобы он работал, надо обязательно, у HttpWebReqest установить пропертю CookieContainer, иначе вам вернётся пустой массив в респонзе.

request.CookieContainer = new CookieContainer()

Немного нелогично (нам ведь только ответные нужны), но в принципе допустимо. И всё из-за весьма странной реализации работы с заголовками.

На этом всё, буду ловить очередные грабли данного класса. 

воскресенье, 15 июня 2014 г.

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

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

Начнём по-порядку.
  • zlibnet — считает метод Flush сигналом для записи финального блока, что приводит к некорректному поведению. Надо чинить (а лучше переписать самому, где там мой блокнотик?)
  • SharpCompress — любитель вызывать метод записи с длиной в 0 байт, что, имхо, весьма неаккуратно. Может на некоторые потоки создавать бессмысленную нагрузку
  • SharpZipLib — никаких нареканий
  • Встроенный GZip — буфер в 8Кб, полностью игнорируется Flush, оказывается для потоков его использовать нельзя.
  • Snappy — блочный алгоритм, блоки с префиксом и контрольной суммой, что накладывает отпечаток на возможностях
  • Snappy for .NET — нет реализации стримов, пропускаем
  • Snappy.NET — блок прибит гвоздями в 64Кб, Flush вызывает запись независимого блока. Т.е. частый флуш приведёт к потере сжатия и увеличению объёма
  • LZ4 — тоже блочный, при уменьшении блока резко падает степень сжатия, поведение аналогично Snappy. В реализациях (lz4-net и LZ4.NET) проблем не обнаружил (кроме самого факта с проблемами потока). Из хороших новостей — потоковое API для LZ4 разрабатывается (прямо в тот момент, как я пишу этот текст). Оно ещё не стандартизировано, но шансы есть, что будет всё круто.
  • LZF — я его достал с дальнего ящика и смотрю на него. Его особенность, что сжатие позволяет писать данные хоть по байту (и читать сжатые по байту). Правда исходные данные пока выглядят блочными, но думаю это можно будет поправить, если поисследовать алгоритм. 

суббота, 14 июня 2014 г.

Выбор быстрых алгоритмов сжатия под .NET

Для начала, пара таблиц для привлечения внимания:

Быстрый компьютер:
MemCopy:         1561.050       1709.7218        100.000%
GZipStd:           66.736        221.6318          6.335%
#ZipLib.Gzip:      52.800        136.0018          6.358%
zlibnet:          100.572        466.2888          6.500%
SharpComp.GZip:    52.568        154.7598          6.501%
Snappy.Net:       970.382        944.8468         13.312%
SnappyforNet:     997.337       1795.2078         14.499%
lz4net/CC:        485.191       1122.0048         10.740%
lz4net/MM:        997.337       1158.1988         10.740%
lz4net/N:         535.883       1122.0048         10.740%
lz4net/S:         386.066        690.4648         10.740%
lz4net/HC:         42.794       1282.2918          7.751%
LZ4.Net:          997.337       1158.1988         10.896%
QuickLZ:          460.310        528.0028          8.032%
LZO_1X   :       1683.082       1561.0508         11.824%
LZF     :         272.001        398.9358         13.882%
Медленный компьютер:
MemCopy:          394,551        394,5518        100,000%
GZipStd:           18,006         50,4278          8,738%
#ZipLib.Gzip:      16,137         45,2198          6,358%
zlibnet:           31,086        105,6008          6,500%
SharpComp.GZip:    18,356         46,6898          6,501%
Fail Snappy.Net: Инициализатор типа "Crc32C.NativeProxy" выдал исключение.
SnappyforNet:     260,175        432,5808         14,499%
Fail lz4net/CC: Ссылка на объект не указывает на экземпляр объекта.
Fail lz4net/MM: Ссылка на объект не указывает на экземпляр объекта.
lz4net/N:         218,928        228,6898         10,740%
lz4net/S:         120,484        141,9148         10,740%
Fail lz4net/HC: Ссылка на объект не указывает на экземпляр объекта.
LZ4.Net:          234,668        274,0778         10,896%
QuickLZ:           60,445         65,0448          8,032%
LZO_1X   :        374,001        505,6928         11,827%
LZF     :          44,880         60,3438         13,882%

Это я тестировал различные реализации алгоритмов сжатия и их скорости. Столбцы: скорость сжатия в MB/s, скорость декомпрессии, процент сжатого текста, относительно исходного материала.

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

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

  • QuickLZ — проблемы с лицензией,
  • LZO — работает через P/Invoke, мутный враппер, какие-то косяки с дебагом, проблемы с 64 битами не ясно дальнейшее развитие, собственно, его высокие показатели в тестах отчасти связаны с ограниченностью функционала, из-за которого, тест оказался в более выгодном положении относительно некоторых других (я даже не уверен, что он стабильно работает, хотя то что работает хотя бы один раз, это точно, я проверил)
  • LZF — хорош как вариант микро-компрессора (собственно, весь код можно зафигачить в 200 строчек не сильно экономя, при этом результат вполне сносный. Но, если вы не специалист по алгоритмам, не очень рекомендую заниматься этим делом. Хотя, возможно идея довести код до ума, вполне неплохая (надо записать себе в блокнот "обязательно сделать в следующей жизни").
Также в алгоритме не приняли участие: BZip2, LZMA и PPMd (степень сжатия отличная, скорость настолько низкая, что даже ради научного интереса их тут не стоит использовать.

Некоторые алгоритмы вида классического LZ77, не были найдены под .NET, поэтому тоже их пропускаем.

Теперь детально разберу оставшиеся GZip, LZ4, Snappy.

Gzip

Собственно, самый известный алгоритм сжатия, использующийся поголовно везде (хотя правильнее сказать, что алгоритм — Deflate, а GZip — поток с дополнительной метаинформацией). Если вы будете использовать его — у вас не будет никаких проблем с совместимостью, так что он очень хорош в плане требования по памяти и работы в потоковом режиме.
Но с выбором реализации есть некоторые проблемы — если вы сравните две верхних таблицы, то увидите что GZipStd (я так обозвал встроенный в .NET) даёт абсолютно разные варианты. Хитрость в том, что до .NET4.5, реализация GZip в .NET была ужасная, и её использовать стоило только в одном случае — никогда. Сейчас всё изменилось, и если вы пишите под 4.5, то вполне стоит использовать этот вариант, если нет критичного требования по скорости.

Если нужна максимальная скорость, то используйте zlibnet, это P/Invoke wrapper, поэтому он работает весьма шустро. Если у вас нет желания использовать P/Invoke (чуть сложнее деплой и требуется больше прав приложению), используйте SharpCompress, он мне показался чуть более удобным, быстрым и функциональным относительно классического SharpZipLib. Есть ещё библиотека SevenZipLib — P/Invoke wrapper к 7zip, но по внешниему интерфейсу я не очень понял, как работать с GZip, хотя в описании указано.

Snappy

Алгоритм от Гугла, ориентированный на максимальную скорость. Для .NET есть 2 реализации P/Invoke с оригинальными названиями: Snappy for .NET и Snappy.Net. Есть Safe-реализация Snappy.Sharp, но я её даже не пробовал, т.к. судя по всему работы ещё дофига, она полузаброшена, ничего особо не протестировано. Опять же, если есть желание — берите сами и дописывайте, иначе не советую использовать (записал второй пункт в блокнот).

Сам алгоритм очень шустрый (судя по всему, разработка велась с учётом особенностей процессоров и их кеширвоания), но сжатие у него так себе. Также у обоих реализаций есть проблемы. Snappy.Net не работает в 32х битах из-за какой-то ошибки в реализации библиотеки, вычисляющей CRC32 (третий пункт в блокнот — написать автору, что он лох и надо поправить). Snappy for .NET — требует VS2010 runtime, о чём надо помнить (я для тестов подложил нужные dll'ки на тестовый компьютер).
В общем, пока следует использовать с осторожностью, это не Production-решение

LZ4

Один из моих фаворитов, благо скорость отличная, но надо выбрать реализацию. Их две и обе хорошие. lz4-net — P/Invoke wrapper и LZ4.NET, флакон от автора с четырьмя разными реализациями, которые выбираются по приоритету и доступности: Mixed Mode, C++/CLI (требуется установленный VS2010 runtime, проверка идёт по наличию пакета в реестре, а не по DLL), Unsafe, Safe. Также, автор, возможно будет пилить дальше и улучшать свой код.

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

Качество сжатия алгоритма зависит от дополнительного буфера на словарь, который в разных реализациях по дефолту 1Мб и 256Кб, в реальности, 64Кб дают пристойный результат, но и 1Мб не очень жалко для объёмных данных. Имейте в виду.

Заключение

Я, пока в раздумьях по поводу алгоритма и реализации, склоняюсь к GZip в P/Invoke исполнении и LZ4 в комплектном. Надо заранее определиться, какая скорость вам требуется: если вы передаёте огромные данные по сети со скоростью 1МБ/c, то GZip'а вам хватит за глаза, а сжатие будет активно помогать уменьшить объёмы. Если же сеть в гигабит, а данных немного, то со сжатием связываться вообще не стоит. LZ4 сидит где-то посередине и при своей скорости подходит для всего мало-мальски сжимаемого.

Решайте сами, я пока думаю, решение напишу позднее, когда потестирую всё это в продакшене (т.е. возможно, спустя длительное время).

суббота, 29 марта 2014 г.

Паранойя в андроиде, или как задолбать пользователей

Я неоднократно говорил, что если перекрутить безопасность в приложении, то это приведёт к тому, что пользователи будут отключать её, лишь бы не бесила. Или бездумно кликать на "Разрешить", потому что это окно лезет всегда.
Примеры: UAC в Vista, который лез на каждый чих (в семёрке стал реже появляться), или sudo в SUSE, которое лезет на каждое действие. Пришлось рутовый пароль поставить в 1, чтобы хоть как-то можно было жить.

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

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

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

Но появилась другая проблема. Мне нужен клиентский сертификат. И он работает. И всё хорошо. Но в гугле решили, что я должен себя обезопасить, и обязан включить блокировку планшета с паролем, пин-кодом или графическим кодом. У кого такая блокировка уже включена, те даже не заметят проблему. Но я тестирую на планшете, основная цель которого — смотреть фильмы, читать книжки и кидаться птицами в свиней. И ради одного сертификата, я должен включать блокировку? Да нафига? Была бы идея, если бы к этой блокировке были бы привязаны другие данные. Но если посмотреть: браузеры сохраняют пароли для сайтов, система сохраняет пароли для WiFi и Bluetooth, приложения сохраняют пароли для почты, аккаунтов синхронизации, и доступа к внешним сайтам. Это нормально. Я могу взяв чужое устройство читать чужие письма, смотреть чатики, ходить по сайтам с паролями. Никаких проблем. Но если мне вдруг понадобился клиентский сертификат, я сразу же должен всё запаролить. На мой взгляд полный бред.
Решения этой проблемы я не нашёл. Только косвенное, с установкой сертификата, тестированием и удалением. По-моему бред. Зато можно жить.

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

воскресенье, 23 февраля 2014 г.

Мысли про возможности инструментов

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

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

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

Т.к. я программист, то пример из этой области: студия без Решарпера, кажется удобной. После его использования, его отсутствие приводит к ломке. Хотя сам по себе инструмент не меняет ничего кардинально. Скрипты на нормальном языке программирования (или хотя бы на перле), приводят к тому, что можно сделать логику, которую раньше просто никто и не думал реализовывать. Знание многих языков, приводит к тому, что можно соединить несколько разных технологий и быстро написать то, что надо. Наличие большого количества оперативной памяти может привести к тому, что можно держать весь объём данных в памяти, тем самым в сотни раз ускорив приложение.

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

PS: Кстати, что удивительно, лучшие кубики Рубика делает не фирма Rubik's, а самые настоящие китайцы, фирма DaYan. Т.е. не в том плане, что в китае делается, а в том, что именно сам китайский бренд котируется лучше всего. Так, в копилку бесполезных фактов.