[{"data":1,"prerenderedAt":1308},["ShallowReactive",2],{"content:\u002F08-kafka\u002F01-kafka-overview":3},{"title":4,"description":5,"path":6,"body":7},"Kafka: модель брокеров, топиков и логов","Kafka часто называют очередью сообщений, но это слишком узкое описание. Практически Kafka - это распределённый append-only лог событий. Producer записывает события в конец лога, Kafka хранит их некоторое время, consumer читает события в своём темпе и запоминает позицию чтения.","\u002F08-kafka\u002F01-kafka-overview",{"type":8,"value":9,"toc":1294},"minimark",[10,14,23,26,37,40,45,144,151,153,157,160,166,176,182,188,190,194,197,203,206,209,225,227,231,234,268,275,282,288,291,293,297,300,366,369,372,374,378,381,387,390,396,479,482,484,488,491,705,719,734,736,740,743,763,766,768,772,795,797,801,827,829,833,865,867,871,902,1081,1290],[11,12,4],"h1",{"id":13},"kafka-модель-брокеров-топиков-и-логов",[15,16,17,18,22],"p",{},"Kafka часто называют очередью сообщений, но это слишком узкое описание. Практически Kafka - это ",[19,20,21],"strong",{},"распределённый append-only лог событий",". Producer записывает события в конец лога, Kafka хранит их некоторое время, consumer читает события в своём темпе и запоминает позицию чтения.",[15,24,25],{},"Главный сдвиг в мышлении: сообщение не исчезает после чтения. Оно лежит в логе до тех пор, пока его не удалит политика хранения. Поэтому одно и то же событие могут читать разные сервисы, аналитика, нотификации, биллинг и аудит.",[27,28,34],"pre",{"className":29,"code":31,"language":32,"meta":33},[30],"language-text","Producer\n   |\n   v\n+------------------- Kafka cluster -------------------+\n| Broker 1        Broker 2        Broker 3            |\n|                                                     |\n| topic: payments                                    |\n|   partition 0: [0][1][2][3][4]                     |\n|   partition 1: [0][1][2][3]                        |\n|   partition 2: [0][1][2][3][4][5]                  |\n+-----------------------------------------------------+\n   |\n   v\nConsumers read by offset\n","text","",[35,36,31],"code",{"__ignoreMap":33},[38,39],"hr",{},[41,42,44],"h2",{"id":43},"базовые-сущности","Базовые сущности",[46,47,48,62],"table",{},[49,50,51],"thead",{},[52,53,54,59],"tr",{},[55,56,58],"th",{"align":57},"left","Термин",[55,60,61],{"align":57},"Что означает",[63,64,65,74,82,101,109,117,128,136],"tbody",{},[52,66,67,71],{},[68,69,70],"td",{"align":57},"Broker",[68,72,73],{"align":57},"Сервер Kafka. Хранит партиции, принимает записи, отдаёт данные consumers.",[52,75,76,79],{},[68,77,78],{"align":57},"Cluster",[68,80,81],{"align":57},"Набор брокеров, работающих как одна Kafka-система.",[52,83,84,87],{},[68,85,86],{"align":57},"Topic",[68,88,89,90,93,94,93,97,100],{"align":57},"Логическая категория событий: ",[35,91,92],{},"orders",", ",[35,95,96],{},"payments",[35,98,99],{},"user-events",".",[52,102,103,106],{},[68,104,105],{"align":57},"Partition",[68,107,108],{"align":57},"Физический append-only лог внутри topic. Topic состоит из одной или нескольких partitions.",[52,110,111,114],{},[68,112,113],{"align":57},"Record",[68,115,116],{"align":57},"Одно сообщение: key, value, headers, timestamp и metadata.",[52,118,119,122],{},[68,120,121],{"align":57},"Offset",[68,123,124,125,100],{"align":57},"Номер record внутри partition. Уникален только в паре ",[35,126,127],{},"topic + partition",[52,129,130,133],{},[68,131,132],{"align":57},"Consumer group",[68,134,135],{"align":57},"Группа consumers, совместно читающих topic. Каждая partition назначается одному consumer внутри группы.",[52,137,138,141],{},[68,139,140],{"align":57},"Retention",[68,142,143],{"align":57},"Политика хранения сообщений по времени или размеру.",[15,145,146,147,150],{},"Важно: Kafka масштабируется не \"топиком\", а ",[19,148,149],{},"партициями",". Если topic имеет 12 partitions, то внутри одной consumer group его могут параллельно читать максимум 12 active consumers. Тринадцатый consumer будет простаивать, пока не появятся свободные partition.",[38,152],{},[41,154,156],{"id":155},"record-что-реально-хранится","Record: что реально хранится",[15,158,159],{},"Kafka record обычно выглядит так:",[27,161,164],{"className":162,"code":163,"language":32,"meta":33},[30],"topic:     payments\npartition: 2\noffset:    1042\ntimestamp: 2026-04-30T12:00:00Z\nkey:       order-42\nheaders:   event-type=PaymentCaptured, schema-version=2\nvalue:     {\"order_id\":\"order-42\",\"amount\":9900,\"currency\":\"RUB\"}\n",[35,165,163],{"__ignoreMap":33},[15,167,168,171,172,175],{},[35,169,170],{},"key"," нужен не только \"для удобства\". По key producer выбирает partition. Если все события по ",[35,173,174],{},"order-42"," должны идти в правильном порядке, у них должен быть одинаковый key. Тогда Kafka отправит их в одну partition, а порядок внутри partition будет сохранён.",[15,177,178,181],{},[35,179,180],{},"headers"," удобны для технической metadata: correlation id, trace id, версия схемы, тип события, tenant id. Не стоит класть туда основную бизнес-нагрузку.",[15,183,184,185,187],{},"Не кладите в record всё, что \"может пригодиться\". Kafka-событие часто читают несколько команд и хранят дольше, чем живёт исходный request, поэтому payload становится контрактом. Для production-события заранее фиксируют владельца topic, смысл ",[35,186,170],{},", версию схемы, PII-поля, retention и правила совместимости.",[38,189],{},[41,191,193],{"id":192},"kafka-как-лог","Kafka как лог",[15,195,196],{},"Partition - это не список задач, который consumer \"разбирает\". Это файлоподобный лог, куда Kafka дописывает новые records в конец.",[27,198,201],{"className":199,"code":200,"language":32,"meta":33},[30],"payments-2\n\noffset:  0    1    2    3    4    5\n       +----+----+----+----+----+----+\nvalue: | p1 | p2 | p3 | p4 | p5 | p6 |\n       +----+----+----+----+----+----+\n                           ^\n                           consumer group billing committed offset = 4\n",[35,202,200],{"__ignoreMap":33},[15,204,205],{},"Consumer не удаляет record. Он хранит свой committed offset: \"я обработал всё до такой позиции\". Если consumer упал, новый consumer из той же группы продолжит читать с committed offset.",[15,207,208],{},"Эта модель даёт важные свойства:",[210,211,212,216,219,222],"ul",{},[213,214,215],"li",{},"можно перечитать историю, если retention ещё не удалил данные;",[213,217,218],{},"разные consumer groups не мешают друг другу;",[213,220,221],{},"Kafka хорошо работает с последовательным чтением и записью;",[213,223,224],{},"хранение сообщений становится частью архитектуры, а не временным буфером.",[38,226],{},[41,228,230],{"id":229},"retention-почему-сообщения-не-живут-вечно","Retention: почему сообщения не живут вечно",[15,232,233],{},"Kafka хранит данные по политике retention. Две самые частые настройки:",[46,235,236,246],{},[49,237,238],{},[52,239,240,243],{},[55,241,242],{"align":57},"Настройка",[55,244,245],{"align":57},"Смысл",[63,247,248,258],{},[52,249,250,255],{},[68,251,252],{"align":57},[35,253,254],{},"retention.ms",[68,256,257],{"align":57},"Сколько времени хранить records.",[52,259,260,265],{},[68,261,262],{"align":57},[35,263,264],{},"retention.bytes",[68,266,267],{"align":57},"Сколько данных хранить на partition.",[15,269,270,271,274],{},"Если ",[35,272,273],{},"retention.ms=7d",", сообщение может быть удалено через неделю независимо от того, прочитали его consumers или нет. Kafka не ждёт отстающих consumers бесконечно. Поэтому consumer lag - это production-метрика, а не просто \"график для красоты\".",[15,276,277,278,281],{},"Отдельный режим - ",[19,279,280],{},"log compaction",". В compacted topic Kafka оставляет последнюю запись для каждого key:",[27,283,286],{"className":284,"code":285,"language":32,"meta":33},[30],"key=user-1 value=email=a@old\nkey=user-2 value=email=b@example\nkey=user-1 value=email=a@new\n\nПосле compaction логически важно:\nuser-1 -> email=a@new\nuser-2 -> email=b@example\n",[35,287,285],{"__ignoreMap":33},[15,289,290],{},"Compaction подходит для changelog-сценариев: текущее состояние пользователя, настройки, feature flags, индексы. Для событий аудита compaction обычно опасен: там важна вся история, а не последнее значение.",[38,292],{},[41,294,296],{"id":295},"use-cases","Use cases",[15,298,299],{},"Kafka хорошо подходит, когда система должна передавать много событий между независимыми потребителями.",[46,301,302,312],{},[49,303,304],{},[52,305,306,309],{},[55,307,308],{"align":57},"Сценарий",[55,310,311],{"align":57},"Как Kafka помогает",[63,313,314,326,334,342,350,358],{},[52,315,316,319],{},[68,317,318],{"align":57},"Event-driven backend",[68,320,321,322,325],{"align":57},"Сервис заказов публикует ",[35,323,324],{},"OrderCreated",", склад, оплата и уведомления реагируют независимо.",[52,327,328,331],{},[68,329,330],{"align":57},"Audit log",[68,332,333],{"align":57},"События пишутся как неизменяемая история действий.",[52,335,336,339],{},[68,337,338],{"align":57},"Data pipeline",[68,340,341],{"align":57},"Backend отправляет события в ClickHouse, S3, DWH или stream processing.",[52,343,344,347],{},[68,345,346],{"align":57},"Integration bus",[68,348,349],{"align":57},"Несколько сервисов обмениваются доменными событиями без прямых HTTP-вызовов.",[52,351,352,355],{},[68,353,354],{"align":57},"Async workloads",[68,356,357],{"align":57},"Тяжёлую обработку можно вынести из request-response пути.",[52,359,360,363],{},[68,361,362],{"align":57},"CDC",[68,364,365],{"align":57},"Изменения из БД превращаются в поток событий.",[15,367,368],{},"Kafka хуже подходит, если нужна простая очередь на 100 сообщений в день, сложная маршрутизация per-message, delayed jobs с точными таймерами или request-response. Для таких случаев часто проще Redis Streams, RabbitMQ, task queue или обычный HTTP.",[15,370,371],{},"Kafka также не должна заменять обычный вызов внутри одного bounded context. Если код находится в одном сервисе и должен завершиться в одной transaction, event bus добавит eventual consistency, дубли и операционную стоимость без пользы.",[38,373],{},[41,375,377],{"id":376},"kafka-vs-классические-очереди","Kafka vs классические очереди",[15,379,380],{},"В очереди сообщение обычно забирает один consumer, после успешной обработки оно удаляется или помечается как acked.",[27,382,385],{"className":383,"code":384,"language":32,"meta":33},[30],"Queue:\n\n[msg1][msg2][msg3] --> worker A\n        [msg2][msg3] --> worker B\n              [msg3]\n",[35,386,384],{"__ignoreMap":33},[15,388,389],{},"В Kafka сообщение остаётся в partition, а каждая consumer group ведёт свою позицию:",[27,391,394],{"className":392,"code":393,"language":32,"meta":33},[30],"Kafka topic:\n\npartition: [0][1][2][3][4][5]\n              ^        ^\n              |        |\n          analytics  billing\n          offset=1   offset=4\n",[35,395,393],{"__ignoreMap":33},[46,397,398,411],{},[49,399,400],{},[52,401,402,405,408],{},[55,403,404],{"align":57},"Вопрос",[55,406,407],{"align":57},"Queue",[55,409,410],{"align":57},"Kafka",[63,412,413,424,435,446,457,468],{},[52,414,415,418,421],{},[68,416,417],{"align":57},"Что происходит после чтения",[68,419,420],{"align":57},"Сообщение обычно исчезает после ack",[68,422,423],{"align":57},"Record остаётся до retention",[52,425,426,429,432],{},[68,427,428],{"align":57},"Модель масштабирования",[68,430,431],{"align":57},"Workers конкурируют за задачи",[68,433,434],{"align":57},"Partitions распределяются между consumers",[52,436,437,440,443],{},[68,438,439],{"align":57},"Повторное чтение",[68,441,442],{"align":57},"Обычно сложнее",[68,444,445],{"align":57},"Естественно, если данные ещё есть",[52,447,448,451,454],{},[68,449,450],{"align":57},"Порядок",[68,452,453],{"align":57},"Зависит от очереди и режима",[68,455,456],{"align":57},"Гарантирован внутри partition",[52,458,459,462,465],{},[68,460,461],{"align":57},"Fan-out",[68,463,464],{"align":57},"Часто через exchange\u002Frouting",[68,466,467],{"align":57},"Через разные consumer groups",[52,469,470,473,476],{},[68,471,472],{"align":57},"Хранение истории",[68,474,475],{"align":57},"Не основная задача",[68,477,478],{"align":57},"Одна из ключевых идей",[15,480,481],{},"Kafka - не замена всем очередям. Она сильна там, где события становятся долговременным потоком данных, который читают разные подсистемы.",[38,483],{},[41,485,487],{"id":486},"мини-пример-события-в-go","Мини-пример события в Go",[15,489,490],{},"Даже если producer появится в следующем уроке, полезно сразу увидеть форму события как часть контракта.",[27,492,497],{"className":493,"code":494,"language":495,"meta":496,"style":33},"language-go shiki shiki-themes github-dark","package events\n\nimport \"time\"\n\ntype OrderCreated struct {\n    EventID   string    `json:\"event_id\"`\n    EventType string    `json:\"event_type\"`\n    Version   int       `json:\"version\"`\n    Occurred  time.Time `json:\"occurred\"`\n    OrderID   string    `json:\"order_id\"`\n    UserID    string    `json:\"user_id\"`\n    Amount    int64     `json:\"amount\"`\n    Currency  string    `json:\"currency\"`\n}\n\nfunc (e OrderCreated) Key() string {\n    return e.OrderID\n}\n","go","no-run",[35,498,499,512,519,535,540,556,568,579,591,607,618,629,641,652,658,663,691,700],{"__ignoreMap":33},[500,501,504,508],"span",{"class":502,"line":503},"line",1,[500,505,507],{"class":506},"snl16","package",[500,509,511],{"class":510},"svObZ"," events\n",[500,513,515],{"class":502,"line":514},2,[500,516,518],{"emptyLinePlaceholder":517},true,"\n",[500,520,522,525,529,532],{"class":502,"line":521},3,[500,523,524],{"class":506},"import",[500,526,528],{"class":527},"sU2Wk"," \"",[500,530,531],{"class":510},"time",[500,533,534],{"class":527},"\"\n",[500,536,538],{"class":502,"line":537},4,[500,539,518],{"emptyLinePlaceholder":517},[500,541,543,546,549,552],{"class":502,"line":542},5,[500,544,545],{"class":506},"type",[500,547,548],{"class":510}," OrderCreated",[500,550,551],{"class":506}," struct",[500,553,555],{"class":554},"s95oV"," {\n",[500,557,559,562,565],{"class":502,"line":558},6,[500,560,561],{"class":554},"    EventID   ",[500,563,564],{"class":506},"string",[500,566,567],{"class":527},"    `json:\"event_id\"`\n",[500,569,571,574,576],{"class":502,"line":570},7,[500,572,573],{"class":554},"    EventType ",[500,575,564],{"class":506},[500,577,578],{"class":527},"    `json:\"event_type\"`\n",[500,580,582,585,588],{"class":502,"line":581},8,[500,583,584],{"class":554},"    Version   ",[500,586,587],{"class":506},"int",[500,589,590],{"class":527},"       `json:\"version\"`\n",[500,592,594,597,599,601,604],{"class":502,"line":593},9,[500,595,596],{"class":554},"    Occurred  ",[500,598,531],{"class":510},[500,600,100],{"class":554},[500,602,603],{"class":510},"Time",[500,605,606],{"class":527}," `json:\"occurred\"`\n",[500,608,610,613,615],{"class":502,"line":609},10,[500,611,612],{"class":554},"    OrderID   ",[500,614,564],{"class":506},[500,616,617],{"class":527},"    `json:\"order_id\"`\n",[500,619,621,624,626],{"class":502,"line":620},11,[500,622,623],{"class":554},"    UserID    ",[500,625,564],{"class":506},[500,627,628],{"class":527},"    `json:\"user_id\"`\n",[500,630,632,635,638],{"class":502,"line":631},12,[500,633,634],{"class":554},"    Amount    ",[500,636,637],{"class":506},"int64",[500,639,640],{"class":527},"     `json:\"amount\"`\n",[500,642,644,647,649],{"class":502,"line":643},13,[500,645,646],{"class":554},"    Currency  ",[500,648,564],{"class":506},[500,650,651],{"class":527},"    `json:\"currency\"`\n",[500,653,655],{"class":502,"line":654},14,[500,656,657],{"class":554},"}\n",[500,659,661],{"class":502,"line":660},15,[500,662,518],{"emptyLinePlaceholder":517},[500,664,666,669,672,676,678,681,684,687,689],{"class":502,"line":665},16,[500,667,668],{"class":506},"func",[500,670,671],{"class":554}," (",[500,673,675],{"class":674},"s9osk","e ",[500,677,324],{"class":510},[500,679,680],{"class":554},") ",[500,682,683],{"class":510},"Key",[500,685,686],{"class":554},"() ",[500,688,564],{"class":506},[500,690,555],{"class":554},[500,692,694,697],{"class":502,"line":693},17,[500,695,696],{"class":506},"    return",[500,698,699],{"class":554}," e.OrderID\n",[500,701,703],{"class":502,"line":702},18,[500,704,657],{"class":554},[15,706,707,710,711,714,715,718],{},[35,708,709],{},"OrderID"," как key означает: все события одного заказа попадут в одну partition и сохранят порядок относительно друг друга. ",[35,712,713],{},"EventID"," пригодится для идемпотентности на стороне consumer. ",[35,716,717],{},"Version"," нужен для эволюции схемы.",[15,720,721,722,725,726,729,730,733],{},"Для RateDesk похожий контракт мог бы быть ",[35,723,724],{},"RateUpdated v1",": key = currency pair или provider+pair, ",[35,727,728],{},"event_id"," = уникальный id изменения, ",[35,731,732],{},"occurred"," = время факта, value содержит новую версию курса и источник. В статье важно понять критерии выбора; конкретный contract и MR остаются в проектном задании Kafka-модуля.",[38,735],{},[41,737,739],{"id":738},"production-инварианты-события","Production-инварианты события",[15,741,742],{},"Перед тем как topic попадает в production, ответьте на вопросы:",[210,744,745,748,751,754,757,760],{},[213,746,747],{},"кто владеет схемой и принимает breaking changes;",[213,749,750],{},"какой key сохраняет нужный порядок и не создаёт горячую partition;",[213,752,753],{},"какие consumers могут безопасно replay старые records;",[213,755,756],{},"какие поля являются персональными или секретными и не должны попадать в payload\u002Flogs;",[213,758,759],{},"сколько времени retention обязан покрывать расследование и восстановление;",[213,761,762],{},"как consumer отличит новую версию события от неизвестного типа.",[15,764,765],{},"Если эти ответы не записаны, Kafka превращается в скрытый distributed contract без review.",[38,767],{},[41,769,771],{"id":770},"типичные-ошибки","Типичные ошибки",[210,773,774,777,780,783,786,789,792],{},[213,775,776],{},"Делать один topic на каждый маленький action без понятной модели владения.",[213,778,779],{},"Использовать случайный key и потом удивляться, что события одного заказа обрабатываются не по порядку.",[213,781,782],{},"Считать Kafka \"бесконечной базой данных\" и не думать о retention.",[213,784,785],{},"Читать всё одной consumer group, хотя разным подсистемам нужны независимые offsets.",[213,787,788],{},"Хранить в value не событие, а \"команду другому сервису\", жёстко связывая producer и consumer.",[213,790,791],{},"Публиковать PII, токены или полный request body, а потом обнаружить это в DLQ, логах и backup topic.",[213,793,794],{},"Менять JSON-поля без версии и contract tests, полагаясь на то, что \"все consumers обновятся одновременно\".",[38,796],{},[41,798,800],{"id":799},"вопросы-на-собеседовании","Вопросы на собеседовании",[210,802,803,806,809,812,815,818,821,824],{},[213,804,805],{},"Почему Kafka называют append-only log?",[213,807,808],{},"Чем topic отличается от partition?",[213,810,811],{},"Что такое offset и где он уникален?",[213,813,814],{},"Почему сообщение в Kafka не удаляется после чтения?",[213,816,817],{},"Чем Kafka отличается от RabbitMQ или обычной очереди задач?",[213,819,820],{},"Как retention влияет на отстающих consumers?",[213,822,823],{},"Когда нужен log compaction?",[213,825,826],{},"Почему key важен для порядка сообщений?",[38,828],{},[41,830,832],{"id":831},"практика","Практика",[834,835,836,839,857,860],"ol",{},[213,837,838],{},"Нарисуйте модель Kafka для интернет-магазина: topics, keys и consumer groups для заказов, оплаты и уведомлений.",[213,840,841,842,845,846,93,848,93,851,853,854,100],{},"Опишите событие ",[35,843,844],{},"PaymentCaptured"," в Go-структуре: добавьте ",[35,847,728],{},[35,849,850],{},"version",[35,852,732],{},", бизнес-поля и метод ",[35,855,856],{},"Key()",[213,858,859],{},"Для трёх сценариев выберите Kafka или обычную очередь: email-рассылка, аудит действий пользователя, генерация PDF-чека.",[213,861,862,863,100],{},"Объясните, что произойдёт с consumer, который отстал на 10 дней при ",[35,864,273],{},[38,866],{},[41,868,870],{"id":869},"интерактивная-практика","Интерактивная практика",[872,873,877,880,897],"quiz",{"answer":874,"id":875,"xp":876},"2","kafka-overview-q1","10",[15,878,879],{},"Почему Kafka в production чаще описывают как распределённый лог, а не как обычную очередь?",[881,882,883],"template",{"v-slot:options":33},[210,884,885,888,891,894],{},[213,886,887],{},"Потому что Kafka всегда удаляет сообщение сразу после первого чтения",[213,889,890],{},"Потому что события хранятся по retention и могут читаться разными consumer groups независимо",[213,892,893],{},"Потому что Kafka не умеет хранить порядок сообщений",[213,895,896],{},"Потому что producer напрямую вызывает consumer",[881,898,899],{"v-slot:explanation":33},[15,900,901],{},"Kafka хранит append-only log до срабатывания retention или compaction. Разные consumer groups ведут собственные offsets и могут читать один и тот же topic независимо.",[903,904,908,911,1076],"predict",{"answer":905,"id":906,"xp":907},"available\\nexpired","kafka-overview-p1","15",[15,909,910],{},"Что выведет программа?",[881,912,913],{"v-slot:code":33},[27,914,916],{"className":493,"code":915,"language":495,"meta":33,"style":33},"package main\n\nimport \"fmt\"\n\nfunc retentionState(ageDays int, retentionDays int) string {\n    if ageDays > retentionDays {\n        return \"expired\"\n    }\n    return \"available\"\n}\n\nfunc main() {\n    fmt.Println(retentionState(3, 7))\n    fmt.Println(retentionState(10, 7))\n}\n",[35,917,918,925,929,940,944,973,987,995,1000,1007,1011,1015,1025,1052,1072],{"__ignoreMap":33},[500,919,920,922],{"class":502,"line":503},[500,921,507],{"class":506},[500,923,924],{"class":510}," main\n",[500,926,927],{"class":502,"line":514},[500,928,518],{"emptyLinePlaceholder":517},[500,930,931,933,935,938],{"class":502,"line":521},[500,932,524],{"class":506},[500,934,528],{"class":527},[500,936,937],{"class":510},"fmt",[500,939,534],{"class":527},[500,941,942],{"class":502,"line":537},[500,943,518],{"emptyLinePlaceholder":517},[500,945,946,948,951,954,957,960,962,965,967,969,971],{"class":502,"line":542},[500,947,668],{"class":506},[500,949,950],{"class":510}," retentionState",[500,952,953],{"class":554},"(",[500,955,956],{"class":674},"ageDays",[500,958,959],{"class":506}," int",[500,961,93],{"class":554},[500,963,964],{"class":674},"retentionDays",[500,966,959],{"class":506},[500,968,680],{"class":554},[500,970,564],{"class":506},[500,972,555],{"class":554},[500,974,975,978,981,984],{"class":502,"line":558},[500,976,977],{"class":506},"    if",[500,979,980],{"class":554}," ageDays ",[500,982,983],{"class":506},">",[500,985,986],{"class":554}," retentionDays {\n",[500,988,989,992],{"class":502,"line":570},[500,990,991],{"class":506},"        return",[500,993,994],{"class":527}," \"expired\"\n",[500,996,997],{"class":502,"line":581},[500,998,999],{"class":554},"    }\n",[500,1001,1002,1004],{"class":502,"line":593},[500,1003,696],{"class":506},[500,1005,1006],{"class":527}," \"available\"\n",[500,1008,1009],{"class":502,"line":609},[500,1010,657],{"class":554},[500,1012,1013],{"class":502,"line":620},[500,1014,518],{"emptyLinePlaceholder":517},[500,1016,1017,1019,1022],{"class":502,"line":631},[500,1018,668],{"class":506},[500,1020,1021],{"class":510}," main",[500,1023,1024],{"class":554},"() {\n",[500,1026,1027,1030,1033,1035,1038,1040,1044,1046,1049],{"class":502,"line":643},[500,1028,1029],{"class":554},"    fmt.",[500,1031,1032],{"class":510},"Println",[500,1034,953],{"class":554},[500,1036,1037],{"class":510},"retentionState",[500,1039,953],{"class":554},[500,1041,1043],{"class":1042},"sDLfK","3",[500,1045,93],{"class":554},[500,1047,1048],{"class":1042},"7",[500,1050,1051],{"class":554},"))\n",[500,1053,1054,1056,1058,1060,1062,1064,1066,1068,1070],{"class":502,"line":654},[500,1055,1029],{"class":554},[500,1057,1032],{"class":510},[500,1059,953],{"class":554},[500,1061,1037],{"class":510},[500,1063,953],{"class":554},[500,1065,876],{"class":1042},[500,1067,93],{"class":554},[500,1069,1048],{"class":1042},[500,1071,1051],{"class":554},[500,1073,1074],{"class":502,"line":660},[500,1075,657],{"class":554},[881,1077,1078],{"v-slot:hint":33},[15,1079,1080],{},"Consumer может перечитать событие только пока оно ещё не удалено политикой retention.",[1082,1083,1087,1105,1273],"code-task",{"expected":1084,"id":1085,"xp":1086},"order-42\\nuser-7\\naudit","kafka-overview-ct1","20",[15,1088,1089,1090,1093,1094,1097,1098,1101,1102,100],{},"Реализуй ",[35,1091,1092],{},"EventKey",": для событий заказа ключом должен быть ",[35,1095,1096],{},"orderID",", для пользовательских событий — ",[35,1099,1100],{},"userID",", для остальных событий верни ",[35,1103,1104],{},"audit",[881,1106,1107],{"v-slot:template":33},[27,1108,1110],{"className":493,"code":1109,"language":495,"meta":33,"style":33},"package main\n\nimport \"fmt\"\n\nfunc EventKey(kind string, orderID string, userID string) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(EventKey(\"order\", \"order-42\", \"user-7\"))\n    fmt.Println(EventKey(\"user\", \"order-42\", \"user-7\"))\n    fmt.Println(EventKey(\"audit\", \"order-42\", \"user-7\"))\n}\n",[35,1111,1112,1118,1122,1132,1136,1169,1176,1180,1184,1192,1219,1244,1269],{"__ignoreMap":33},[500,1113,1114,1116],{"class":502,"line":503},[500,1115,507],{"class":506},[500,1117,924],{"class":510},[500,1119,1120],{"class":502,"line":514},[500,1121,518],{"emptyLinePlaceholder":517},[500,1123,1124,1126,1128,1130],{"class":502,"line":521},[500,1125,524],{"class":506},[500,1127,528],{"class":527},[500,1129,937],{"class":510},[500,1131,534],{"class":527},[500,1133,1134],{"class":502,"line":537},[500,1135,518],{"emptyLinePlaceholder":517},[500,1137,1138,1140,1143,1145,1148,1151,1153,1155,1157,1159,1161,1163,1165,1167],{"class":502,"line":542},[500,1139,668],{"class":506},[500,1141,1142],{"class":510}," EventKey",[500,1144,953],{"class":554},[500,1146,1147],{"class":674},"kind",[500,1149,1150],{"class":506}," string",[500,1152,93],{"class":554},[500,1154,1096],{"class":674},[500,1156,1150],{"class":506},[500,1158,93],{"class":554},[500,1160,1100],{"class":674},[500,1162,1150],{"class":506},[500,1164,680],{"class":554},[500,1166,564],{"class":506},[500,1168,555],{"class":554},[500,1170,1171,1173],{"class":502,"line":558},[500,1172,696],{"class":506},[500,1174,1175],{"class":527}," \"todo\"\n",[500,1177,1178],{"class":502,"line":570},[500,1179,657],{"class":554},[500,1181,1182],{"class":502,"line":581},[500,1183,518],{"emptyLinePlaceholder":517},[500,1185,1186,1188,1190],{"class":502,"line":593},[500,1187,668],{"class":506},[500,1189,1021],{"class":510},[500,1191,1024],{"class":554},[500,1193,1194,1196,1198,1200,1202,1204,1207,1209,1212,1214,1217],{"class":502,"line":609},[500,1195,1029],{"class":554},[500,1197,1032],{"class":510},[500,1199,953],{"class":554},[500,1201,1092],{"class":510},[500,1203,953],{"class":554},[500,1205,1206],{"class":527},"\"order\"",[500,1208,93],{"class":554},[500,1210,1211],{"class":527},"\"order-42\"",[500,1213,93],{"class":554},[500,1215,1216],{"class":527},"\"user-7\"",[500,1218,1051],{"class":554},[500,1220,1221,1223,1225,1227,1229,1231,1234,1236,1238,1240,1242],{"class":502,"line":620},[500,1222,1029],{"class":554},[500,1224,1032],{"class":510},[500,1226,953],{"class":554},[500,1228,1092],{"class":510},[500,1230,953],{"class":554},[500,1232,1233],{"class":527},"\"user\"",[500,1235,93],{"class":554},[500,1237,1211],{"class":527},[500,1239,93],{"class":554},[500,1241,1216],{"class":527},[500,1243,1051],{"class":554},[500,1245,1246,1248,1250,1252,1254,1256,1259,1261,1263,1265,1267],{"class":502,"line":631},[500,1247,1029],{"class":554},[500,1249,1032],{"class":510},[500,1251,953],{"class":554},[500,1253,1092],{"class":510},[500,1255,953],{"class":554},[500,1257,1258],{"class":527},"\"audit\"",[500,1260,93],{"class":554},[500,1262,1211],{"class":527},[500,1264,93],{"class":554},[500,1266,1216],{"class":527},[500,1268,1051],{"class":554},[500,1270,1271],{"class":502,"line":643},[500,1272,657],{"class":554},[881,1274,1275],{"v-slot:hints":33},[210,1276,1277,1280,1285],{},[213,1278,1279],{},"Key определяет partition и порядок для связанных событий.",[213,1281,1282,1283,100],{},"Для заказа порядок обычно нужен внутри ",[35,1284,1096],{},[213,1286,1287,1288,100],{},"Для событий пользователя часто нужен порядок внутри ",[35,1289,1100],{},[1291,1292,1293],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}",{"title":33,"searchDepth":514,"depth":514,"links":1295},[1296,1297,1298,1299,1300,1301,1302,1303,1304,1305,1306,1307],{"id":43,"depth":514,"text":44},{"id":155,"depth":514,"text":156},{"id":192,"depth":514,"text":193},{"id":229,"depth":514,"text":230},{"id":295,"depth":514,"text":296},{"id":376,"depth":514,"text":377},{"id":486,"depth":514,"text":487},{"id":738,"depth":514,"text":739},{"id":770,"depth":514,"text":771},{"id":799,"depth":514,"text":800},{"id":831,"depth":514,"text":832},{"id":869,"depth":514,"text":870},1781022064931]