Бесконфликтный реплицируемый тип данных
В распределенных вычислениях бесконфликтный реплицируемый тип данных ( CRDT ) представляет собой структуру данных , которая реплицируется на нескольких компьютерах в сети , со следующими функциями: [1] [2] [3] [4] [5] [6] [7] [8]
- Приложение может обновлять любую реплику самостоятельно, одновременно и без координации с другими репликами.
- Алгоритм (который сам является частью типа данных) автоматически устраняет любые несоответствия, которые могут возникнуть.
- Хотя реплики могут иметь разное состояние в любой конкретный момент времени, в конечном итоге они гарантированно сходятся.
Концепция CRDT была официально определена в 2011 году Марком Шапиро, Нуно Прегуисой, Карлосом Бакеро и Мареком Завирски. [9] Первоначально разработка была мотивирована совместным редактированием текста и мобильными компьютерами . CRDT также использовались в системах онлайн-чатов , онлайн-азартных играх и на платформе распространения аудио SoundCloud . NoSQL Riak Распределенные базы данных Redis , . и Cosmos DB имеют типы данных CRDT
Фон
[ редактировать ]Одновременные обновления нескольких реплик одних и тех же данных без координации между компьютерами, на которых размещены реплики, могут привести к несогласованности между репликами, которые в общем случае могут оказаться неразрешимыми. Восстановление согласованности и целостности данных при возникновении конфликтов между обновлениями может потребовать полного или частичного удаления некоторых или всех обновлений.
Соответственно, большая часть распределенных вычислений сосредоточена на проблеме предотвращения одновременных обновлений реплицируемых данных. Но другой возможный подход — это оптимистическая репликация , при которой все одновременные обновления могут выполняться с возможным возникновением несогласованностей, а результаты объединяются или «разрешаются» позже. В этом подходе согласованность между репликами в конечном итоге восстанавливается посредством «слияния» разных реплик. Хотя оптимистическая репликация может не работать в общем случае, существует важный и практически полезный класс структур данных, CRDT, где она работает — где всегда можно без конфликтов объединить или разрешить одновременные обновления в разных репликах структуры данных. . Это делает CRDT идеальными для оптимистического репликации.
Например, односторонний логический флаг события представляет собой тривиальный CRDT: один бит со значением true или false. True означает, что какое-то конкретное событие произошло хотя бы один раз. False означает, что событие не произошло. Если флаг установлен в значение true, его нельзя вернуть обратно в значение false (произошедшее событие не может повториться). Метод разрешения — «истинный выигрыш»: при слиянии реплики, где флаг имеет значение true (эта реплика наблюдала событие), и другой реплики, где флаг имеет значение false (эта реплика не наблюдала событие), решенный результат true — событие наблюдалось.
Типы CRDT
[ редактировать ]Существует два подхода к CRDT, оба из которых могут обеспечить строгую конечную согласованность : CRDT на основе операций. [10] [11] и государственные CRDT. [12] [13]
Эти две альтернативы теоретически эквивалентны, поскольку каждая может имитировать другую. [1] Однако существуют практические различия. CRDT на основе состояний часто проще спроектировать и реализовать; единственное их требование от коммуникационного субстрата — это какой-то протокол сплетен . Их недостатком является то, что все состояние каждого CRDT в конечном итоге должно передаваться каждой второй реплике, что может быть дорогостоящим. Напротив, CRDT на основе операций передают только операции обновления, которые обычно небольшие. Однако CRDT на основе операций требуют гарантий от коммуникационного промежуточного программного обеспечения ; что операции не отбрасываются и не дублируются при передаче другим репликам и что они доставляются в причинно-следственном порядке . [1]
CRDT на основе операций
[ редактировать ]CRDT на основе операций также называются коммутативными реплицируемыми типами данных или CmRDT . Реплики CmRDT распространяют состояние, передавая только операцию обновления. Например, CmRDT одного целого числа может транслировать операции (+10) или (-20). Реплики получают обновления и применяют их локально. Операции коммутативны . Однако они не обязательно идемпотентны . Поэтому инфраструктура связи должна гарантировать, что все операции с репликой передаются другим репликам без дублирования, но в любом порядке.
исключительно на операциях CRDT, основанные [11] представляют собой вариант CRDT на основе операций, который уменьшает размер метаданных.
Государственные CRDT
[ редактировать ]CRDT на основе состояния называются конвергентными реплицируемыми типами данных или CvRDT . В отличие от CmRDT, CvRDT отправляют свое полное локальное состояние другим репликам, где состояния объединяются с помощью функции, которая должна быть коммутативной , ассоциативной и идемпотентной . Функция слияния обеспечивает соединение любой пары состояний реплики, поэтому набор всех состояний образует полурешетку . Функция обновления должна монотонно увеличивать внутреннее состояние в соответствии с теми же правилами частичного порядка, что и полурешетка.
дельта-состояния CRDT [13] [14] (или просто Delta CRDT) — это оптимизированные CRDT на основе состояния, в которых распространяются только недавно примененные изменения состояния, а не все состояние.
Сравнение
[ редактировать ]Хотя CmRDT предъявляет больше требований к протоколу для передачи операций между репликами, они используют меньшую пропускную способность, чем CvRDT, когда количество транзакций невелико по сравнению с размером внутреннего состояния. Однако, поскольку функция слияния CvRDT является ассоциативной, слияние с состоянием некоторой реплики дает все предыдущие обновления этой реплики. Протоколы Gossip хорошо работают для распространения состояния CvRDT на другие реплики, одновременно сокращая использование сети и обрабатывая изменения топологии.
Некоторые нижние границы [15] о сложности хранения CRDT на основе состояний известны.
Известные CRDT
[ редактировать ]G-Counter (Счетчик только для роста)
[ редактировать ]payload integer[n] P
initial [0,0,...,0]
update increment()
let g = myId()
P[g] := P[g] + 1
query value() : integer v
let v = Σi P[i]
compare (X, Y) : boolean b
let b = (∀i ∈ [0, n - 1] : X.P[i] ≤ Y.P[i])
merge (X, Y) : payload Z
let ∀i ∈ [0, n - 1] : Z.P[i] = max(X.P[i], Y.P[i])
Этот CvRDT реализует счетчик для кластера из n узлов. Каждому узлу в кластере присваивается идентификатор от 0 до n — 1, который извлекается с помощью вызова myId (). Таким образом, каждому узлу назначается свой собственный слот в массиве P , который он увеличивает локально. Обновления распространяются в фоновом режиме и объединяются путем получения max () каждого элемента в P . Функция сравнения включена для иллюстрации частичного порядка состояний. Функция слияния является коммутативной, ассоциативной и идемпотентной. Функция обновления монотонно увеличивает внутреннее состояние в соответствии с функцией сравнения. Таким образом, это правильно определенный CvRDT, который обеспечит строгую конечную согласованность. Эквивалент CmRDT передает операции увеличения по мере их получения. [2]
PN-счетчик (положительно-отрицательный счетчик)
[ редактировать ]payload integer[n] P, integer[n] N
initial [0,0,...,0], [0,0,...,0]
update increment()
let g = myId()
P[g] := P[g] + 1
update decrement()
let g = myId()
N[g] := N[g] + 1
query value() : integer v
let v = Σi P[i] - Σi N[i]
compare (X, Y) : boolean b
let b = (∀i ∈ [0, n - 1] : X.P[i] ≤ Y.P[i] ∧ ∀i ∈ [0, n - 1] : X.N[i] ≤ Y.N[i])
merge (X, Y) : payload Z
let ∀i ∈ [0, n - 1] : Z.P[i] = max(X.P[i], Y.P[i])
let ∀i ∈ [0, n - 1] : Z.N[i] = max(X.N[i], Y.N[i])
Общая стратегия разработки CRDT заключается в объединении нескольких CRDT для создания более сложной CRDT. В этом случае два G-счетчика объединяются для создания типа данных, поддерживающего операции как увеличения, так и уменьшения. G-счетчик «P» считает приращения; а G-счетчик «N» считает уменьшение. Значение счетчика PN равно значению счетчика P минус значение счетчика N. Слияние обрабатывается, позволяя объединенному счетчику P быть объединением двух счетчиков P G, и аналогично для счетчиков N. Обратите внимание, что внутреннее состояние CRDT должно монотонно увеличиваться, даже если его внешнее состояние, полученное через запрос, может вернуться к предыдущим значениям. [2]
G-Set (набор только для выращивания)
[ редактировать ]payload set A
initial ∅
update add(element e)
A := A ∪ {e}
query lookup(element e) : boolean b
let b = (e ∈ A)
compare (S, T) : boolean b
let b = (S.A ⊆ T.A)
merge (S, T) : payload U
let U.A = S.A ∪ T.A
G-Set (набор только для роста) — это набор, в который можно только добавлять. Добавленный элемент невозможно удалить. Слияние двух G-множеств — это их объединение. [2]
2P-Set (Двухфазный комплект)
[ редактировать ]payload set A, set R
initial ∅, ∅
query lookup(element e) : boolean b
let b = (e ∈ A ∧ e ∉ R)
update add(element e)
A := A ∪ {e}
update remove(element e)
pre lookup(e)
R := R ∪ {e}
compare (S, T) : boolean b
let b = (S.A ⊆ T.A ∧ S.R ⊆ T.R)
merge (S, T) : payload U
let U.A = S.A ∪ T.A
let U.R = S.R ∪ T.R
Два G-набора (наборы только для выращивания) объединяются в один 2P-набор. С добавлением набора удаления (называемого набором «надгробие») элементы можно добавлять и удалять. После удаления элемент нельзя добавить повторно; то есть, как только элемент e окажется в наборе захоронений, запрос никогда больше не вернет True для этого элемента. Набор 2P использует семантику «удалить-победит», поэтому удаление ( e ) имеет приоритет над добавлением ( e ). [2]
LWW-Набор-Элементов (Набор-Элементов-Выигрышей Последней Записи)
[ редактировать ]LWW-Element-Set похож на 2P-Set в том, что он состоит из «добавления набора» и «удаления набора» с временной меткой для каждого элемента. Элементы добавляются в LWW-Element-Set путем вставки элемента в набор добавления с отметкой времени. Элементы удаляются из LWW-Element-Set путем добавления в набор удаления, опять же с отметкой времени. Элемент является членом LWW-Element-Set, если он находится в добавленном наборе и либо не входит в удаляемый набор, либо в удаляемый набор, но имеет более раннюю временную метку, чем последняя временная метка в добавленном наборе. Слияние двух реплик LWW-Element-Set состоит из объединения добавляемых наборов и объединения удаляемых наборов. Когда временные метки равны, в игру вступает «смещение» LWW-Element-Set. LWW-Element-Set может быть смещен в сторону добавления или удаления. Преимущество LWW-Element-Set перед 2P-Set заключается в том, что, в отличие от 2P-Set, LWW-Element-Set позволяет повторно вставить элемент после его удаления. [2]
OR-Set (набор наблюдаемого-удаления)
[ редактировать ]OR-Set похож на LWW-Element-Set, но вместо временных меток использует уникальные теги. Для каждого элемента в наборе ведется список добавляемых тегов и список удаляемых тегов. Элемент вставляется в набор OR путем создания нового уникального тега и добавления его в список добавленных тегов для элемента. Элементы удаляются из набора OR путем добавления всех тегов из списка добавления тегов элемента в список удаления тегов (надгробия). Чтобы объединить два набора OR, для каждого элемента пусть его список добавляемых тегов будет объединением двух списков добавляемых тегов, и аналогично для двух списков удаления тегов. Элемент является членом набора тогда и только тогда, когда список добавляемых тегов за вычетом списка удаляемых тегов непуст. [2] Возможна оптимизация, которая устраняет необходимость поддержания набора захоронений; это позволяет избежать потенциально неограниченного роста набора надгробий. Оптимизация достигается за счет сохранения вектора временных меток для каждой реплики. [16]
Последовательность CRDT
[ редактировать ]Последовательность, список или упорядоченный набор CRDT можно использовать для создания совместного редактора реального времени в качестве альтернативы оперативному преобразованию (OT).
Некоторые известные CRDT последовательностей: Treedoc, [5] РГА, [17] Вуот, [4] Логотип, [18] и ЛСЭК. [19] ЯЩИК [20] — это децентрализованный редактор реального времени, созданный на основе LSEQSplit (расширение LSEQ) и запускаемый в сети браузеров с использованием WebRTC . LogootSplit [21] был предложен как расширение Logoot, чтобы уменьшить количество метаданных для последовательностей CRDT. НЕМОЙ [22] [23] — это онлайновый одноранговый редактор для совместной работы в режиме реального времени, основанный на алгоритме LogootSplit.
Известно, что промышленные последовательности CRDT, в том числе с открытым исходным кодом, превосходят академические реализации благодаря оптимизации и более реалистичной методологии тестирования. [24] Основной популярный пример — Yjs CRDT, пионер использования простого списка вместо дерева (аля автослияние Клеппмана ). [25]
Промышленное использование
[ редактировать ]- Zed — это среда разработки для совместной работы с открытым исходным кодом, созданная zed-industries, которая использует CRDT в своих «буферах» для разрешения конфликтов при совместном редактировании одного и того же файла.
- Fluid Framework — это платформа для совместной работы с открытым исходным кодом, созданная Microsoft , которая предоставляет как эталонные серверные реализации, так и клиентские SDK для создания современных веб-приложений реального времени с использованием CRDT.
- Nimbus Note — это приложение для совместного создания заметок, которое использует Yjs CRDT для совместного редактирования. [26]
- Redis — это распределенная, высокодоступная и масштабируемая база данных в памяти с функцией «база данных с поддержкой CRDT». [27]
- SoundCloud открыл исходный код Roshi — CRDT с набором LWW-элементов для потока SoundCloud, реализованный поверх Redis. [28]
- Riak — это распределенное хранилище данных «ключ-значение» NoSQL, основанное на CRDT. [29] League of Legends использует реализацию Riak CRDT для своей внутриигровой системы чата, которая обслуживает 7,5 миллионов одновременных пользователей и 11 000 сообщений в секунду. [30]
- Bet365 хранит сотни мегабайт данных в реализации OR-Set от Riak. [31]
- TomTom использует CRDT для синхронизации навигационных данных между устройствами пользователя. [32]
- Phoenix , веб-фреймворк, написанный на Elixir , использует CRDT для поддержки обмена информацией между несколькими узлами в режиме реального времени в версии 1.2. [33]
- Facebook внедряет CRDT в свою базу данных Apollo с малой задержкой и «согласованностью в масштабе». [34]
- Facebook использует CRDT в своей системе FlightTracker для внутреннего управления графиком Facebook. [35]
- Teletype для Atom использует CRDT, чтобы разработчики могли делиться своим рабочим пространством с членами команды и совместно работать над кодом в режиме реального времени. [36]
- OrbitDB от Haja Networks использует CRDT на основе операций в своей базовой структуре данных IPFS-Log. [37]
- Apple реализует CRDT в приложении Notes для синхронизации автономных изменений между несколькими устройствами. [38]
- Swim — это платформа для запуска распределенных потоковых приложений в реальном времени, обеспечивающих непрерывную аналитику. Он использует потоковые актеры, которые передают обновления состояния CRDT на основе операций другим актерам в группе обеспечения доступности баз данных, которая реализует конвейер потоковой передачи данных.
- RxDB — это клиентская база данных NoSQL для распределенных потоковых приложений в реальном времени. Он имеет плагин CRDT , который позволяет обновлять документ, сохраняя дельты CRDT на основе NoSQL и реплицируя их с другими клиентами или внутренним сервером.
- PGD — это решение репликации с несколькими хозяевами, основанное на PostgreSQL. Он поддерживает типы столбцов CRDT .
См. также
[ редактировать ]- Синхронизация данных
- Совместные редакторы в реальном времени
- Модели согласованности
- Оптимистическая репликация
- Операционная трансформация
- Самостабилизирующиеся алгоритмы
Ссылки
[ редактировать ]- ^ Перейти обратно: а б с Шапиро, Марк; Прегиса, Нуно; Бакеро, Карлос; Завирски, Марек (2011). «Бесконфликтные реплицируемые типы данных». Стабилизация, безопасность и защищенность распределенных систем (PDF) . Конспекты лекций по информатике. Том. 6976. Гренобль, Франция: Springer Berlin Heidelberg. стр. 386–400. дои : 10.1007/978-3-642-24550-3_29 . ISBN 978-3-642-24549-7 . S2CID 51995307 .
- ^ Перейти обратно: а б с д и ж г Шапиро, Марк; ЛЕНЬ, Нуно; БАКУЭРО, Карлос; Завирски, Марек (13 января 2011 г.). «Комплексное исследование конвергентных и коммутативных реплицируемых типов данных». Рр-7506 .
- ^ Шапиро, Марк; Прегиса, Нуно (2007). «Проектирование коммутативного реплицируемого типа данных». arXiv : 0710.1784 [ cs.DC ].
- ^ Перейти обратно: а б Остер, Джеральд; Урсо, Паскаль; Молли, Паскаль; Имин, Абдессамад (2006). Материалы 20-й юбилейной конференции 2006 г. по совместной работе с компьютерной поддержкой - CSCW '06 . п. 259. CiteSeerX 10.1.1.554.3168 . дои : 10.1145/1180875.1180916 . ISBN 978-1595932495 . S2CID 14596943 .
- ^ Перейти обратно: а б Летия, Михай; Прегиса, Нуно; Шапиро, Марк (2009). «CRDT: согласованность без контроля параллелизма». Репозиторий компьютерных исследований . arXiv : 0907.0929 .
- ^ Прегиса, Нуно; Маркес, Жоан Мануэль; Шапиро, Марк; Летиа, Михай (июнь 2009 г.), «Коммутативный реплицируемый тип данных для совместного редактирования» (PDF) , Материалы 29-й Международной конференции IEEE по распределенным вычислительным системам , Монреаль, Квебек, Канада: Компьютерное общество IEEE, стр. 395–403, doi : 10.1109/ICDCS.2009.20 , ISBN 978-0-7695-3659-0 , S2CID 8956372
- ^ Бакеро, Карлос; Моура, Франциско (1997), Спецификация конвергентных абстрактных типов данных для автономных мобильных вычислений , Университет Минхо
- ^ Шнайдер, Фред (декабрь 1990 г.). «Реализация отказоустойчивых сервисов с использованием подхода конечного автомата: учебное пособие» . Обзоры вычислительной техники ACM . 22 (4): 299–319. дои : 10.1145/98163.98167 . S2CID 678818 .
- ^ «Бесконфликтные реплицируемые типы данных» (PDF) . inria.fr. 19 июля 2011 г.
- ^ Летия, Михай; Прегиса, Нуно; Шапиро, Марк (1 апреля 2010 г.). «Согласованность без управления параллелизмом в больших динамических системах» (PDF) . СИГОПС Опер. Сист. Преподобный . 44 (2): 29–34. дои : 10.1145/1773912.1773921 . S2CID 6255174 .
- ^ Перейти обратно: а б Бакеро, Карлос; Алмейда, Пауло Сержио; Шокер, Али (3 июня 2014 г.). «Превращение CRDTS, основанного на операциях, на основе операций». В Магутисе, Костас; Пицух, Питер (ред.). Распределенные приложения и взаимодействующие системы . Конспекты лекций по информатике. Том. 8460. Шпрингер Берлин Гейдельберг. стр. 126–140. CiteSeerX 10.1.1.492.8742 . дои : 10.1007/978-3-662-43352-2_11 . ISBN 9783662433515 .
- ^ Бакеро, Карлос; Моура, Франциско (1 октября 1999 г.). «Использование конструктивных характеристик для автономной работы». СИГОПС Опер. Сист. Преподобный . 33 (4): 90–96. дои : 10.1145/334598.334614 . hdl : 1822/34984 . S2CID 13882850 .
- ^ Перейти обратно: а б Алмейда, Пауло Сержио; Шокер, Али; Бакеро, Карлос (13 мая 2015 г.). «Эффективный CRDTS на основе состояния посредством дельта-мутации». В Буаджани, Ахмед; Фоконье, Хьюг (ред.). Сетевые системы . Конспекты лекций по информатике. Том. 9466. Международное издательство Springer. стр. 62–76. arXiv : 1410.2803 . дои : 10.1007/978-3-319-26850-7_5 . ISBN 9783319268491 . S2CID 7852769 .
- ^ Алмейда, Пауло Сержио; Шокер, Али; Бакеро, Карлос (04 марта 2016 г.). «Реплицированные типы данных дельта-состояния». Журнал параллельных и распределенных вычислений . 111 : 162–173. arXiv : 1603.01529 . дои : 10.1016/j.jpdc.2017.08.003 . S2CID 7990602 .
- ^ Буркхардт, Себастьян; Гоцман, Алексей; Ян, Хонсок; Завирски, Марек (23 января 2014 г.). «Реплицируемые типы данных: спецификация, проверка, оптимальность». Материалы 41-го симпозиума ACM SIGPLAN-SIGACT по принципам языков программирования (PDF) . стр. 271–284. дои : 10.1145/2535838.2535848 . ISBN 9781450325448 . S2CID 15023909 .
- ^ Биениуса, Аннетт; Завирски, Марек; ЛЕНЬ, Нуно; Шапиро, Марк; БАКУЭРО, Карлос; Балегас, Уолтер; ДУАРТЕ, Сержио (2012). «Оптимизированный бесконфликтный реплицируемый набор». arXiv : 1210.3368 [ cs.DC ].
- ^ Рох, Хуйн-Гуль; Чон, Мёнджэ; Ким, Джин Су; Ли, Джунвон (2011). «Реплицированные абстрактные типы данных: строительные блоки для приложений для совместной работы». Журнал параллельных и распределенных вычислений . 71 (2): 354–368. дои : 10.1016/j.jpdc.2010.12.006 .
- ^ Вайс, Стефан; Урсо, Паскаль; Молли, Паскаль (2010). «Logoot-Undo: распределенная система совместного редактирования в одноранговых сетях». Транзакции IEEE в параллельных и распределенных системах . 21 (8): 1162–1174. дои : 10.1109/TPDS.2009.173 . ISSN 1045-9219 . S2CID 14172605 .
- ^ Неделек, Брис; Молли, Паскаль; Мостефауи, Ашур; Демонтиль, Эммануэль (2013). «LSEQ: адаптивная структура для последовательностей при распределенном совместном редактировании». Материалы симпозиума ACM по документальной инженерии 2013 г. (PDF) . стр. 37–46. дои : 10.1145/2494266.2494278 . ISBN 9781450317894 . S2CID 9215663 .
- ^ Неделек, Брис; Молли, Паскаль; Мостефауи, Ачур (2016). «CRATE: написание историй вместе с нашими браузерами». Материалы 25-й Международной конференции «Спутник по всемирной паутине» . п. 231. дои : 10.1145/2872518.2890539 . S2CID 5096789 . Архивировано из оригинала 01 января 2020 г. Проверено 01 января 2020 г.
- ^ Андре, Люк; Мартин, Стефан; Остер, Джеральд; Игнат, Клаудия-Лавиния (2013). «Поддержка адаптируемой детализации изменений для крупномасштабного совместного редактирования». Материалы Международной конференции по совместным вычислениям: сети, приложения и совместная работа — CollaborateCom 2013 . стр. 50–59. doi : 10.4108/icst.collaboratecom.2013.254123 . ISBN 978-1-936968-92-3 .
- ^ "НЕМОЙ" . Береговая команда. 24 марта 2016 г.
- ^ Николя, Матье; Эльвингер, Викториен; Остер, Джеральд; Игнат, Клавдия-Лавиния; Шаруа, Франсуа (2017). «MUTE: одноранговый веб-редактор для совместной работы в режиме реального времени». Материалы панелей, демонстраций и плакатов ECSCW 2017 . дои : 10.18420/ecscw2017_p5 . S2CID 43984228 .
- ^ Нежный, Сеф. «Быстрые CRDT: приключение в оптимизации» . josephg.com . Проверено 1 августа 2021 г.
- ^ «yjs/yjs: общие типы данных для создания программного обеспечения для совместной работы» . Гитхаб .
- ^ «О ЦРДЦ» . Проверено 18 июня 2020 г.
- ^ «Погружение в CRDT» . Редис . Проверено 22 мая 2024 г.
- ^ Бургон, Питер (9 мая 2014 г.). «Роши: система CRDT для событий с метками времени» . Саундклауд.
- ^ «Представляем Riak 2.0: типы данных, строгая согласованность, полнотекстовый поиск и многое другое» . Basho Technologies, Inc., 29 октября 2013 г.
- ^ Хофф, Тодд (13 октября 2014 г.). «Как League of Legends увеличила чат до 70 миллионов игроков — для этого нужно много миньонов» . Высокая масштабируемость .
- ^ Маклин, Дэн. «bet365: Почему bet365 выбрал Риак» . Басё.
- ^ Иванов Дмитрий. «Практическая демистификация CRDT» .
- ^ МакКорд, Крис (25 марта 2016 г.). «Что делает Phoenix Presence особенным» .
- ^ Мак, Сандер. «Facebook объявляет об Apollo на QCon NY 2014» .
- ^ «FlightTracker: единообразие в интернет-магазинах Facebook, оптимизированных для чтения» . исследование.facebook.com . Проверено 8 декабря 2022 г.
- ^ «Комбинируйте код в режиме реального времени с помощью Teletype для Atom» . Атом.ио. 15 ноября 2017 г.
- ^ «OrbitDB/ipfs-log на Github» . Гитхаб . Проверено 7 сентября 2018 г.
- ^ «Заголовки IOS Objective-C, полученные в результате самоанализа во время выполнения: NST/IOS-Runtime-Headers» . Гитхаб . 25 июля 2019 г.