пятница, 21 июня 2019 г.

Секретное поле в PerformanceNodeTiming

     Пока разбирался с PerformanceNodeTiming обнаружил там недокументированное поле environment, содержащее время когда закончилась инициализация структуры environment (относительно времени старта процесса). В очередной раз наконтрибьютил в ноду - https://github.com/nodejs/node/pull/28280  Этот pull request прошел в мастер довольно быстро. Мой предыдущий pull request c новым методом в модуле v8 до сих пор ждет мерджа.

вторник, 18 июня 2019 г.

PerformanceTiming API

      PerformanceTimingAPI - довольно занятное API, появившееся еще в версии 8.5 но почему-то обделенное вниманием js программистов. К примеру это API позволяет относительно легко (без погружения в C++ код) замониторить GC, ну и много чего еще.  Возможно причина тому - не очень хорошая документация, да и сам API нельзя назвать интуитивно понятным. Я считаю себя в общем-то не глупым парнем, но я разобрался в нем не с первого раза. Поэтому я и решил потратить время на написание этой статьи.
В основе интерфейса PerformanceTimingAPI (или perf_hooks как оно называется в исходникахлежит иерархия классов основанная на PerformanceEntry. PerformanceEntry - это нечто что ты можешь измерить. У PerformanceEntry есть поле entryType которое собственно показывает что же ты измерил. Для того чтобы получать PerformanceEntry тебе нужно с помощью PerformanceObserver подписаться на них. 
       Если у PerformanceEntry поле entryType="mark" то мы имеем дело с отметиной на timeline.  startTime = время когда произошло это событие, duration = 0, name = имя события. Единственный способ получить mark - самим создать его с помощью фукнции performance.mark("Start of something")
       Единственный сценарий использования performance mark - создать два performance.mark и измерить время между ними используя performance.measure(measureName, startMark, endMark). Этот вызов возвращает нам еще один PerformanceEntry с типом measure. В общем такая красивая и бестолковая обертка вокруг timestamp. 
    Далее следует PerformanceEntry с типом function. Этот тип PerformanceEntry можно получить если обернуть некоторую функцию в мониторинг (через performance.timerify(fn)). В этом случае каждый раз когда функция вызовется будет создан новый PerformanceEntry с типом function описывающий время выполнения функции. Функция timrify использует кастомные атрибуты Object.defineProperty() что негативно сказывается на скорости работы с объектом.
        PerformanceEntry с типом gc - описывает запуск сборщика мусора. В этом случае поле kind будет указывать на тип сборки- major, minor, incremental и тд. На самом деле PerformanceEntry  с типом gc это отдельный класс в C++ коде - GCPerformanceEntry. По каким-то причинам разработчики решили не экспортировать его в мир JS. 
        PerforamanceEntry с типом http2 предназначены для мониторинга http2 сессий и потоков. Если name = HTTP2Stream то этот PerformanceEntry описывает создание нового http2 stream. Если name = HTTP2Session - то соответсвенно сессии.
      Напоследок расскажу о самом сомнительном на мой взгляд архитектурном решении - PerformanceNodeTiming. Он наследуется от PerformanceEntry но у своего родителя он использует только  два поля - entityType = node и duration. Все остальные поля его собственные - они описывают процесс инициализации Node.js: сколько времени потратили на инициализацию процесса ноды(nodeStart), сколько потратили на инициализацию v8 (v8Start), сколько потратили на инициализацию environment-а(поле так и называется environment и оно почему-то не документировано),  сколько времени потратили на bootstrap (bootstrapComplete), сколько времени потребовалось для запуска event loop (loopStart). 
        В отличии от всех остальных PerformanceEntry, работа с PerformanceNodeTiming ведется  не через PerformanceObserver, а через статическое поле performance.nodeTiming. Если ты подпишешься на PerformanceObserver с типом "node" то ты не получишь ничего.  Второй момент - мне не совсем понятно как мониторить инициализацию воркеров. Ведь у них как минимум свой environment и свой eventLoop который также будет инициализироваться. А если я два воркера запускаю одновременно ? В общем это лишний раз доказывает что singleton это антипаттерн проектирования. 

вторник, 11 июня 2019 г.

libuv internals и как эта библиотека интегрирована в Node.js

       С libuv сложилась такая парадоксальная ситуация - про нее все одновременно знают и не знают. Все знают что это нечто используемое Node.js для работы с сокетами и файловой системой и нечто обладающее магическим event loop. Ну а дальше показания расходятся. Причем даже у "экспертов" выступающих на конференциях. 
        В документации libuv есть довольно красивая и но малоинформативная диаграмма:

На мой взгляд функциональность предоставляемая libuv очень просто и понятно описана вот тут https://github.com/danbev/learning-libuv libuv можно использовать для того чтобы:
  • Работать с сетью (tcp, upd, pipe и тд)
  • Асинхронно работать с файловой системой
  • Асинхронно работать с DNS
  • Работать с нитями
  • Обрабатывать сигналы
  • Работать с таймерами

Это довольно большой список, но на самом деле интерфейс libuv основывается всего на трех концепциях: event loop(uv_loop_t), handle (uv_handle_t), request(uv_request_t):
  • event loop - это то, чем собственно занят поток выполнения. бегаем по кругу, дергаем callback-и. Детальнее опишу ниже.
  • handle  представляет собой некий ресурс. Это может быть обертка вокруг ресурса операционной системы (сокет, файловый дескриптор) или просто нечто вроде таймера умеющее дергать callback   
  • request - это некий запрос. Он может использовать хендлер (запрос на запись в файл) или может не использовать хендлеры.
Также внутри libuv сидит thread pool но я бы рассматривал его как деталь реализации а не часть интерфейса. Thread pool используется для превращения синхронных действий в асинхронные и в настоящее время используется только при работе с файловой системой и DNS. Ну еще мы сами момжем попросить libuv выполнить какую-то работу асинхронно в thread pool а не в основном потоке исполнения.
        Так вот, что же из себя представляет event loop ? На мой взгляд его проще всего описать вот такой диаграммой: 
Все прямоугольнички нарисованные на этой диаграмме детально описаны вот тут - https://github.com/libuv/libuv/blob/v1.x/docs/src/design.rst#the-io-loop
Я же хочу остановится на том как это все интегрировано в Node.js. Практически вся работа с libuv сосредоточена в evn.cc. Начинается все с void Environment::InitializeLibuv() - тут мы инициализируем таймеры, check handles и тд. 
        Теперь собственно посмотрим что из прямоугольничков оригинального event loop'а используется в Node.js:
  • Первое это таймеры. При инициализации Node.js создает один libuv таймер который запускает все JS таймеры. Время срабатывания таймера - минимальное среди всех установленных JS таймеров
  • Idle handlers в мир JavaScript никак не транслируются. Они используются внутри для управления логикой которую libuv использует для рассчета времени которое она должна провести заблокированной в poll вызове
  • Prepare handles - также в мир JavaScript не транслируются. Там стоит системный хендлер который служит для измерения времени которое мы фактически проведем заблокированными в poll вызове
  • Check handles - здесь Node.js также вешает один хэндлер который выполняет все callback-и установленные через setImmediate()
  • Close handlers - также не транслируются в JS. Здесь libuv запускает on close callback-и закрываемых ресурсов (хендлеров). 
В общем как-то так. Вообще по моему скроменому мнению - libuv одна из самых понятно написанных библиотек. Тут довольно интересно организорвано наследование структур - через макросы. Все поля структуры объявляются внутри макроса. Соответсвенно в дочерней структуре сначала идет макрос объявляющий родительские поля, а потом макрос объявляющий собственные поля:







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

V8 internals - отслеживание ссылок на объекты в куче V8

       То что спецификацию (как минимум первые версии) для языка JavaScript писали не очень умные люди с лихвой компенсируется мозгами тех кто пишет V8.  Чем больше разбираюсь в проекте - тем больше восхищаюсь этими людьми. И так - как же GC V8 узнает о ссылках на объекты в куче из C++ кода ? 
         Вся работа с объектами размещенными в куче построена на так называемых хендлерах. Так как GC может перемещать объекты размещенные в куче во время сборки мусора прямая работа с этими объектами (по указателю) запрещена. Все обращения только через хендлеры. Этих хендлеров есть несколько типов, все они объявлены в v8.h
  • Local - это хендлер время жизни которого ограничено временем жизни области видимости в которой он объявлен.  Как только мы выходим из области видимости - стэк отматывается и хендлер становится недействительным.
  • Persistent - это хендлер время жизни которого не контролируется областью видимости. Живет до тех пока явно не вызовешь Reset()
  • Global - тоже самое что и Persistent но c поддержкой move семантики
  • Eternal - это хендлер используемый для "вечных" ссылок. То есть ссылок живущих до тех пор пока жив Isolate. Они немного более удобны с точки зрения GC чем Persistent 
  • TracedGlobal - тоже самое что и Global но с подержкой трассировки GC. Для чего он может использоваться честно до конца не разобрался
Вся магия по управлению Local хэндлерами через область видимости реализуюется в классе HandleScope и Isolate.  Инстанс HandleScope обычно создается до создания первого хендлера. Все созданные после этого Local хэндлеры создаются в этой области видимости. HandleScope могут быть вложенными. При выходе за границы области видимости вызывается деструктор HandleScope  в котором происходит вызов HandleScope::CloseScope() который подчищает Local хэндлеры созданные в этой области видимости.  
        Тут собственно и начинается самое интересное. Выясняется что внутри Isolate (экземпляр виртуальной машины V8 со своей кучей и всем остальным) есть список буферов в которых собственно размещаются указатели на объекты размещенные в куче. val_ который хранится в Local хендлере это указатель на этот адрес, размещенный в буфере Isolate. Каждый раз когда мы создаем новый хендлер HandleScope::CreateHandle() выделает в буфере класса Isolate новый указатель который указывает на кучу. А возвращает указатель на этот указатель. Собственно когда нужно собрать мусор, GC пробегает по этому списку буферов заполнгенному указателями на кучу и помечает объекты как используемые. При перемещении объектов GC также меняет указатели если кто-то ссылается на этот объект. В общем получается весьма эффективная система.

четверг, 6 июня 2019 г.

V8 internals - хранение объектов и массивов. Инициализация глобального контекста

       Итак, я продолжаю свои упражнения в археологии. Для начала меня заинтересовал вопрос - вот есть у меня виртуальная машина, а как в ней появляются все эти прекрасные "встроенные функции" ? Собственно в этой статье это довольно подробно объясняется - https://v8.dev/blog/custom-startup-snapshots  Если в кратце то  в папочке tools есть утилита js2c.py которая конвертирует javascript код из папочки lib в бинарный массив который можно напрямую загрузить в V8 Heap.  Таким образом инициализация контекста (объект global) очень сильно ускоряется.
        Что касается хранения объектов в памяти V8 (memory layout) - по этой теме есть много статей, и даже доклад от Fedor Indutnyhttps://www.youtube.com/watch?v=tLyIs_0cUyc но на этот раз меня его доклад не впечатлил. Он прошелся по верхушкам и прорекламировал llnode - node.js плагин для lldb. Большое спасибо Федору за инструмент, но тема memory layout в его докладе была раскрыта на 10% не больше. Лучше всего это объяснено в этой статье - https://v8.dev/blog/fast-properties В ней акцент сделан на хранении свойств, а в следующей статье https://v8.dev/blog/elements-kinds более подробно объяснено хранение элементов массива.   Вообще в статьях очень много полезной информации по оптимизации - как писать код так чтобы V8 его быстрее исполнял. Ведь благодаря создателям JavaScript - одну и ту же вещь там можно написать 20 способами. Вообще мне кажется что люди проектировавшие JavaScript изначально - прямо-таки ненавидили разработчиков компиляторов/интерпретаторов и сделали все чтобы затруднить разработку компиляторов/интерпретаторов для JavaScript. 
В общем обе статьи я бы рекомендовал прочитать каждому JS разработчику стремящемуся писать оптимальный код. Для тех кто не в ладах с английским языком перескажу в кратце:
  • Если вы хотите чтобы доступ к свойствам объекта происходил быстро - явно определяйте все его свойства при создании объекта. Определяйте их в одном и том же порядке(в конструкторе). Не добавляйте новые свойства по ходу использования объекта. Не используйте Object.defineProperty() если у вас нет для этого большой необходимости. 
  • Избегайте полиморфизма. То есть передайвайте в функцию только аргументы одного типа. 
  • Не храните элементы разных типов в одном массиве. Явно приводите их к одному виду.
  • Избегайте разряженных массивов (массивов с пропусками). Сплошные массивы работают намного быстрее
  • Не пытайтесь обращаться к несуществующим элементам массива (требует обращения к prototype)

среда, 5 июня 2019 г.

V8 internals/Кишки V8

       Продолжаю разбираться как внутри устроен V8. Из интересного - доклад про Ignition - интерпретатор JS который V8 использует при первом исполнении JS кода:

При многократном выполнении код компилируется оптимизирующим компилятором TurboFan (с ним пока не разбирался).
    Начал копать memory layout по исходникам. Начал с src/objects.h - там общая иерархия объектов описана.  Если в кратце то V8 имеет следующие типы объектов(OBJECT_TYPE_LIST):

  • Smi - immediate small integer 
  • LayoutDescriptor
  • HeapObject - superclass for everything allocated in the heap
  • Primitive
  • Number
  • Numeric
В V8 используется хитрая схема тэгирования указателей (tagging scheme) -  так как все что они читают/пишут из памяти выровненно по границе машинного слова (4/8 байт) то младшие биты указателя всегда должны быть равны нулю. Они используют их для тэгирования значения хранящегося в области памяти. Если младший бит ноль - то это вовсе не указатель а Smi -целочисленное значение хранящиеся в верхних 31 бите указателя. Если младший бит равен 1 - то это указатель на HeapObject. Второй с конца бит используется для тэгирования WeakObject. Если второй бит равен 1 то это указатель на WeakHeapObject. 
Реализация лежит в include/v8-internal.h
 В добавок ко всему этому если архитектура 64-битная то Node.js умеет сжимать указатели (также как и Java Runtime).  
       Из источников информации отдельно хочется отметить https://github.com/danbev/learning-v8 Автор провел просто фантастическую работу по разбору кишков V8 с отладчиком в руках. И не поленился все это задокументировать. Вообще сама по себе идея документировать то что ты исследуешь в github очень крутая. У этого же автора еще штук пять подобных репозиториев где он разбирает libuv, node.js и прочее. 
      Еще для понимания очень полезным оказался V8’s Object Model Using Well-Defined C++  

понедельник, 3 июня 2019 г.

Этот день мы приближали как могли...

    Этот пост я пишу из поезда Ульяновск-Москва. Мало кто знает но я как минимум раньше очень часто ездил на поездах. Практически каждые выходные катал из Москвы в Ульяновск и обратно. Это продолжалось долгие семь лет (с перерывом на полтора года Вьетнама). И все это время я думал - когда же в поезде появится интернет ??? Так вот в купейных вагонах ТКС он наконец-то появился. Бесплатный. Я практически уверен что на просмотр фильма его не хватит - но для серфинга более чем достаточно. Я не знаю каких усилий потребовала реализация подобного проекта в условиях бюрократии РЖД - но я думаю не малых. В общем респект тем кто это сделал. А то что это еще и бесплатно - это в двойне приятно!

четверг, 30 мая 2019 г.

Внутренности Node.js

            Не могу не поделится классным одним докладом по внутренностям Node.js:
Судя по акценту парень говорит на русском лучше чем на английском. Слайды можно посмотреть http://jsconfbp.indutny.com/#/ 
Не смотря что доклад 4-х летней давности, основные моменты он раскрывает. Все ObjectWrap, AsyncWrap, TCPConnectWrap до сих пор используются в коде ноды. Также раскрывается то как инициализируются встроенные модули написанные на плюсах. 
Ну и напоследок не могу не похвастаться - мой первый пул реквест в node.js попал в мастер - https://github.com/nodejs/node/pull/27933 
Второй на рассмотрении https://github.com/nodejs/node/pull/27978 

среда, 29 мая 2019 г.

Мониторинг Node.js

      В последнее время озадачился мониторингом Node.js приложений. И понял что там все довольно печально. Начал контрибьютить в несколько довольно известных npm модулей дабы исправить эту ситуацию. Пока дела обстоят с переменным успехом:

  • Удалось добавить статистики в node-gcstats https://github.com/dainis/node-gcstats/pull/38#issuecomment-496421764 Научил  этот NPM модуль возвращать number_of_native_contexts() / number_of_detached_contexts().  В нормальном приложении number_of_detached_contexts() должно быть равно 0. Если в вашем приложении это не так - у вас большие проблемы с утечкой памяти.
  • Не удалось пропихнуть пул реквест в Node.js Prometheus клиент - к сожалению не могу убедить автора что breaking changes это плохо и лучше их избегать - https://github.com/siimon/prom-client/pull/260 В общем если кто-то читает этот блог - плюсаните пожалуйста pull реквест, может это сподвигнет автора принять его.

вторник, 28 мая 2019 г.

Node.js

          Последние лет 5 я был очень погружен в бэкэнд и инфраструктуру и все что происходило с фронтенд технологиями обходило меня стороной. В первую очередь Node.js. Сейчас у меня есть чуть больше чем дохрена свободного времени и я решил разобраться что же это такое. 
          Если совсем просто - взяли библиотеку под названием V8 которая может выполнять JS код. Прикрутили к ней другую библиотеку - libuv,  добавили немного обвязки по вкусу:
Если сравнить с  runtime других языков программирования - мягко говоря не густо. Но на мой взгляд в этой простоте - единственное достоинство ноды. Ну и еще npm - огонь. Все остальное я бы записал в недостатки. 
         Главный из них - однопоточность ноды. Нода однопоточна потому что V8 создавался как движок для браузера, а в браузере весь JS исполняется в одном потоке. И на клиенте это всех в принципе устраивает.  Но для server-side это на мой взгляд это большой недостаток. Львиная часть всех статей про производительность ноды повторяет одну фразу - не занимайте event loop.  Не выполняйте никаких долгих операций, не загружайте большие JSON. Только представьте, Node.js которая создана для  JS не может кушать большие куски JSON. JS не может осилить JSON, Карл! В общем это второе пришествие корпоративной многозадачности для тех кто не успел по программировать для Windows 3.11
         Второй большой недостаток ноды - это JavaScript. Да, вы не ослышались. JavaScript не плохо подходит для написание небольших кусков кода для клиента, где сборщик мусора по большому счету не нужен. Но когда это все крутится на backend - сборка мусора и ее скорость становится довольно критичной. В V8 реализовали очень приличный сборщик мусора, конкурентный mark and sweep, с поддержкой поколений и отдельной кучей для больших объектов - https://v8.dev/blog/trash-talk Но если на объект остаются ссылки - то даже самый лучше сборщик мусора не соберет этот объект. 
            Структура языка JavaScript провоцирует создание утечек памяти. Куча колбеков и анонимных функций. Каждый объект замыкания - это ссылка на все переменные находящиеся в области видимости. Замыканий много, переменный находящиеся в области видимости далеко не всегда прописываются в коде явным образом. Поэтому отследить образование нежелательных ссылок на объекты очень трудно.

четверг, 23 мая 2019 г.

Базовое знание OS и сетевых протоколов

        Практически на всех своих собеседованиях я задаю простой вопрос: Представь я открываю браузер и ввожу в адресную строку www.ozon.travel и нажимаю enter. Что при этом происходит?     
      Этот вопрос с одной стороны простой, с другой стороны - он позволяет быстро понять насколько человек знает сразу две области - работу операционных систем и работу сетевых протоколов. Для разных позиций я требую разную глубину понимания этого процесса. 
         Для ручного тестировщика достаточно просто знания что есть dns, есть http, есть tcp и ip и что они вместе работают как-то так. 
       Для разработчика/тестировщика-автоматизатора обязательно знание того как просиходит резолвинг имен, как происходит установление tcp коннекта и 100% понимание того как работает HTTP. 
       Для админа добавляется знание о том что есть системные вызовы, есть процессы, основные настройки влияющие на прохождение этого flow, базовые понятия о маршрутизации. Какие возможны сценарии закрытия TCP соединения, TCP slow start, TCP congestion control, и тд.
       Долгое время я считал что какого-то одного источника, который все это сразу сможет объяснить - не существует. Но не давно наткнулся на лекцию своего бывшего коллеги Владимира Иванова, которую тот давал еще в Яндексе - https://events.yandex.ru/lib/talks/2336/
Всем кто не смотрел - всячески рекомендую.  Он очень простыми и понятными словами доносит весьма сложные для понимания вещи - начиная от работы системных вызовов и заканчивая работой протоколов маршрутизации.  
     

пятница, 8 февраля 2019 г.

Selenoid/Moon

      Раньше у людей отвечающих за QA инфраструктуру было любимое развлечение - по приходу в новую компанию руками разворачивать инфраструктуру для Selenium автотестов. Сделать это хорошо довольно сложно а времени на поддержку инфраструктурных проектов вечно не хватает. Поэтому веселье обычно растягивалось минимум на пол года. Сейчас похоже лавочку прикрыли.  С приходом кубера в широкие массы развернуть Selenoid можно не запачкав ручки - все за вас сделает Moon. По крайней мере обещает что сделает :-)

вторник, 5 февраля 2019 г.

CLR via C# by Jeffrey Richter

       Наконец-то дочитал книгу CLR via C# by Jeffrey Richter. Скажу честно - дочитал через силу. Прям заставлял себя дочитывать.  В книге очень много воды и сомнительных рекомендаций по оптимизации.  Временами автор даже сваливается в какие-то внутри мелкософтовские разборки(как например при обсуждении EAP - event based asynchronous pattern).  
      В общем из плюсов - очень много информации по .NET CLR в одном месте, объяснены некоторые интересные аспекты работы .NET CLR. Из минусов - очень много воды, объяснения очевиднейших вещей, пересказывания мануалов. В общем временами читается как инструкция к стиральной машинке. 
        Если говорить о том мнении которое я составил о C# после прочтения этой книги: C# и его runtime писались с оглядкой на Java, и они всегда старались сделать немного лучше чем сделано в Java.  Иногда это получалось, но в некоторых случаях Java выигрывает за  счет открытости платформы и того что более качественные идеи рождаются и реализуются в community. 
     Параллельное программирование на .NET никогда не будет таким же эффективным как в Golang или другими языками где идея легковесных потоков была заложена с основания языка. Причина этого не в технологиях - я уверен что в microsoft достаточно компетенции написать scheduler легковесных потоков  а в legacy - то что раньше было возможно запускать потоки операционной системы с разными контекстами (правами, приоритетами и тд). И каждый раз при переключении на другой таск ThreadPool обязан все эти огромные структуры данных копировать. Добавьте к этому поддержку иерархической системы тасков которая требует большого количества памяти и хранение не перехваченных exceptions. 
       В общем отказаться от поддержки всего этого они не могут, а без этого просто взять и вкрутить планировщик легковесных потоков в существующий runtime будет очень трудно/не возможно.

среда, 30 января 2019 г.

Thanos

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

вторник, 15 января 2019 г.

Как работает ApplePay

     Не так давно мне пришлось вплотную столкнуться с ApplePay, хотя вообще я с платежками давно уже не работаю. Попытался понять как работает ApplePay и с удивлением обнаружил что  нормального технического описания нет. Есть куча маркетингового булшита и отдельных догадок.  Единственное что я нашел - https://medium.freecodecamp.org/how-apple-pay-works-under-the-hood-8c3978238324  Это тоже не официальная документация, но выглядит вполне правдоподобно.
       Если в двух словах - когда вы добавляете карточку в ApplePay, Apple валидирует данные и отправляет к МПС(Visa/MasterCard) запрос на генерацию секретного токена(DAN - device account number), который по сути представляет "фейковый" номер кредитной карточки. Этот фейковый номер запоминается в устройстве. Во время транзакции iphone "прикидывается" этой фейковой карточкой и NFC терминал передает этот номер карты в МПС. МПС подменяет фейковый номер карточки реальным номером карточки добавленной ранее в ApplePay. Дальше транзакция проходит как обычно.