[{"data":1,"prerenderedAt":2288},["ShallowReactive",2],{"content:\u002F08-kafka\u002F07-deployment-production":3},{"title":4,"description":5,"path":6,"body":7},"Kafka deployment и production-практики","Локально Kafka нужна, чтобы разработчик мог поднять сервис и проверить producer\u002Fconsumer без внешней инфраструктуры. В production Kafka - это stateful distributed system: диски, replication, security, upgrades, monitoring, capacity planning и runbooks.","\u002F08-kafka\u002F07-deployment-production",{"type":8,"value":9,"toc":2275},"minimark",[10,14,17,20,23,28,31,228,231,250,253,256,258,262,265,268,452,455,475,478,480,484,487,532,535,543,546,549,569,572,575,577,581,584,587,625,628,642,645,648,650,654,657,660,666,669,675,678,1511,1517,1520,1522,1526,1529,1532,1564,1567,1590,1593,1595,1599,1602,1622,1624,1628,1631,1768,1770,1774,1809,1811,1815,1849,1851,1855,1886,2090,2271],[11,12,4],"h1",{"id":13},"kafka-deployment-и-production-практики",[15,16,5],"p",{},[15,18,19],{},"Для backend-разработчика цель не в том, чтобы самому заменить platform team. Цель - понимать, какие решения в коде зависят от deployment-модели и какие вопросы нужно задать до релиза.",[21,22],"hr",{},[24,25,27],"h2",{"id":26},"docker-compose-для-разработки","Docker Compose для разработки",[15,29,30],{},"Для локальной разработки можно поднять Kafka одним broker. Это не production-модель, но достаточно для интеграционных тестов и ручной проверки.",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-yaml shiki shiki-themes github-dark","services:\n  kafka:\n    image: bitnami\u002Fkafka:3.7\n    ports:\n      - \"9092:9092\"\n    environment:\n      KAFKA_CFG_NODE_ID: 1\n      KAFKA_CFG_PROCESS_ROLES: broker,controller\n      KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093\n      KAFKA_CFG_LISTENERS: PLAINTEXT:\u002F\u002F:9092,CONTROLLER:\u002F\u002F:9093\n      KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT:\u002F\u002Flocalhost:9092\n      KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER\n      KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT\n      KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: \"false\"\n    volumes:\n      - kafka-data:\u002Fbitnami\u002Fkafka\n\nvolumes:\n  kafka-data:\n","yaml","",[39,40,41,54,62,75,83,92,100,112,123,134,145,156,167,178,189,197,205,212,220],"code",{"__ignoreMap":37},[42,43,46,50],"span",{"class":44,"line":45},"line",1,[42,47,49],{"class":48},"s4JwU","services",[42,51,53],{"class":52},"s95oV",":\n",[42,55,57,60],{"class":44,"line":56},2,[42,58,59],{"class":48},"  kafka",[42,61,53],{"class":52},[42,63,65,68,71],{"class":44,"line":64},3,[42,66,67],{"class":48},"    image",[42,69,70],{"class":52},": ",[42,72,74],{"class":73},"sU2Wk","bitnami\u002Fkafka:3.7\n",[42,76,78,81],{"class":44,"line":77},4,[42,79,80],{"class":48},"    ports",[42,82,53],{"class":52},[42,84,86,89],{"class":44,"line":85},5,[42,87,88],{"class":52},"      - ",[42,90,91],{"class":73},"\"9092:9092\"\n",[42,93,95,98],{"class":44,"line":94},6,[42,96,97],{"class":48},"    environment",[42,99,53],{"class":52},[42,101,103,106,108],{"class":44,"line":102},7,[42,104,105],{"class":48},"      KAFKA_CFG_NODE_ID",[42,107,70],{"class":52},[42,109,111],{"class":110},"sDLfK","1\n",[42,113,115,118,120],{"class":44,"line":114},8,[42,116,117],{"class":48},"      KAFKA_CFG_PROCESS_ROLES",[42,119,70],{"class":52},[42,121,122],{"class":73},"broker,controller\n",[42,124,126,129,131],{"class":44,"line":125},9,[42,127,128],{"class":48},"      KAFKA_CFG_CONTROLLER_QUORUM_VOTERS",[42,130,70],{"class":52},[42,132,133],{"class":73},"1@kafka:9093\n",[42,135,137,140,142],{"class":44,"line":136},10,[42,138,139],{"class":48},"      KAFKA_CFG_LISTENERS",[42,141,70],{"class":52},[42,143,144],{"class":73},"PLAINTEXT:\u002F\u002F:9092,CONTROLLER:\u002F\u002F:9093\n",[42,146,148,151,153],{"class":44,"line":147},11,[42,149,150],{"class":48},"      KAFKA_CFG_ADVERTISED_LISTENERS",[42,152,70],{"class":52},[42,154,155],{"class":73},"PLAINTEXT:\u002F\u002Flocalhost:9092\n",[42,157,159,162,164],{"class":44,"line":158},12,[42,160,161],{"class":48},"      KAFKA_CFG_CONTROLLER_LISTENER_NAMES",[42,163,70],{"class":52},[42,165,166],{"class":73},"CONTROLLER\n",[42,168,170,173,175],{"class":44,"line":169},13,[42,171,172],{"class":48},"      KAFKA_CFG_INTER_BROKER_LISTENER_NAME",[42,174,70],{"class":52},[42,176,177],{"class":73},"PLAINTEXT\n",[42,179,181,184,186],{"class":44,"line":180},14,[42,182,183],{"class":48},"      KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE",[42,185,70],{"class":52},[42,187,188],{"class":73},"\"false\"\n",[42,190,192,195],{"class":44,"line":191},15,[42,193,194],{"class":48},"    volumes",[42,196,53],{"class":52},[42,198,200,202],{"class":44,"line":199},16,[42,201,88],{"class":52},[42,203,204],{"class":73},"kafka-data:\u002Fbitnami\u002Fkafka\n",[42,206,208],{"class":44,"line":207},17,[42,209,211],{"emptyLinePlaceholder":210},true,"\n",[42,213,215,218],{"class":44,"line":214},18,[42,216,217],{"class":48},"volumes",[42,219,53],{"class":52},[42,221,223,226],{"class":44,"line":222},19,[42,224,225],{"class":48},"  kafka-data",[42,227,53],{"class":52},[15,229,230],{},"Для локальной среды допустимы упрощения:",[232,233,234,238,241,244,247],"ul",{},[235,236,237],"li",{},"один broker;",[235,239,240],{},"plaintext listener;",[235,242,243],{},"короткий retention;",[235,245,246],{},"auto topic creation можно выключить, чтобы ловить опечатки;",[235,248,249],{},"topics создавать init-script или командой в README.",[15,251,252],{},"Но не делайте выводы о production durability по single-broker compose. Там нет настоящего failover.",[15,254,255],{},"Интеграционные тесты на compose всё равно полезны, если проверяют не \"Kafka поднялась\", а контракт: topic создан явно, producer пишет с нужным key\u002Fheaders, consumer commit происходит после обработки, duplicate не портит состояние, shutdown по context завершается без зависших goroutines.",[21,257],{},[24,259,261],{"id":260},"kubernetes-и-strimzi","Kubernetes и Strimzi",[15,263,264],{},"Kafka в Kubernetes требует StatefulSet-подхода: стабильные имена, persistent volumes, аккуратные rolling upgrades. Вручную это поддерживать сложно, поэтому часто используют operator.",[15,266,267],{},"Strimzi - популярный Kafka operator для Kubernetes. Он описывает Kafka cluster через CRD:",[32,269,271],{"className":34,"code":270,"language":36,"meta":37,"style":37},"apiVersion: kafka.strimzi.io\u002Fv1beta2\nkind: Kafka\nmetadata:\n  name: mentor-kafka\nspec:\n  kafka:\n    version: 3.7.0\n    replicas: 3\n    listeners:\n      - name: plain\n        port: 9092\n        type: internal\n        tls: false\n    storage:\n      type: persistent-claim\n      size: 100Gi\n      class: fast-ssd\n  entityOperator:\n    topicOperator: {}\n    userOperator: {}\n",[39,272,273,283,293,300,310,317,323,333,343,350,362,372,382,392,399,409,419,429,436,444],{"__ignoreMap":37},[42,274,275,278,280],{"class":44,"line":45},[42,276,277],{"class":48},"apiVersion",[42,279,70],{"class":52},[42,281,282],{"class":73},"kafka.strimzi.io\u002Fv1beta2\n",[42,284,285,288,290],{"class":44,"line":56},[42,286,287],{"class":48},"kind",[42,289,70],{"class":52},[42,291,292],{"class":73},"Kafka\n",[42,294,295,298],{"class":44,"line":64},[42,296,297],{"class":48},"metadata",[42,299,53],{"class":52},[42,301,302,305,307],{"class":44,"line":77},[42,303,304],{"class":48},"  name",[42,306,70],{"class":52},[42,308,309],{"class":73},"mentor-kafka\n",[42,311,312,315],{"class":44,"line":85},[42,313,314],{"class":48},"spec",[42,316,53],{"class":52},[42,318,319,321],{"class":44,"line":94},[42,320,59],{"class":48},[42,322,53],{"class":52},[42,324,325,328,330],{"class":44,"line":102},[42,326,327],{"class":48},"    version",[42,329,70],{"class":52},[42,331,332],{"class":110},"3.7.0\n",[42,334,335,338,340],{"class":44,"line":114},[42,336,337],{"class":48},"    replicas",[42,339,70],{"class":52},[42,341,342],{"class":110},"3\n",[42,344,345,348],{"class":44,"line":125},[42,346,347],{"class":48},"    listeners",[42,349,53],{"class":52},[42,351,352,354,357,359],{"class":44,"line":136},[42,353,88],{"class":52},[42,355,356],{"class":48},"name",[42,358,70],{"class":52},[42,360,361],{"class":73},"plain\n",[42,363,364,367,369],{"class":44,"line":147},[42,365,366],{"class":48},"        port",[42,368,70],{"class":52},[42,370,371],{"class":110},"9092\n",[42,373,374,377,379],{"class":44,"line":158},[42,375,376],{"class":48},"        type",[42,378,70],{"class":52},[42,380,381],{"class":73},"internal\n",[42,383,384,387,389],{"class":44,"line":169},[42,385,386],{"class":48},"        tls",[42,388,70],{"class":52},[42,390,391],{"class":110},"false\n",[42,393,394,397],{"class":44,"line":180},[42,395,396],{"class":48},"    storage",[42,398,53],{"class":52},[42,400,401,404,406],{"class":44,"line":191},[42,402,403],{"class":48},"      type",[42,405,70],{"class":52},[42,407,408],{"class":73},"persistent-claim\n",[42,410,411,414,416],{"class":44,"line":199},[42,412,413],{"class":48},"      size",[42,415,70],{"class":52},[42,417,418],{"class":73},"100Gi\n",[42,420,421,424,426],{"class":44,"line":207},[42,422,423],{"class":48},"      class",[42,425,70],{"class":52},[42,427,428],{"class":73},"fast-ssd\n",[42,430,431,434],{"class":44,"line":214},[42,432,433],{"class":48},"  entityOperator",[42,435,53],{"class":52},[42,437,438,441],{"class":44,"line":222},[42,439,440],{"class":48},"    topicOperator",[42,442,443],{"class":52},": {}\n",[42,445,447,450],{"class":44,"line":446},20,[42,448,449],{"class":48},"    userOperator",[42,451,443],{"class":52},[15,453,454],{},"Что operator берёт на себя:",[232,456,457,460,463,466,469,472],{},[235,458,459],{},"создание brokers и persistent volumes;",[235,461,462],{},"rolling restart;",[235,464,465],{},"topic\u002Fuser resources;",[235,467,468],{},"certificates и listeners;",[235,470,471],{},"часть upgrade choreography;",[235,473,474],{},"status и reconciliation.",[15,476,477],{},"Но operator не отменяет capacity planning. Если диски медленные или partitions слишком много, operator не сделает Kafka быстрой магией.",[21,479],{},[24,481,483],{"id":482},"security-tls-sasl-acl","Security: TLS, SASL, ACL",[15,485,486],{},"В production Kafka не должна быть \"открытым 9092\".",[488,489,490,504],"table",{},[491,492,493],"thead",{},[494,495,496,501],"tr",{},[497,498,500],"th",{"align":499},"left","Механизм",[497,502,503],{"align":499},"Что решает",[505,506,507,516,524],"tbody",{},[494,508,509,513],{},[510,511,512],"td",{"align":499},"TLS",[510,514,515],{"align":499},"Шифрование соединения и иногда mutual authentication.",[494,517,518,521],{},[510,519,520],{"align":499},"SASL",[510,522,523],{"align":499},"Authentication: кто подключается.",[494,525,526,529],{},[510,527,528],{"align":499},"ACL",[510,530,531],{"align":499},"Authorization: что этому principal можно делать.",[15,533,534],{},"Пример модели доступа:",[32,536,541],{"className":537,"code":539,"language":540,"meta":37},[538],"language-text","principal: User:orders-service\n  WRITE orders.events\n  READ  orders.commands\n\nprincipal: User:billing-service\n  READ  orders.events as group billing-service\n  WRITE payments.events\n  WRITE orders.events.dlq\n","text",[39,542,539],{"__ignoreMap":37},[15,544,545],{},"ACL должны быть минимальными. Consumer не должен иметь write во все topics \"на всякий случай\". Producer не должен читать чужие topics.",[15,547,548],{},"Для Go-клиента это означает, что конфиг Kafka обычно содержит:",[232,550,551,554,557,560,563,566],{},[235,552,553],{},"brokers;",[235,555,556],{},"client id;",[235,558,559],{},"TLS certs или CA;",[235,561,562],{},"SASL mechanism;",[235,564,565],{},"username\u002Fpassword или другой credential;",[235,567,568],{},"topic names и group id.",[15,570,571],{},"Секреты хранятся в secret manager\u002FKubernetes Secret, а не в git.",[15,573,574],{},"Не включайте debug logs Kafka-клиента с полным конфигом в production: туда легко утекают SASL credentials, broker URLs внутренних сетей и payload fragments. Для инцидента лучше иметь безопасный набор diagnostic fields: client id, topic, partition, offset, error class, request latency.",[21,576],{},[24,578,580],{"id":579},"schema-registry","Schema Registry",[15,582,583],{},"Kafka хранит bytes, но команды работают с контрактами. Schema Registry даёт централизованное место для схем и compatibility rules.",[15,585,586],{},"Популярные форматы:",[488,588,589,599],{},[491,590,591],{},[494,592,593,596],{},[497,594,595],{"align":499},"Формат",[497,597,598],{"align":499},"Когда удобен",[505,600,601,609,617],{},[494,602,603,606],{},[510,604,605],{"align":499},"Avro",[510,607,608],{"align":499},"Часто используется в Kafka ecosystem, компактный binary.",[494,610,611,614],{},[510,612,613],{"align":499},"Protobuf",[510,615,616],{"align":499},"Удобен Go\u002Fbackend-командам, строгие messages.",[494,618,619,622],{},[510,620,621],{"align":499},"JSON Schema",[510,623,624],{"align":499},"Ближе к JSON-миру, проще для ручной отладки.",[15,626,627],{},"Compatibility policies:",[232,629,630,633,636,639],{},[235,631,632],{},"backward: новые consumers читают старые данные;",[235,634,635],{},"forward: старые consumers читают новые данные;",[235,637,638],{},"full: оба направления;",[235,640,641],{},"none: без гарантий, обычно опасно.",[15,643,644],{},"Даже без Schema Registry держите дисциплину: event type, version, документация, примеры payload, правила изменения полей.",[15,646,647],{},"Схемы полезны только вместе с процессом. Breaking change должен идти через review: кто читает topic, какие старые records останутся в retention, как consumer ведёт себя при неизвестной версии, и есть ли fixtures для backward\u002Fforward compatibility.",[21,649],{},[24,651,653],{"id":652},"outbox-pattern","Outbox pattern",[15,655,656],{},"Outbox решает классическую проблему двойной записи: нужно сохранить бизнес-изменение в БД и опубликовать событие в Kafka.",[15,658,659],{},"Плохой вариант:",[32,661,664],{"className":662,"code":663,"language":540,"meta":37},[538],"INSERT order\nCOMMIT\npublish OrderCreated to Kafka\n\nЕсли publish упал, заказ есть, события нет.\n",[39,665,663],{"__ignoreMap":37},[15,667,668],{},"Outbox:",[32,670,673],{"className":671,"code":672,"language":540,"meta":37},[538],"HTTP request\n  |\n  v\nDB transaction\n  INSERT orders\n  INSERT outbox_events(id, topic, key, payload, status)\n  COMMIT\n  |\n  v\nOutbox publisher\n  SELECT unpublished events\n  publish to Kafka\n  mark as published\n",[39,674,672],{"__ignoreMap":37},[15,676,677],{},"Go-скелет publisher:",[32,679,684],{"className":680,"code":681,"language":682,"meta":683,"style":37},"language-go shiki shiki-themes github-dark","package outbox\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n\n    \"github.com\u002Fsegmentio\u002Fkafka-go\"\n)\n\ntype Event struct {\n    ID      string\n    Topic   string\n    Key     string\n    Payload []byte\n}\n\ntype Store interface {\n    LockUnpublished(ctx context.Context, limit int) ([]Event, error)\n    MarkPublished(ctx context.Context, id string) error\n}\n\nfunc RunPublisher(ctx context.Context, store Store, writer *kafka.Writer, interval time.Duration) error {\n    ticker := time.NewTicker(interval)\n    defer ticker.Stop()\n\n    for {\n        if err := publishBatch(ctx, store, writer); err != nil {\n            return err\n        }\n\n        select {\n        case \u003C-ctx.Done():\n            return ctx.Err()\n        case \u003C-ticker.C:\n        }\n    }\n}\n\nfunc publishBatch(ctx context.Context, store Store, writer *kafka.Writer) error {\n    events, err := store.LockUnpublished(ctx, 100)\n    if err != nil {\n        return fmt.Errorf(\"lock unpublished outbox events: %w\", err)\n    }\n\n    for _, event := range events {\n        writeCtx, cancel := context.WithTimeout(ctx, 5*time.Second)\n        err := writer.WriteMessages(writeCtx, kafka.Message{\n            Topic: event.Topic,\n            Key:   []byte(event.Key),\n            Value: event.Payload,\n            Headers: []kafka.Header{\n                {Key: \"event-id\", Value: []byte(event.ID)},\n            },\n        })\n        cancel()\n        if err != nil {\n            return fmt.Errorf(\"publish outbox event id=%s: %w\", event.ID, err)\n        }\n\n        if err := store.MarkPublished(ctx, event.ID); err != nil {\n            return fmt.Errorf(\"mark outbox event id=%s published: %w\", event.ID, err)\n        }\n    }\n\n    return nil\n}\n","go","no-run",[39,685,686,696,700,708,719,728,737,741,750,755,759,773,781,788,795,803,808,812,824,867,896,901,906,967,985,1000,1005,1013,1038,1047,1053,1058,1066,1084,1097,1107,1112,1118,1123,1128,1169,1191,1205,1231,1236,1241,1257,1282,1309,1315,1327,1333,1348,1365,1371,1377,1385,1398,1424,1429,1434,1457,1482,1487,1492,1497,1506],{"__ignoreMap":37},[42,687,688,692],{"class":44,"line":45},[42,689,691],{"class":690},"snl16","package",[42,693,695],{"class":694},"svObZ"," outbox\n",[42,697,698],{"class":44,"line":56},[42,699,211],{"emptyLinePlaceholder":210},[42,701,702,705],{"class":44,"line":64},[42,703,704],{"class":690},"import",[42,706,707],{"class":52}," (\n",[42,709,710,713,716],{"class":44,"line":77},[42,711,712],{"class":73},"    \"",[42,714,715],{"class":694},"context",[42,717,718],{"class":73},"\"\n",[42,720,721,723,726],{"class":44,"line":85},[42,722,712],{"class":73},[42,724,725],{"class":694},"fmt",[42,727,718],{"class":73},[42,729,730,732,735],{"class":44,"line":94},[42,731,712],{"class":73},[42,733,734],{"class":694},"time",[42,736,718],{"class":73},[42,738,739],{"class":44,"line":102},[42,740,211],{"emptyLinePlaceholder":210},[42,742,743,745,748],{"class":44,"line":114},[42,744,712],{"class":73},[42,746,747],{"class":694},"github.com\u002Fsegmentio\u002Fkafka-go",[42,749,718],{"class":73},[42,751,752],{"class":44,"line":125},[42,753,754],{"class":52},")\n",[42,756,757],{"class":44,"line":136},[42,758,211],{"emptyLinePlaceholder":210},[42,760,761,764,767,770],{"class":44,"line":147},[42,762,763],{"class":690},"type",[42,765,766],{"class":694}," Event",[42,768,769],{"class":690}," struct",[42,771,772],{"class":52}," {\n",[42,774,775,778],{"class":44,"line":158},[42,776,777],{"class":52},"    ID      ",[42,779,780],{"class":690},"string\n",[42,782,783,786],{"class":44,"line":169},[42,784,785],{"class":52},"    Topic   ",[42,787,780],{"class":690},[42,789,790,793],{"class":44,"line":180},[42,791,792],{"class":52},"    Key     ",[42,794,780],{"class":690},[42,796,797,800],{"class":44,"line":191},[42,798,799],{"class":52},"    Payload []",[42,801,802],{"class":690},"byte\n",[42,804,805],{"class":44,"line":199},[42,806,807],{"class":52},"}\n",[42,809,810],{"class":44,"line":207},[42,811,211],{"emptyLinePlaceholder":210},[42,813,814,816,819,822],{"class":44,"line":214},[42,815,763],{"class":690},[42,817,818],{"class":694}," Store",[42,820,821],{"class":690}," interface",[42,823,772],{"class":52},[42,825,826,829,832,836,839,842,845,848,851,854,857,860,862,865],{"class":44,"line":222},[42,827,828],{"class":694},"    LockUnpublished",[42,830,831],{"class":52},"(",[42,833,835],{"class":834},"s9osk","ctx",[42,837,838],{"class":694}," context",[42,840,841],{"class":52},".",[42,843,844],{"class":694},"Context",[42,846,847],{"class":52},", ",[42,849,850],{"class":834},"limit",[42,852,853],{"class":690}," int",[42,855,856],{"class":52},") ([]",[42,858,859],{"class":694},"Event",[42,861,847],{"class":52},[42,863,864],{"class":690},"error",[42,866,754],{"class":52},[42,868,869,872,874,876,878,880,882,884,887,890,893],{"class":44,"line":446},[42,870,871],{"class":694},"    MarkPublished",[42,873,831],{"class":52},[42,875,835],{"class":834},[42,877,838],{"class":694},[42,879,841],{"class":52},[42,881,844],{"class":694},[42,883,847],{"class":52},[42,885,886],{"class":834},"id",[42,888,889],{"class":690}," string",[42,891,892],{"class":52},") ",[42,894,895],{"class":690},"error\n",[42,897,899],{"class":44,"line":898},21,[42,900,807],{"class":52},[42,902,904],{"class":44,"line":903},22,[42,905,211],{"emptyLinePlaceholder":210},[42,907,909,912,915,917,919,921,923,925,927,930,932,934,937,940,943,945,948,950,953,956,958,961,963,965],{"class":44,"line":908},23,[42,910,911],{"class":690},"func",[42,913,914],{"class":694}," RunPublisher",[42,916,831],{"class":52},[42,918,835],{"class":834},[42,920,838],{"class":694},[42,922,841],{"class":52},[42,924,844],{"class":694},[42,926,847],{"class":52},[42,928,929],{"class":834},"store",[42,931,818],{"class":694},[42,933,847],{"class":52},[42,935,936],{"class":834},"writer",[42,938,939],{"class":690}," *",[42,941,942],{"class":694},"kafka",[42,944,841],{"class":52},[42,946,947],{"class":694},"Writer",[42,949,847],{"class":52},[42,951,952],{"class":834},"interval",[42,954,955],{"class":694}," time",[42,957,841],{"class":52},[42,959,960],{"class":694},"Duration",[42,962,892],{"class":52},[42,964,864],{"class":690},[42,966,772],{"class":52},[42,968,970,973,976,979,982],{"class":44,"line":969},24,[42,971,972],{"class":52},"    ticker ",[42,974,975],{"class":690},":=",[42,977,978],{"class":52}," time.",[42,980,981],{"class":694},"NewTicker",[42,983,984],{"class":52},"(interval)\n",[42,986,988,991,994,997],{"class":44,"line":987},25,[42,989,990],{"class":690},"    defer",[42,992,993],{"class":52}," ticker.",[42,995,996],{"class":694},"Stop",[42,998,999],{"class":52},"()\n",[42,1001,1003],{"class":44,"line":1002},26,[42,1004,211],{"emptyLinePlaceholder":210},[42,1006,1008,1011],{"class":44,"line":1007},27,[42,1009,1010],{"class":690},"    for",[42,1012,772],{"class":52},[42,1014,1016,1019,1022,1024,1027,1030,1033,1036],{"class":44,"line":1015},28,[42,1017,1018],{"class":690},"        if",[42,1020,1021],{"class":52}," err ",[42,1023,975],{"class":690},[42,1025,1026],{"class":694}," publishBatch",[42,1028,1029],{"class":52},"(ctx, store, writer); err ",[42,1031,1032],{"class":690},"!=",[42,1034,1035],{"class":110}," nil",[42,1037,772],{"class":52},[42,1039,1041,1044],{"class":44,"line":1040},29,[42,1042,1043],{"class":690},"            return",[42,1045,1046],{"class":52}," err\n",[42,1048,1050],{"class":44,"line":1049},30,[42,1051,1052],{"class":52},"        }\n",[42,1054,1056],{"class":44,"line":1055},31,[42,1057,211],{"emptyLinePlaceholder":210},[42,1059,1061,1064],{"class":44,"line":1060},32,[42,1062,1063],{"class":690},"        select",[42,1065,772],{"class":52},[42,1067,1069,1072,1075,1078,1081],{"class":44,"line":1068},33,[42,1070,1071],{"class":690},"        case",[42,1073,1074],{"class":690}," \u003C-",[42,1076,1077],{"class":52},"ctx.",[42,1079,1080],{"class":694},"Done",[42,1082,1083],{"class":52},"():\n",[42,1085,1087,1089,1092,1095],{"class":44,"line":1086},34,[42,1088,1043],{"class":690},[42,1090,1091],{"class":52}," ctx.",[42,1093,1094],{"class":694},"Err",[42,1096,999],{"class":52},[42,1098,1100,1102,1104],{"class":44,"line":1099},35,[42,1101,1071],{"class":690},[42,1103,1074],{"class":690},[42,1105,1106],{"class":52},"ticker.C:\n",[42,1108,1110],{"class":44,"line":1109},36,[42,1111,1052],{"class":52},[42,1113,1115],{"class":44,"line":1114},37,[42,1116,1117],{"class":52},"    }\n",[42,1119,1121],{"class":44,"line":1120},38,[42,1122,807],{"class":52},[42,1124,1126],{"class":44,"line":1125},39,[42,1127,211],{"emptyLinePlaceholder":210},[42,1129,1131,1133,1135,1137,1139,1141,1143,1145,1147,1149,1151,1153,1155,1157,1159,1161,1163,1165,1167],{"class":44,"line":1130},40,[42,1132,911],{"class":690},[42,1134,1026],{"class":694},[42,1136,831],{"class":52},[42,1138,835],{"class":834},[42,1140,838],{"class":694},[42,1142,841],{"class":52},[42,1144,844],{"class":694},[42,1146,847],{"class":52},[42,1148,929],{"class":834},[42,1150,818],{"class":694},[42,1152,847],{"class":52},[42,1154,936],{"class":834},[42,1156,939],{"class":690},[42,1158,942],{"class":694},[42,1160,841],{"class":52},[42,1162,947],{"class":694},[42,1164,892],{"class":52},[42,1166,864],{"class":690},[42,1168,772],{"class":52},[42,1170,1172,1175,1177,1180,1183,1186,1189],{"class":44,"line":1171},41,[42,1173,1174],{"class":52},"    events, err ",[42,1176,975],{"class":690},[42,1178,1179],{"class":52}," store.",[42,1181,1182],{"class":694},"LockUnpublished",[42,1184,1185],{"class":52},"(ctx, ",[42,1187,1188],{"class":110},"100",[42,1190,754],{"class":52},[42,1192,1194,1197,1199,1201,1203],{"class":44,"line":1193},42,[42,1195,1196],{"class":690},"    if",[42,1198,1021],{"class":52},[42,1200,1032],{"class":690},[42,1202,1035],{"class":110},[42,1204,772],{"class":52},[42,1206,1208,1211,1214,1217,1219,1222,1225,1228],{"class":44,"line":1207},43,[42,1209,1210],{"class":690},"        return",[42,1212,1213],{"class":52}," fmt.",[42,1215,1216],{"class":694},"Errorf",[42,1218,831],{"class":52},[42,1220,1221],{"class":73},"\"lock unpublished outbox events: ",[42,1223,1224],{"class":110},"%w",[42,1226,1227],{"class":73},"\"",[42,1229,1230],{"class":52},", err)\n",[42,1232,1234],{"class":44,"line":1233},44,[42,1235,1117],{"class":52},[42,1237,1239],{"class":44,"line":1238},45,[42,1240,211],{"emptyLinePlaceholder":210},[42,1242,1244,1246,1249,1251,1254],{"class":44,"line":1243},46,[42,1245,1010],{"class":690},[42,1247,1248],{"class":52}," _, event ",[42,1250,975],{"class":690},[42,1252,1253],{"class":690}," range",[42,1255,1256],{"class":52}," events {\n",[42,1258,1260,1263,1265,1268,1271,1273,1276,1279],{"class":44,"line":1259},47,[42,1261,1262],{"class":52},"        writeCtx, cancel ",[42,1264,975],{"class":690},[42,1266,1267],{"class":52}," context.",[42,1269,1270],{"class":694},"WithTimeout",[42,1272,1185],{"class":52},[42,1274,1275],{"class":110},"5",[42,1277,1278],{"class":690},"*",[42,1280,1281],{"class":52},"time.Second)\n",[42,1283,1285,1288,1290,1293,1296,1299,1301,1303,1306],{"class":44,"line":1284},48,[42,1286,1287],{"class":52},"        err ",[42,1289,975],{"class":690},[42,1291,1292],{"class":52}," writer.",[42,1294,1295],{"class":694},"WriteMessages",[42,1297,1298],{"class":52},"(writeCtx, ",[42,1300,942],{"class":694},[42,1302,841],{"class":52},[42,1304,1305],{"class":694},"Message",[42,1307,1308],{"class":52},"{\n",[42,1310,1312],{"class":44,"line":1311},49,[42,1313,1314],{"class":52},"            Topic: event.Topic,\n",[42,1316,1318,1321,1324],{"class":44,"line":1317},50,[42,1319,1320],{"class":52},"            Key:   []",[42,1322,1323],{"class":690},"byte",[42,1325,1326],{"class":52},"(event.Key),\n",[42,1328,1330],{"class":44,"line":1329},51,[42,1331,1332],{"class":52},"            Value: event.Payload,\n",[42,1334,1336,1339,1341,1343,1346],{"class":44,"line":1335},52,[42,1337,1338],{"class":52},"            Headers: []",[42,1340,942],{"class":694},[42,1342,841],{"class":52},[42,1344,1345],{"class":694},"Header",[42,1347,1308],{"class":52},[42,1349,1351,1354,1357,1360,1362],{"class":44,"line":1350},53,[42,1352,1353],{"class":52},"                {Key: ",[42,1355,1356],{"class":73},"\"event-id\"",[42,1358,1359],{"class":52},", Value: []",[42,1361,1323],{"class":690},[42,1363,1364],{"class":52},"(event.ID)},\n",[42,1366,1368],{"class":44,"line":1367},54,[42,1369,1370],{"class":52},"            },\n",[42,1372,1374],{"class":44,"line":1373},55,[42,1375,1376],{"class":52},"        })\n",[42,1378,1380,1383],{"class":44,"line":1379},56,[42,1381,1382],{"class":694},"        cancel",[42,1384,999],{"class":52},[42,1386,1388,1390,1392,1394,1396],{"class":44,"line":1387},57,[42,1389,1018],{"class":690},[42,1391,1021],{"class":52},[42,1393,1032],{"class":690},[42,1395,1035],{"class":110},[42,1397,772],{"class":52},[42,1399,1401,1403,1405,1407,1409,1412,1415,1417,1419,1421],{"class":44,"line":1400},58,[42,1402,1043],{"class":690},[42,1404,1213],{"class":52},[42,1406,1216],{"class":694},[42,1408,831],{"class":52},[42,1410,1411],{"class":73},"\"publish outbox event id=",[42,1413,1414],{"class":110},"%s",[42,1416,70],{"class":73},[42,1418,1224],{"class":110},[42,1420,1227],{"class":73},[42,1422,1423],{"class":52},", event.ID, err)\n",[42,1425,1427],{"class":44,"line":1426},59,[42,1428,1052],{"class":52},[42,1430,1432],{"class":44,"line":1431},60,[42,1433,211],{"emptyLinePlaceholder":210},[42,1435,1437,1439,1441,1443,1445,1448,1451,1453,1455],{"class":44,"line":1436},61,[42,1438,1018],{"class":690},[42,1440,1021],{"class":52},[42,1442,975],{"class":690},[42,1444,1179],{"class":52},[42,1446,1447],{"class":694},"MarkPublished",[42,1449,1450],{"class":52},"(ctx, event.ID); err ",[42,1452,1032],{"class":690},[42,1454,1035],{"class":110},[42,1456,772],{"class":52},[42,1458,1460,1462,1464,1466,1468,1471,1473,1476,1478,1480],{"class":44,"line":1459},62,[42,1461,1043],{"class":690},[42,1463,1213],{"class":52},[42,1465,1216],{"class":694},[42,1467,831],{"class":52},[42,1469,1470],{"class":73},"\"mark outbox event id=",[42,1472,1414],{"class":110},[42,1474,1475],{"class":73}," published: ",[42,1477,1224],{"class":110},[42,1479,1227],{"class":73},[42,1481,1423],{"class":52},[42,1483,1485],{"class":44,"line":1484},63,[42,1486,1052],{"class":52},[42,1488,1490],{"class":44,"line":1489},64,[42,1491,1117],{"class":52},[42,1493,1495],{"class":44,"line":1494},65,[42,1496,211],{"emptyLinePlaceholder":210},[42,1498,1500,1503],{"class":44,"line":1499},66,[42,1501,1502],{"class":690},"    return",[42,1504,1505],{"class":110}," nil\n",[42,1507,1509],{"class":44,"line":1508},67,[42,1510,807],{"class":52},[15,1512,1513,1514,1516],{},"Если событие ушло в Kafka, но ",[39,1515,1447],{}," упал, publisher может отправить duplicate после restart. Поэтому consumers всё равно должны быть идемпотентными.",[15,1518,1519],{},"Outbox publisher также нуждается в operability: retry_count, next_attempt_at, last_error, метрика backlog age и runbook для stuck rows. Иначе outbox превращается в ещё одну очередь без visibility.",[21,1521],{},[24,1523,1525],{"id":1524},"monitoring-и-runbooks","Monitoring и runbooks",[15,1527,1528],{},"Production Kafka без monitoring - это будущая авария.",[15,1530,1531],{},"Минимальный dashboard:",[232,1533,1534,1537,1540,1543,1546,1549,1552,1555,1558,1561],{},[235,1535,1536],{},"broker disk usage;",[235,1538,1539],{},"under-replicated partitions;",[235,1541,1542],{},"offline partitions;",[235,1544,1545],{},"request latency;",[235,1547,1548],{},"network in\u002Fout;",[235,1550,1551],{},"consumer lag по group\u002Ftopic\u002Fpartition;",[235,1553,1554],{},"producer error rate;",[235,1556,1557],{},"DLQ rate;",[235,1559,1560],{},"rebalance count;",[235,1562,1563],{},"JVM\u002Fheap\u002FGC для broker, если вы отвечаете за cluster.",[15,1565,1566],{},"Runbook должен отвечать:",[232,1568,1569,1572,1575,1578,1581,1584,1587],{},[235,1570,1571],{},"что делать при росте lag;",[235,1573,1574],{},"как replay DLQ;",[235,1576,1577],{},"как увеличить partitions;",[235,1579,1580],{},"как проверить ACL;",[235,1582,1583],{},"как понять, что producer не пишет;",[235,1585,1586],{},"как действовать при заполнении диска;",[235,1588,1589],{},"кто владеет каждым topic.",[15,1591,1592],{},"При replay из DLQ runbook должен требовать dry-run выборки, лимит скорости, владельца approval и способ остановки. Массовый replay без throttling легко создаёт вторую аварию: producer spike, рост lag у соседних consumers или повтор внешних side effects.",[21,1594],{},[24,1596,1598],{"id":1597},"rollout-и-rollback-kafka-changes","Rollout и rollback Kafka changes",[15,1600,1601],{},"Kafka-интеграции редко откатываются как обычный HTTP endpoint. После релиза могли появиться новые records, offsets и side effects. Поэтому rollout plan должен включать:",[232,1603,1604,1607,1610,1613,1616,1619],{},[235,1605,1606],{},"feature flag или config switch для producer, чтобы временно остановить публикацию;",[235,1608,1609],{},"backward-compatible schema до обновления всех consumers;",[235,1611,1612],{},"отдельный plan для изменения key или partitions;",[235,1614,1615],{},"canary consumer group или shadow consumer для проверки payload до включения side effects;",[235,1617,1618],{},"rollback, который не требует удаления topic или ручного сброса offsets без review;",[235,1620,1621],{},"alerts на producer errors, lag, DLQ и rebalance во время deploy.",[21,1623],{},[24,1625,1627],{"id":1626},"checklist-backend-разработчика","Checklist backend-разработчика",[15,1629,1630],{},"Перед релизом Kafka-интеграции проверьте:",[488,1632,1633,1643],{},[491,1634,1635],{},[494,1636,1637,1640],{},[497,1638,1639],{"align":499},"Вопрос",[497,1641,1642],{"align":499},"Готово?",[505,1644,1645,1652,1659,1666,1673,1684,1691,1698,1705,1712,1719,1726,1733,1740,1747,1754,1761],{},[494,1646,1647,1650],{},[510,1648,1649],{"align":499},"Topic создан явно, с partitions\u002Freplication\u002Fretention?",[510,1651],{"align":499},[494,1653,1654,1657],{},[510,1655,1656],{"align":499},"Key выбран по доменной гарантии порядка?",[510,1658],{"align":499},[494,1660,1661,1664],{},[510,1662,1663],{"align":499},"Producer имеет timeout, retry и понятные acks?",[510,1665],{"align":499},[494,1667,1668,1671],{},[510,1669,1670],{"align":499},"Consumer commit происходит после успешной обработки?",[510,1672],{"align":499},[494,1674,1675,1682],{},[510,1676,1677,1678,1681],{"align":499},"Handler идемпотентен по ",[39,1679,1680],{},"event_id"," или бизнес-ключу?",[510,1683],{"align":499},[494,1685,1686,1689],{},[510,1687,1688],{"align":499},"Есть стратегия poison messages и DLQ?",[510,1690],{"align":499},[494,1692,1693,1696],{},[510,1694,1695],{"align":499},"DLQ мониторится и имеет replay-процесс?",[510,1697],{"align":499},[494,1699,1700,1703],{},[510,1701,1702],{"align":499},"Событие версионируется?",[510,1704],{"align":499},[494,1706,1707,1710],{},[510,1708,1709],{"align":499},"Есть contract\u002Fschema documentation?",[510,1711],{"align":499},[494,1713,1714,1717],{},[510,1715,1716],{"align":499},"Secrets не лежат в git?",[510,1718],{"align":499},[494,1720,1721,1724],{},[510,1722,1723],{"align":499},"ACL минимальны?",[510,1725],{"align":499},[494,1727,1728,1731],{},[510,1729,1730],{"align":499},"Lag, errors и latency попали в monitoring?",[510,1732],{"align":499},[494,1734,1735,1738],{},[510,1736,1737],{"align":499},"Metric labels не создают high-cardinality?",[510,1739],{"align":499},[494,1741,1742,1745],{},[510,1743,1744],{"align":499},"Есть capacity estimate: throughput, retention, disk, replay speed?",[510,1746],{"align":499},[494,1748,1749,1752],{},[510,1750,1751],{"align":499},"Rollout\u002Frollback описан для schema, producer и consumer отдельно?",[510,1753],{"align":499},[494,1755,1756,1759],{},[510,1757,1758],{"align":499},"Shutdown проверен под SIGTERM?",[510,1760],{"align":499},[494,1762,1763,1766],{},[510,1764,1765],{"align":499},"Outbox нужен? Если да, реализован?",[510,1767],{"align":499},[21,1769],{},[24,1771,1773],{"id":1772},"вопросы-на-собеседовании","Вопросы на собеседовании",[232,1775,1776,1779,1782,1785,1788,1791,1794,1797,1800,1803,1806],{},[235,1777,1778],{},"Почему Docker Compose с одним broker не доказывает production-надежность?",[235,1780,1781],{},"Зачем Kafka в Kubernetes обычно запускают через operator?",[235,1783,1784],{},"Что такое Strimzi?",[235,1786,1787],{},"Чем TLS, SASL и ACL отличаются по назначению?",[235,1789,1790],{},"Зачем нужен Schema Registry?",[235,1792,1793],{},"Какие compatibility policies бывают у схем?",[235,1795,1796],{},"Как outbox pattern решает проблему двойной записи?",[235,1798,1799],{},"Почему outbox всё равно может породить duplicate?",[235,1801,1802],{},"Какие Kafka-метрики вы добавите на dashboard backend-сервиса?",[235,1804,1805],{},"Почему rollback producer-а не удаляет уже опубликованные события?",[235,1807,1808],{},"Зачем canary или shadow consumer перед включением side effects?",[21,1810],{},[24,1812,1814],{"id":1813},"практика","Практика",[1816,1817,1818,1825,1840,1843],"ol",{},[235,1819,1820,1821,1824],{},"Напишите ",[39,1822,1823],{},"docker-compose.yml"," для локальной Kafka и отключите auto topic creation.",[235,1826,1827,1828,1831,1832,1835,1836,1839],{},"Составьте ACL для ",[39,1829,1830],{},"orders-service",", который пишет ",[39,1833,1834],{},"orders.events",", и ",[39,1837,1838],{},"billing-service",", который читает этот topic.",[235,1841,1842],{},"Спроектируйте outbox-таблицу: поля, индексы, статусы, retry-счётчик.",[235,1844,1845,1846,841],{},"Сделайте production checklist для нового consumer ",[39,1847,1848],{},"notifications-service",[21,1850],{},[24,1852,1854],{"id":1853},"интерактивная-практика","Интерактивная практика",[1856,1857,1861,1864,1881],"quiz",{"answer":1858,"id":1859,"xp":1860},"3","kafka-deployment-q1","10",[15,1862,1863],{},"Почему локальный Docker Compose с одним broker не доказывает production-надежность Kafka-интеграции?",[1865,1866,1867],"template",{"v-slot:options":37},[232,1868,1869,1872,1875,1878],{},[235,1870,1871],{},"Потому что Kafka нельзя запускать в контейнерах",[235,1873,1874],{},"Потому что producer и consumer не работают с одним broker",[235,1876,1877],{},"Потому что он не проверяет replication, rolling upgrades, ACL, TLS, capacity и отказоустойчивость",[235,1879,1880],{},"Потому что в одном broker всегда есть exactly-once для всех consumers",[1865,1882,1883],{"v-slot:explanation":37},[15,1884,1885],{},"Single-broker compose полезен для разработки, но не моделирует отказ брокеров, ISR, security, upgrades, quotas, storage и operational runbooks.",[1887,1888,1892,1895,2085],"predict",{"answer":1889,"id":1890,"xp":1891},"auth\\nencrypt\\nallow","kafka-deployment-p1","15",[15,1893,1894],{},"Что выведет программа?",[1865,1896,1897],{"v-slot:code":37},[32,1898,1900],{"className":680,"code":1899,"language":682,"meta":37,"style":37},"package main\n\nimport \"fmt\"\n\nfunc securityPurpose(layer string) string {\n    switch layer {\n    case \"SASL\":\n        return \"auth\"\n    case \"TLS\":\n        return \"encrypt\"\n    default:\n        return \"allow\"\n    }\n}\n\nfunc main() {\n    fmt.Println(securityPurpose(\"SASL\"))\n    fmt.Println(securityPurpose(\"TLS\"))\n    fmt.Println(securityPurpose(\"ACL\"))\n}\n",[39,1901,1902,1909,1913,1924,1928,1949,1957,1967,1974,1983,1990,1997,2004,2008,2012,2016,2026,2047,2064,2081],{"__ignoreMap":37},[42,1903,1904,1906],{"class":44,"line":45},[42,1905,691],{"class":690},[42,1907,1908],{"class":694}," main\n",[42,1910,1911],{"class":44,"line":56},[42,1912,211],{"emptyLinePlaceholder":210},[42,1914,1915,1917,1920,1922],{"class":44,"line":64},[42,1916,704],{"class":690},[42,1918,1919],{"class":73}," \"",[42,1921,725],{"class":694},[42,1923,718],{"class":73},[42,1925,1926],{"class":44,"line":77},[42,1927,211],{"emptyLinePlaceholder":210},[42,1929,1930,1932,1935,1937,1940,1942,1944,1947],{"class":44,"line":85},[42,1931,911],{"class":690},[42,1933,1934],{"class":694}," securityPurpose",[42,1936,831],{"class":52},[42,1938,1939],{"class":834},"layer",[42,1941,889],{"class":690},[42,1943,892],{"class":52},[42,1945,1946],{"class":690},"string",[42,1948,772],{"class":52},[42,1950,1951,1954],{"class":44,"line":94},[42,1952,1953],{"class":690},"    switch",[42,1955,1956],{"class":52}," layer {\n",[42,1958,1959,1962,1965],{"class":44,"line":102},[42,1960,1961],{"class":690},"    case",[42,1963,1964],{"class":73}," \"SASL\"",[42,1966,53],{"class":52},[42,1968,1969,1971],{"class":44,"line":114},[42,1970,1210],{"class":690},[42,1972,1973],{"class":73}," \"auth\"\n",[42,1975,1976,1978,1981],{"class":44,"line":125},[42,1977,1961],{"class":690},[42,1979,1980],{"class":73}," \"TLS\"",[42,1982,53],{"class":52},[42,1984,1985,1987],{"class":44,"line":136},[42,1986,1210],{"class":690},[42,1988,1989],{"class":73}," \"encrypt\"\n",[42,1991,1992,1995],{"class":44,"line":147},[42,1993,1994],{"class":690},"    default",[42,1996,53],{"class":52},[42,1998,1999,2001],{"class":44,"line":158},[42,2000,1210],{"class":690},[42,2002,2003],{"class":73}," \"allow\"\n",[42,2005,2006],{"class":44,"line":169},[42,2007,1117],{"class":52},[42,2009,2010],{"class":44,"line":180},[42,2011,807],{"class":52},[42,2013,2014],{"class":44,"line":191},[42,2015,211],{"emptyLinePlaceholder":210},[42,2017,2018,2020,2023],{"class":44,"line":199},[42,2019,911],{"class":690},[42,2021,2022],{"class":694}," main",[42,2024,2025],{"class":52},"() {\n",[42,2027,2028,2031,2034,2036,2039,2041,2044],{"class":44,"line":207},[42,2029,2030],{"class":52},"    fmt.",[42,2032,2033],{"class":694},"Println",[42,2035,831],{"class":52},[42,2037,2038],{"class":694},"securityPurpose",[42,2040,831],{"class":52},[42,2042,2043],{"class":73},"\"SASL\"",[42,2045,2046],{"class":52},"))\n",[42,2048,2049,2051,2053,2055,2057,2059,2062],{"class":44,"line":214},[42,2050,2030],{"class":52},[42,2052,2033],{"class":694},[42,2054,831],{"class":52},[42,2056,2038],{"class":694},[42,2058,831],{"class":52},[42,2060,2061],{"class":73},"\"TLS\"",[42,2063,2046],{"class":52},[42,2065,2066,2068,2070,2072,2074,2076,2079],{"class":44,"line":222},[42,2067,2030],{"class":52},[42,2069,2033],{"class":694},[42,2071,831],{"class":52},[42,2073,2038],{"class":694},[42,2075,831],{"class":52},[42,2077,2078],{"class":73},"\"ACL\"",[42,2080,2046],{"class":52},[42,2082,2083],{"class":44,"line":446},[42,2084,807],{"class":52},[1865,2086,2087],{"v-slot:hint":37},[15,2088,2089],{},"SASL отвечает за аутентификацию, TLS — за шифрование канала, ACL — за права на topics\u002Fgroups.",[2091,2092,2096,2110,2258],"code-task",{"expected":2093,"id":2094,"xp":2095},"ready\\nnot-ready\\nready","kafka-deployment-ct1","20",[15,2097,2098,2099,2102,2103,2106,2107,841],{},"Реализуй ",[39,2100,2101],{},"Readiness",": HTTP API с outbox может оставаться ",[39,2104,2105],{},"ready"," при краткой недоступности Kafka, а сервис без outbox должен стать ",[39,2108,2109],{},"not-ready",[1865,2111,2112],{"v-slot:template":37},[32,2113,2115],{"className":680,"code":2114,"language":682,"meta":37,"style":37},"package main\n\nimport \"fmt\"\n\nfunc Readiness(kafkaAvailable bool, hasOutbox bool) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(Readiness(true, false))\n    fmt.Println(Readiness(false, false))\n    fmt.Println(Readiness(false, true))\n}\n",[39,2116,2117,2123,2127,2137,2141,2169,2176,2180,2184,2192,2214,2234,2254],{"__ignoreMap":37},[42,2118,2119,2121],{"class":44,"line":45},[42,2120,691],{"class":690},[42,2122,1908],{"class":694},[42,2124,2125],{"class":44,"line":56},[42,2126,211],{"emptyLinePlaceholder":210},[42,2128,2129,2131,2133,2135],{"class":44,"line":64},[42,2130,704],{"class":690},[42,2132,1919],{"class":73},[42,2134,725],{"class":694},[42,2136,718],{"class":73},[42,2138,2139],{"class":44,"line":77},[42,2140,211],{"emptyLinePlaceholder":210},[42,2142,2143,2145,2148,2150,2153,2156,2158,2161,2163,2165,2167],{"class":44,"line":85},[42,2144,911],{"class":690},[42,2146,2147],{"class":694}," Readiness",[42,2149,831],{"class":52},[42,2151,2152],{"class":834},"kafkaAvailable",[42,2154,2155],{"class":690}," bool",[42,2157,847],{"class":52},[42,2159,2160],{"class":834},"hasOutbox",[42,2162,2155],{"class":690},[42,2164,892],{"class":52},[42,2166,1946],{"class":690},[42,2168,772],{"class":52},[42,2170,2171,2173],{"class":44,"line":94},[42,2172,1502],{"class":690},[42,2174,2175],{"class":73}," \"todo\"\n",[42,2177,2178],{"class":44,"line":102},[42,2179,807],{"class":52},[42,2181,2182],{"class":44,"line":114},[42,2183,211],{"emptyLinePlaceholder":210},[42,2185,2186,2188,2190],{"class":44,"line":125},[42,2187,911],{"class":690},[42,2189,2022],{"class":694},[42,2191,2025],{"class":52},[42,2193,2194,2196,2198,2200,2202,2204,2207,2209,2212],{"class":44,"line":136},[42,2195,2030],{"class":52},[42,2197,2033],{"class":694},[42,2199,831],{"class":52},[42,2201,2101],{"class":694},[42,2203,831],{"class":52},[42,2205,2206],{"class":110},"true",[42,2208,847],{"class":52},[42,2210,2211],{"class":110},"false",[42,2213,2046],{"class":52},[42,2215,2216,2218,2220,2222,2224,2226,2228,2230,2232],{"class":44,"line":147},[42,2217,2030],{"class":52},[42,2219,2033],{"class":694},[42,2221,831],{"class":52},[42,2223,2101],{"class":694},[42,2225,831],{"class":52},[42,2227,2211],{"class":110},[42,2229,847],{"class":52},[42,2231,2211],{"class":110},[42,2233,2046],{"class":52},[42,2235,2236,2238,2240,2242,2244,2246,2248,2250,2252],{"class":44,"line":158},[42,2237,2030],{"class":52},[42,2239,2033],{"class":694},[42,2241,831],{"class":52},[42,2243,2101],{"class":694},[42,2245,831],{"class":52},[42,2247,2211],{"class":110},[42,2249,847],{"class":52},[42,2251,2206],{"class":110},[42,2253,2046],{"class":52},[42,2255,2256],{"class":44,"line":169},[42,2257,807],{"class":52},[1865,2259,2260],{"v-slot:hints":37},[232,2261,2262,2265,2268],{},[235,2263,2264],{},"Если Kafka доступна, сервис готов.",[235,2266,2267],{},"Если Kafka недоступна и outbox нет, API может терять события.",[235,2269,2270],{},"Outbox позволяет принять запрос и доставить событие позже.",[2272,2273,2274],"style",{},"html pre.shiki code .s4JwU, html code.shiki .s4JwU{--shiki-default:#85E89D}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":37,"searchDepth":56,"depth":56,"links":2276},[2277,2278,2279,2280,2281,2282,2283,2284,2285,2286,2287],{"id":26,"depth":56,"text":27},{"id":260,"depth":56,"text":261},{"id":482,"depth":56,"text":483},{"id":579,"depth":56,"text":580},{"id":652,"depth":56,"text":653},{"id":1524,"depth":56,"text":1525},{"id":1597,"depth":56,"text":1598},{"id":1626,"depth":56,"text":1627},{"id":1772,"depth":56,"text":1773},{"id":1813,"depth":56,"text":1814},{"id":1853,"depth":56,"text":1854},1781022066777]