[{"data":1,"prerenderedAt":2607},["ShallowReactive",2],{"content:\u002F08-kafka\u002F03-producers-go":3},{"title":4,"description":5,"path":6,"body":7},"Kafka producer в Go","Для Go есть несколько клиентов Kafka. В этом модуле будем использовать github.com\u002Fsegmentio\u002Fkafka-go: он написан на Go, простой для чтения, хорошо подходит для учебных и production-сценариев, не требует CGO и удобно показывает базовую модель Kafka.","\u002F08-kafka\u002F03-producers-go",{"type":8,"value":9,"toc":2592},"minimark",[10,14,23,26,51,54,59,1230,1233,1275,1277,1281,1284,1378,1381,1383,1387,1390,1398,1450,1453,1459,1461,1464,1471,1474,1485,1488,1490,1494,1497,1813,1816,1818,1822,1825,1845,1848,1859,1861,1865,1868,1951,1954,1957,1959,1963,1970,1973,2058,2061,2085,2088,2090,2094,2097,2125,2127,2131,2134,2140,2150,2152,2156,2185,2187,2191,2220,2222,2226,2256,2409,2588],[11,12,4],"h1",{"id":13},"kafka-producer-в-go",[15,16,17,18,22],"p",{},"Для Go есть несколько клиентов Kafka. В этом модуле будем использовать ",[19,20,21],"code",{},"github.com\u002Fsegmentio\u002Fkafka-go",": он написан на Go, простой для чтения, хорошо подходит для учебных и production-сценариев, не требует CGO и удобно показывает базовую модель Kafka.",[15,24,25],{},"В больших компаниях также часто используют Sarama или Confluent Kafka Go client. Но для понимания producer logic важнее не конкретная библиотека, а решения: key, batch, compression, timeout, retries, delivery guarantees, schema evolution.",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-bash shiki shiki-themes github-dark","go get github.com\u002Fsegmentio\u002Fkafka-go\n","bash","",[19,34,35],{"__ignoreMap":32},[36,37,40,44,48],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"svObZ","go",[36,45,47],{"class":46},"sU2Wk"," get",[36,49,50],{"class":46}," github.com\u002Fsegmentio\u002Fkafka-go\n",[52,53],"hr",{},[55,56,58],"h2",{"id":57},"минимальный-producer","Минимальный producer",[27,60,64],{"className":61,"code":62,"language":43,"meta":63,"style":32},"language-go shiki shiki-themes github-dark","package main\n\nimport (\n    \"context\"\n    \"encoding\u002Fjson\"\n    \"errors\"\n    \"fmt\"\n    \"log\u002Fslog\"\n    \"os\"\n    \"os\u002Fsignal\"\n    \"syscall\"\n    \"time\"\n\n    \"github.com\u002Fsegmentio\u002Fkafka-go\"\n)\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 main() {\n    logger := slog.New(slog.NewTextHandler(os.Stdout, nil))\n\n    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)\n    defer stop()\n\n    writer := &kafka.Writer{\n        Addr:         kafka.TCP(\"localhost:9092\"),\n        Topic:        \"orders.events\",\n        Balancer:     &kafka.Hash{},\n        RequiredAcks: kafka.RequireAll,\n        Async:        false,\n        BatchSize:    100,\n        BatchTimeout: 20 * time.Millisecond,\n        Compression:  kafka.Snappy,\n    }\n    defer func() {\n        if err := writer.Close(); err != nil {\n            logger.Error(\"close kafka writer\", \"error\", err)\n        }\n    }()\n\n    event := OrderCreated{\n        EventID:   \"evt-1001\",\n        EventType: \"OrderCreated\",\n        Version:   1,\n        Occurred:  time.Now().UTC(),\n        OrderID:   \"order-42\",\n        UserID:    \"user-7\",\n        Amount:    9900,\n        Currency:  \"RUB\",\n    }\n\n    if err := publishOrderCreated(ctx, writer, event); err != nil {\n        logger.Error(\"publish event\", \"error\", err)\n        os.Exit(1)\n    }\n\n    logger.Info(\"event published\", \"event_id\", event.EventID)\n}\n\nfunc publishOrderCreated(ctx context.Context, writer *kafka.Writer, event OrderCreated) error {\n    payload, err := json.Marshal(event)\n    if err != nil {\n        return fmt.Errorf(\"marshal order created: %w\", err)\n    }\n\n    publishCtx, cancel := context.WithTimeout(ctx, 5*time.Second)\n    defer cancel()\n\n    msg := kafka.Message{\n        Key:   []byte(event.OrderID),\n        Value: payload,\n        Time:  event.Occurred,\n        Headers: []kafka.Header{\n            {Key: \"event-type\", Value: []byte(event.EventType)},\n            {Key: \"schema-version\", Value: []byte(\"1\")},\n            {Key: \"content-type\", Value: []byte(\"application\u002Fjson\")},\n        },\n    }\n\n    if err := writer.WriteMessages(publishCtx, msg); err != nil {\n        if errors.Is(err, context.DeadlineExceeded) {\n            return fmt.Errorf(\"publish timeout: %w\", err)\n        }\n        return fmt.Errorf(\"write kafka message: %w\", err)\n    }\n\n    return nil\n}\n","no-run",[19,65,66,75,82,92,104,114,124,134,144,154,164,174,184,189,198,204,209,224,236,247,259,276,287,298,310,321,327,332,344,375,380,403,415,420,442,460,472,491,497,508,519,534,540,546,556,584,607,613,619,624,636,647,658,669,687,698,709,720,731,736,741,763,782,797,802,807,829,834,839,888,905,918,943,948,953,979,989,994,1012,1024,1030,1036,1051,1068,1088,1107,1113,1118,1123,1146,1160,1181,1186,1206,1211,1216,1225],{"__ignoreMap":32},[36,67,68,72],{"class":38,"line":39},[36,69,71],{"class":70},"snl16","package",[36,73,74],{"class":42}," main\n",[36,76,78],{"class":38,"line":77},2,[36,79,81],{"emptyLinePlaceholder":80},true,"\n",[36,83,85,88],{"class":38,"line":84},3,[36,86,87],{"class":70},"import",[36,89,91],{"class":90},"s95oV"," (\n",[36,93,95,98,101],{"class":38,"line":94},4,[36,96,97],{"class":46},"    \"",[36,99,100],{"class":42},"context",[36,102,103],{"class":46},"\"\n",[36,105,107,109,112],{"class":38,"line":106},5,[36,108,97],{"class":46},[36,110,111],{"class":42},"encoding\u002Fjson",[36,113,103],{"class":46},[36,115,117,119,122],{"class":38,"line":116},6,[36,118,97],{"class":46},[36,120,121],{"class":42},"errors",[36,123,103],{"class":46},[36,125,127,129,132],{"class":38,"line":126},7,[36,128,97],{"class":46},[36,130,131],{"class":42},"fmt",[36,133,103],{"class":46},[36,135,137,139,142],{"class":38,"line":136},8,[36,138,97],{"class":46},[36,140,141],{"class":42},"log\u002Fslog",[36,143,103],{"class":46},[36,145,147,149,152],{"class":38,"line":146},9,[36,148,97],{"class":46},[36,150,151],{"class":42},"os",[36,153,103],{"class":46},[36,155,157,159,162],{"class":38,"line":156},10,[36,158,97],{"class":46},[36,160,161],{"class":42},"os\u002Fsignal",[36,163,103],{"class":46},[36,165,167,169,172],{"class":38,"line":166},11,[36,168,97],{"class":46},[36,170,171],{"class":42},"syscall",[36,173,103],{"class":46},[36,175,177,179,182],{"class":38,"line":176},12,[36,178,97],{"class":46},[36,180,181],{"class":42},"time",[36,183,103],{"class":46},[36,185,187],{"class":38,"line":186},13,[36,188,81],{"emptyLinePlaceholder":80},[36,190,192,194,196],{"class":38,"line":191},14,[36,193,97],{"class":46},[36,195,21],{"class":42},[36,197,103],{"class":46},[36,199,201],{"class":38,"line":200},15,[36,202,203],{"class":90},")\n",[36,205,207],{"class":38,"line":206},16,[36,208,81],{"emptyLinePlaceholder":80},[36,210,212,215,218,221],{"class":38,"line":211},17,[36,213,214],{"class":70},"type",[36,216,217],{"class":42}," OrderCreated",[36,219,220],{"class":70}," struct",[36,222,223],{"class":90}," {\n",[36,225,227,230,233],{"class":38,"line":226},18,[36,228,229],{"class":90},"    EventID   ",[36,231,232],{"class":70},"string",[36,234,235],{"class":46},"    `json:\"event_id\"`\n",[36,237,239,242,244],{"class":38,"line":238},19,[36,240,241],{"class":90},"    EventType ",[36,243,232],{"class":70},[36,245,246],{"class":46},"    `json:\"event_type\"`\n",[36,248,250,253,256],{"class":38,"line":249},20,[36,251,252],{"class":90},"    Version   ",[36,254,255],{"class":70},"int",[36,257,258],{"class":46},"       `json:\"version\"`\n",[36,260,262,265,267,270,273],{"class":38,"line":261},21,[36,263,264],{"class":90},"    Occurred  ",[36,266,181],{"class":42},[36,268,269],{"class":90},".",[36,271,272],{"class":42},"Time",[36,274,275],{"class":46}," `json:\"occurred\"`\n",[36,277,279,282,284],{"class":38,"line":278},22,[36,280,281],{"class":90},"    OrderID   ",[36,283,232],{"class":70},[36,285,286],{"class":46},"    `json:\"order_id\"`\n",[36,288,290,293,295],{"class":38,"line":289},23,[36,291,292],{"class":90},"    UserID    ",[36,294,232],{"class":70},[36,296,297],{"class":46},"    `json:\"user_id\"`\n",[36,299,301,304,307],{"class":38,"line":300},24,[36,302,303],{"class":90},"    Amount    ",[36,305,306],{"class":70},"int64",[36,308,309],{"class":46},"     `json:\"amount\"`\n",[36,311,313,316,318],{"class":38,"line":312},25,[36,314,315],{"class":90},"    Currency  ",[36,317,232],{"class":70},[36,319,320],{"class":46},"    `json:\"currency\"`\n",[36,322,324],{"class":38,"line":323},26,[36,325,326],{"class":90},"}\n",[36,328,330],{"class":38,"line":329},27,[36,331,81],{"emptyLinePlaceholder":80},[36,333,335,338,341],{"class":38,"line":334},28,[36,336,337],{"class":70},"func",[36,339,340],{"class":42}," main",[36,342,343],{"class":90},"() {\n",[36,345,347,350,353,356,359,362,365,368,372],{"class":38,"line":346},29,[36,348,349],{"class":90},"    logger ",[36,351,352],{"class":70},":=",[36,354,355],{"class":90}," slog.",[36,357,358],{"class":42},"New",[36,360,361],{"class":90},"(slog.",[36,363,364],{"class":42},"NewTextHandler",[36,366,367],{"class":90},"(os.Stdout, ",[36,369,371],{"class":370},"sDLfK","nil",[36,373,374],{"class":90},"))\n",[36,376,378],{"class":38,"line":377},30,[36,379,81],{"emptyLinePlaceholder":80},[36,381,383,386,388,391,394,397,400],{"class":38,"line":382},31,[36,384,385],{"class":90},"    ctx, stop ",[36,387,352],{"class":70},[36,389,390],{"class":90}," signal.",[36,392,393],{"class":42},"NotifyContext",[36,395,396],{"class":90},"(context.",[36,398,399],{"class":42},"Background",[36,401,402],{"class":90},"(), os.Interrupt, syscall.SIGTERM)\n",[36,404,406,409,412],{"class":38,"line":405},32,[36,407,408],{"class":70},"    defer",[36,410,411],{"class":42}," stop",[36,413,414],{"class":90},"()\n",[36,416,418],{"class":38,"line":417},33,[36,419,81],{"emptyLinePlaceholder":80},[36,421,423,426,428,431,434,436,439],{"class":38,"line":422},34,[36,424,425],{"class":90},"    writer ",[36,427,352],{"class":70},[36,429,430],{"class":70}," &",[36,432,433],{"class":42},"kafka",[36,435,269],{"class":90},[36,437,438],{"class":42},"Writer",[36,440,441],{"class":90},"{\n",[36,443,445,448,451,454,457],{"class":38,"line":444},35,[36,446,447],{"class":90},"        Addr:         kafka.",[36,449,450],{"class":42},"TCP",[36,452,453],{"class":90},"(",[36,455,456],{"class":46},"\"localhost:9092\"",[36,458,459],{"class":90},"),\n",[36,461,463,466,469],{"class":38,"line":462},36,[36,464,465],{"class":90},"        Topic:        ",[36,467,468],{"class":46},"\"orders.events\"",[36,470,471],{"class":90},",\n",[36,473,475,478,481,483,485,488],{"class":38,"line":474},37,[36,476,477],{"class":90},"        Balancer:     ",[36,479,480],{"class":70},"&",[36,482,433],{"class":42},[36,484,269],{"class":90},[36,486,487],{"class":42},"Hash",[36,489,490],{"class":90},"{},\n",[36,492,494],{"class":38,"line":493},38,[36,495,496],{"class":90},"        RequiredAcks: kafka.RequireAll,\n",[36,498,500,503,506],{"class":38,"line":499},39,[36,501,502],{"class":90},"        Async:        ",[36,504,505],{"class":370},"false",[36,507,471],{"class":90},[36,509,511,514,517],{"class":38,"line":510},40,[36,512,513],{"class":90},"        BatchSize:    ",[36,515,516],{"class":370},"100",[36,518,471],{"class":90},[36,520,522,525,528,531],{"class":38,"line":521},41,[36,523,524],{"class":90},"        BatchTimeout: ",[36,526,527],{"class":370},"20",[36,529,530],{"class":70}," *",[36,532,533],{"class":90}," time.Millisecond,\n",[36,535,537],{"class":38,"line":536},42,[36,538,539],{"class":90},"        Compression:  kafka.Snappy,\n",[36,541,543],{"class":38,"line":542},43,[36,544,545],{"class":90},"    }\n",[36,547,549,551,554],{"class":38,"line":548},44,[36,550,408],{"class":70},[36,552,553],{"class":70}," func",[36,555,343],{"class":90},[36,557,559,562,565,567,570,573,576,579,582],{"class":38,"line":558},45,[36,560,561],{"class":70},"        if",[36,563,564],{"class":90}," err ",[36,566,352],{"class":70},[36,568,569],{"class":90}," writer.",[36,571,572],{"class":42},"Close",[36,574,575],{"class":90},"(); err ",[36,577,578],{"class":70},"!=",[36,580,581],{"class":370}," nil",[36,583,223],{"class":90},[36,585,587,590,593,595,598,601,604],{"class":38,"line":586},46,[36,588,589],{"class":90},"            logger.",[36,591,592],{"class":42},"Error",[36,594,453],{"class":90},[36,596,597],{"class":46},"\"close kafka writer\"",[36,599,600],{"class":90},", ",[36,602,603],{"class":46},"\"error\"",[36,605,606],{"class":90},", err)\n",[36,608,610],{"class":38,"line":609},47,[36,611,612],{"class":90},"        }\n",[36,614,616],{"class":38,"line":615},48,[36,617,618],{"class":90},"    }()\n",[36,620,622],{"class":38,"line":621},49,[36,623,81],{"emptyLinePlaceholder":80},[36,625,627,630,632,634],{"class":38,"line":626},50,[36,628,629],{"class":90},"    event ",[36,631,352],{"class":70},[36,633,217],{"class":42},[36,635,441],{"class":90},[36,637,639,642,645],{"class":38,"line":638},51,[36,640,641],{"class":90},"        EventID:   ",[36,643,644],{"class":46},"\"evt-1001\"",[36,646,471],{"class":90},[36,648,650,653,656],{"class":38,"line":649},52,[36,651,652],{"class":90},"        EventType: ",[36,654,655],{"class":46},"\"OrderCreated\"",[36,657,471],{"class":90},[36,659,661,664,667],{"class":38,"line":660},53,[36,662,663],{"class":90},"        Version:   ",[36,665,666],{"class":370},"1",[36,668,471],{"class":90},[36,670,672,675,678,681,684],{"class":38,"line":671},54,[36,673,674],{"class":90},"        Occurred:  time.",[36,676,677],{"class":42},"Now",[36,679,680],{"class":90},"().",[36,682,683],{"class":42},"UTC",[36,685,686],{"class":90},"(),\n",[36,688,690,693,696],{"class":38,"line":689},55,[36,691,692],{"class":90},"        OrderID:   ",[36,694,695],{"class":46},"\"order-42\"",[36,697,471],{"class":90},[36,699,701,704,707],{"class":38,"line":700},56,[36,702,703],{"class":90},"        UserID:    ",[36,705,706],{"class":46},"\"user-7\"",[36,708,471],{"class":90},[36,710,712,715,718],{"class":38,"line":711},57,[36,713,714],{"class":90},"        Amount:    ",[36,716,717],{"class":370},"9900",[36,719,471],{"class":90},[36,721,723,726,729],{"class":38,"line":722},58,[36,724,725],{"class":90},"        Currency:  ",[36,727,728],{"class":46},"\"RUB\"",[36,730,471],{"class":90},[36,732,734],{"class":38,"line":733},59,[36,735,545],{"class":90},[36,737,739],{"class":38,"line":738},60,[36,740,81],{"emptyLinePlaceholder":80},[36,742,744,747,749,751,754,757,759,761],{"class":38,"line":743},61,[36,745,746],{"class":70},"    if",[36,748,564],{"class":90},[36,750,352],{"class":70},[36,752,753],{"class":42}," publishOrderCreated",[36,755,756],{"class":90},"(ctx, writer, event); err ",[36,758,578],{"class":70},[36,760,581],{"class":370},[36,762,223],{"class":90},[36,764,766,769,771,773,776,778,780],{"class":38,"line":765},62,[36,767,768],{"class":90},"        logger.",[36,770,592],{"class":42},[36,772,453],{"class":90},[36,774,775],{"class":46},"\"publish event\"",[36,777,600],{"class":90},[36,779,603],{"class":46},[36,781,606],{"class":90},[36,783,785,788,791,793,795],{"class":38,"line":784},63,[36,786,787],{"class":90},"        os.",[36,789,790],{"class":42},"Exit",[36,792,453],{"class":90},[36,794,666],{"class":370},[36,796,203],{"class":90},[36,798,800],{"class":38,"line":799},64,[36,801,545],{"class":90},[36,803,805],{"class":38,"line":804},65,[36,806,81],{"emptyLinePlaceholder":80},[36,808,810,813,816,818,821,823,826],{"class":38,"line":809},66,[36,811,812],{"class":90},"    logger.",[36,814,815],{"class":42},"Info",[36,817,453],{"class":90},[36,819,820],{"class":46},"\"event published\"",[36,822,600],{"class":90},[36,824,825],{"class":46},"\"event_id\"",[36,827,828],{"class":90},", event.EventID)\n",[36,830,832],{"class":38,"line":831},67,[36,833,326],{"class":90},[36,835,837],{"class":38,"line":836},68,[36,838,81],{"emptyLinePlaceholder":80},[36,840,842,844,846,848,852,855,857,860,862,865,867,869,871,873,875,878,880,883,886],{"class":38,"line":841},69,[36,843,337],{"class":70},[36,845,753],{"class":42},[36,847,453],{"class":90},[36,849,851],{"class":850},"s9osk","ctx",[36,853,854],{"class":42}," context",[36,856,269],{"class":90},[36,858,859],{"class":42},"Context",[36,861,600],{"class":90},[36,863,864],{"class":850},"writer",[36,866,530],{"class":70},[36,868,433],{"class":42},[36,870,269],{"class":90},[36,872,438],{"class":42},[36,874,600],{"class":90},[36,876,877],{"class":850},"event",[36,879,217],{"class":42},[36,881,882],{"class":90},") ",[36,884,885],{"class":70},"error",[36,887,223],{"class":90},[36,889,891,894,896,899,902],{"class":38,"line":890},70,[36,892,893],{"class":90},"    payload, err ",[36,895,352],{"class":70},[36,897,898],{"class":90}," json.",[36,900,901],{"class":42},"Marshal",[36,903,904],{"class":90},"(event)\n",[36,906,908,910,912,914,916],{"class":38,"line":907},71,[36,909,746],{"class":70},[36,911,564],{"class":90},[36,913,578],{"class":70},[36,915,581],{"class":370},[36,917,223],{"class":90},[36,919,921,924,927,930,932,935,938,941],{"class":38,"line":920},72,[36,922,923],{"class":70},"        return",[36,925,926],{"class":90}," fmt.",[36,928,929],{"class":42},"Errorf",[36,931,453],{"class":90},[36,933,934],{"class":46},"\"marshal order created: ",[36,936,937],{"class":370},"%w",[36,939,940],{"class":46},"\"",[36,942,606],{"class":90},[36,944,946],{"class":38,"line":945},73,[36,947,545],{"class":90},[36,949,951],{"class":38,"line":950},74,[36,952,81],{"emptyLinePlaceholder":80},[36,954,956,959,961,964,967,970,973,976],{"class":38,"line":955},75,[36,957,958],{"class":90},"    publishCtx, cancel ",[36,960,352],{"class":70},[36,962,963],{"class":90}," context.",[36,965,966],{"class":42},"WithTimeout",[36,968,969],{"class":90},"(ctx, ",[36,971,972],{"class":370},"5",[36,974,975],{"class":70},"*",[36,977,978],{"class":90},"time.Second)\n",[36,980,982,984,987],{"class":38,"line":981},76,[36,983,408],{"class":70},[36,985,986],{"class":42}," cancel",[36,988,414],{"class":90},[36,990,992],{"class":38,"line":991},77,[36,993,81],{"emptyLinePlaceholder":80},[36,995,997,1000,1002,1005,1007,1010],{"class":38,"line":996},78,[36,998,999],{"class":90},"    msg ",[36,1001,352],{"class":70},[36,1003,1004],{"class":42}," kafka",[36,1006,269],{"class":90},[36,1008,1009],{"class":42},"Message",[36,1011,441],{"class":90},[36,1013,1015,1018,1021],{"class":38,"line":1014},79,[36,1016,1017],{"class":90},"        Key:   []",[36,1019,1020],{"class":70},"byte",[36,1022,1023],{"class":90},"(event.OrderID),\n",[36,1025,1027],{"class":38,"line":1026},80,[36,1028,1029],{"class":90},"        Value: payload,\n",[36,1031,1033],{"class":38,"line":1032},81,[36,1034,1035],{"class":90},"        Time:  event.Occurred,\n",[36,1037,1039,1042,1044,1046,1049],{"class":38,"line":1038},82,[36,1040,1041],{"class":90},"        Headers: []",[36,1043,433],{"class":42},[36,1045,269],{"class":90},[36,1047,1048],{"class":42},"Header",[36,1050,441],{"class":90},[36,1052,1054,1057,1060,1063,1065],{"class":38,"line":1053},83,[36,1055,1056],{"class":90},"            {Key: ",[36,1058,1059],{"class":46},"\"event-type\"",[36,1061,1062],{"class":90},", Value: []",[36,1064,1020],{"class":70},[36,1066,1067],{"class":90},"(event.EventType)},\n",[36,1069,1071,1073,1076,1078,1080,1082,1085],{"class":38,"line":1070},84,[36,1072,1056],{"class":90},[36,1074,1075],{"class":46},"\"schema-version\"",[36,1077,1062],{"class":90},[36,1079,1020],{"class":70},[36,1081,453],{"class":90},[36,1083,1084],{"class":46},"\"1\"",[36,1086,1087],{"class":90},")},\n",[36,1089,1091,1093,1096,1098,1100,1102,1105],{"class":38,"line":1090},85,[36,1092,1056],{"class":90},[36,1094,1095],{"class":46},"\"content-type\"",[36,1097,1062],{"class":90},[36,1099,1020],{"class":70},[36,1101,453],{"class":90},[36,1103,1104],{"class":46},"\"application\u002Fjson\"",[36,1106,1087],{"class":90},[36,1108,1110],{"class":38,"line":1109},86,[36,1111,1112],{"class":90},"        },\n",[36,1114,1116],{"class":38,"line":1115},87,[36,1117,545],{"class":90},[36,1119,1121],{"class":38,"line":1120},88,[36,1122,81],{"emptyLinePlaceholder":80},[36,1124,1126,1128,1130,1132,1134,1137,1140,1142,1144],{"class":38,"line":1125},89,[36,1127,746],{"class":70},[36,1129,564],{"class":90},[36,1131,352],{"class":70},[36,1133,569],{"class":90},[36,1135,1136],{"class":42},"WriteMessages",[36,1138,1139],{"class":90},"(publishCtx, msg); err ",[36,1141,578],{"class":70},[36,1143,581],{"class":370},[36,1145,223],{"class":90},[36,1147,1149,1151,1154,1157],{"class":38,"line":1148},90,[36,1150,561],{"class":70},[36,1152,1153],{"class":90}," errors.",[36,1155,1156],{"class":42},"Is",[36,1158,1159],{"class":90},"(err, context.DeadlineExceeded) {\n",[36,1161,1163,1166,1168,1170,1172,1175,1177,1179],{"class":38,"line":1162},91,[36,1164,1165],{"class":70},"            return",[36,1167,926],{"class":90},[36,1169,929],{"class":42},[36,1171,453],{"class":90},[36,1173,1174],{"class":46},"\"publish timeout: ",[36,1176,937],{"class":370},[36,1178,940],{"class":46},[36,1180,606],{"class":90},[36,1182,1184],{"class":38,"line":1183},92,[36,1185,612],{"class":90},[36,1187,1189,1191,1193,1195,1197,1200,1202,1204],{"class":38,"line":1188},93,[36,1190,923],{"class":70},[36,1192,926],{"class":90},[36,1194,929],{"class":42},[36,1196,453],{"class":90},[36,1198,1199],{"class":46},"\"write kafka message: ",[36,1201,937],{"class":370},[36,1203,940],{"class":46},[36,1205,606],{"class":90},[36,1207,1209],{"class":38,"line":1208},94,[36,1210,545],{"class":90},[36,1212,1214],{"class":38,"line":1213},95,[36,1215,81],{"emptyLinePlaceholder":80},[36,1217,1219,1222],{"class":38,"line":1218},96,[36,1220,1221],{"class":70},"    return",[36,1223,1224],{"class":370}," nil\n",[36,1226,1228],{"class":38,"line":1227},97,[36,1229,326],{"class":90},[15,1231,1232],{},"Главные детали:",[1234,1235,1236,1243,1253,1263,1269],"ul",{},[1237,1238,1239,1242],"li",{},[19,1240,1241],{},"Key: []byte(event.OrderID)"," сохраняет порядок событий одного заказа;",[1237,1244,1245,1248,1249,1252],{},[19,1246,1247],{},"RequiredAcks: kafka.RequireAll"," соответствует ",[19,1250,1251],{},"acks=all",";",[1237,1254,1255,1258,1259,1262],{},[19,1256,1257],{},"BatchSize"," и ",[19,1260,1261],{},"BatchTimeout"," дают producer шанс объединить сообщения;",[1237,1264,1265,1268],{},[19,1266,1267],{},"Compression"," снижает network\u002Fdisk cost;",[1237,1270,1271,1274],{},[19,1272,1273],{},"context.WithTimeout"," не даёт HTTP request или job зависнуть навсегда.",[52,1276],{},[55,1278,1280],{"id":1279},"key-бизнес-решение-а-не-техническая-мелочь","Key: бизнес-решение, а не техническая мелочь",[15,1282,1283],{},"Key выбирают по вопросу: \"для какой сущности мне нужен порядок?\"",[1285,1286,1287,1304],"table",{},[1288,1289,1290],"thead",{},[1291,1292,1293,1298,1301],"tr",{},[1294,1295,1297],"th",{"align":1296},"left","Событие",[1294,1299,1300],{"align":1296},"Частый key",[1294,1302,1303],{"align":1296},"Причина",[1305,1306,1307,1326,1344,1359],"tbody",{},[1291,1308,1309,1318,1323],{},[1310,1311,1312,600,1315],"td",{"align":1296},[19,1313,1314],{},"OrderCreated",[19,1316,1317],{},"OrderPaid",[1310,1319,1320],{"align":1296},[19,1321,1322],{},"order_id",[1310,1324,1325],{"align":1296},"Жизненный цикл заказа должен быть последовательным.",[1291,1327,1328,1336,1341],{},[1310,1329,1330,600,1333],{"align":1296},[19,1331,1332],{},"UserRegistered",[19,1334,1335],{},"UserEmailChanged",[1310,1337,1338],{"align":1296},[19,1339,1340],{},"user_id",[1310,1342,1343],{"align":1296},"Состояние пользователя обновляется по порядку.",[1291,1345,1346,1351,1356],{},[1310,1347,1348],{"align":1296},[19,1349,1350],{},"PaymentAuthorized",[1310,1352,1353],{"align":1296},[19,1354,1355],{},"payment_id",[1310,1357,1358],{"align":1296},"Платёжная сущность живёт отдельно.",[1291,1360,1361,1366,1375],{},[1310,1362,1363],{"align":1296},[19,1364,1365],{},"InventoryReserved",[1310,1367,1368,1371,1372],{"align":1296},[19,1369,1370],{},"sku"," или ",[19,1373,1374],{},"reservation_id",[1310,1376,1377],{"align":1296},"Зависит от модели консистентности склада.",[15,1379,1380],{},"Если key не задан, producer распределяет records иначе: round-robin, least-bytes или другой balancer. Это может быть нормально для независимых логов, метрик или событий без требования порядка.",[52,1382],{},[55,1384,1386],{"id":1385},"batching-и-latency","Batching и latency",[15,1388,1389],{},"Producer не обязан отправлять каждое сообщение отдельным network call. Он может собрать batch.",[27,1391,1396],{"className":1392,"code":1394,"language":1395,"meta":32},[1393],"language-text","small writes:\n  msg1 -> network\n  msg2 -> network\n  msg3 -> network\n\nbatching:\n  [msg1 msg2 msg3 ... msg100] -> network\n","text",[19,1397,1394],{"__ignoreMap":32},[1285,1399,1400,1410],{},[1288,1401,1402],{},[1291,1403,1404,1407],{},[1294,1405,1406],{"align":1296},"Настройка",[1294,1408,1409],{"align":1296},"Что делает",[1305,1411,1412,1421,1431,1440],{},[1291,1413,1414,1418],{},[1310,1415,1416],{"align":1296},[19,1417,1257],{},[1310,1419,1420],{"align":1296},"Максимальное количество messages в batch.",[1291,1422,1423,1428],{},[1310,1424,1425],{"align":1296},[19,1426,1427],{},"BatchBytes",[1310,1429,1430],{"align":1296},"Максимальный размер batch.",[1291,1432,1433,1437],{},[1310,1434,1435],{"align":1296},[19,1436,1261],{},[1310,1438,1439],{"align":1296},"Сколько ждать заполнения batch перед отправкой.",[1291,1441,1442,1447],{},[1310,1443,1444],{"align":1296},[19,1445,1446],{},"Async",[1310,1448,1449],{"align":1296},"Возвращать управление до подтверждения broker.",[15,1451,1452],{},"Большие batches увеличивают throughput и эффективность compression, но добавляют latency. Для user-facing запроса обычно важнее предсказуемая задержка. Для фонового экспорта событий можно дать producer больше времени на batch.",[15,1454,1455,1458],{},[19,1456,1457],{},"Async=true"," выглядит заманчиво, но для важных доменных событий это почти всегда плохой default: caller не получает ошибку доставки и не может связать результат publish с use case. Async producer уместен для best-effort telemetry, где потеря допустима и явно описана.",[52,1460],{},[55,1462,1267],{"id":1463},"compression",[15,1465,1466,1467,1470],{},"Kafka хорошо дружит с compression, потому что события часто похожи друг на друга. В ",[19,1468,1469],{},"kafka-go"," доступны разные codecs, например Snappy, Gzip, Lz4, Zstd в зависимости от версии клиента.",[15,1472,1473],{},"Практически:",[1234,1475,1476,1479,1482],{},[1237,1477,1478],{},"Snappy - быстрый и простой выбор;",[1237,1480,1481],{},"Gzip - сильнее сжимает, но дороже CPU;",[1237,1483,1484],{},"Zstd - часто хороший современный баланс, если поддерживается всей инфраструктурой.",[15,1486,1487],{},"Compression применяется к batch. Поэтому batch size влияет не только на network, но и на ratio сжатия.",[52,1489],{},[55,1491,1493],{"id":1492},"retries-и-timeout","Retries и timeout",[15,1495,1496],{},"Retries нужны, потому что Kafka cluster может временно отвечать ошибками: leader election, network hiccup, broker rolling restart. Но retry без timeout превращается в зависание.",[27,1498,1500],{"className":61,"code":1499,"language":43,"meta":63,"style":32},"func publishWithRetry(ctx context.Context, writer *kafka.Writer, msg kafka.Message) error {\n    var lastErr error\n\n    for attempt := 1; attempt \u003C= 3; attempt++ {\n        attemptCtx, cancel := context.WithTimeout(ctx, 3*time.Second)\n        err := writer.WriteMessages(attemptCtx, msg)\n        cancel()\n\n        if err == nil {\n            return nil\n        }\n        if errors.Is(ctx.Err(), context.Canceled) {\n            return ctx.Err()\n        }\n\n        lastErr = err\n\n        select {\n        case \u003C-ctx.Done():\n            return ctx.Err()\n        case \u003C-time.After(time.Duration(attempt) * 200 * time.Millisecond):\n        }\n    }\n\n    return fmt.Errorf(\"publish after retries: %w\", lastErr)\n}\n",[19,1501,1502,1548,1559,1563,1593,1613,1627,1634,1638,1651,1657,1661,1678,1689,1693,1697,1708,1712,1719,1736,1746,1777,1781,1785,1789,1809],{"__ignoreMap":32},[36,1503,1504,1506,1509,1511,1513,1515,1517,1519,1521,1523,1525,1527,1529,1531,1533,1536,1538,1540,1542,1544,1546],{"class":38,"line":39},[36,1505,337],{"class":70},[36,1507,1508],{"class":42}," publishWithRetry",[36,1510,453],{"class":90},[36,1512,851],{"class":850},[36,1514,854],{"class":42},[36,1516,269],{"class":90},[36,1518,859],{"class":42},[36,1520,600],{"class":90},[36,1522,864],{"class":850},[36,1524,530],{"class":70},[36,1526,433],{"class":42},[36,1528,269],{"class":90},[36,1530,438],{"class":42},[36,1532,600],{"class":90},[36,1534,1535],{"class":850},"msg",[36,1537,1004],{"class":42},[36,1539,269],{"class":90},[36,1541,1009],{"class":42},[36,1543,882],{"class":90},[36,1545,885],{"class":70},[36,1547,223],{"class":90},[36,1549,1550,1553,1556],{"class":38,"line":77},[36,1551,1552],{"class":70},"    var",[36,1554,1555],{"class":90}," lastErr ",[36,1557,1558],{"class":70},"error\n",[36,1560,1561],{"class":38,"line":84},[36,1562,81],{"emptyLinePlaceholder":80},[36,1564,1565,1568,1571,1573,1576,1579,1582,1585,1588,1591],{"class":38,"line":94},[36,1566,1567],{"class":70},"    for",[36,1569,1570],{"class":90}," attempt ",[36,1572,352],{"class":70},[36,1574,1575],{"class":370}," 1",[36,1577,1578],{"class":90},"; attempt ",[36,1580,1581],{"class":70},"\u003C=",[36,1583,1584],{"class":370}," 3",[36,1586,1587],{"class":90},"; attempt",[36,1589,1590],{"class":70},"++",[36,1592,223],{"class":90},[36,1594,1595,1598,1600,1602,1604,1606,1609,1611],{"class":38,"line":106},[36,1596,1597],{"class":90},"        attemptCtx, cancel ",[36,1599,352],{"class":70},[36,1601,963],{"class":90},[36,1603,966],{"class":42},[36,1605,969],{"class":90},[36,1607,1608],{"class":370},"3",[36,1610,975],{"class":70},[36,1612,978],{"class":90},[36,1614,1615,1618,1620,1622,1624],{"class":38,"line":116},[36,1616,1617],{"class":90},"        err ",[36,1619,352],{"class":70},[36,1621,569],{"class":90},[36,1623,1136],{"class":42},[36,1625,1626],{"class":90},"(attemptCtx, msg)\n",[36,1628,1629,1632],{"class":38,"line":126},[36,1630,1631],{"class":42},"        cancel",[36,1633,414],{"class":90},[36,1635,1636],{"class":38,"line":136},[36,1637,81],{"emptyLinePlaceholder":80},[36,1639,1640,1642,1644,1647,1649],{"class":38,"line":146},[36,1641,561],{"class":70},[36,1643,564],{"class":90},[36,1645,1646],{"class":70},"==",[36,1648,581],{"class":370},[36,1650,223],{"class":90},[36,1652,1653,1655],{"class":38,"line":156},[36,1654,1165],{"class":70},[36,1656,1224],{"class":370},[36,1658,1659],{"class":38,"line":166},[36,1660,612],{"class":90},[36,1662,1663,1665,1667,1669,1672,1675],{"class":38,"line":176},[36,1664,561],{"class":70},[36,1666,1153],{"class":90},[36,1668,1156],{"class":42},[36,1670,1671],{"class":90},"(ctx.",[36,1673,1674],{"class":42},"Err",[36,1676,1677],{"class":90},"(), context.Canceled) {\n",[36,1679,1680,1682,1685,1687],{"class":38,"line":186},[36,1681,1165],{"class":70},[36,1683,1684],{"class":90}," ctx.",[36,1686,1674],{"class":42},[36,1688,414],{"class":90},[36,1690,1691],{"class":38,"line":191},[36,1692,612],{"class":90},[36,1694,1695],{"class":38,"line":200},[36,1696,81],{"emptyLinePlaceholder":80},[36,1698,1699,1702,1705],{"class":38,"line":206},[36,1700,1701],{"class":90},"        lastErr ",[36,1703,1704],{"class":70},"=",[36,1706,1707],{"class":90}," err\n",[36,1709,1710],{"class":38,"line":211},[36,1711,81],{"emptyLinePlaceholder":80},[36,1713,1714,1717],{"class":38,"line":226},[36,1715,1716],{"class":70},"        select",[36,1718,223],{"class":90},[36,1720,1721,1724,1727,1730,1733],{"class":38,"line":238},[36,1722,1723],{"class":70},"        case",[36,1725,1726],{"class":70}," \u003C-",[36,1728,1729],{"class":90},"ctx.",[36,1731,1732],{"class":42},"Done",[36,1734,1735],{"class":90},"():\n",[36,1737,1738,1740,1742,1744],{"class":38,"line":249},[36,1739,1165],{"class":70},[36,1741,1684],{"class":90},[36,1743,1674],{"class":42},[36,1745,414],{"class":90},[36,1747,1748,1750,1752,1755,1758,1761,1764,1767,1769,1772,1774],{"class":38,"line":261},[36,1749,1723],{"class":70},[36,1751,1726],{"class":70},[36,1753,1754],{"class":90},"time.",[36,1756,1757],{"class":42},"After",[36,1759,1760],{"class":90},"(time.",[36,1762,1763],{"class":42},"Duration",[36,1765,1766],{"class":90},"(attempt) ",[36,1768,975],{"class":70},[36,1770,1771],{"class":370}," 200",[36,1773,530],{"class":70},[36,1775,1776],{"class":90}," time.Millisecond):\n",[36,1778,1779],{"class":38,"line":278},[36,1780,612],{"class":90},[36,1782,1783],{"class":38,"line":289},[36,1784,545],{"class":90},[36,1786,1787],{"class":38,"line":300},[36,1788,81],{"emptyLinePlaceholder":80},[36,1790,1791,1793,1795,1797,1799,1802,1804,1806],{"class":38,"line":312},[36,1792,1221],{"class":70},[36,1794,926],{"class":90},[36,1796,929],{"class":42},[36,1798,453],{"class":90},[36,1800,1801],{"class":46},"\"publish after retries: ",[36,1803,937],{"class":370},[36,1805,940],{"class":46},[36,1807,1808],{"class":90},", lastErr)\n",[36,1810,1811],{"class":38,"line":323},[36,1812,326],{"class":90},[15,1814,1815],{},"В реальном сервисе retry policy часто выносят в общий компонент, логируют попытки и различают retriable\u002Fnon-retriable ошибки. Но принцип тот же: каждый retry ограничен context, а общий operation тоже должен иметь deadline.",[52,1817],{},[55,1819,1821],{"id":1820},"idempotence","Idempotence",[15,1823,1824],{},"Idempotent producer защищает от дублей при retry на уровне producer session: producer может повторить запись после сетевой ошибки, а broker понимает sequence numbers и не записывает duplicate.",[15,1826,1827,1828,1831,1832,1835,1836,1840,1841,1844],{},"В Java client это стандартная production-настройка: ",[19,1829,1830],{},"enable.idempotence=true",". В Go-клиентах поддержка зависит от библиотеки и версии. Например, высокоуровневый ",[19,1833,1834],{},"segmentio\u002Fkafka-go"," Writer удобен, но не стоит проектировать систему так, будто он автоматически даёт все гарантии Java producer transactions\u002Fidempotence. Если клиент не даёт полноценный idempotent producer или вы не включили его явно, backend всё равно должен проектировать ",[1837,1838,1839],"strong",{},"идемпотентные consumers"," через ",[19,1842,1843],{},"event_id",", уникальные ключи, upsert и таблицы обработанных событий.",[15,1846,1847],{},"Важно различать:",[1234,1849,1850,1853,1856],{},[1237,1851,1852],{},"idempotent producer уменьшает дубли при отправке;",[1237,1854,1855],{},"idempotent consumer защищает систему, когда duplicate всё равно пришёл;",[1237,1857,1858],{},"exactly-once в Kafka не отменяет необходимость думать о БД и внешних side effects.",[52,1860],{},[55,1862,1864],{"id":1863},"headers","Headers",[15,1866,1867],{},"Headers полезны для технической информации:",[27,1869,1871],{"className":61,"code":1870,"language":43,"meta":63,"style":32},"Headers: []kafka.Header{\n    {Key: \"event-type\", Value: []byte(\"OrderCreated\")},\n    {Key: \"schema-version\", Value: []byte(\"1\")},\n    {Key: \"trace-id\", Value: []byte(traceID)},\n    {Key: \"correlation-id\", Value: []byte(requestID)},\n}\n",[19,1872,1873,1886,1903,1919,1933,1947],{"__ignoreMap":32},[36,1874,1875,1878,1880,1882,1884],{"class":38,"line":39},[36,1876,1877],{"class":90},"Headers: []",[36,1879,433],{"class":42},[36,1881,269],{"class":90},[36,1883,1048],{"class":42},[36,1885,441],{"class":90},[36,1887,1888,1891,1893,1895,1897,1899,1901],{"class":38,"line":77},[36,1889,1890],{"class":90},"    {Key: ",[36,1892,1059],{"class":46},[36,1894,1062],{"class":90},[36,1896,1020],{"class":70},[36,1898,453],{"class":90},[36,1900,655],{"class":46},[36,1902,1087],{"class":90},[36,1904,1905,1907,1909,1911,1913,1915,1917],{"class":38,"line":84},[36,1906,1890],{"class":90},[36,1908,1075],{"class":46},[36,1910,1062],{"class":90},[36,1912,1020],{"class":70},[36,1914,453],{"class":90},[36,1916,1084],{"class":46},[36,1918,1087],{"class":90},[36,1920,1921,1923,1926,1928,1930],{"class":38,"line":94},[36,1922,1890],{"class":90},[36,1924,1925],{"class":46},"\"trace-id\"",[36,1927,1062],{"class":90},[36,1929,1020],{"class":70},[36,1931,1932],{"class":90},"(traceID)},\n",[36,1934,1935,1937,1940,1942,1944],{"class":38,"line":106},[36,1936,1890],{"class":90},[36,1938,1939],{"class":46},"\"correlation-id\"",[36,1941,1062],{"class":90},[36,1943,1020],{"class":70},[36,1945,1946],{"class":90},"(requestID)},\n",[36,1948,1949],{"class":38,"line":116},[36,1950,326],{"class":90},[15,1952,1953],{},"Не стоит класть в headers большие payloads. Headers часто нужны middleware, observability и routing внутри consumer, а не бизнес-объектам.",[15,1955,1956],{},"Headers также проходят через DLQ, retry topics и иногда попадают в логи. Не храните там access tokens, raw cookies, номера карт и другие секреты. Для трассировки достаточно trace\u002Fcorrelation id, которые не раскрывают бизнес-данные.",[52,1958],{},[55,1960,1962],{"id":1961},"schema-и-versioning","Schema и versioning",[15,1964,1965,1966,1969],{},"Kafka не знает, что лежит в ",[19,1967,1968],{},"Value",". Для broker это bytes. Поэтому контракт события - ответственность команды.",[15,1971,1972],{},"Минимальный JSON-подход:",[27,1974,1976],{"className":61,"code":1975,"language":43,"meta":63,"style":32},"type EventEnvelope[T any] struct {\n    ID       string    `json:\"id\"`\n    Type     string    `json:\"type\"`\n    Version  int       `json:\"version\"`\n    Occurred time.Time `json:\"occurred\"`\n    Data     T         `json:\"data\"`\n}\n",[19,1977,1978,2002,2012,2022,2031,2044,2054],{"__ignoreMap":32},[36,1979,1980,1982,1985,1988,1991,1994,1997,2000],{"class":38,"line":39},[36,1981,214],{"class":70},[36,1983,1984],{"class":42}," EventEnvelope",[36,1986,1987],{"class":90},"[",[36,1989,1990],{"class":850},"T",[36,1992,1993],{"class":42}," any",[36,1995,1996],{"class":90},"] ",[36,1998,1999],{"class":70},"struct",[36,2001,223],{"class":90},[36,2003,2004,2007,2009],{"class":38,"line":77},[36,2005,2006],{"class":90},"    ID       ",[36,2008,232],{"class":70},[36,2010,2011],{"class":46},"    `json:\"id\"`\n",[36,2013,2014,2017,2019],{"class":38,"line":84},[36,2015,2016],{"class":90},"    Type     ",[36,2018,232],{"class":70},[36,2020,2021],{"class":46},"    `json:\"type\"`\n",[36,2023,2024,2027,2029],{"class":38,"line":94},[36,2025,2026],{"class":90},"    Version  ",[36,2028,255],{"class":70},[36,2030,258],{"class":46},[36,2032,2033,2036,2038,2040,2042],{"class":38,"line":106},[36,2034,2035],{"class":90},"    Occurred ",[36,2037,181],{"class":42},[36,2039,269],{"class":90},[36,2041,272],{"class":42},[36,2043,275],{"class":46},[36,2045,2046,2049,2051],{"class":38,"line":116},[36,2047,2048],{"class":90},"    Data     ",[36,2050,1990],{"class":42},[36,2052,2053],{"class":46},"         `json:\"data\"`\n",[36,2055,2056],{"class":38,"line":126},[36,2057,326],{"class":90},[15,2059,2060],{},"Правила эволюции:",[1234,2062,2063,2066,2069,2072,2082],{},[1237,2064,2065],{},"добавляйте поля так, чтобы старые consumers могли их игнорировать;",[1237,2067,2068],{},"не меняйте смысл существующего поля без новой версии;",[1237,2070,2071],{},"не удаляйте поле, пока есть consumers, которые его читают;",[1237,2073,2074,2075,1258,2078,2081],{},"добавляйте ",[19,2076,2077],{},"event_type",[19,2079,2080],{},"version"," явно;",[1237,2083,2084],{},"для серьёзных систем используйте Schema Registry, Avro, Protobuf или JSON Schema.",[15,2086,2087],{},"Breaking change в событии обычно дороже breaking change в HTTP API: старые records могут replay через недели, а consumers обновляются не одновременно. Поэтому contract tests должны проверять не только новый payload, но и несколько старых примеров из fixtures.",[52,2089],{},[55,2091,2093],{"id":2092},"go-client-pitfalls","Go client pitfalls",[15,2095,2096],{},"У Go Kafka-клиентов есть практические ловушки:",[1234,2098,2099,2102,2105,2112,2115,2122],{},[1237,2100,2101],{},"не создавайте writer на каждый request: переиспользуйте один writer на lifecycle сервиса, иначе потеряете batching и получите лишние connections;",[1237,2103,2104],{},"закрывайте writer при shutdown, чтобы flush pending messages успел завершиться;",[1237,2106,2107,2108,2111],{},"не используйте ",[19,2109,2110],{},"context.Background()"," внутри publish-функций: deadline должен приходить от caller или orchestration layer;",[1237,2113,2114],{},"проверяйте частичные ошибки batch write, если клиент возвращает per-message результат;",[1237,2116,2117,2118,2121],{},"задавайте ",[19,2119,2120],{},"ClientID",", чтобы broker logs, quotas и метрики показывали понятный сервис;",[1237,2123,2124],{},"держите topic names и group IDs в конфиге с fail-fast validation, а не в случайных строках по коду.",[52,2126],{},[55,2128,2130],{"id":2129},"producer-в-http-handler","Producer в HTTP handler",[15,2132,2133],{},"Публикация в Kafka из handler может быть частью use case, но не должна ломать transaction boundary. Если событие должно строго соответствовать записи в БД, используйте outbox pattern: сначала transaction в БД, потом отдельный publisher читает outbox и отправляет Kafka.",[27,2135,2138],{"className":2136,"code":2137,"language":1395,"meta":32},[1393],"HTTP request\n  |\n  v\nDB transaction:\n  INSERT orders\n  INSERT outbox_events\n  COMMIT\n  |\n  v\nbackground publisher -> Kafka\n",[19,2139,2137],{"__ignoreMap":32},[15,2141,2142,2143,2145,2146,2149],{},"Прямой ",[19,2144,1136],{}," после ",[19,2147,2148],{},"INSERT orders"," может создать рассинхрон: заказ сохранён, Kafka недоступна, событие потеряно. Или наоборот: событие ушло, transaction откатилась.",[52,2151],{},[55,2153,2155],{"id":2154},"вопросы-на-собеседовании","Вопросы на собеседовании",[1234,2157,2158,2161,2164,2167,2170,2173,2176,2179,2182],{},[1237,2159,2160],{},"Как producer выбирает partition?",[1237,2162,2163],{},"Почему key - это часть доменной модели?",[1237,2165,2166],{},"Что дают batching и compression?",[1237,2168,2169],{},"Почему producer operation должен иметь context timeout?",[1237,2171,2172],{},"Что такое idempotent producer?",[1237,2174,2175],{},"Почему idempotent producer не заменяет идемпотентность consumer?",[1237,2177,2178],{},"Зачем headers в Kafka message?",[1237,2180,2181],{},"Как версионировать события?",[1237,2183,2184],{},"Почему outbox pattern часто лучше прямой отправки в Kafka из transaction-sensitive use case?",[52,2186],{},[55,2188,2190],{"id":2189},"практика","Практика",[2192,2193,2194,2202,2214,2217],"ol",{},[1237,2195,2196,2197,2199,2200,269],{},"Напишите Go-структуру ",[19,2198,1335],{}," и producer-функцию, которая публикует событие с key=",[19,2201,1340],{},[1237,2203,2204,2205,600,2208,600,2211,269],{},"Добавьте в сообщение headers: ",[19,2206,2207],{},"event-type",[19,2209,2210],{},"schema-version",[19,2212,2213],{},"trace-id",[1237,2215,2216],{},"Подберите настройки batch\u002Fcompression для двух сценариев: интерактивный API и фоновый экспорт.",[1237,2218,2219],{},"Опишите, как вы защититесь от рассинхрона \"БД записалась, Kafka не приняла событие\".",[52,2221],{},[55,2223,2225],{"id":2224},"интерактивная-практика","Интерактивная практика",[2227,2228,2231,2234,2251],"quiz",{"answer":1608,"id":2229,"xp":2230},"kafka-producer-q1","10",[15,2232,2233],{},"Зачем producer-сообщению обычно нужен key?",[2235,2236,2237],"template",{"v-slot:options":32},[1234,2238,2239,2242,2245,2248],{},[1237,2240,2241],{},"Чтобы Kafka автоматически сжимала только эти сообщения",[1237,2243,2244],{},"Чтобы consumer group могла читать без offset",[1237,2246,2247],{},"Чтобы связанные события попадали в одну partition и сохраняли порядок",[1237,2249,2250],{},"Чтобы сообщение удалялось сразу после чтения",[2235,2252,2253],{"v-slot:explanation":32},[15,2254,2255],{},"Key участвует в выборе partition. Если события одного заказа или пользователя должны обрабатываться по порядку, key должен отражать эту доменную связь.",[2257,2258,2262,2265,2404],"predict",{"answer":2259,"id":2260,"xp":2261},"low-latency\\nthroughput","kafka-producer-p1","15",[15,2263,2264],{},"Что выведет программа?",[2235,2266,2267],{"v-slot:code":32},[27,2268,2270],{"className":61,"code":2269,"language":43,"meta":32,"style":32},"package main\n\nimport \"fmt\"\n\nfunc producerMode(batchSize int) string {\n    if batchSize \u003C= 1 {\n        return \"low-latency\"\n    }\n    return \"throughput\"\n}\n\nfunc main() {\n    fmt.Println(producerMode(1))\n    fmt.Println(producerMode(100))\n}\n",[19,2271,2272,2278,2282,2293,2297,2318,2331,2338,2342,2349,2353,2357,2365,2384,2400],{"__ignoreMap":32},[36,2273,2274,2276],{"class":38,"line":39},[36,2275,71],{"class":70},[36,2277,74],{"class":42},[36,2279,2280],{"class":38,"line":77},[36,2281,81],{"emptyLinePlaceholder":80},[36,2283,2284,2286,2289,2291],{"class":38,"line":84},[36,2285,87],{"class":70},[36,2287,2288],{"class":46}," \"",[36,2290,131],{"class":42},[36,2292,103],{"class":46},[36,2294,2295],{"class":38,"line":94},[36,2296,81],{"emptyLinePlaceholder":80},[36,2298,2299,2301,2304,2306,2309,2312,2314,2316],{"class":38,"line":106},[36,2300,337],{"class":70},[36,2302,2303],{"class":42}," producerMode",[36,2305,453],{"class":90},[36,2307,2308],{"class":850},"batchSize",[36,2310,2311],{"class":70}," int",[36,2313,882],{"class":90},[36,2315,232],{"class":70},[36,2317,223],{"class":90},[36,2319,2320,2322,2325,2327,2329],{"class":38,"line":116},[36,2321,746],{"class":70},[36,2323,2324],{"class":90}," batchSize ",[36,2326,1581],{"class":70},[36,2328,1575],{"class":370},[36,2330,223],{"class":90},[36,2332,2333,2335],{"class":38,"line":126},[36,2334,923],{"class":70},[36,2336,2337],{"class":46}," \"low-latency\"\n",[36,2339,2340],{"class":38,"line":136},[36,2341,545],{"class":90},[36,2343,2344,2346],{"class":38,"line":146},[36,2345,1221],{"class":70},[36,2347,2348],{"class":46}," \"throughput\"\n",[36,2350,2351],{"class":38,"line":156},[36,2352,326],{"class":90},[36,2354,2355],{"class":38,"line":166},[36,2356,81],{"emptyLinePlaceholder":80},[36,2358,2359,2361,2363],{"class":38,"line":176},[36,2360,337],{"class":70},[36,2362,340],{"class":42},[36,2364,343],{"class":90},[36,2366,2367,2370,2373,2375,2378,2380,2382],{"class":38,"line":186},[36,2368,2369],{"class":90},"    fmt.",[36,2371,2372],{"class":42},"Println",[36,2374,453],{"class":90},[36,2376,2377],{"class":42},"producerMode",[36,2379,453],{"class":90},[36,2381,666],{"class":370},[36,2383,374],{"class":90},[36,2385,2386,2388,2390,2392,2394,2396,2398],{"class":38,"line":191},[36,2387,2369],{"class":90},[36,2389,2372],{"class":42},[36,2391,453],{"class":90},[36,2393,2377],{"class":42},[36,2395,453],{"class":90},[36,2397,516],{"class":370},[36,2399,374],{"class":90},[36,2401,2402],{"class":38,"line":200},[36,2403,326],{"class":90},[2235,2405,2406],{"v-slot:hint":32},[15,2407,2408],{},"Маленькие batch обычно уменьшают ожидание, большие batch улучшают throughput и compression ratio.",[2410,2411,2414,2428,2575],"code-task",{"expected":2412,"id":2413,"xp":527},"outbox\\ndirect\\noutbox","kafka-producer-ct1",[15,2415,2416,2417,2420,2421,2424,2425,269],{},"Реализуй ",[19,2418,2419],{},"PublishPattern",": если событие связано с транзакционной записью в БД, выбирай ",[19,2422,2423],{},"outbox","; если это best-effort telemetry без бизнес-транзакции — ",[19,2426,2427],{},"direct",[2235,2429,2430],{"v-slot:template":32},[27,2431,2433],{"className":61,"code":2432,"language":43,"meta":32,"style":32},"package main\n\nimport \"fmt\"\n\nfunc PublishPattern(transactional bool, bestEffort bool) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(PublishPattern(true, false))\n    fmt.Println(PublishPattern(false, true))\n    fmt.Println(PublishPattern(true, true))\n}\n",[19,2434,2435,2441,2445,2455,2459,2487,2494,2498,2502,2510,2531,2551,2571],{"__ignoreMap":32},[36,2436,2437,2439],{"class":38,"line":39},[36,2438,71],{"class":70},[36,2440,74],{"class":42},[36,2442,2443],{"class":38,"line":77},[36,2444,81],{"emptyLinePlaceholder":80},[36,2446,2447,2449,2451,2453],{"class":38,"line":84},[36,2448,87],{"class":70},[36,2450,2288],{"class":46},[36,2452,131],{"class":42},[36,2454,103],{"class":46},[36,2456,2457],{"class":38,"line":94},[36,2458,81],{"emptyLinePlaceholder":80},[36,2460,2461,2463,2466,2468,2471,2474,2476,2479,2481,2483,2485],{"class":38,"line":106},[36,2462,337],{"class":70},[36,2464,2465],{"class":42}," PublishPattern",[36,2467,453],{"class":90},[36,2469,2470],{"class":850},"transactional",[36,2472,2473],{"class":70}," bool",[36,2475,600],{"class":90},[36,2477,2478],{"class":850},"bestEffort",[36,2480,2473],{"class":70},[36,2482,882],{"class":90},[36,2484,232],{"class":70},[36,2486,223],{"class":90},[36,2488,2489,2491],{"class":38,"line":116},[36,2490,1221],{"class":70},[36,2492,2493],{"class":46}," \"todo\"\n",[36,2495,2496],{"class":38,"line":126},[36,2497,326],{"class":90},[36,2499,2500],{"class":38,"line":136},[36,2501,81],{"emptyLinePlaceholder":80},[36,2503,2504,2506,2508],{"class":38,"line":146},[36,2505,337],{"class":70},[36,2507,340],{"class":42},[36,2509,343],{"class":90},[36,2511,2512,2514,2516,2518,2520,2522,2525,2527,2529],{"class":38,"line":156},[36,2513,2369],{"class":90},[36,2515,2372],{"class":42},[36,2517,453],{"class":90},[36,2519,2419],{"class":42},[36,2521,453],{"class":90},[36,2523,2524],{"class":370},"true",[36,2526,600],{"class":90},[36,2528,505],{"class":370},[36,2530,374],{"class":90},[36,2532,2533,2535,2537,2539,2541,2543,2545,2547,2549],{"class":38,"line":166},[36,2534,2369],{"class":90},[36,2536,2372],{"class":42},[36,2538,453],{"class":90},[36,2540,2419],{"class":42},[36,2542,453],{"class":90},[36,2544,505],{"class":370},[36,2546,600],{"class":90},[36,2548,2524],{"class":370},[36,2550,374],{"class":90},[36,2552,2553,2555,2557,2559,2561,2563,2565,2567,2569],{"class":38,"line":176},[36,2554,2369],{"class":90},[36,2556,2372],{"class":42},[36,2558,453],{"class":90},[36,2560,2419],{"class":42},[36,2562,453],{"class":90},[36,2564,2524],{"class":370},[36,2566,600],{"class":90},[36,2568,2524],{"class":370},[36,2570,374],{"class":90},[36,2572,2573],{"class":38,"line":186},[36,2574,326],{"class":90},[2235,2576,2577],{"v-slot:hints":32},[1234,2578,2579,2582,2585],{},[1237,2580,2581],{},"Outbox защищает от рассинхрона между commit в БД и publish в Kafka.",[1237,2583,2584],{},"Best-effort telemetry можно отправлять напрямую, если потеря допустима.",[1237,2586,2587],{},"Transactional-событие важнее признака best-effort.",[2589,2590,2591],"style",{},"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 .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 .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":32,"searchDepth":77,"depth":77,"links":2593},[2594,2595,2596,2597,2598,2599,2600,2601,2602,2603,2604,2605,2606],{"id":57,"depth":77,"text":58},{"id":1279,"depth":77,"text":1280},{"id":1385,"depth":77,"text":1386},{"id":1463,"depth":77,"text":1267},{"id":1492,"depth":77,"text":1493},{"id":1820,"depth":77,"text":1821},{"id":1863,"depth":77,"text":1864},{"id":1961,"depth":77,"text":1962},{"id":2092,"depth":77,"text":2093},{"id":2129,"depth":77,"text":2130},{"id":2154,"depth":77,"text":2155},{"id":2189,"depth":77,"text":2190},{"id":2224,"depth":77,"text":2225},1781022065006]