пятница, 7 июня 2013 г.

On IIS, PowerShell, compatibility and deprecation

Recently I was developing a deployment script – nothing really special (4 web applications + 3 Windows services, main and geo-failover environment). In scope of deployment I need to find running web applications and then start them again (after deploying binaries and configuration). With PowerShell we have the following 2 ways for doing this:

  1. WMI (against classes in root\MicrosoftIISv2 namespace introduced with IIS 6.0);
  2. WebAdministration API (introduces with IIS 7.0).

It’s quite unsurprising that both have their own pros and cons:

  • Using WMI requires quite some wondering among documentation; getting used to it; knowing and using different classes (not so obvious at times); dealing with security and permissions; respecting the fact that once you’ve obtained an object and did smth (that changes its state) the object you have won’t refresh automatically (i.e. if you have an object that represents a Site and you stopped it, the object you have in your PowerShell session with still be in “Running” state), etc. But overall it’s ok – once you get an idea on how the whole thing works and get over some immediate problems you’ll be up and running with no major problems.
  • WebAdministration API at first sight looks easier / more attractive – high level API (cmdlets for PowerShell) with virtual path provider; classes closely reflect objects that we mange in IIS through interface. Second sight, however, reveals some not-so-pleasant details: is packed into a separate dll and PowerShell module (which you need to find / install correctly); no direct option for managing remote instances of IIS; quite a lot of cmdlets (which is comparable in number with WMI classes for IIS 6.0).

A deeper investigation and some testing reveal a few exciting and interesting facts:

  • root\MicrosoftIISv2 namespace is actually considered as ‘deprecated’ (since IIS 7.0) and you’ll need to install “IIS 6 WMI compatibility” component in order for it to be available;
  • WebAdministration API actually relies on WMI classes underneath (which is good if you managed to master WMI path) which suggests that
    • you can use this new way for doing whatever you want;
    • potentially you don’t need additional dll and module;
    • will seamlessly work for managing remote machines.

All is good indeed, until you realize that new new WMI classes are … well … do not provide you with the functionality that you expect. Microsoft did a good job on providing transition matrix (use this for that, etc), but the point is that new classes add more friction where you would not expect it. For instance, I need to get a list of all running sites (see above). With IIS 6 WMI provider it’s quite straightforward:


Get-WmiObject `
-Namespace root\MicrosoftIISv2 `
-ComputerName $currentNode.HostName `
-Authentication PacketPrivacy `
-Query "SELECT * FROM IISWebServer WHERE ServerState = 2"


conditions – that’s why we have SQL-like syntax here, right?



What about new IIS 7 classes? Surprise! Site / application pool state is no longer a property – now it’s a method! Calling GetState() on ApplicationPool or Site is pretty much a no-brainer, but how should I handle that inside a query? No way? Are you kidding? What if I have dozens of sites and I don’t need / want to go through most of them? You don’t care? But that worked perfectly (ok, ok, just worked) for me before, why did you decided to implement new API and deprecate this one just 1 major release after it was shipped? That’s something I don’t understand about Microsoft lately – and this is just one example (with more around Windows Phone, Windows, Office).



HTH,

AlexS

четверг, 25 октября 2012 г.

Process notes: QA

QA process works towards the following goals:

  1. Provide a qualitative metric of product quality (number of open and fixed/verified bugs)
  2. Provide qualitative metric of functional requirements fulfillment (implemented and verified test cases or user stories against a target total)
  3. Ensure stable product quality over time (regression testing)

QA process is based on product requirements and works with artifacts (labeled builds) produced by release management process. Below you will find some key points for the QA process to meet its goals:

  • QA activities are applied to each official [labeled] build (the set of applied tests might differ, though)
  • Product quality is evaluated against a set of test-cases. Test cases are usually derived into groups (by functional area; by priority) – this allows more flexibility in applying test efforts.
  • Each produced [labeled] build should have an associated test report which specifies the tests being executed and a result of execution (positive/negative).
  • Each build that’s publicly deployed (i.e. uat/staging/production) should be tested at least against a short-list of critical test cases (usually referred to as ‘smoke test’). If a build does not pass this ‘smoke test’ it can’t (won’t) be deployed anywhere.
  • When adding new functionality or updating existing features, a list of test cases is updated to reflect these changes (new features usually result in new test cases being added to this list).
  • Having a consistent track of test results provides an immediate insight on:
    • Development progress (new test cases mean new functionality being added)
    • Stability of each build we produced (number of bugs and their priority) – which greatly simplifies a task of choosing a ‘good enough’ build for urgent delivery
    • Amount of work that lies on our plate (blocked test cases + open bugs)

When maintaining automated [selenium] tests with iterative development process, the practice is usually as follows:

  1. Assuming we have a sufficient test coverage for the previous iteration
  2. During current iteration, QA team does the following:
    1. Preparing a list of test cases to cover new functionality (during current sprint there test cases are usually executed manually)
    2. Implementing [automated] tests for test cases that were implemented/delivered during previous sprint
    3. [optional] backing up critical bugs by automated tests in order to ensure proper regression testing

The reason for automated tests to be one iteration behind development is that in order to develop them, the functionality has to be in place, which is not the case during ‘current’ sprint. With this approach to testing (i.e. using automates tests) a sign of a healthy project is an increase in number of [passing] automated tests after each iteration.

HTH,
AlexS

среда, 24 октября 2012 г.

Process notes: release management

Recently I had to spend some time to produce comments around different aspects of software development. Publishing these notes here is a good way of saving it for future use.

 

Basic definitions and requirements

Artifact – a set of files that we work with or produce. This can refer to a source code drop or to a binaries produced by building the application from this sources.

Process – a repeatable and traceable set of actions that are performed on a given artifact(s).

In scope of release management process we work with the following artifacts:

  • Source code – a complete set of project source code together with required configuration files.
  • Build – a labeled set of binary files that is sufficient for deployment to any environment we have.

Processes we need to have in place:

  1. Build process – takes a source code and produces a labeled build [package].
  2. Deployment process – takes a build and deploys it to a specified environment.

Process requirements:

  1. Repeatability – we need to be able to repeat any process and be sure that for a given input it will produce exactly the same output.
  2. Traceability – we need to be able to trace when a process was initiated, who initiated a process, what were the inputs and what were the outputs/results or process execution.

Security considerations:

  1. We’d like to control who can trigger certain processes (like deployment to production).
  2. We’d like to control who have influence on the artifacts used to produce the build.

Implementation notes

The crucial aspect of organizing release management process is a definition and labelling of artefacts. Here’s what might help us improve our process:

  • Having a dedicated release branch which is used as an input for build process (helps with both repeatability and security)
  • Establishing a consistent build labeling mechanism and using the same labels across all the environments we run.
    • Build label should consist of major and minor release number (might be based on sprint number)
    • Should include source control revision number in some form
    • Should be stamped on each binary we produce during the builds – usually embedded in each assembly via AssemblyVersion attribute
    • Should be surfaced somewhere on the web-site – does not necessary visible to end user, can be a hidden field or a hint
      • Simplifies support and solving technical issues
      • Helps in development and testing by making it 100% clear which version is being deployed/tested
      • Allows easy and reliable tracking of what was fixed when and where it is deployed to

Bottom-line:

  1. Define consistent build artifacts labeling mechanism
  2. Define how to build official (i.e. labeled) builds (release branch or some other strategy)
  3. Define responsible personnel and limit access to corresponding artifacts/processes
  4. Define how we ensure repeatability (the simplest and most common way is to store official builds somewhere and allowing deployment to be triggered for a given set of [build] artifacts)

HTH,
AlexS

четверг, 2 августа 2012 г.

CI and msdeploy: I want to parameterize my package

When we deploy to different environments, we’d like to have different configuration values to be set during deployment. Some of them (like connection strings) can be parameterized by msdeploy itself when it creates deployment package, but that’s only part of the story. Say we want some values in web.config to be changed depending on environment we’re deploying to. Good news: msdeploy has a mechanism called ‘deployment parameters’ – you can supply a file declaring these parameters and msdeploy will parameterize your package (this is not the same as web.config transformation – a good overview of difference can be found here). Bad news: there’s no UI in Visual Studio that allows you to either specify these parameters or specify a file where they are declared. In case you decide to build deployment package on your own (by invoking msdeploy directly of from an msbuild script) you will face another problem: you can either supply deployment parameters declaration in xml file or have msdeploy gather/discover them – but not both. So you can’t define some of the parameters in a file and let msdeploy do the rest of the job for you. This comes especially inconvenient when you have a database deployment [sql] scripts in your package – in order for msdeploy to pick them up [when invoked from command line] you have to do a lot of plumbing around declaration and command-line arguments. From the other side, when msdeploy gathers parameters on its own, it tends to parameterize things that you might not want to be parameterized (like IIS application name or application pool name).

This is how it can be solved:
1) When Visual Studio creates a deployment package for your application it looks for [YourWebProjectName].wpp.targets file and imports it into running msbuild script (well, it is actually Microsoft.Web.Publishing.targets which looks for and imports this file in case it exists). This little file lets you extend / influence package building pipeline.
2) EnablePackageProcessLoggingAndAssert msbuild parameter allows you to see extended log of what happens when a deployment package is build for your project – by examining this you can discover a set of msbuild targets that can give you a clue on what to extend/precede.
3) DisableAllVSGeneratedMSDeployParameter – allows you to prevent VS from parameterizing your package (so you don’t have duplicate / unnecessary parameters).
4) ParametersXMLFiles item type allows you to supply your own parameters declaration file (you have to put it inside ItemGroup element and reference a file with parameters declaration).
5) When defining you own target, you can hook on a ‘standard’ one (i.e. the one, defined in Microsoft.Web.Publishing.targets) by using either of the following attributes: BeforeTargets, AfterTargets – they contain a coma-separated list of targets you want to hook on.

HTH,
AlexS

среда, 1 августа 2012 г.

CI and msdeploy: I want deployment package to be build by my CI tool

Here’s a scenario I have in my current project: ASP.NET MVC application is being deployed to a number of environments (staging, test, uat, production). Web Deployment tool (aka msdeploy) is used for assembling deployment package and for the deployment itself. There’s a CI tool employed (which is TeamCity) with corresponding set of [ms]build scripts.

At first sight the problem seems to be fairly simple – if Visual Studio can build deployment package for us, so why can’t we? It ain’t so simple, because in order to invoke msdeploy [for building deployment package] you need to provide it a lot of arguments and (which is more important) have all the files to be packaged ready at some location (which is not your project directory). This is done by Visual Studio when you … create a deployment package (via project context menu –> Build Deployment Package). But the problem is that this is an explicit action, triggered via a dedicated UI element (i.e. it’s not part of the build process). Fortunately for us, deeps inside it actually relies on a set of MSBuild targets that do all the job (for the curious ones: they are defined in Microsoft.Web.Publishing.targets file which resides in Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\Web\ folder on your system drive). And there’s a ‘magic switch’ which can be passed to MSBuild so it actually build deployment package as part of a normal build process.  These parameters are:

DeployOnBuild=True
DeployTarget=Package

so you will have to invoke msbuild like this

msbuild MySolution.sln /p:Configuration=Release,DeployOnBuild=True,DeployTarget=Package

In case you want to always create a deployment package during build, you can define the same parameters in your project file (but this will require editing it outside Visual Studio).

HTH,
AlexS

вторник, 31 июля 2012 г.

EF Migrations: database schema maintenance burden solved? … Not really.

Just a couple of quick notes about EF Migrations [with ‘code first’ approach]. It sounded like EF Migrations were going to solve all the problems around database development process and allow all the magic to happen right inside Visual Studio. A lot of magic is happening under the hood indeed, but it does not seem to be able to let us forget about sql scripts once and forever (it’s not that I ever believed it really will Winking smile).

First comes a task of deploying a database. We can have a connection string in [web].config, and in case the database is empty, it will be populated by applying all migrations right from the start (starting from Initial). It works ok unless you have some [initial] data to be present in the database upon first launch of the application. You might argue that this is a problem of deployment, not EF Migrations itself, but wait a minute – how are we supposed to deploy a database without a [sql] script? Ok, EF Migrations give you an option for creating a sql script rather than applying a particular migration to an existing database. But there’s a trick – you still have to have a database in place in order to do this. I’ve found it a bit annoying [and frustrating] to have to create an empty database just for getting migration scripted – Update-Database does not work without a connection string. I wish it could fake it.

Ok, you’ve done your homework and now you have an empty database to deed Update-Database with. My first intention was to let EF Migrations to script the latest state of the schema for me [by applying all the migrations one by one]. I issued

Update-Database –script –ConnectionString “[my connection string]”

and got a script (finally!). But guess what? It was not working – due to the fact that EF Migrations does not put each migration into a separate batch, it got a number of very simple syntax errors (declaring a variable multiply times). It does not take long to fix it, but in general it does not look reliable enough for me to trust it.

I ended up with scripting only ‘Initial’ migration and letting EF Migrations do the rest of schema update (with initial data already in) upon first launch of the application. This leaves [me] an open question, however: what if we need to start developing ‘version Next’ and start with the database of the current one? Sure, we can script it into ‘Initial’ in new project. Or just make a branch and continue development having all the migrations from the previous version. Both options does not look great in terms of maintenance.

Despite these little issues, EF Migrations are a huge step ahead and I see that employing code-first approach simplifies life for both app and db developers – less sql to produce/review/maintain.

HTH,
AlexS

суббота, 21 июля 2012 г.

Web Deployment Tool – making it install application remotely

The key is in precisely following this instruction when configuring remote server (it took me quite a while to identify which tutorial/instruction is the one to follow). There are, of cause, some other issues that can stand on your way:

  • you’d want to install Web Deployment tool manually and do a complete install – otherwise you’ll spend a fair amount of time looking for missing options in IIS Manger console;
  • when setting up Management Service Delegation, make sure that you enable the following providers: contentPath, createApp, dbSqlite, iisApp, package, setAcl – otherwise you will have to carefully read error messages and reiterate over delegation config (by default only contentPath and iisApp are included);
  • I would definitely suggest using dedicated low privileged domain users (and you have to have a domain, of cause).

HTH,
AlexS

четверг, 22 марта 2012 г.

Пара заметок об Open XML

В очередном проекте в очередной раз нужно генерировать отчеты в Excel (ну ладно: наконец-то выпала возможность “попедалить”! Улыбка ). Из имеющихся в распоряжении средств: генерировать html (просто, но слишком коряво), office xml (по-моему до сих пор вполне пристойный вариант) и Open XML aka Office 2007+ – был выбран последний. Аргументы: по отзывам на habrahabr все должно быть просто и быстро, к тому же на выходе мы получаем актуальную версию формата.

Open XML SDK 2.0 “весит” немногим больше 100 Мб – по нынешним временам, конечно, не объем, но все-равно возникает вопрос: “А что туда такого напихали и почему так много?” В комплект входит “Open XML SDK 2.0 Productivity Tool for Offce”, который может по данному файлу сгенерировать код, который сгенерирует точно такой же файл. Вроде бы все просто, но когда начинаешь разбираться в коде, становится грустно.

  1. API – жуть какое неудобное.
    Вроде как обещается, что это высокоуровневое API, упрощающее создание/редактирование документов (цитирую: “The Open XML SDK 2.0 encapsulates many common tasks that developers perform on Open XML packages, so that you can perform complex operations with just a few lines of code.”).
    • Почему тогда изо всех углов торчит XML и API по работе с ним (все эти бесконечные Append, Text, AddNamespaceDeclaration)?
    • Зачем заставлять меня пользоваться специальными типами для всего, что только можно придумать (включая специальный тип UInt32Value)? В результате приходится пользоваться прямо-таки умопомрачительным количеством разнообразных типов.
    • В высокоуровневом API(!) для создания документов нужно не забыть закрыть пакет (о, вы не знали, что документ – это на самом деле пакет?) – иначе на выходе получим “битый” файл.
    • Как бы глупо это не звучало, но строго типизированное API “молча” позволяет вам сгенерировать невалидный документ. Вот так-то. Добиться этого довольно легко – просто попробуйте сохранить в ячейку какую-нибудь строку в обход таблицы SharedStrings. Для того, чтобы понять, как же это делать “по-правильному”, нужно потратить уйму времени и [внимательно] перечитать примеры кода (код, сгенерированный Productivity Tool-ом вам в этом не поможет).
  2. Убогая документация.
    • Есть примеры. Но почему-то код в примерах “ни разу не похож” на то, что генерирует тот самый Productivity Tool. Более того,
    • Описание классов поражают своей информативностью. Для большинства классов все описание – “When the object is serialized as xml, its qualified name is XXX”. В некоторых случаях еще ссылаются на раздел стандарта. Такая документация очень помогает разобраться в том, как мне сделать что-то.

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

Гораздо приятнее выглядит проект с издевательским (для формата Open XML) именем ClosedXML – поверх стандарта соорудили человеческое API, которое действительно позволяет достигнуть нужного результата буквально парой строк кода.

HTH,

AlexS

среда, 1 декабря 2010 г.

“В жизни всегда есть место для подвига” © или как реализовать Singleton по-новому

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

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





public interface ISingleton
{

}

class Singleton : ISingleton
{
public delegate ISingleton InstanceGetterDelegate();

private static Singleton _singleton;
private static InstanceGetterDelegate _instanceGetter;
static Singleton()
{
var type = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
// assign the closure that executes on the first call of GetInstance() method
_instanceGetter = () =>
{
Singleton instance = null;
lock (type)
{
var rslt = Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
if (rslt == null) // indicates is first call
{
instance = new Singleton(/* init arguments go here*/);
Interlocked.CompareExchange<Singleton>(ref _singleton, null, instance);

// now replace with a closure that executes on all subsequent calls of GetInstance() method
InstanceGetterDelegate fn = () =>
{
return Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
};
Interlocked.Exchange<InstanceGetterDelegate>(ref _instanceGetter, fn);
}
else
{
instance = rslt; // was already set with singleton instance
}
}
return instance;
};
}
private Singleton() { } // disable default instance constructor
public static ISingleton GetInstance() { return _instanceGetter(); }
}


И вот тут, что называется, “накрывает”. Я бы назвал этот способ “JavaScript-style Singleton” (конечно же за использования замыканий). В целом (за исключение блокировки по Type (о чем я уже писал) и использования рефлексии в статическом конструкторе) реализация выглядит любопытной и уж во всяком случае расширяет горизонт.



HTH, AlexS

суббота, 27 ноября 2010 г.

sqlite: приятные мелочи в области агрегирующих функций

Продолжаю очень приятно удивляться sqlite. Очередным поводом стали агрегирующие функции:

  • есть две функции для вычисления сумм: традиционная sum() и чуть менее традиционная total() – обе суммируют не-NULL значения, но первая (в соответствии со стандартом SQL) возвращает пустой набор данных в случае, если таких значений не было, а вот вторая всегда возвращает 0.0 (что в некоторых случаях может быть весьма удобно). Кроме того, функция total() всегда возвращает double и не подвержена арифметическому переполнению;
  • есть крайне полезная функция group_concat(field[, separator]), которая объединяет значения поля в одну строку. Разделитель по-умолчанию – запятая. Причем в варианте с разделителем по-умолчанию можно использовать еще и DISTINCT:
    SELECT group_concat(DISTINCT user_name) FROM …. GROUP BY …
    Замечу, что в SQL Server-е (горячо и искренне мною любимом) нет простого способа сделать тоже самое – только через custom aggregate (т.е. специальную сборку, которую писать/разворачивать).

В общем: чем больше смотрю - тем больше нравится.

HTH, AlexS

вторник, 16 ноября 2010 г.

Эволюция MS Full-Text Search: доступ к анализатору полнотекстовых запросов

Так и работает эволюция: незаметно и по чуть-чуть. Недавно на StackOverflow задали вопрос (в вольном переводе):

Можно ли получить доступ к стеммеру, который используется в SQL Server FullText Search?

Моей первой мыслью было “нельзя”. Но на всякий случай решил свериться с MSDN. Так вот оказалось, что “почти можно”: в SQL Server 2008 в FullText Search помимо радикального изменения архитектуры, внесли менее заметные, но полезные изменения. В частности, была добавлена следующая системная табличная функция:

sys.dm_fts_parser('query_string', lcid, stoplist_id, accent_sensitivity)

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

select * from sys.dm_fts_parser('FORMSOF(inflectional, Worked)', 1033, 0, 0)

Т.е. мы запросили все словоформы слова “worked” в английской локали (locId = 1033). И вот какие ключевые слова будут соответствовать этому запросу:

image

Было бы еще хорошо иметь возможность заглянуть внутрь самого индекса (как это делает Luke для Lucene), но даже с этими средствами жить  становится намного веселее.

HTH, AlexS

четверг, 29 июля 2010 г.

Чем точнее указан путь – тем быстрее можно по нему идти ((С) Кэп)

Недавно попросили помочь с оптимизацией обработки XML в SQL Server-е. Запрос выглядит примерно следующим образом:

SELECT
  nref.value('./../../@id', 'int') AS articleid,
  nref.value('type[1]', 'nvarchar(256)') AS type,
  nref.value('theme[1]', 'varchar(256)') AS theme,
  nref.value('score[1]', 'varchar(32)') AS score
INTO #tmpTheme
FROM @bulk_xml.nodes('//theme') AS R(nref)

Очень простое действие – указание точного XPath выражения вместо обобщенного ('//theme') позволяет ускорить работу данного запроса в 1.5 – 2 раза.

HTH,

AlexS

воскресенье, 11 июля 2010 г.

Заблуждения: “restore from database”

Недавно пришлось развенчивать миф: “restore from database” (см. скриншот) позволяет использовать журнал транзакций [базы данных А] для “point in time recovery” [базы банных Б, восстановленной из резервной копии базы данных А].

image

Контекст, в котором происходило обсуждение, тоже представляет определенный интерес Есть база данных [пусть будет “База А”], которая довольно сильно нагружена. Она работает в режиме восстановления от сбоев “Full”. Каждую ночь делается полный бэкап этой базы, никакие другие бэкапы (дифференциальный, журнала) не делаются. Совет перевести базу в режим восстановления “Simple” (нагрузка на подсистему ввода/вывода меньше, размер журнала меньше, по возможностям  восстановления ничего не теряем) наталкивается на следующий аргумент  (вольный перевод)

“если База А будет повреждена, то мы сможем восстановить ее из резервной копии и донакатить транзакции из журнала, поэтому Simple нам не подходит”

Так вот, “эта штука так не работает”. Опция “from database” просто-напросто подхватывает историю резервных копий выбранной базы данных – так что пользователю не нужно лазить по дискам в поисках какого-то конкретного бэкапа (или самого свежего бэкапа). Журнал транзакций “намертво” привязан к базе и только SQL Server может что-то с ним делать (пользователь может только посмотреть что в нем, да и то используя недокументированные функции). Единственный [для пользователя] способ использовать информацию об операциях, хранящуюся в журнале, – сделать резервную копию журнала после создания полного бэкапа базы данных. В этом случае резервная копия журнала (а не сам журнал!) может быть использована для повторного выполнения операций в базе, восстановленной из этого полного бэкапа (т.е. “point in time recovery”).

HTH, AlexS

воскресенье, 16 мая 2010 г.

Право на жизнь для табличных переменных

С момента появления в SQL Server табличных переменных я все никак не мог понять (для себя): зачем же они прямо так нужны (по большому счету). Аргумент “передавать много параметров в хранимую процедуру в табличной форме” звучал (и до сих пор звучит) не слишком убедительно: никто не отменял упаковки/распаковки (в случае, когда вызов идет из кода) и … собственно таблиц (в случае, когда мы вызываем хранимку из другой хранимки/скрипта).

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

Проверил: да, таки работает. Но тут как в том старом анекдоте: “ложечки нашлись, но осадок остался”. Табличная переменная на самом деле – это временная таблица (что легко проверить). При этом изменения, вносимые в “обычные” временные таблицы протоколируются и являются частью транзакции, в которой эти изменения происходят (что тоже легко проверяется). Получается некоторое противоречие: табличная переменная – это временная таблица, но изменения, которые в нее вносятся не протоколируются. Покопавшись в гуглоридере, нашел (отмеченный мною же!) пост в блоге SQL Server Storage Engine на эту тему – “перечитал и переосмыслил” :-)

HTH

AlexS

вторник, 6 апреля 2010 г.

Бытовые особенности SQLite

В последние пару недель работаю с SQLite (не то чтобы очень плотно, но плотнее, чем “просто поковырять”). Штуковина безусловно полезная и нужная, но не без особенностей. Ниже приведу список того, обо что споткнулся сам:

  • Нет типа данных DateTime или чего-то похожего – все даты нужно хранить в виде строки; проблема мелкая, но стоит о ней помнить. Более подробное описание типов данных SQLite лежит здесь.
  • Нет функции поиска подстроки/символа в строке (т.е. ничего, аналогичного CHARINDEX в SQL Server-е). Можно, конечно, самому написать такую функцию (на C) и подключить ее на лету, но … в общем лучше бы она была сразу.
  • Не поддерживается [ставшая уже привычной] конструкция UPDATE|DELETE|INSERT …. FROM ….
  • Крайне любопытно организовано хранение данных, в частности – в каждой таблице неявно присутствует “автоматический первичный ключ” (поле ROWID), который к тому же является “кластерным” (в привычных терминах). Причем явное объявление “целочисленного, автоинкрементного первичного ключа” в большинстве случаев – просто псевдоним для ROWID.
  • Есть доступ к метаданным (их, правда, немного) – таблица sqlite_master.
  • Сравнение строк может производиться как с учетом регистра, так и без оного – зависит от того, как была собрана библиотека и от того, какие строки (ASCII/Unicode) сравниваются.
  • Поддерживаются триггеры, причем можно повесить триггер на обновление какой-то конкретной колонки.
  • Параметры могут определяться так же, как и в SQL Server-е - @ParamName (но поддерживаются и другие варианты: ?Name, :Name, $Name).

HTH,

AlexS

четверг, 11 февраля 2010 г.

Синхронизация доступа: on Type or not on Type

Сегодня на собеседовании задал кандидату вопрос о том, как реализовать Singleton в C#. Сначала он предложил самый простой вариант (не thread safe) и в ходе дальнейшего обсуждения (“а почему так? а что если у нас многопоточное приложение? и что делать в этом случае?” и т.п.) предлагается вариант с блокировкой (внимание):

lock(typeof(MySingleton)) { …

В этот момент я подумал: “однако…”. На вопрос: “А почему так?” кандидат ответить затруднился, сославшись на некую статью, читаную им когда-то на просторах интернета.

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

A Type object that represents a type is unique; that is, two Type object references refer to the same object if and only if they represent the same type.

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

Note:

In multithreading scenarios, do not lock Type objects in order to synchronize access to static data. Other code, over which you have no control, might also lock your class type. This might result in a deadlock. Instead, synchronize access to static data by locking a private static object.

Так что “увы и ах” - не судьба (даром что выглядит элегантно). И не забываем о пользе чтения документации.

HTH,

AlexS

среда, 3 февраля 2010 г.

Первые впечатления от StreamInsight (aka Complex Event Processing от Microsoft)

В состав SQL Server 2008 R2 релиз которого намечен на май этого года, входит технология StreamInsight (официальная страница и блог). Несмотря на очевидную ассоциированность, к SQL Server-у эта технология не имеет практически никакого отношения – кроме того факта, что их “положили в одну коробку”.

StreamInsight – это попытка Microsoft выйти на новый для себя рынок Complex Event Processing (в конце поста есть несколько ссылок на информацию о других игроках). Общее описание идея очень сильно напомнило мне системы обработки сообщений: скорее всего из-за наличия потоков, очередей, обработки и прочих схожих концепций. Ключевым, на мой взгляд, является слово “Complex”: предполагается, что мы производим над входным потоком событий некоторые нетривиальные манипуляции, которые позволяют нам осуществлять анализ этого потока в реальном времени (точнее “с известной задержкой”, поскольку зачастую речь идет об операциях, проводимых на каком-то [скользящем] окне наблюдений). Технология доступна пока только для предварительно просмотра, скачать можно отсюда. Дистрибутив компактен (около 10 Мб) и содержит неплохую подборку примеров и обзорную документацию. Последняя, однако, не настолько актуальна, как MSDN.

В обзорной документации все описано простым и понятным языком:

  1. Определяем входной поток событий.
  2. Задаем механизм обработки - на самом деле Linq-запрос, в котором можно использовать некоторые специфичные для потоков событий функции (вроде обращения к “окну”).
  3. Задаем характеристики выходного потока - поток результатов выполнения запроса из п.2 на входном потоке событий.

Обещается масштабируемость “куда хочешь” и параллельная обработка “как хочешь”.

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

  • Для того, чтобы “воткнуть” поток в CEP Engine (который занимается вычислениями/обработкой) необходимо реализовать InputStreamAdapter (который выполняется в контексте того же процесса, который выполняет CEP Engine). Этот адаптер на самом деле является конечным автоматом (aka State Machine). Состояния вроде бы описаны, но машина далеко не так проста, как хотелось бы. Причем со всеми этими переходами приходится быть чрезвычайно аккуратным, ибо в примерах есть грозные предупреждения вида “если не сделать вот этот ‘финт ушами’, то в CEP Engine будет утечка памяти”. Таким образом, для реализации простейшего адаптера приходится писать довольно много не относящегося к делу кода. Аналогичная ситуация и с выходными потоками – тот же конечный автомат и те же проблемы: довольно много сервисного кода на ровном месте.
  • Не до конца понятен механизм типизации событий: она (типизация) вроде бы заложена в сам поток (который является generic-классом), но отсутствует в адаптере потока (который, собственно, и “производит” поток для CEP Engine).
  • Пока не совсем понятно, как все-таки эта штука масштабируется и какая у нее пропускная способность.
  • Также не очень ясно, насколько сложный анализ можно производить: в расчете простейших агрегаций особого (революционного) смысла нет, а вот относительно более сложных расчетов возникают вопросы: как их “скрестить” с Linq? какую величину окна сможет безболезненно (для производительности) сохранять CEP Engine? Ответов я пока не нашел.

Однако “попилим еще” и будет видно. Глядишь, к моменту выхода (или к версии 2 ;-) ) станет более понятно что к чему.

Где еще можно об этом почитать:

HTH,

AlexS

понедельник, 11 января 2010 г.

SqlDependency: размножение дикобразов

Как размножаются дикобразы?

Очень, очень осторожно либо безрезультатно – что-то дикобразов нигде не видно.

(С) “Афера Томаса Крауна”

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

Вкратце предыстория такова: в какой-то момент времени было принято решение использовать SqlDependency для очистки кэша настроек приложения – сценарий достаточно стандартный. Как всегда нестандартными (на первый взгляд) были проблемы, возникшие позже. Настройки хранятся в специальной БД (ConfigDB), к которой обращаются приложения (ASP.NET App, несколько экземпляров потенциально на разных серверах) и [вспомогательные] “сервисы” в виде служб и консольных приложений, запускаемых по расписанию.

В какой-то момент с ConfigDB начинаются проблемы:

  • невозможно сохранить измененные настройки (timeout)
  • измененные настройки не подхватываются клиентами
  • SqlServer начинает ощутимо тормозить (хотя поначалу это не связывается с ConfigDB)
  • журнал ошибок SqlServer-а забивается сообщениями с “жалобами” на query norifications (“The query notifications dialog conversation handle ‘{xxxxxx….}’ closed due to the following error …”, with error code 8490 or 8470)

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

  • создание и использование новой ConfigDB помогает, правда ненадолго – через пару дней/неделю все возвращается на круги своя
  • восстановление ConfigDB из резервной копии занимает очень много времени – база объемом 500 Мб (из которых 400 – это журнал транзакций) при восстановлении на двухьядерной машине с 2 Гб оперативной памяти намертво подвешивает ее (процесс не завершился за 1.5 часа)
  • выясняется, что в ConfigDB очень много открытых подписок (query notification) и их количество растет

Проблему в итоге удалось устранить путем:

  1. Вдумчивого чтения и переработки кода, который работает с объектами SqlDependency.
  2. Закрытия всех открытых подписок (об этом ниже).

По ходу работы пару раз слышались голоса “SqlDependency – это зло”, “и зачем вы вообще это используете”, “да никогда и ни в жизни не пользовать ЭТО”, “лучше сделать все ‘руками’”. В итоге скептики были посрамлены, механизм работает как и ожидалось, проблему устранили. Но должен признать, что работать с SqlDependency необходимо очень осторожно и внимательно.

Итак, о чем нужно помнить при использовании этой технологии:

  • Строго следите за тем, чтобы на каждый вызов SqlDependency.Start приходился вызов SqlDependency.Stop – об этом явно написано в MSDN, но на моих глазах (в 2х случаях из 2х) об этом “забывали”, а потом долго искали решение возникших проблем. Хорошим решением будет обернуть всю работу с SqlDependency в некий класс (отвечающий за кеширование/инвалидацию), реализаций IDisposable, и помещение вызовов Start и Stop в конструктор и Dispose соответственно.
  • В обработчике события SqlDependency.OnChange не ленитесь анализировать причину вызова обработчика (SqlNotificationEventArgs.Info). Игнорирование этого простого совета может привести (и рано или поздно приводит) к возникновению эффекта, похожего на "положительную обратную связь”: когда на сервере что-то пошло не так, вызвался обработчик, мы тут же создали новую подписку, добавив тем самым проблем серверу, который тут же снова вызовет наш обработчик.
  • Не забывайте о протоколировании – наличие информации о том, когда и с какими параметрами вызывается обработчик события OnChange здорово упрощает диагностику.
  • Проводите мониторинг SqlServer-а. Отслеживание количества активных подписок (select count(*) from sys.dm_qn_subscriptions) позволит вам на самых ранних этапах обнаружить “утечку” и устранить ее пока это не переросло в БОЛЬШУЮ проблему. Обычно именно мониторингом все склонны пренебрегать.
  • Считайте количество подписок, которые будут регистрироваться на сервере в ходе нормального (штатного) функционирования вашего приложения. Десятки подписок – не проблема. При сотнях стоит уже быть ооочень осторожным. Если же расчетное количество подписок исчисляется тысячами, то следует подумать о изменении архитектуры с целью уменьшения этого числа.
  • Не самой лучше идеей будет [активно] использовать этот механизм в базе данных с высокой нагрузкой.
  • Если что-то пошло не так, помним о возможности “убить” подписку: KILL QUERY NOTIFICATION SUBSCRIPTION <subscription id/ALL>.

UPDATE: как было совершенно справедливо замечено Airex-ом, при использовании SqlDependency в контексте ASP.NET, наилучшим местом для вызова SqlDependency.Stop является Application_End (в Global.asax). И не стоит надеяться на деструктор того класса, в который вы “запрячете” всю работу с SqlDependency – в этой связи немного обновил (уточнил) первый пункт выше.

HTH,

AlexS

вторник, 5 января 2010 г.

Enum.Parse vs string comparison

В ходе code review столкнулся с подобным кодом:

if ((SomeEnum)Enum.Parse(typeof(SomeEnum), value) == SomeEnum.SomeValue)
{
// делается что-то полезное
}

И так много раз подряд. Выглядит громоздко и не слишком элегантно. К тому же меня стало терзать сомнение что Enum.Parse – не самый быстрый метод. Первой мыслью было заменить на что-нибудь вроде такого:

if (value == SomeEnum.SomeValue.ToString())
{
// делается что-то полезное
}

Но потом решил проверить. Так вот, первый вариант работает в 3-4 раза быстрее второго. Использование Reflector-а позволяет узнать, что внутри Enum.Parse кэшируются значения перечислений и в результате со второго вызова вся эта операция сводится к поиску значения в хэш-таблице. Преобразование элемента перечисления к строке (второй вариант) оказывается гораздо сложнее и использует рефлексию.
Ситуация, однако, кардинально меняется, если value содержит значение, которому нет соответствия SomeEnum. При этом выбрасывается исключение (которое в оригинальном коде никак не обрабатывается) и выполнение кода существенно замедляется – в 30 раз по сравнению с вариантом №2.
Резюме: если вы уверены в том, что в подавляющем большинстве случаев к вам придет допустимое значение, то используйте Enum.Parse - думаю 3-4х кратный прирост производительности компенсирует некоторое ухудшение читаемости кода.

HTH,
AlexS

суббота, 31 октября 2009 г.

Dev magic и прочий ooops

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

DevMagic

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

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

 

HTH,

AlexS