Tiny Deltas, большие победы: без схем платчик для платформы на планете Scale

Tiny Deltas, большие победы: без схем платчик для платформы на планете Scale

25 июня 2025 г.

Представьте себе этот общий сценарий: у вас есть двоичная комиссионная капля, возможно, содержит важные данные о транзакции или метаданные изображения, хранящиеся в распределенном кэше. Внезапно одно поле в этом капсе нуждается в обновлении - может быть, изменения состояния транзакции, или изображение помечено как чувствительное. Улов? У вас нет схемы Thrift IDL (язык определения интерфейса), легко доступная на обслуживающем уровне, и перераспределение производителей данных просто - простоне вариантИз -за чистого масштаба и сложности ваших операций.

Вот гдеfbthriftбиблиотекаparseObject/serializeObjectAPI сияет, предлагая удивительно элегантное решение. Это позволяет вам детериализовать, мутировать и вновь эмитировать благотворительную рамку, используяТолько числовые идентификаторы поля, обход необходимости генерации кода или загрузки схемы. Эта возможность бесценна для сценариев, таких какГорячие патчиВБыстрое флаг-флаг, илиСоответствующие изменения данных, все без накладных расходов на переосмысление или повторное обработку всего сообщения.

Рассмотрим эти реальные приложения, где такая возможность изменяет игру:

  • Исправление статуса транзакции: Платежный шлюз пишетTransactionBlobs (содержащие дополнительные поля, такие какidВamountВcurrencyВstatusВnote) в кеш книги. Когда детектор мошенничества вниз по течению идентифицирует ожидающую транзакцию, которую необходимо отклонить, он может излучать мелководье пластыря, содержащий пластинку, содержащийтолькоПолевый идентификатор 4(представляющийstatusПоле) со значением «отвергнуто». Служба кеша, блаженно не подозревая о полной схеме, может эффективно проанализировать как базовый каплей, так и патч, перезаписать поле состояния и переиграть его. Все остальные поля остаются нетронутыми, обеспечивая минимальную передачу данных и обработку.
  • Оценка чувствительного содержания в метаданных изображениях: Крайный узел в сети доставки контента (CDN) обслуживаетImageMetaкапли (с такими полями, какidВwidthВheightВis_sensitiveВtags) Если классификатор контента позже обнаруживает неподходящий контент, он может отправить крошечный патч, содержащий простоПолевый идентификатор 4 (is_sensitive = true) Крайный узел объединяет это единственное поле в существующую силу, прежде чем кэшировать его на диск - опять же,без необходимости компилировать схему ImageMetaлокально.

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

Справочная информация: Как эффективно упаковывает ваши данные

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

Рассмотрим простоTransactionструктура:

struct Transaction {
  1: optional i64      id
  2: optional double   amount
  3: optional string   currency
  4: optional string   status   // PENDING, REJECTED, etc.
  5: optional string   note
}

Каждое поле объявленоoptional, то есть данная полезная нагрузка будет включать только поля, которые были явно установлены. Это приводит к очень компактным бинарным представлениям.

Бетонный бинарный снимок:

Когда вы кодируетеTransaction{id = 555, amount = 100.25, currency = "USD", status = "PENDING"}Используя бинарный протокол, результирующая рама удивительно худой. Это только включает в себя:

  • Номер поля: Числовой идентификатор (например,,4дляstatus)
  • Тип проволоки: Указывает тип данных (например,STRINGдляstatus)
  • Необработанные данные: Фактическое значение (например, «отвергнуто»).

Важно отметить, что полезная нагрузка содержитНет ссылки наTransactionСхема, порядок полей или любые читаемые на человеку имена. Без файла IDL потребитель, осматривающий этот двоичный поток, не знает ничего больше, чем «Поле 4 содержит строку». Этот неотъемлемый дизайн бинарных протоколов Thrift-это именно то, что позволяет мощной без схемы, которые мы обсуждаем.

Фактическая проблема, которую мы пытаемся решить: исправление по шкале планеты

На глобальной платформе платежных платежей миллиардыTransactionКапли ежедневно принимаются в высокопроизводительный кеш книги (например, Redis или Memcached). Сложная служба обнаружения мошенничества вниз по течению постоянно пересматривает риск. В течение миллисекундов эта услуга может определить, чтоPENDINGОплата должна бытьREJECTEDПолем

Задача огромна: вместо того, чтобы потребовать от детектора мошенничества переиграть и повторно податьвесь50-байтовая запись транзакций, которая генерирует существенный сетевой трафик и увеличит задержку, она должна передатьтолько изменениеПолем В идеале, он должен излучать крошечный 7–11 байт «капля патча», содержащийтолько:

field-id 4 | wire-type STRING | "REJECTED"

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

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

Наше решение,Без схемы исправление, адресат этого, позволяя слою кэша:

  1. В целом десериализуйте как исходную базовую кадр, так и входящий патч.
  2. Перезаписать конкретное поле в базовом объекте со значением из патча.
  3. Повторно-сердитализируйте модифицированный объект и напишите его обратно в кэш.

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

Без схемы исправление с API объекта

Ядро этой мощной техники заключается вfbthriftS.ObjectAPI. Этот API позволяет манипулировать бинарными бинарными данными схемой, агрессическим образом, рассматривая данные просто как набор полевых идентификаторов и соответствующих их значений.

Полный пример

Давайте посмотрим на конкретный пример C ++, демонстрирующий, как кеш книги будет применять патч, используяObjectAPI:

#include <iostream>
#include <thrift/lib/cpp2/protocol/Object.h>
#include <thrift/lib/cpp2/protocol/Serializer.h>
#include <folly/Range.h>

// Using declarations for convenience
using apache::thrift::FieldId;
using apache::thrift::protocol::Object;
using apache::thrift::protocol::parseObject;
using apache::thrift::protocol::serializeObject;
using apache::thrift::CompactProtocolReader;
using apache::thrift::CompactProtocolWriter;

int main() {
  // 1) Simulate ledger cache holding a base Transaction blob
  // An 'Object' is a generic representation of a Thrift struct, mapping FieldId to values.
  Object base;
  base[FieldId{1}] = int64_t{555}; // id
  base[FieldId{2}] = 100.25;       // amount
  base[FieldId{3}] = "USD";        // currency
  base[FieldId{4}] = "PENDING";    // status
  // serializeObject converts the Object into a binary blob using the CompactProtocolWriter.
  auto baseBlob = *serializeObject<CompactProtocolWriter>(base);

  // 2) Fraud detector ships a tiny patch -> status = "REJECTED"
  // The patch also uses an Object, containing only the field to be updated.
  Object patch;
  patch[FieldId{4}] = "REJECTED"; // Only FieldId 4 (status) is present
  auto patchBlob = *serializeObject<CompactProtocolWriter>(patch);

  // 3) Cache merges without schema knowledge
  // parseObject reads the binary blob back into an Object.
  Object currentBase = parseObject<CompactProtocolReader>(baseBlob);
  Object delta = parseObject<CompactProtocolReader>(patchBlob);

  // Iterate through the fields in the 'delta' (patch) object
  for (const auto& kv : delta) {
    // Overwrite the singular field in 'currentBase' with the value from 'delta'.
    // This is where the actual patching happens.
    currentBase[FieldId{kv.first}] = kv.second;
  }
  // Re-serialize the modified 'currentBase' object back into a new binary blob.
  auto mergedBlob = *serializeObject<CompactProtocolWriter>(currentBase);

  // 4) Verify result for demo purposes
  // Parse the merged blob to confirm the update.
  Object verify = parseObject<CompactProtocolReader>(mergedBlob);
  std::cout << "Final status: "
            << verify[FieldId{4}].as_string() << "\n"; // Expected: REJECTED
}

Ожидаемый выход:

Final status: REJECTED

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

Что происходит под капюшоном

Эффективность этого без схемы исправления связана с оптимизированными операциями, выполненнымиfbthriftS.ObjectAPI:

  • parseObject: Эта операция сканирует поток входящего провода один раз. Затем он эффективно нарезает бинарное представление каждого поля и хранит эти срезы на внутренней карте, которая заправлена ​​ихFieldIdПолем Стоимость в основном единственнаяпоследовательное чтение, пропорционально размеру капли, проанализированного.
  • Поле перезаписать: Когда вы назначаете новое значениеFieldIdвObjectВfbthriftРазумно меняет внутренний указатель или ссылку на это целевое поле. Важно, чтобы поля былинетПрикоснулся к патчу, остаетсянулевая копия, то есть их основные бинарные данные не обработаны и не дублируются. Эта операция имеет почти незначительнуюO (1) СтоимостьПолем
  • serializeObject: Во время повторной сериализации,fbthriftПовторно вносит все оригинальные, нетронутые бинарные ломтики дословно. Только модифицированное поле (напримерstatusВ нашем примере) перекодируется свежими компактными заголовками протокола. Стоимость здесьпоследовательная запись, пропорционально окончательному размеру объединенной капли.

Внутренние тесты Meta (доступны вProtocolBench.cppвfbthriftРепозиторий GitHub) показывает, что весь цикл Parse-Patch-Serialize добавляет приблизительно8–10 мкс на 100 байтовПолем Этот накладнезначительныйПо сравнению с типичной задержкой сети или операциями ввода-вывода диска, что делает его жизнеспособным решением для высокопроизводительных систем.

Когда схема без схемы (и не является) правильным инструментом

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

Где это превосходит

  • Меньшие полезную нагрузку: Это основное преимущество. Дельта -сообщения часто бывают5–10 раз легчечем полные объекты (например, 11 байтов против 50 байтов в нашихTransactionдемо). Это резко сокращает потребление полосы пропускания поперечной регионы, снижает сетевую нагрузку и значительно сводит к минимуму отставание репликации в распределенных системах.
  • Свободная связь: Слои обслуживания могут применять патчи без необходимости благотворительного IDL или сгенерированного кода. Это способствуетНезависимая эволюцияМежду производителями и потребителями снижение зависимости развертывания и позволяет командам итерации быстрее.
  • Быстрое исправление: Возможность обновления только целевого поля означает, что настройки соответствия, критические исправления ошибок или флаг-флаг могут приземлитьсянемедленноПолем Это позволяет избежать дорогостоящих промывок кеша, сложных миграций данных или многократных перераспределений услуг в большой инфраструктуре.

Где сгенерированные заглушки все еще выигрывают

  • ЦП-связанные горячие пути: Отражение, котороеObjectAPI полагается, вводит примерно2–3х над головойПо сравнению с прямой, сгенерированной кодом сериализацией/десериализацией (8-10 мкс на 100-байтовые капля, упомянутые ранее). Если сериализация/десериализация вашего приложения уже является значительным узким местом на критическом пути, эта премия может повредить сквозной задержке.
  • Тяжелые обновления списка: Рабочие нагрузки, которые в первую очередь включают в себя добавление, удаление или переупорядочение больших повторных полей (списки, наборы) могут получить большеСогласованная кодовая логика слиянияПолем Это связано с тем, что без схемы исправление обычно включает перезапись или простой конкатенацию, что может быть не так оптимизировано для манипуляций с сложными списками, как высокоспециализированный сгенерированный код.
  • Единое право собственности: В сценариях, где одна команда или тесно связанная команда контролирует как развертывание производителя, так и потребителей,Регенерирующие благотворительные заглушкичасто бывает проще и простым, чем поддержание отдельных отображений поля для операций без схемы.

Контрольный список лучшей практики

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

  • Проверить типы проводов перед мутированием: ВсегдаПодтвердите, что тип проволоки поля соответствует вашим ожиданиямПеред попыткой изменить его значение (например, обеспечитьFieldId{4}действительноSTRINGвведите перед назначением строки). Неспособность этого сделатьиспортить бинарный каплей, что приводит к непредсказуемому поведению или потере данных.
  • Слох с собственными размерами полезной нагрузки: Прибыль от без схемы исправления наиболее значимы, когдаРазмер патча значительно меньше(обычно ≪ 20%), чем полный исходный объект. Всегда сравните с фактическими размерами полезной нагрузки и шаблонами трафика, чтобы гарантировать, что сбережения сети перевешивают скромные накладные расходы ЦП для вашего конкретного варианта использования.

Как это сравнивается с Protobuf?

Для тех, кто знаком с буферами протокола Google (Протобуф), возникает естественный вопрос: как этоfbthriftСравнение без схемы сравнивается?

Protobuf предлагает возможности на основе отражения через егоDynamicMessageAPI и более низкий уровеньUnknownFieldSetПолем Оба позволяют вам читать, мутировать и вновь вновь обрести сообщения. Однако ключевым различием является то, чтоВозможности отражения Protobuf требуют, чтобы вы загрузили дескриптор, установленную во время выполненияПолем Этот набор дескриптора содержит информацию о схеме (типы поля, имена и т. Д.)нетприсутствует в самой полезной нагрузке провода Protobuf. Это означает, что вы должны управлятьбоковой канал для дескрипторов схемыи загружать их динамически. Как только дескриптор присутствует, вы можете выполнять операции, аналогичныеfbthriftS.ObjectAPI, и вы обычно заплатите аналогичный2-3 раза стоимость процессорадля размышлений.

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

Заключение

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

Ссылки и дальнейшее чтение

  • : FBthrift Github
    • Объект API
    • Протокол
  • Мартин Клеппманн, Эволюция схемы в AVRO, протокольные буферы и благотворительность:
  • Meta Engineering Blog, StructPatch: Delta Encoding для The Forift
  • Протокол буферов документация


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE