...

суббота, 19 мая 2018 г.

[Из песочницы] [Перевод] Обработка ошибок и транзакций в SQL Server. Часть 1. Обработка ошибок – быстрый старт

Привет, Хабр! Представляю вашему вниманию перевод статьи «Error and Transaction Handling in SQL Server. Part One – Jumpstart Error Handling» автора Erland Sommarskog.

1. Введение


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

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

Все статьи описывают обработку ошибок и транзакций в SQL Server для версии 2005 и более поздних версий.

1.1 Зачем нужна обработка ошибок?


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

Мы часто хотим, чтобы в базе данных изменения были атомарными. Например, задача по переводу денег с одного счета на другой. С этой целью мы должны изменить две записи в таблице CashHoldings и добавить две записи в таблицу Transactions. Абсолютно недопустимо, чтобы ошибки или сбой привели к тому, что деньги будут переведены на счет получателя, а со счета отправителя они не будут списаны. По этой причине обработка ошибок также касается и обработки транзакций. В приведенном примере нам нужно обернуть операцию в BEGIN TRANSACTION и COMMIT TRANSACTION, но не только это: в случае ошибки мы должны убедиться, что транзакция откачена.

2. Основные команды


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

2.1 TRY-CATCH


Основным механизмом обработки ошибок является конструкция TRY-CATCH, очень напоминающая подобные конструкции в других языках. Структура такова:
BEGIN TRY
   <обычный код>
END TRY
BEGIN CATCH
   <обработка ошибок>
END CATCH

Если какая-либо ошибка появится в <обычный код>, выполнение будет переведено в блок CATCH, и будет выполнен код обработки ошибок.

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

Вот очень быстрый пример:

BEGIN TRY
   DECLARE @x int
   SELECT @x = 1/0
   PRINT 'Not reached'
END TRY
BEGIN CATCH 
   PRINT 'This is the error: ' + error_message()
END CATCH

Результат выполнения: This is the error: Divide by zero error encountered.

Мы вернемся к функции error_message() позднее. Стоит отметить, что использование PRINT в обработчике CATCH приводится только в рамках экспериментов и не следует делать так в коде реального приложения.

Если <обычный код> вызывает хранимую процедуру или запускает триггеры, то любая ошибка, которая в них возникнет, передаст выполнение в блок CATCH. Если более точно, то, когда возникает ошибка, SQL Server раскручивает стек до тех пор, пока не найдёт обработчик CATCH. И если такого обработчика нет, SQL Server отправляет сообщение об ошибке напрямую клиенту.

Есть одно очень важное ограничение у конструкции TRY-CATCH, которое нужно знать: она не ловит ошибки компиляции, которые возникают в той же области видимости. Рассмотрим пример:

CREATE PROCEDURE inner_sp AS
   BEGIN TRY
      PRINT 'This prints'
      SELECT * FROM NoSuchTable
      PRINT 'This does not print'
   END TRY
   BEGIN CATCH
      PRINT 'And nor does this print'
   END CATCH
go
EXEC inner_sp

Выходные данные:
This prints
Msg 208, Level 16, State 1, Procedure inner_sp, Line 4
Invalid object name 'NoSuchTable'

Как можно видеть, блок TRY присутствует, но при возникновении ошибки выполнение не передается блоку CATCH, как это ожидалось. Это применимо ко всем ошибкам компиляции, таким как пропуск колонок, некорректные псевдонимы и тому подобное, которые возникают во время выполнения. (Ошибки компиляции могут возникнуть в SQL Server во время выполнения из-за отложенного разрешения имен – особенность, благодаря которой SQL Server позволяет создать процедуру, которая обращается к несуществующим таблицам.)

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

CREATE PROCEDURE outer_sp AS
   BEGIN TRY
      EXEC inner_sp
   END TRY
   BEGIN CATCH
      PRINT 'The error message is: ' + error_message()
   END CATCH
go
EXEC outer_sp

Теперь мы получим на выходе это:
This prints
The error message is: Invalid object name 'NoSuchTable'.

На этот раз ошибка была перехвачена, потому что сработал внешний обработчик CATCH.

2.2 SET XACT_ABORT ON


В начало ваших хранимых процедур следует всегда добавлять это выражение:
SET XACT_ABORT, NOCOUNT ON

Оно активирует два параметра сессии, которые выключены по умолчанию в целях совместимости с предыдущими версиями, но опыт доказывает, что лучший подход – это иметь эти параметры всегда включенными. Поведение SQL Server по умолчанию в той ситуации, когда не используется TRY-CATCH, заключается в том, что некоторые ошибки прерывают выполнение и откатывают любые открытые транзакции, в то время как с другими ошибками выполнение последующих инструкций продолжается. Когда вы включаете XACT_ABORT ON, почти все ошибки начинают вызывать одинаковый эффект: любая открытая транзакция откатывается, и выполнение кода прерывается. Есть несколько исключений, среди которых наиболее заметным является выражение RAISERROR.

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

Для надежной обработки ошибок в SQL Server вам необходимы как TRY-CATCH, так и SET XACT_ABORT ON. Среди них инструкция SET XACT_ABORT ON наиболее важна. Если для кода на промышленной среде только на нее полагаться не стоит, то для быстрых и простых решений она вполне подходит.

Параметр NOCOUNT не имеет к обработке ошибок никакого отношения, но включение его в код является хорошей практикой. NOCOUNT подавляет сообщения вида (1 row(s) affected), которые вы можете видеть в панели Message в SQL Server Management Studio. В то время как эти сообщения могут быть полезны при работе c SSMS, они могут негативно повлиять на производительность в приложении, так как увеличивают сетевой трафик. Сообщение о количестве строк также может привести к ошибке в плохо написанных клиентских приложениях, которые могут подумать, что это данные, которые вернул запрос.

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

SET NOCOUNT ON
SET XACT_ABORT ON

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

3. Основной пример обработки ошибок


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

Для примера я буду использовать эту простую таблицу.

CREATE TABLE sometable(a int NOT NULL,
                       b int NOT NULL,
                       CONSTRAINT pk_sometable PRIMARY KEY(a, b))

Вот хранимая процедура, которая демонстрирует, как вы должны работать с ошибками и транзакциями.
CREATE PROCEDURE insert_data @a int, @b int AS 
   SET XACT_ABORT, NOCOUNT ON
   BEGIN TRY
      BEGIN TRANSACTION
      INSERT sometable(a, b) VALUES (@a, @b)
      INSERT sometable(a, b) VALUES (@b, @a)
      COMMIT TRANSACTION
   END TRY
   BEGIN CATCH
      IF @@trancount > 0 ROLLBACK TRANSACTION
      DECLARE @msg nvarchar(2048) = error_message()  
      RAISERROR (@msg, 16, 1)
      RETURN 55555
   END CATCH

Первая строка в процедуре включает XACT_ABORT и NOCOUNT в одном выражении, как я показывал выше. Эта строка – единственная перед BEGIN TRY. Все остальное в процедуре должно располагаться после BEGIN TRY: объявление переменных, создание временных таблиц, табличных переменных, всё. Даже если у вас есть другие SET-команды в процедуре (хотя причины для этого встречаются редко), они должны идти после BEGIN TRY.

Причина, по которой я предпочитаю указывать SET XACT_ABORT и NOCOUNT перед BEGIN TRY, заключается в том, что я рассматриваю это как одну строку шума: она всегда должна быть там, но я не хочу, чтобы это мешало взгляду. Конечно же, это дело вкуса, и если вы предпочитаете ставить SET-команды после BEGIN TRY, ничего страшного. Важно то, что вам не следует ставить что-либо другое перед BEGIN TRY.

Часть между BEGIN TRY и END TRY является основной составляющей процедуры. Поскольку я хотел использовать транзакцию, определенную пользователем, я ввел довольно надуманное бизнес-правило, в котором говорится, что если вы вставляете пару, то обратная пара также должна быть вставлена. Два выражения INSERT находятся внутри BEGIN и COMMIT TRANSACTION. Во многих случаях у вас будет много строк кода между BEGIN TRY и BEGIN TRANSACTION. Иногда у вас также будет код между COMMIT TRANSACTION и END TRY, хотя обычно это только финальный SELECT, возвращающий данные или присваивающий значения выходным параметрам. Если ваша процедура не выполняет каких-либо изменений или имеет только одно выражение INSERT/UPDATE/DELETE/MERGE, то обычно вам вообще не нужно явно указывать транзакцию.

В то время как блок TRY будет выглядеть по-разному от процедуры к процедуре, блок CATCH должен быть более или менее результатом копирования и вставки. То есть вы делаете что-то короткое и простое и затем используете повсюду, не особо задумываясь. Обработчик CATCH, приведенный выше, выполняет три действия:

  1. Откатывает любые открытые транзакции.
  2. Повторно вызывает ошибку.
  3. Убеждается, что возвращаемое процедурой значение отлично от нуля.

Эти три действия должны всегда быть там. Мы можете возразить, что строка
IF @@trancount > 0 ROLLBACK TRANSACTION

не нужна, если нет явной транзакции в процедуре, но это абсолютно неверно. Возможно, вы вызываете хранимую процедуру, которая открывает транзакцию, но которая не может ее откатить из-за ограничений TRY-CATCH. Возможно, вы или кто-то другой добавите явную транзакцию через два года. Вспомните ли вы тогда о том, что нужно добавить строку с откатом? Не рассчитывайте на это. Я также слышу читателей, которые возражают, что если тот, кто вызывает процедуру, открыл транзакцию, мы не должны ее откатывать… Нет, мы должны, и если вы хотите знать почему, вам нужно прочитать вторую и третью части. Откат транзакции в обработчике CATCH – это категорический императив, у которого нет исключений.

Код повторной генерации ошибки включает такую строку:

DECLARE @msg nvarchar(2048) = error_message()

Встроенная функция error_message() возвращает текст возникшей ошибки. В следующей строке ошибка повторно вызывается с помощью выражения RAISERROR. Это не самый простой способ вызова ошибки, но он работает. Другие способы мы рассмотрим в следующей главе.

Замечание: синтаксис для присвоения начального значения переменной в DECLARE был внедрен в SQL Server 2008. Если у вас SQL Server 2005, вам нужно разбить строку на DECLARE и выражение SELECT.

Финальное выражение RETURN – это страховка. RAISERROR никогда не прерывает выполнение, поэтому выполнение следующего выражения будет продолжено. Пока все процедуры используют TRY-CATCH, а также весь клиентский код обрабатывает исключения, нет повода для беспокойства. Но ваша процедура может быть вызвана из старого кода, написанного до SQL Server 2005 и до внедрения TRY-CATCH. В те времена лучшее, что мы могли делать, это смотреть на возвращаемые значения. То, что вы возвращаете с помощью RETURN, не имеет особого значения, если это не нулевое значение (ноль обычно обозначает успешное завершение работы).

Последнее выражение в процедуре – это END CATCH. Никогда не следует помещать какой-либо код после END CATCH. Кто-нибудь, читающий процедуру, может не увидеть этот кусок кода.

После прочтения теории давайте попробуем тестовый пример:

EXEC insert_data 9, NULL

Результат выполнения:
Msg 50000, Level 16, State 1, Procedure insert_data, Line 12
Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.

Давайте добавим внешнюю процедуру для того, чтобы увидеть, что происходит при повторном вызове ошибки:
CREATE PROCEDURE outer_sp @a int, @b int AS
   SET XACT_ABORT, NOCOUNT ON
   BEGIN TRY
      EXEC insert_data @a, @b
   END TRY
   BEGIN CATCH
      IF @@trancount > 0 ROLLBACK TRANSACTION
      DECLARE @msg nvarchar(2048) = error_message()
      RAISERROR (@msg, 16, 1)
      RETURN 55555
   END CATCH
go
EXEC outer_sp 8, 8

Результат работы:
Msg 50000, Level 16, State 1, Procedure outer_sp, Line 9
Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

Мы получили корректное сообщение об ошибке, но если вы посмотрите на заголовки этого сообщения и на предыдущее поближе, то можете заметить проблему:
Msg 50000, Level 16, State 1, Procedure insert_data, Line 12
Msg 50000, Level 16, State 1, Procedure outer_sp, Line 9

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

4. Три способа генерации ошибки


4.1 Использование error_handler_sp


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

Позвольте представить вам error_handler_sp:

CREATE PROCEDURE error_handler_sp AS
 
   DECLARE @errmsg   nvarchar(2048),
           @severity tinyint,
           @state    tinyint,
           @errno    int,
           @proc     sysname,
           @lineno   int
           
   SELECT @errmsg = error_message(), @severity = error_severity(),
          @state  = error_state(), @errno = error_number(),
          @proc   = error_procedure(), @lineno = error_line()
       
   IF @errmsg NOT LIKE '***%'
   BEGIN
      SELECT @errmsg = '*** ' + coalesce(quotename(@proc), '<dynamic SQL>') + 
                       ', Line ' + ltrim(str(@lineno)) + '. Errno ' + 
                       ltrim(str(@errno)) + ': ' + @errmsg
   END
   RAISERROR('%s', @severity, @state, @errmsg)

Первое из того, что делает error_handler_sp – это сохраняет значение всех error_xxx() функций в локальные переменные. Я вернусь к выражению IF через секунду. Вместо него давайте посмотрим на выражение SELECT внутри IF:
SELECT @errmsg = '*** ' + coalesce(quotename(@proc), '<dynamic SQL>') + 
                 ', Line ' + ltrim(str(@lineno)) + '. Errno ' + 
                 ltrim(str(@errno)) + ': ' + @errmsg

Цель этого SELECT заключается в форматировании сообщения об ошибке, которое передается в RAISERROR. Оно включает в себя всю информацию из оригинального сообщения об ошибке, которое мы не можем вставить напрямую в RAISERROR. Мы должны обработать имя процедуры, которое может быть NULL для ошибок в обычных скриптах или в динамическом SQL. Поэтому используется функция COALESCE. (Если вы не понимаете форму выражения RAISERROR, я рассказываю о нем более детально во второй части.)

Отформатированное сообщение об ошибке начинается с трех звездочек. Этим достигаются две цели: 1) Мы можем сразу видеть, что это сообщение вызвано из обработчика CATCH. 2) Это дает возможность для error_handler_sp отфильтровать ошибки, которые уже были сгенерированы один или более раз, с помощью условия NOT LIKE ‘***%’ для того, чтобы избежать изменения сообщения во второй раз.

Вот как обработчик CATCH должен выглядеть, когда вы используете error_handler_sp:

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC error_handler_sp
   RETURN 55555
END CATCH

Давайте попробуем несколько тестовых сценариев.
EXEC insert_data 8, NULL
EXEC outer_sp 8, 8

Результат выполнения:
Msg 50000, Level 16, State 2, Procedure error_handler_sp, Line 20
*** [insert_data], Line 5. Errno 515: Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.
Msg 50000, Level 14, State 1, Procedure error_handler_sp, Line 20
*** [insert_data], Line 6. Errno 2627: Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

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

Я покажу еще два метода вызова ошибок. Однако error_handler_sp является моей главной рекомендацией для читателей, которые читают эту часть. Это — простой вариант, который работает на всех версиях SQL Server начиная с 2005. Существует только один недостаток: в некоторых случаях SQL Server генерирует два сообщения об ошибках, но функции error_xxx() возвращают только одну из них, и поэтому одно из сообщений теряется. Это может быть неудобно при работе с административными командами наподобие BACKUP\RESTORE, но проблема редко возникает в коде, предназначенном чисто для приложений.

4.2. Использование ;THROW


В SQL Server 2012 Microsoft представил выражение ;THROW для более легкой обработки ошибок. К сожалению, Microsoft сделал серьезную ошибку при проектировании этой команды и создал опасную ловушку.

С выражением ;THROW вам не нужно никаких хранимых процедур. Ваш обработчик CATCH становится таким же простым, как этот:

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   ;THROW
   RETURN 55555
END CATCH

Достоинство ;THROW в том, что сообщение об ошибке генерируется точно таким же, как и оригинальное сообщение. Если изначально было два сообщения об ошибках, оба сообщения воспроизводятся, что делает это выражение еще привлекательнее. Как и со всеми другими сообщениями об ошибках, ошибки, сгенерированные ;THROW, могут быть перехвачены внешним обработчиком CATCH и воспроизведены. Если обработчика CATCH нет, выполнение прерывается, поэтому оператор RETURN в данном случае оказывается не нужным. (Я все еще рекомендую оставлять его, на случай, если вы измените свое отношение к ;THROW позже).

Если у вас SQL Server 2012 или более поздняя версия, измените определение insert_data и outer_sp и попробуйте выполнить тесты еще раз. Результат в этот раз будет такой:

Msg 515, Level 16, State 2, Procedure insert_data, Line 5
Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.
Msg 2627, Level 14, State 1, Procedure insert_data, Line 6
Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

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

В этом месте вы можете сказать себе: действительно ли Microsoft назвал команду ;THROW? Разве это не просто THROW? На самом деле, если вы посмотрите в Books Online, там не будет точки с запятой. Но точка с запятой должны быть. Официально они отделяют предыдущее выражение, но это опционально, и далеко не все используют точку с запятой в выражениях T-SQL. Более важно, что если вы пропустите точку с запятой перед THROW, то не будет никакой синтаксической ошибки. Но это повлияет на поведение при выполнении выражения, и это поведение будет непостижимым для непосвященных. При наличии активной транзакции вы получите сообщение об ошибке, которое будет полностью отличаться от оригинального. И еще хуже, что при отсутствии активной транзакции ошибка будет тихо выведена без обработки. Такая вещь, как пропуск точки с запятой, не должно иметь таких абсурдных последствий. Для уменьшения риска такого поведения, всегда думайте о команде как о ;THROW (с точкой с запятой).

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

4.3. Использование SqlEventLog


Третий способ обработки ошибок – это использование SqlEventLog, который я описываю очень детально в третьей части. Здесь я лишь сделаю короткий обзор.

SqlEventLog предоставляет хранимую процедуру slog.catchhandler_sp, которая работает так же, как и error_handler_sp: она использует функции error_xxx() для сбора информации и выводит сообщение об ошибке, сохраняя всю информацию о ней. Вдобавок к этому, она логирует ошибку в таблицу splog.sqleventlog. В зависимости от типа приложения, которое у вас есть, эта таблица может быть очень ценным объектом.

Для использования SqlEventLog, ваш обработчик CATCH должен быть таким:

BEGIN CATCH
   IF @@trancount > 0 ROLLBACK TRANSACTION
   EXEC slog.catchhandler_sp @@procid
   RETURN 55555
END CATCH

@@procid возвращает идентификатор объекта текущей хранимой процедуры. Это то, что SqlEventLog использует для логирования информации в таблицу. Используя те же тестовые сценарии, получим результат их работы с использованием catchhandler_sp:
Msg 50000, Level 16, State 2, Procedure catchhandler_sp, Line 125
{515} Procedure insert_data, Line 5
Cannot insert the value NULL into column 'b', table 'tempdb.dbo.sometable'; column does not allow nulls. INSERT fails.
Msg 50000, Level 14, State 1, Procedure catchhandler_sp, Line 125
{2627} Procedure insert_data, Line 6
Violation of PRIMARY KEY constraint 'pk_sometable'. Cannot insert duplicate key in object 'dbo.sometable'. The duplicate key value is (8, 8).

Как вы видите, сообщение об ошибке отформатировано немного не так, как это делает error_handler_sp, но основная идея такая же. Вот образец того, что было записано в таблицу slog.sqleventlog:
logid logdate errno severity logproc linenum msgtext
1 2015-01-25 22:40:24.393 515 16 insert_data 5 Cannot insert ...
2 2015-01-25 22:40:24.395 2627 14 insert_data 6 Violation of ...

Если вы хотите попробовать SqlEventLog, вы можете загрузить файл sqleventlog.zip. Инструкция по установке находится в третьей части, раздел Установка SqlEventLog.

5. Финальные замечания


Вы изучили основной образец для обработки ошибок и транзакций в хранимых процедурах. Он не идеален, но он должен работать в 90-95% вашего кода. Есть несколько ограничений, на которые стоит обратить внимание:
  1. Как мы видели, ошибки компиляции не могут быть перехвачены в той же процедуре, в которой они возникли, а только во внешней процедуре.
  2. Пример не работает с пользовательскими функциями, так как ни TRY-CATCH, ни RAISERROR нельзя в них использовать.
  3. Когда хранимая процедура на Linked Server вызывает ошибку, эта ошибка может миновать обработчик в хранимой процедуре на локальном сервере и отправиться напрямую клиенту.
  4. Когда процедура вызвана как INSERT-EXEC, вы получите неприятную ошибку, потому что ROLLBACK TRANSACTION не допускается в данном случае.
  5. Как упомянуто выше, если вы используете error_handler_sp или SqlEventLog, мы потеряете одно сообщение, когда SQL Server выдаст два сообщения для одной ошибки. При использовании ;THROW такой проблемы нет.

Я рассказываю об этих ситуациях более подробно в других статьях этой серии.

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

Триггеры


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

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

Клиентский код


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

Здесь я только обращу внимание на важную вещь: реакцией на ошибку, возвращенную SQL Server, должно быть завершение запроса во избежание открытых бесхозных транзакций:

IF @@trancount > 0 ROLLBACK TRANSACTION

Это также применимо к знаменитому сообщению Timeout expired (которое является не сообщением от SQL Server, а от API).

6. Конец первой части


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

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

SET XACT_ABORT, NOCOUNT ON

Let's block ads! (Why?)

[recovery mode] Закончились IPv4 — регистратор RIPE отдал последний блок

Региональный регистратор RIPE отдал последний блок IPv4. У части других регистраторов блоки кончились еще несколько лет назад: у APNIC — в 2011, у RIPE — в 2012, а у ARIN — в 2015 году. Немного публичных адресов еще осталось у AFRINIC, однако и эти «запасы» выйдут очень скоро (эксперты прогнозируют, что это случится уже в 2019 году).

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


/ Flickr / Susan Adams / CC

Что привело к «истощению» адресов


В семидесятые годы на планете проживало 4,5 млрд человек, а о ПК-революции пока не было и речи. Поэтому в момент перехода на TCP/IP в 1983 году был сделан выбор в пользу 32-разрядных адресов. Казалось, что 4,3 млрд адресов вполне достаточно.

Однако уже в начале девяностых люди стали в этом сомневаться. Первым документом, в котором обсуждалась проблема истощения адресов, стал RFC 1287. Чтобы попытаться исправить положение, в 1992 году были созданы региональные интернет-регистраторы: AFRINIC (Африка), APNIC (Азиатско-Тихоокеанский регион), ARIN (Канада, США, Карибские и Североатлантические острова), LACNIC (Латинская Америка и страны Карибского бассейна), RIPE NCC (Европа, Ближний Восток и страны Центральной Азии). Они должны были управлять адресным пространством и раздачей IP-адресов. Однако это не решило проблему недостатка последних – рост пользователей сети был слишком велик, и IPv4 постепенно истощались.

Возможные решения проблемы


Несмотря на то что практически все регистраторы отдали доступные блоки, отдельные адреса до сих пор можно покупать и продавать. Одна из первых таких сделок состоялась еще в 2011 году: Microsoft приобрели 700 тысяч IPv4-адресов у обанкротившегося поставщика сетевого оборудования Nortel Networks за 7,5 млн долларов.

Также можно попытаться найти диапазоны адресов, выданных ранее, разбить на блоки поменьше и раздать снова. Однако обнаружить их непросто, так как их реестр не вели. Один из примеров — кейс Массачусетского технологического института (MIT). В 2017 году в MIT обнаружили, что у них есть 14 млн «лишних» IP-адресов, которые не использовались. 8 млн было решено продать.

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

Другое решение проблемы — использовать NAT. Метод NAT позволяет преобразовывать IP-адреса транзитных пакетов: заменяет локальный адрес на публичный, прописывает его в пакете, который идет на веб-сервер, и возвращает на устройство. Самое большое число портов NAT — 65 тысяч, значит, столько же локальных адресов можно превратить в один публичный. При этом в структуре сети виден только маршрутизатор, а сами устройства скрыты.

Однако у NAT есть ряд недостатков. Например, протоколы, которые появились раньше этого метода (например, FTP), могут работать через NAT нестабильно. Кроме того, если все сотрудники компании решат зайти на один сайт, сервер может принять это за DDoS-атаку — поскольку запрос совершается с одного публичного адреса — и заблокировать доступ для всех устройств с этим IP.

Также можно обратить внимание на CG-NAT. Это решение заточено под операторов связи и корпоративные сети и эффективнее использует ограниченное адресное пространство IPv4. Подробнее о том, как работает CG-NAT, можно прочитать в нашем корпоративном блоге.


/ Flickr / Dave Young / CC

Переход на IPv6


Вышеописанные подходы немного уменьшают размеры таблиц маршрутизации и оптимизируют использование IP-адресов. Однако не решают проблему нехватки на 100%. Поэтому еще с середины девяностых началась разработка протокола IPv6. Официальные описания IPv6 начали выпускать с 1996 года в рамках RFC (с RFC 1883 и далее).

Главное преимущество протокола — теоретическая неисчерпаемость IP-адресов (более 300 млн IP-адресов на каждого жителя Земли). Другое достоинство IPv4 — упрощенная маршрутизация: не нужно фрагментировать пакеты, а из заголовка адреса убрали контрольную сумму. Протокол следующего поколения имеет встроенный компонент IPSec, который шифрует и аутентифицирует каждый отдельный пакет данных. Он может усложнить работу для киберпреступников, хотя, безусловно, и в IPv6 встречаются уязвимости.

В 2008 году шестую версию протокола стали внедрять в Google, а в 2011 состоялся Международный день IPv6, в рамках которого большинство крупных провайдеров предоставляли доступ к сети по IPv4 и IPv6. Новую версию протокола уже используют в крупных зарубежных компаниях (AT&T, Cisco, Facebook и других). Отечественные провайдеры также переводят своих клиентов на IPv6: в «Вымпелкоме» уже успешно подключили несколько регионов, а в «Яндексе» используют протокол во внутренних сетях.

И хотя мировое сообщество настаивает на массовом переходе на новый протокол, полноценная «миграция» задерживается. По данным Google, всего 23% пользователей сервисов компании выходят в сеть по IPv6. И всего 12% сайтов поддерживают новый протокол. При этом из тысячи самых крупных сайтов из списка Alexa только 27% предлагает доступ по IPv6.

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

Однако Пол Уилсон (Paul Wilson), который 20 лет был главой APNIC, отмечает, что переход на IPv6 неизбежен, поскольку новый протокол — единственная достойная замена IPv4.

Но пока единого мнения о датах полноценного внедрения IPv6 у общественности нет. Например, пользователи Quora предполагают, что IPv4 еще «проживет» от нескольких лет до вечности. В The Register считают, что топ-1000 сайтов будут поддерживать IPv6 уже к маю 2021 года. Исследователи и представители RIR пророчат, что IPv4 осталось существовать от года до десяти лет.

P.S. Полезные материалы из блога VAS Experts:


P.P.S. Статьи по теме из нашего блога на Хабре:

Let's block ads! (Why?)

[Перевод] Выбор места для сервера и софта, тестирование рыночной неэффективности: как на самом деле создают роботов для торговли н

Автор блога Financial Hacker рассказал о том, как на самом деле устроен процесс разработки высокочастотных стратегий для торговли на бирже — от важности анализа возможных задержек, до вопросов получения данных и тестирования (все с примерами кода). Для примера используется стратегия арбитражной торговли на американских биржах. Мы подготовили адаптированный перевод этого материала.

Введение


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

Преимущество HFT в получении рыночных данных и исполнении своих заявок раньше большинства участников. Итоговая прибыльность системы зависит от ее скорости задержки, временем между получением котировки и исполнением заявки в торговом ядре биржи. Задержка (latency) — наиболее релевантный фактор при оценке HFT-системы. Его можно оптимизировать двумя способами: минимизируя физическое расстояние до биржи, и увеличивая скорость работы самой системы. И первое гораздо важнее второго.

Местоположение


В идеале, HFT-сервер должен быть расположен прямо на бирже. И большинство торговых площадок в мире с удовольствием продают серверные места в своих дата-центрах — чем ближе к хабу главной сети биржи, тем лучше считается место. Электрические сигналы в экранированном проводе передаются со скоростью в 0,7 — 0,9 от скорости света (300 км/мс). Сокращение расстояния до источника сигнала на один метр выливается в целых 8 наносекунд преимущества в раундтрипе (времени от отправк заявки до получения информации об ее исполнении). Сколько торговых возможностей можно упустить за 8 наносекунд? Никто не знает, но люди готовы платить за каждую сэкономленную наносекунду.

К сожалению (или к счастью с точки зрения экономии — размещение в дата-центрах бирж стоит огромных денег), анализируемая в данной статье HFT-система по ряду причин не может быть размещена на колокации в ЦОД торговой площадки. При этом для торговли ей нужно получать данные с бирж NYSE в Нью-Йорке и CME (Чикаго) одновременно.

Между двумя этими городами протянуты высокоскоростные кабели, а также функционирует микроволновая сеть. В теории, идеальное расположение для системы c аналогичными требованиями — это городок Уоррен, штат Огайо. Он расположен ровно посередине между Нью-Йорком и Чикаго. Неизвестно, есть ли там хаб для высокоскоростных торговцев, однако расстояние в 357 миль до обеих бирж выливается примерно в 4 мс задержку раундтрипа.

image

Уоррен, Огайо – Мекка HFT торговцев (изображение: Jack Pearce / Wikipedia Commons)

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

Софт


Когда вы уже вложили деньги в выбор оптимальной локации и каналы связи для HFT-системы, вам определенно захочется получить и софт, который будет соответствовать необходимой скорости. Коммерческие торговые платформы обычно недостаточно быстры, к тому же их код всегда закрыт, точно неизвестно, что и как в них работает. Поэтому HFT-системы почти никогда не базируются на существующих платформах, а пишутся с нуля. Не на R или Python, а на каком-либо из «быстрых» языков. В этот список входят:
  • C или C++ — отличная комбинация высокоуровневости и высокой скорости. C легко читать, при этом он почти также быстр и эффективен, как машинные языки.
  • Pentium Assembler — напишите свой алгоритм с помощью машинных инструкций и он обгонит даже разработанные на C системы. Из минусов такого подхода: поддерживать такой код будет непросто, все программисты знают, насколько тяжело читать программы на ассемблере, написанные кем-то другим.
  • CUDA, HLSL или ассемблер GPU — если алгоритм активно использует векторные или матричные операции, то запустить его на видеокарте может быть отличное идеей.
  • VHDL — если любой софт будет слишком медленным, а успех сделки для конкретного алгоритма будет зависеть от наносекунд, то «ультимативным решением» здесь будет кодирование системы напрямую в железе. В VHDL можно определять арифметические единицы, цифровые фильтры и секвенсоры FPGA чипов с тактовой частотой до нескольких сотен мегагерц. Такие чипы можно напрямую подключать к сетевому интерфейсу.

За исключением VHDL, все вышеописанное должно быть знакомо многим специалистам (особенном разработчикам компьютерных игр в 3D). Но стандартным языком для высокочастотной стратегии можно назвать C/C++. В этом материале используется именно он.

Алгоритм


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

Арбитраж будет происходить между финансовыми инструментами ES и SPY. ES — это торгуемый в Чикаго фьючерс S&P500. SPY — торгуемая в Нью-Йорке ETF, которая также привязана к индексу S&P500. Один пункт ES равняется 10 центам SPY, так что цена ES примерно в десять раз выше SPY. Поскольку оба актива основаны на одном и том же индексе, можно ожидать высокой корреляции их цен. Существуют публикации, авторы которых доказывают, что эта корреляция будет «ломаться» на небольших временных отрезках. Любая возникающая на короткое время разница в ценах пары ES-SPY, превышающая спред бид-аск, создает возможности для арбитража. Алгоритм из примера будет работать по следующей стратегии:

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

Алгоритм записан на C. Если вы до этого никогда не видели кода HFT-алгоритмов, он может показаться немного странным:
#define THRESHOLD  0.4  // Entry/Exit threshold 

// Алгоритм HFT арбитража 
// возвращает 0 для закрытия всех позиций
// возвращает 1 для открытия длинной позиции по ES, короткой по SPY 
// возвращает 2 для открытия короткой позиции по SPY, длинной по ES
// в противном случае возвращает -1 
int tradeHFT(double AskSPY,double BidSPY,double AskES,double BidES)
{
        double SpreadSPY = AskSPY-BidSPY, SpreadES = AskES-BidES;
        double Arbitrage = 0.5*(AskSPY+BidSPY-AskES-BidES);

        static double ArbMean = Arbitrage;      
        ArbMean = 0.999*ArbMean + 0.001*Arbitrage;
        static double Deviation = 0;
        Deviation = 0.75*Deviation + 0.25*(Arbitrage - ArbMean);

        static int Position = 0;        
        if(Position == 0) {
                if(Deviation > SpreadSPY+THRESHOLD)
                        return Position = 1;
                if(-Deviation > SpreadES+THRESHOLD)
                        return Position = 2;
        } else {
                if(Position == 1 && -Deviation > SpreadES+THRESHOLD/2)
                        return Position = 0;
                if(Position == 2 && Deviation > SpreadSPY+THRESHOLD/2)
                        return Position = 0;
        }
        return -1;      
}

Функция traderHFT вызывается из некого фреймворка (в статье он не рассматривается), который получает котировки и отправляет приказы. В качестве параметров используются текущие лучшие цены на покупку и продажу по ES и SPY из верхней части книги заявок (предполагается, что цена SPY умножается на десять, чтобы оба актива находились в одном масштабе). Функция возвращает код, который говорит фреймворку, открывать или закрывать позиции, или ничего не делать. Переменная Arbitrage представляет средняя разница цен между SPY и ES. Ее среднее (ArbMean) фильтруется медленной экспоненциальной скользящей средней, а Deviation от среднего также фильтруется быстрой скользящей средней для предотвращение реакций на котировки вне нужного диапазона. Переменная Position обозначает машинное состояние, которое может принимать значение лонг, шорт и ничего. Пороговое значение для входа или выхода из позиции (Threshold) установлен на отметке в 40 центов. Это единственный регулируемый параметр системы. Если бы стратегия предназначалась для реальной торговли, нужно было бы также оптимизировать пороговое значение с использованием нескольких месяцев данных по ES и SPY.

Такую минималистичную систему совсем не трудно перевести на ассемблер или даже запрограммировать в чипе FPGA. Однако такой необходимости нет: даже если использовать для компиляции компилятор фреймворка Zorro (его развивает автор статьи), функция tradeHFT исполняется всего за 750 наносекунд. Если использовать более продвинутый компилятор вроде Microsoft VC++, это значение можно снизить до 650 наносекунд. Поскольку время между двумя котировками по ES составляет 10 микросекунд или более, скорости C вполне достаточно.

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

Данные


Для бэктестинга HFT-системы данные, которые можно обычно получить у брокеров бесплатно, не подойдут. Нужно раскошелиться на покупку данных по книге заявок в нужном разрешении или данных BBO (Best Bid and Offer), с включенными временными метками биржи. Без информации о том, в какое время котировка была получена на бирже, определить максимальную задержку не получится.

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

typedef struct T1    // single tick
{
        double time; // time stamp, OLE DATE format
        float fVal;  // positive = ask price, negative = bid price      
} T1; 

Одна из компаний, отслеживающих ситуацию на бирже CME, поставляет данные в формате CSV с множеством дополнительных полей, большинство из которых для решаемой задачи не нужны. Все котировки за день хранятся в одном CSV-файле. Ниже скрипт для «вытягивания» из него данных по ES за декабрь 2016 и его конвертации в датасет котировок Т1:


//////////////////////////////////////////////////////
// Convert price history from Nanotick BBO to .t1
//////////////////////////////////////////////////////

#define STARTDAY 20161004
#define ENDDAY   20161014

string InName = "History\\CME.%08d-%08d.E.BBO-C.310.ES.csv";  // name of a day file
string OutName = "History\\ES_201610.t1";
string Code = "ESZ";    // December contract symbol

string Format = "2,,%Y%m%d,%H:%M:%S,,,s,,,s,i,,";  // Nanotick csv format
void main()
{
        int N,Row,Record,Records;
        for(N = STARTDAY; N <= ENDDAY; N++)
        {
                string FileName = strf(InName,N,N+1);
                if(!file_date(FileName)) continue;
                Records = dataParse(1,Format,FileName);  // read BBO data
                printf("\n%d rows read",Records);
                dataNew(2,Records,2);  // create T1 dataset
                for(Record = 0,Row = 0; Record < Records; Record++)
                {
                        if(!strstr(Code,dataStr(1,Record,1))) continue; // select only records with correct symbol
                        T1* t1 = dataStr(2,Row,0);  // store record in T1 format
                        float Price = 0.01 * dataInt(1,Record,3);  // price in cents
                        if(Price < 1000) continue;  // no valid price
                        string AskBid = dataStr(1,Record,2);
                        if(AskBid[0] == 'B')  // negative price for Bid
                                Price = -Price;
                        t1->fVal = Price;
                        t1->time = dataVar(1,Record,0) + 1./24.;  // add 1 hour Chicago-NY time difference
                        Row++;
                }
                printf(", %d stored",Row);
                dataAppend(3,2,0,Row);  // append dataset
                if(!wait(0)) return;
        }
        dataSave(3,OutName);  // store complete dataset
}


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

Компания, отслеживающая Нью-Йоркскую биржу, поставляет данные в сильно сжатом специально формате NxCore Tape, его нужно сконвертировать во второй список Т1 с помощью специального плагина:

//////////////////////////////////////////////////////
// Convert price history from Nanex .nx2 to .t1
//////////////////////////////////////////////////////

#define STARTDAY 20161004
#define ENDDAY   20161014
#define BUFFER   10000

string InName = "History\\%8d.GS.nx2";  // name of a single day tape
string OutName = "History\\SPY_201610.t1";
string Code = "eSPY";

int Row,Rows;

typedef struct QUOTE {
        char    Name[24];
        var     Time,Price,Size;
} QUOTE;

int callback(QUOTE *Quote)
{
        if(!strstr(Quote->Name,Code)) return 1;
        T1* t1 = dataStr(1,Row,0);  // store record in T1 format
        t1->time = Quote->Time;
        t1->fVal = Quote->Price;
        Row++; Rows++;
        if(Row >= BUFFER)    {   // dataset full?
                Row = 0;
                dataAppend(2,1);    // append to dataset 2
        }
        return 1;
}


void main()
{
        dataNew(1,BUFFER,2); // create a small dataset
        login(1);            // open the NxCore plugin
        
        int N;
        for(N = STARTDAY; N <= ENDDAY; N++) {
                string FileName = strf(InName,N);
                if(!file_date(FileName)) continue;
                printf("\n%s..",FileName);
                Row = Rows = 0;  // initialize global variables
                brokerCommand(SET_HISTORY,FileName); // parse the tape
                dataAppend(2,1,0,Row);  // append the rest to dataset 2
                printf("\n%d rows stored",Rows);
                if(!wait(0)) return;  // abort when [Stop] was hit
        }
        dataSave(2,OutName); // store complete dataset
}


Функция Callback вызывается любой котировкой в исходном файле, однако большая часть данных не нужна, поэтому отфильтровываются только котировки по SPY (“eSPY”).

Подтверждение рыночной неэффективности


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

image

SPY (черный) vs. ES (красный), 5 октября, 2017, 10:01:25 – 10:01.35

Разрешение здесь — одна миллисекунда. ES отрисован в долларовых единицах, SPY — в десятицентовых. Цены на графики — это цены «аск» (запрашиваемая цена). Кажется, что цены сильно коррелируют даже на столь малом интервале. ES чуть отстает.

Возможность для арбитража возникает на участке в центре — примерно в 10:01:30 ES реагировал на изменения чуть медленнее, но сильнее. Причиной могло послужить какое-то событие вроде резкого скачка цен одной из акций, входящих в индекс S&P 500. На протяжение нескольких миллисекунд разница ES-SPY превысила спред бид-аск двух активов (обычно это 25 центов по ES и 1-4 цента по SPY). В идеале, здесь можно было бы продать ES и купить SPY. Таким образом, мы подтвердили ранее предполагаемое в теории наличие рыночной неэффективности, открывающей возможности для заработка.

Скрипт для отрисовки графиков в высоком разрешении:

#define ES_HISTORY      "ES_201610.t1"
#define SPY_HISTORY     "SPY_201610.t1"
#define TIMEFORMAT      "%Y%m%d %H:%M:%S"
#define FACTOR          10
#define OFFSET          3.575

void main()
{
        var StartTime = wdatef(TIMEFORMAT,"20161005 10:01:25"),
                EndTime = wdatef(TIMEFORMAT,"20161005 10:01:35");
        MaxBars = 10000;
        BarPeriod = 0.001/60.;  // 1 ms plot resolution
        Outlier = 1.002;  // filter out 0.2% outliers

        assetList("HFT.csv");
        dataLoad(1,ES_HISTORY,2);
        dataLoad(2,SPY_HISTORY,2);
        int RowES=0, RowSPY=0;
        
        while(Bar < MaxBars)
        {
                var TimeES = dataVar(1,RowES,0), 
                        PriceES = dataVar(1,RowES,1), 
                        TimeSPY = dataVar(2,RowSPY,0), 
                        PriceSPY = dataVar(2,RowSPY,1);

                if(TimeES < TimeSPY) RowES++;
                else RowSPY++;

                if(min(TimeES,TimeSPY) < StartTime) continue;
                if(max(TimeES,TimeSPY) > EndTime) break;

                if(TimeES < TimeSPY) {
                        asset("ES");
                        priceQuote(TimeES,PriceES);
                } else {
                        asset("SPY");
                        priceQuote(TimeSPY,PriceSPY);
                }
                
                asset("ES");
                if(AssetBar > 0) plot("ES",AskPrice+OFFSET,LINE,RED);
                asset("SPY");
                if(AssetBar > 0) plot("SPY",FACTOR*AskPrice,LINE,BLACK);
        }
}

Сначала скрипт считывает два файла с историческими данными, которые мы создали ранее, а затем парсит их построчно.

Тестирование системы


Для бэктестинга получившейся HFT-системы необходимо немного изменить скрипт, и вызвать функцию tradeHFT в цикле:
#define LATENCY          4.0     // milliseconds

function main()
{
        var StartTime = wdatef(TIMEFORMAT,"20161005 09:30:00"),
                EndTime = wdatef(TIMEFORMAT,"20161005 15:30:00");
        MaxBars = 200000;
        BarPeriod = 0.1/60.;    // 100 ms bars 
        Outlier = 1.002;

        assetList("HFT.csv");
        dataLoad(1,ES_HISTORY,2);
        dataLoad(2,SPY_HISTORY,2);
        int RowES=0, RowSPY=0;
        
        EntryDelay = LATENCY/1000.;
        Hedge = 2;
        Fill = 8; // HFT fill mode;
        Slippage = 0;
        Lots = 100;
                
        while(Bar < MaxBars)
        {
                var TimeES = dataVar(1,RowES,0), 
                        PriceES = dataVar(1,RowES,1),
                        TimeSPY = dataVar(2,RowSPY,0),
                        PriceSPY = dataVar(2,RowSPY,1);

                if(TimeES < TimeSPY) RowES++;
                else RowSPY++;

                if(min(TimeES,TimeSPY) < StartTime) continue;
                if(max(TimeES,TimeSPY) > EndTime) break;

                if(TimeES < TimeSPY) {
                        asset("ES");
                        priceQuote(TimeES,PriceES);
                } else {
                        asset("SPY");
                        priceQuote(TimeSPY,FACTOR*PriceSPY);
                }
                
                asset("ES");
                if(!AssetBar) continue;
                var AskES = AskPrice, BidES = AskPrice-Spread;
                asset("SPY");
                if(!AssetBar) continue;
                var AskSPY = AskPrice, BidSPY = AskPrice-Spread;

                int Order = tradeHFT(AskSPY,BidSPY,AskES,BidES);        
                switch(Order) {
                        case 1: 
                        asset("ES"); enterLong();
                        asset("SPY"); enterShort();
                        break;
                
                        case 2: 
                        asset("ES"); enterShort();
                        asset("SPY"); enterLong();
                        break;
                
                        case 0:
                        asset("ES"); exitLong(); exitShort();
                        asset("SPY"); exitLong(); exitShort();
                        break;
                }
        }
        printf("\nProfit %.2f at NY Time %s",
                Equity,strdate(TIMEFORMAT,dataVar(1,RowES,0)));
}


Скрипт запускает бэктест для одного торгового дня в период с 9:30 до 15:30 по Нью-Йорку. По сути, просто происходит вызов функции HFT с ценами ES и SPY, а затем выполняется код для переключения состояний. Он открывает позиции по ста единицам каждого актива (2 контракта по ES и 1000 по SPY). Задержка устанавливается с помощью переменной EntryDelay. В режиме HFT (Fill = 8) сделка проходит по последней цене после времени задержки. Это позволяет приблизить симуляцию к реальным условиям.

В таблице ниже показана прибыль по итогам симуляции с разными значениями задержки:

Задержка 0.5 мс 4.0 мс 6.0 мс 10 мс
Прибыль / день + $793 + $273 + $205 – $15

Как видно, арбитражная стратегия ES-SPY может зарабатывать по $800 в день — при нереалистично маленькой задержке в 500 микросекунд. К сожалению, при наличии 700 миль между NYSE и CME, чтобы добиться такого результата понадобится машина времени (или какой-то инструмент квантовой телепортации). Сервер в Уоррене, штат Огайо, при задержке в 4 мс принесет примерно $300 в день. Если сервер будет чуть в стороне от высокоскоростного канала между Нью-Йорков и Чикаго, прибыль составит $200. Если инфраструктура для торговли будет еще дальше — скажем, в Нэшвилле — то заработать не удастся ничего.

Даже $300 в день выльются в годовой доход на уровне $75 000. Но для достижения такого результата понадобится, помимо железа и софта, еще и много денег. Контракт SPY стоит $250, 100 единиц для торговли выльются в 100*$2500 + 100*10*$250 = полмиллиона долларов объема торгов. Так что годовой возврат на инвестиции не превысит 15%. Результаты, однако, можно улучшить, добавив больше пар финансовых инструментов для арбитража.

Выводы


  • Если система реагирует достаточно быстро, заработать она может даже очень примитивными методами, вроде арбитража между сильно коррелированными финансовыми инструментами на разных биржах.
  • Физическое расположение сервера очень важно в HFT.
  • ES-SPY арбитраж нельзя проводить откуда угодно. Вам придется соперничать с теми, кто уже этим занимается, и весьма вероятно из Уоррена в штате Огайо.

Другие материалы по теме финансов и фондового рынка от ITI Capital:


Let's block ads! (Why?)

[Перевод] Самая сложная программа

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

Пристегни ремни.

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

Это программа – компьютерный червь. Червь был написан, судя по всему, между 2005 и 2010 годами. Поскольку этот червь является таким сложным, я могу дать лишь общее описание того, что он делает.
Сначала червь появляется на USB диске. Кто-то мог найти диск, лежащий на земле, получить его по почте и заинтересоваться его содержанием. Как только диск вставлялся в компьютер c Windows PC, без ведома пользователя, червь автоматически запускал себя и копировал на этот компьютер. Существовало как минимум три способа, с помощью которых он мог себя запустить. Если не работал один, он пробовал другой. Как минимум два из этих методов запуска были абсолютно новыми и оба использовали две независимых, секретных ошибки в Windows о которых никто не знал до того момента, как появился этот червь.

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

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

Затем червь проверяет, может ли он выйти в Интернет. Если может, он пытается посетить сайты www.mypremierfutbol.com или www.todaysfutbol.com. В то время эти серверы были Малайзии и Дании. Он открывает зашифрованный канал связи и сообщает этим серверам, что новый компьютер успешно захвачен. Зачем червь автоматически обновляет себя до самой новой версии.

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

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

Червь, о котором мы говорим очень сложный. И мы ещё даже не начали.

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

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

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

Например, уран.

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

Но нет. Это сложный червь. И у червя есть другие планы.

Как только он захватил все центрифуга в твоем заводе… червь просто засыпает.

Проходят дни. Или недели. Или секунды.

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

И затем, потихоньку, червь начинает крутить эти центрифуги… немножко неправильно. Совсем не намного. Просто, знаете, чуть-чуть слишком быстро. Или самую малость слишком медленно. Лишь немного за пределами безопасных параметров.

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

Центрифуги не любят работать слишком быстро или слишком медленно. И камни им тоже не нравятся.

Но у червя остался последний трюк. И он гениален.

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

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

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

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

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

Но центрифуги бы всё равно ломались. И у вас даже не было возможности узнать почему.

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

Именно это и произошло на самом деле

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

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

Let's block ads! (Why?)

«Жизнь после Java 10»: какие изменения принесет Java 11

Буквально недавно, в конце марта, вышел Java 10. Но в связи с тем, что компания Oracle внесла изменения в релизный цикл (новый релиз каждые полгода), к выходу готовится 11-я версия.

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


/ фото Markus Spiske PD

Java 10: краткая сводка


Нововведениями десятой версии стали: локальный вывод типов с помощью var, улучшения в процессах «сборки мусора» и возможность использовать Graal как основной JIT-компилятор.

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

var list = new ArrayList<String>();  // infers ArrayList<String>
var stream = list.stream();          // infers Stream<String>


Однако решение ввести var получило неоднозначные отзывы участников сообщества. Кто-то высказывался за нововведение и говорил, что дедупликация повышает читабельность кода. Но были и те, кто отмечал, что теперь ряд типов переменных (например, connection) не будут очевидными. И хотя IDE теперь смогут показывать их по требованию, в других средах будут возникать проблемы.

Для улучшенной сборки мусора в десятый релиз были включены сразу два изменения: JEP 304 и JEP 307. JEP 304 улучшил изоляцию кода от различных сборщиков мусора за счет нового интерфейса GC, что позволило быстрее интегрировать сторонние сборщики. JEP 307 же дал возможность «собирать мусор» в несколько потоков.

Что касается нового JIT-компилятора, то он направлен на улучшение производительности JVM. Прежняя версия JVM была написана на C++, однако в рамках проекта Metropolis большую часть JVM перепишут на Java. Экспериментальный компилятор является первым шагом на пути к этой цели.


/ фото Betsy Weber CC

Возможные нововведения в Java 11


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

Часть разработчиков недовольна тем, как быстро меняется язык. Один из резидентов Hacker News сказал: «Java превращается в совершенно новый язык. Одна из причин, почему я раньше использовал Java — это обратная совместимость. Если придется учить новый язык программирования каждые 6 месяцев, я выучу что-нибудь другое».

Но есть и те, кто отмечает, что Java наконец-то обзаводится функциями, которых ему не хватало, и которые уже давно были реализованы в других ЯП. Еще один пользователь HN пишет: «Я не использовал Java с начала нулевых и удивлен, что они так долго вводят фичи, которые давно существуют в других языках». Вот некоторые из них.

Изменения в локальном выводе типов

Java 10 уже предоставил возможность использовать var для обозначения типа локальных переменных, перекладывая эту задачу на плечи компилятора. Однако Java 11 идет дальше и делает так, что var можно использовать при объявлении формальных параметров неявно типизированных лямбда-выражений.

За подробным руководством о том, когда и как использовать var, можно обратиться к этой статье на OpenJDK.

Добавление необработанных строковых литералов

Это еще одно дополнение, над которым сейчас ведется работа. В необработанной строке каждый символ читается «как есть», включая символы разрыва. Например, такой строкой может быть разметка HTML или SQL-запрос:
String html = "<html>\n" +
              "  <body>\n" +
              "    <p>Hello World.</p>\n" +
              "  </body>\n" +
              "</html>\n";


Используя строковый литерал, этот код можно записать следующим образом:
String html = `<html>
                 <body>
                   <p>Hello World.</p>
                 </body>
               </html>
              `;


Для обозначения необработанной строки используется обратный апостроф (`). Если вам нужно прописать апостроф внутри самой строки, то в этом случае для маркировки её границ используются двойные (или тройные, четверные и т. д.) обратные апострофы:
String str = ```This is a raw `string` with ``backticks`` inside```;

Появятся switch-выражения

Сейчас оформление switch выполняется следующим образом:
int val;
switch (str) {
  case "foo": val = 1; break;
  case "bar": val = 2; break;
  case "baz": val = 3; break;
  default: val = -1;
}


С появлением switch-выражений, конструкция сократится:
int val = switch (str) {
  case "foo": break 1;
  case "bar": break 2;
  case "baz": break 3;
  default: break -1;
}


При этом если в case встречается только break, возможно использовать упрощенную нотацию:
int val = switch (str) {
  case "foo" -> 1;
  case "bar" -> 2;
  case "baz" -> 3;
  default -> -1;
}


Добавление switch expressions — это шаг вперед на пути к появлению метода сопоставления с образцом.

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



P.S. О чем еще мы пишем в Первом блоге о корпоративном IaaS:
P.P.S. Несколько материалов из нашего блога на Хабре:

Let's block ads! (Why?)

/boot на ZFS зеркале

Небольшая заметка, в дополнение к статье о корневом разделе на ZFS.

В предыдущей статье /boot был продублирован на двух ext4 разделах, и в будущем планировалось сделать нормально.
Ядро обновляется достаточно часто и каждый раз приходилось монтировать оба /boot, обновлять ядро, копировать содержимое, делать update-grub, update-initramfs и т.п…
Это порядком надоело.
Будущее настало.
Возможно сделать это скриптом, но grub2 поддерживает загрузку с ZFS.
Потому, правильный и менее затратный вариант — это сделать /boot на ZFS зеркале.
Предполагается, что условия те же, что описаны в предыдущей статье: Debian, root на ZFS.


Предварительные шаги

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

mount /dev/disk/by-id/usb-Corsair_Flash_Voyager-0\:0-part1 /mnt/usb/
dd if=/dev/disk/by-id/ata-Micron_1100-part2 of=/mnt/usb/micron_boot.img bs=4M
dd if=/dev/disk/by-id/ata-Samsung_SSD_850_PRO-part2 of=/mnt/usb/samsung_boot.img bs=4M
umount /mnt/usb

Обязательно извлеките флешку из USB после этого.

Надо проверить загружается ли модуль zfs в grub:

grep -R zfs /boot/grub/grub.cfg 

В результате должна быть выведена строка insmod zfs.
Если её там нет, надо добавить такую строку в /etc/default/grub:

GRUB_PRELOAD_MODULES="zfs"

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

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

mount /dev/disk/by-id/ata-Micron_1100-part2 /boot
tar -C / -cf ~/boot.tar /boot
tar tf ~/boot.tar

В результате, на экран должен быть выведен список файлов из /boot.

Теперь ФС возможно отмонтировать:

umount /boot

Создание ZFS пула и загрузочной ФС

rm -rf /boot
zpool create -f -o ashift=12 \
  -O atime=off -O compression=lz4 -O normalization=formD \
  -O mountpoint=none \
  boot_pool mirror /dev/disk/by-id/ata-Micron_1100-part2 /dev/disk/by-id/ata-Samsung_SSD_850_PRO-part2
zfs create -o mountpoint=/boot boot_pool/boot
zpool set bootfs=boot_pool/boot boot_pool
zfs mount|grep /boot

Если в результате, появится строка boot_pool /boot, пул был создан корректно, а dataset примонтирован.

zpool list boot_pool  -v

Должен вывести что-то подобное:

NAME   SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
boot_pool  1008M   220M   788M         -     7%    21%  1.00x  ONLINE  -
  mirror  1008M   220M   788M         -     7%    21%
    /dev/disk/by-id/ata-Micron_1100-part2      -      -      -         -      -      -
    /dev/disk/by-id/ata-Samsung_SSD_850_PRO-part2      -      -      -         -      -      -

Установка загрузчика

Предварительно надо проверить, что grub понимает ФС:

grub-probe /boot

Должна быть выведена строка zfs.

tar -C / -xf ~/boot.tar
ls /boot

После завершения распаковки на экран будет выведен список файлов в /boot.

Далее, обновление initramfs и установка загрузчика:

update-initramfs -k all -u
grub-install --bootloader-id=debian1 --recheck --no-floppy /dev/disk/by-id/ata-Samsung_SSD_850_PRO
grub-install --bootloader-id=debian2 --recheck --no-floppy /dev/disk/by-id/ata-Micron_1100
ZPOOL_VDEV_NAME_PATH=YES update-grub

Процесс займёт некоторое время. Загрузчик, по-идее возможно не переустанавливать, но у меня без этого не заработало.

Теперь надо перезагрузиться:

reboot

После перезагрузки zfs mount|grep /boot выведет boot_pool/boot /boot, что означает: всё прошло корректно.


Если что-то пошло не так

Достаточно загрузиться с Live USB и скопировать один из образов обратно:

mount /dev/disk/by-id/usb-Corsair_Flash_Voyager-0\:0-part1 /mnt/usb/
dd if=micron_boot of=/dev/disk/by-id/ata-Micron_1100-part2 bs=4M
umount /boot

После этого возможно грузиться с восстановленного загрузочного раздела.

Let's block ads! (Why?)

Блокировка Telegram затронула Viber и WhatsApp

image

Большое количество пользователей интернет-мессенджеров (не только Telegram) из России остались без связи из-за блокировки Telegram Роскомнадзором. Ведомство блокирует разные пулы IP в попытке закрыть доступ к сервису Павла Дурова пользователям на территории Российской Федерации. Эти действия сопровождаются «побочными эффектами» в виде нарушения работы никак не связанных с Telegram сервисов.

В пятницу стало известно, что блокировать начали часть IP-адресов, которые используются WhatsApp и Viber. Несколько позже они были удалены из черного списка, однако многие пользователи этих мессенджеров на территории РФ до сих пор не могут нормально с ними работать, пишет «Коммерсант».
Вчера, в пятницу представители Viber официально заявили, что операторы связи заблокировали доступ к серверам мессенджера, из-за чего множество людей не смогли подключиться к сервису. «Похоже, эта ситуация обусловлена решением некоторых операторов связи блокировать доступ к ряду серверов, в том числе тех, которые использует Viber. На данном этапе мы не понимаем причины этого. Повторимся: пользователи Viber страдают от решения, не связанного с Viber напрямую», — говорилось в сообщении компании.

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

Так, 98% пользователей Viber в этом регионе испытывали те либо иные проблемы с мессенджером, нормально с ним работать вчера было невозможно из-за технических сбоев. По данным Down Detector больше половины (51%) пользователей жаловались на отсутствие подключения, еще 33% не могут отправлять и получать сообщения, а у 15% возникли проблемы с авторизацией.

Представитель«Вымпелкома» сделал заявление, в котором указывалось, что блокировка сервиса напрямую связана с блокировкой IP-адресов Telegram. А вот Мегафон похвастался отсутствием сбоев: «Там, где это возможно, мы стараемся минимизировать негативное влияние блокировок на предоставление услуг пользователям. В сети «МегаФона» никаких сбоев в работе мессенджеров нет, все работает в нормальном режиме».

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

При анализе той же выгрузки становится ясно, что Роскомнадзор внес 329 IP-адресов WhatsApp и 18 IP-адресов российского хостинг-провайдера Selectel. По словам генерального директора провайдера DIPHost Филиппа Кулина, уже через час после «падения» мессенджеров, записи были удалены из реестра. Ситуация после этого улучшилась, но не намного. WhatsApp продолжает работать с перебоями, демонстрируя невозможность подключения к сети во многих случаях.

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

На данный момент аудитория WhatsApp превышает 24,6 млн. активных пользователей в месяц. У Viber показатели несколько ниже — 19,8 млн. человек.

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

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

Убытки российских компаний из-за действий Роскомнадзора составили около миллиарда долларов США. По информации Медузы, в год на систему учета данных о блокировках тратится примерно 117 млн рублей.

Let's block ads! (Why?)

Хроматическое число плоскости не меньше 5

[Перевод] Марвин Мински «The Emotion Machine»: Глава 5 «Уровни Психической Деятельности»

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

Глава 5. Уровни Психической Деятельности


«Мы, очевидно, являемся уникальным видом благодаря способности создавать символы, а также способности контролировать условия нашего существования, используя эти символы. Наша способность представлять и моделировать реальность подразумевает, что мы можем приблизительно оценивать различные порядки (разрядности) бытия и… дает нам ощущение управления собственным опытом».
— Хайнц Паджелс, «Мечты Разума» (The Dreams of Reason)

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

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

image

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

Первая глава этой главы покажет, как каждый уровень вышеприведённой диаграммы может объяснить что происходило в уме Джоан в тот момент. Мы постоянно реагируем на различные события «без размышлений», как будто мы управляемся лишь правилами «Если –> То», которые были описаны в главе §1-4. Но подобная простая схема реагирования может объяснить лишь несколько первых событий, которые разворачивались в вышеприведённом рассказе, остальные же события могут быть объяснены различной активностью, которая протекала на других уровнях мышления Джоан.

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

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

§5-1. Инстинктивные реакции


«… Это показывает, что все хвалебные отзывы о знании, как о великолепной вещи, приукрашены и лишь 1 из 40 может быть настоящим»
— Марк Твен в «Том Сойер за границей».

Джоан услышала звук и повернула её голову.

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

Как эти животные всё ещё остались в живых? Сперва, они должны были найти достаточно еды для пропитания. Затем, они должны уметь защитить себя, потому что другие животные тоже ищут еду. Для регуляции своей температуры они должны строить различные укрытия и гнёзда. У них есть стремление к размножению (а если и нет – значит их предки не развили таких механизмов), следовательно им надо искать себе пару и воспитывать молодняк. Таким образом, каждый вид должен был развить определённую машинерию, которая бы позволяла их молодняку делать различные действия и вещи, без какого0либо предыдущего знания о них.
Таким образом, это говорит о том, что они должны пользоваться некоторыми выбранными правилами «Если -> То», наподобие нижеописанных:

image
  • Если что-то прикоснулось к коже, То смахни это
  • Если это не сработало, То перемести своё тело
  • Если свет слишком яркий, То отверни лицо от источника света

Тем не менее, лишь немногие из правил «Если –> То» являются такими же простыми, как и вышеописанные, потому что большинство правил человеческого поведения зависит от ментального контекста, в котором мы сейчас находимся. Например, такое правило, как «Если ты видишь еду, То съешь её» будет заставлять съедать всю еду, которую вы видите, вне зависимости от того голодны ли вы или нужна ли она вам. Таким образом, подобные «Если» должны включать в себя отсылку к некоторым целям, например «Если ты голоден, и ты видишь еду …». В противном случае, вы будете сидеть на каждом стуле, который видите, или постоянно переключать переключатель перед которым стоите.

image

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

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

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

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

Тем не менее, когда мы сталкиваемся с наиболее сложными проблемами, простые правила «Если –> То» работать не будут, потому что нам нужно определить, как ситуация будет развиваться в дальнейшем и какой результат в итоге мы можем получить. Итак, сейчас мы кратко поговорим о трёхсоставных правилах, которые служат для предсказания каждого нашего действия.

image

Если у нас есть адекватный набор «Если –> То -> В таком случае» правил, тогда мы можем отвечать на вопрос: «Что будет если…» до того, как мы сделаем какое-либо действие. Затем, делая эту операцию циклично, мы можем представить гораздо сложное дерево решений. Мы вскоре вернёмся к обсуждению данной темы, но пока мы обсудим, как именно системы могут изучать простейшие правила «Если –> То».

§5-2. Изученные Реакции


image

Все животные рождаются с «инстинктами», такими как «отойти от быстро приближающегося объекта». Подобные встроенные реакции, как правило, хорошо служат до тех пор, пока животные остаются в окружающей среде, схожей с той, в которой эти инстинкты были приобретены. Но когда окружающая среда изменяется, эти же существа должны выработать совершенной другой способ реагировать на окружающие опасности. К примеру, когда Джоан услышала приближающуюся машину, она частично действует на основании инстинктов, но она также реагирует на основании своего знания о природе этой опасности. Но как она учится и что конкретно она изучила? Мы вернёмся к этому вопросу в конце этой книги, потому что обучение людей невероятно сложно, и здесь мы лишь вскользь упомянем, как обучение может протекать у некоторых животных. В течение 20 века, многие известные психологи имели следующие убеждения, как животные изучают новые правила «Если –> То»:

Когда животное сталкивается с новой ситуацией, оно пробует случайную последовательность действий. Затем, если какое-либо действие даёт какую-либо «награду» это действие «закрепляется». Это заставляет животное использовать данное действие в любых подобных ситуациях.

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

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

Какие аспекты последних событий нужно запоминать? Для того, чтобы «То» работало хорошо, необходимо чтобы это правило содержало только релевантные функции, потому что оно может быть сбито нерелевантными функциями. (Если вы узнали новый способ завязать узелок, то вы не должны запоминать что вы его завязали в какой-то определённый день или неделю). Как мы увидим далее в главе §8 Находчивость, если ваше описание «То» слишком специфично, то оно очень редко будет соответствовать реальным ситуациям. Но если ваше описание «То» будет слишком абстрактно, то оно будет подходить под слишком большое количество ситуаций, и в любом случае вы не сможете многому обучиться из различных ситуаций.

Например, предположим, вы хотите создать робота, который распознавал бы визуальный образ человеческой руки. Это довольно сложная задача – потому что мы никогда не видим одну и ту же картинку дважды, даже одной и той же руки, потому что каждый палец может менять его позицию и форму, мы можем рассматривать руку с разных углов зрения, и каждая часть может захватывать разное количество света. Это значит, что мы будем нуждаться в использовании триллионов правил «Если –> То», пока не сможем начать использовать некоторые трюки, которые помогут выделять только самые важные функции, или же, как мы увидим в §6-2, мы сможем формулировать высокоуровневые описания, например: «объект в виде ладони с прикреплёнными к ней пальцами».

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

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



За перевод спасибо Станиславу Суханицкому. Кто хочет помочь с переводом — пишите в личку или на почту magisterludi2016@yandex.ru
Оглавление книги The Emotion Machine
Введение
Chapter 1. Falling in Love
Chapter 2. ATTACHMENTS AND GOALS
Chapter 3. FROM PAIN TO SUFFERING
Chapter 4. CONSCIOUSNESS
Chapter 5. LEVELS OF MENTAL ACTIVITIES
Chapter 6. COMMON SENSE
Chapter 7. Thinking.
Chapter 8. Resourcefulness.
Chapter 9. The Self.

Об авторе


image

Марвин Ли Минский (англ. Marvin Lee Minsky; 9 августа 1927 — 24 января 2016) — американский учёный в области искусственного интеллекта, сооснователь Лаборатории искусственного интеллекта в Массачусетском технологическом институте. [Википедия]

Интересные факты:

  • Минский дружил с критиком Харольдом Блумом из Йельского университета (Yale University), который отзывался о нём не иначе как «зловещий Марвин Минский».
  • Айзек Азимов описывал Минского как одного из двух людей, которые умнее, чем он сам; вторым, по его мнению, был Карл Саган.
  • Марвин — робот с искусственным интеллектом из цикла романов Дугласа Адамса Автостопом по галактике и фильма Автостопом по галактике (фильм).
  • Минский имеет контракт на заморозку своего мозга после смерти для того, чтобы его «воскресили» в будущем.
  • В честь Минского назван пес главного героя в фильме Трон: Наследие. [Википедия]



Про #philtech
image

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

1. Открытые и копируемые, а не конкурентно-проприетарные.
2. Построенные на принципах самоорганизации и горизонтального взаимодействия.
3. Устойчивые и перспективо-ориентированные, а не преследующие локальную выгоду.
4. Построенные на [открытых] данных, а не традициях и убеждениях
5. Ненасильственные и неманипуляционные.
6. Инклюзивные, и не работающие на одну группу людей за счёт других.

Акселератор социальных технологических стартапов PhilTech — программа интенсивного развития проектов ранних стадий, направленных на выравнивание доступа к информации, ресурсам и возможностям. Второй поток: март–июнь 2018.

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

#philtech news
Телеграм-канал с новостями о проектах в идеологии #philtech и ссылками на полезные материалы.

Подписаться на еженедельную рассылку

Let's block ads! (Why?)