Мой канал в Telegram: t.me/ntuzov Пишу там новости, анонсы своих активностей, небольшие текстовые гайды и просто интересные мысли. Также через него я получаю от вас оперативный фидбэк по роликам - что нравится, что не нравится, какой ролик делать следующим и т.п.
Отличное видео! Я как-то о самом названии Outbox не слышал. Поэтому не знал, что меня ожидает и на первой же минуте приостановил видео и пошел в рассуждения что может пойти не так и какие проблемы будем решать (и как). Радости, после того, как видео было снято с паузы, было столько же, как когда впервые получил оффер :) Круто, что про гарантию доставки тоже было упомянуто. В общем, чувство как-будто вместе поработали - спасибо, с меня лайк :)
Спасибо, но лучше пересматривать на ютубе, чтобы статистика учитывала это. Если ролики часто пересматривают, то их лучше будут рекомендовать другим людям =)
отличный формат видео ! я трейни go могу сказать что ощутил себя в качестве стажера в первом коммерческом прожэкте ) очень интересная необычная подача !
Спасибо за ролик. Хотел бы заметить: ты создал отдельную модель в слое storage, чтобы сканить из БД сразу в структуру и не писать отдельно скан в каждое поле. Но после скана в структуру ты маппишь ее в структуру доменной модели отдельно прописывая каждое поле, т.е. ничего особо не изменилось.
@@nikolay_tuzov можно уже на существующие проекты накидать юнит-тестов и мок (ну тот минимум базы, которую ждут от твердого джуна) Понятно, что уже есть таких много примеров, но ты пожалуй один из лучших лекторов по Го (в снг-сегменте так точно)
Мне эта тема чем-то event sourcing напомнила. Все события кладутся в dynamodb табличку например, на котором по триггеру работает лямбда, которая уже может отправить в какой-то sqs/sns
По поводу транзакции. Разве нельзя сразу писать defer tx.Rollback() ? То есть если код дошел до tx.Commit(), Rollback выполнится но уже ни на что влиять не будет
Просто забить на все это и работать в монолите. Хочешь транзакшн фид? Ну создай в бд еще табличку, будет тебе транзакшн фид. )))) Есть мнение, что микросервисы для надувания щек перед инвесторами. По крайней мере - это одно из главных назначений этого подхода.
Возможно пример с фидом неудачный. В общем случае как только есть две системы, между которыми невозможна транзакция, то возникает эта проблема. И одно из решений это аутбокс. В вашем монолите наверняка есть подобная проблема. Например пользователь зарегался, ему надо отправить письмо. В бд записали, письмо не отправили. Или письмо отправили, а транзакция упала. Часто на это просто забивают, и алгоритм аутбокса выполняет сотрудник поддержки :-)
@@БанинЕгор Будьте добры, перечислите еще 10 случаев аутбокса в монолите. Кроме письма. Что-то мне подсказывает, что не наберется. А вот в микросервисах они будут на каждый чих и о всех помнить не будешь. В один прекрасный пятничный вечер руководство донесет, что надо восстанавливать какие-то данные, потому что оно где-то скрыто и очень давно работало не так из за того, что в такой сложной системе кто-то что-то когда-то проглядел. У меня на работе монолит и куча интеграций. У нас так бизнес процесс устроен, что если интеграция не ответила, то все, до свидания, смысла развлекать пользователя нет. Эксепшн, обращайтесь завтра после обеда.
@@buginsystem8925 в чем прикол? Эти все языки об одном и том же. Чем golang лучше C#? Решает те же задачи. Выглядит по другому. Ну и что. Языкодрочка - онанизм. И последнее, что хочет нормальный бизнес - это зоопарк языков на своем борту.
Кажется есть очень простое решение проблемы - танзакшн менеджер. Просто оборачиваем в транзакцию на сервисном уровне сохранение в бд и вывоз продьюса. Если гдето по дороге падаем - транзакция не комитится и все останется консистентно
а нельзя в рамках БДшной транзакции отправлять сообщение в очередь просто? т.е. создали транзакцию, сделали инсерт в БД, отправили сообщение в очерель и сделали коммит. И если мы получили ошибку очереди, то просто сделать роллбэк транзакции. Есть конечно недостаток: если у нас упадет очередь, то пользователь не сможет делать переводы. Но есть ли еще какие-то подводные камни в таком подходе?
Конечно есть, сообщение отправится, а транзакция может ролбекнуть. В итоге сообщение уже в очереди и возможно обрабатывается, а по факту в бд ничего не произошло
Спасибо за видео! А валидно ли архитектурное решение, когда отправляем сообщения в кафку, а воркер уже считывает с кафки и пишет в базу. В то же время другой сервис тоже может считывать с кафки.
Нет, не решает. Допустим, все сервисы узнали о новом переводе, а что дальше? Наш Transfer Manager начинает этот перевод обрабатывать. В процессе его статус будет меняться: initial -> processing -> done / failed. Как другие сервисы будут узнавать о смене статуса? Ну и дальше та же логика - мы обновляем статус платежа в БД и отправляем сообщение об этом. По сути, Transfer Manager здесь является исходным источником информации. В общем, это не решает текущую проблему, ты просто смещаешь угол обзора. В моём рассказе не принципиально, по какому каналу Transfer Manager получил инфу о новом переводе - по grpc, из кафки и т.п. Суть не меняется. Если хочешь обсудить подробней, приходи в наш чат Gopher Club, обсудим.
на 25:00 метод tx.Commit() выплняется только и тогда (!) когда err == nil, то есть когда ошибок на выходе не было до выполнения коммита, то есть нет никакого либо либо и переменная commitErr не нужна, можно сразу записывать выхлоп в err = tx.Commit()
Нет, ничего не потеряется, мы просто будем врапить одно в другое. В итоге, получится цепочка из всех ошибок, что мы успели завернуть. Попробуй вернуть ошибку из saveEvent, и посмотри что получтся.
Хотел уточнить, вот кейс когда говорится в начале что сервер упадет, не могу понять одно. У нас же сохранение в бд и отправка сообщения в кафку происходит в рамках одной транзакции? Просто если так, то по-идее при перезагрузке сервера, у нас не получится запродюсить сообщение в кафку и транзакция откатится, нет? Как тогда произойдет неконсистентность?
Не факт. А если получилось запродюсить данные в брокер? Тогда данные есть в брокере (и потенциально в консьюмере), а в БД их нет и наоборот. Идея паттерна в том и заключается, чтобы исключить момент когда в рамках транзакции пишем в разные места (бд и брокер).
Если открыть транзакцию, добавить запись, отправить сообщение в кафку и только потом закоммитить транзакцию, то это существенно замедлит работу с базой.
У тебя тут главная ошибка в этом месте: "сохранение в бд и отправка сообщения в кафку происходит в рамках одной транзакции". Транзакции обеспечивают атомарность в рамках одной только БД, а кафка сама по себе. Поэтому, не очень понимаю, что ты имеешь ввиду. Да, бывают распределнные транзакции, например 2-phase commit, о котором я упоминал, но это более сложная штука. В любом случае, приходи лучше к нам в Gopher Club, и там обсудим. В комментах неудобно вести долгие дискуссии.
@@nikolay_tuzovизвиняюсь, я просто из-за того, что на спринге пишу, привык что у нас можно транзакции ролбэкать не только при ошибках связанных с бд. То есть я могу в рамках одной spring transactional сделать сохранение в бд и продюсить в кафку. И например первым делом я сохраню в бд, а брокер мог отвалится, то у меня при попытке запродюсить выбросится исключение и транзакция не отработает.
Если мы откатываем транзакцию через defer, то не зависнет. Потому что defer выполняется даже в случае паники. Но тут надо быть осторожным, т.к. он не выполнится, к примеру, в случае os.Exit() или log.Fatal(). Или если сам сервер перезагрузился.
URL Shortener давно уже выложен на гитхабе, а вот ссылка конкретно на ветку с изменениями из этого ролика: github.com/GolangLessons/url-shortener/tree/outbox
И зачем тут 30 сервисов с кафкой. Почему сервис истории не может просто опираться на таблицу транзакций, в которую пишется уже что зачислили, а что списали. Ну слать туда poll-select банальный. То есть быть асинхронным. А то получается, хотим писать данные в 2 таблицы двух разных микросервисов. Чтобы первый через кафку во второй сервис пулял сообщения. И о ужас у нас несогласованная транзакция. Надо срочно пойти исправиться, и сделать 3й сервис, который лечит проблемы первого и второго. Выкидываем отсюда кафку. Даём доступ второго сервиса до бд первого и нет проблем. Они нам рассказывают что микро- сервисы не должны знать про данные друг друга. Мол на то они и микросервисы. Зато потом как манну небесную впаривают "паттерны проектирования", которые возникли просто потому что как костыль на костыль из-за парадигмы микросервисов
проблема воркера на тикере в том, что он увеличивает задержку обработки сообщения. Конкретно в вашем примере клиент будет зря триггерить поддержку: он отправил деньги, а в ленту сообщение попадает не сразу. Поэтому он сразу бежит жаловаться. В данном случае что делать? Меньше интервал в тикере делать? Тогда база заспамится. Плюс будут лишние запросы в базу скорее всего
Мой канал в Telegram: t.me/ntuzov
Пишу там новости, анонсы своих активностей, небольшие текстовые гайды и просто интересные мысли.
Также через него я получаю от вас оперативный фидбэк по роликам - что нравится, что не нравится, какой ролик делать следующим и т.п.
Николай, спасибо за видео очень интересное и полезное видео. Благодарю тебя за твой контент бро.
Отличное видео! Я как-то о самом названии Outbox не слышал. Поэтому не знал, что меня ожидает и на первой же минуте приостановил видео и пошел в рассуждения что может пойти не так и какие проблемы будем решать (и как). Радости, после того, как видео было снято с паузы, было столько же, как когда впервые получил оффер :) Круто, что про гарантию доставки тоже было упомянуто. В общем, чувство как-будто вместе поработали - спасибо, с меня лайк :)
Спасибо за такой крутой отзыв
Вы получили оффер без знания паттернов микросервисной архитектуры, хм
Всегда ставлю лайк, смотрю с удовольствием, потом скачиваю и пересматриваю часиенько.
Благодарю
Спасибо, но лучше пересматривать на ютубе, чтобы статистика учитывала это. Если ролики часто пересматривают, то их лучше будут рекомендовать другим людям =)
О! Класс, спасибо!
Было бы супер увидеть видео про Сагу
И сразу в Озон
Спасибо за видос! Смотрю вас периодически. Классно объясняеете. Будет круто, если сделаете видос по inbox)
Про inbox будет, скорее всего)
Спасибо большое, было очень познавательно, учусь по твоим видео!
Николай, спасибо за очередное супер видео
Очень помогаешь
Николай внатуре легенда!
Пока не смотрел, но для продвижения сразу поставил лайк, успехов автору!)
отличный формат видео ! я трейни go могу сказать что ощутил себя в качестве стажера в первом коммерческом прожэкте ) очень интересная необычная подача !
Толковый и интересный материал, спасибо !
Николай, спасибо!
Как обычно очень полезно!
Здравствуйте Николай! Спасибо за видео! Как обычно - все понятно даже дурочку!
Лол. Не ожидал тут дбд ютубера увидеть)))
Ютуб не кормит?
@@FerelUltra Меня с руки кормит лично Николай
Супер! Спасибо за отличный материал 👍
Очень круто, хочется с такой же подачей материал посложнее
Скоро будет ролик посложнее)
🤓🤓🤓
Вижу видос на канале - ставлю лайк заранее
четко объяснил, спасибо!
Николай, спасибо что живой ❤
Спасибо за ролик. Хотел бы заметить: ты создал отдельную модель в слое storage, чтобы сканить из БД сразу в структуру и не писать отдельно скан в каждое поле. Но после скана в структуру ты маппишь ее в структуру доменной модели отдельно прописывая каждое поле, т.е. ничего особо не изменилось.
на фразе "пардон - не та картинка" пришла жена из соседней комнаты и стала спрашивать чего со мной случилось что я так ржу
Ну хоть кто-то оценил шутку, спасибо
Очень жду ролик о тестировании!
А расскажи подробней, что ты хотел бы увидеть в этом ролике?
@@nikolay_tuzov можно уже на существующие проекты накидать юнит-тестов и мок (ну тот минимум базы, которую ждут от твердого джуна)
Понятно, что уже есть таких много примеров, но ты пожалуй один из лучших лекторов по Го (в снг-сегменте так точно)
Мне эта тема чем-то event sourcing напомнила. Все события кладутся в dynamodb табличку например, на котором по триггеру работает лямбда, которая уже может отправить в какой-то sqs/sns
очень полезно. спасибо
ждем сагу ))
По поводу транзакции. Разве нельзя сразу писать defer tx.Rollback() ? То есть если код дошел до tx.Commit(), Rollback выполнится но уже ни на что влиять не будет
Как всегда супер видос) подскажи модель кресла
DxRacer Air но мне оно не очень понравилось, неудобное. Хотя, дизайн крутой
Николай спасибо огромное
well done )
Спасибо за видео! Николай, вопрос не по теме: не подскажешь, что за кресло используешь?
dxracer air
Николай, спасибо за очередной опыт.
Есть один вопрос, почему мы не прописали логику отправки сообщения внутри case блока тикера ?
Просто забить на все это и работать в монолите. Хочешь транзакшн фид? Ну создай в бд еще табличку, будет тебе транзакшн фид. )))) Есть мнение, что микросервисы для надувания щек перед инвесторами. По крайней мере - это одно из главных назначений этого подхода.
Возможно пример с фидом неудачный. В общем случае как только есть две системы, между которыми невозможна транзакция, то возникает эта проблема. И одно из решений это аутбокс. В вашем монолите наверняка есть подобная проблема. Например пользователь зарегался, ему надо отправить письмо. В бд записали, письмо не отправили. Или письмо отправили, а транзакция упала. Часто на это просто забивают, и алгоритм аутбокса выполняет сотрудник поддержки :-)
Ага, а потом нагрузка на монолит вырастет в 100 раз, а монолит уже не вырастет.
Или в 100 раз и в 5ти странах.
@@БанинЕгор Будьте добры, перечислите еще 10 случаев аутбокса в монолите. Кроме письма. Что-то мне подсказывает, что не наберется. А вот в микросервисах они будут на каждый чих и о всех помнить не будешь. В один прекрасный пятничный вечер руководство донесет, что надо восстанавливать какие-то данные, потому что оно где-то скрыто и очень давно работало не так из за того, что в такой сложной системе кто-то что-то когда-то проглядел. У меня на работе монолит и куча интеграций. У нас так бизнес процесс устроен, что если интеграция не ответила, то все, до свидания, смысла развлекать пользователя нет. Эксепшн, обращайтесь завтра после обеда.
Сервисы могут быть написаны на разных языках и их легче масштабировать.
@@buginsystem8925 в чем прикол? Эти все языки об одном и том же. Чем golang лучше C#? Решает те же задачи. Выглядит по другому. Ну и что. Языкодрочка - онанизм. И последнее, что хочет нормальный бизнес - это зоопарк языков на своем борту.
Спасибо за видео! Ты упомянул двухфазный коммит, про него будет ролик?
Вряд ли, это уже не моя тема
говорят он все еще пытается написать все сам, а copilot все еще мешает..
Воркеры не должны брать записи из базы с блокировкой? for update skip locked. Чтобы несколько воркеров не взяли одно и тоже сообщение.
Да, всё верно, если воркеров больше одного, то так и нужно. Я здесь немного упрощал, чтобы не затягивать, и чтобы было проще для понимания.
Кажется есть очень простое решение проблемы - танзакшн менеджер. Просто оборачиваем в транзакцию на сервисном уровне сохранение в бд и вывоз продьюса.
Если гдето по дороге падаем - транзакция не комитится и все останется консистентно
а нельзя в рамках БДшной транзакции отправлять сообщение в очередь просто? т.е. создали транзакцию, сделали инсерт в БД, отправили сообщение в очерель и сделали коммит. И если мы получили ошибку очереди, то просто сделать роллбэк транзакции. Есть конечно недостаток: если у нас упадет очередь, то пользователь не сможет делать переводы. Но есть ли еще какие-то подводные камни в таком подходе?
Конечно есть, сообщение отправится, а транзакция может ролбекнуть. В итоге сообщение уже в очереди и возможно обрабатывается, а по факту в бд ничего не произошло
А видео про garbage collector будет))
Будет)
Спасибо за видео! А валидно ли архитектурное решение, когда отправляем сообщения в кафку, а воркер уже считывает с кафки и пишет в базу. В то же время другой сервис тоже может считывать с кафки.
Нет, не решает. Допустим, все сервисы узнали о новом переводе, а что дальше? Наш Transfer Manager начинает этот перевод обрабатывать. В процессе его статус будет меняться: initial -> processing -> done / failed. Как другие сервисы будут узнавать о смене статуса? Ну и дальше та же логика - мы обновляем статус платежа в БД и отправляем сообщение об этом. По сути, Transfer Manager здесь является исходным источником информации.
В общем, это не решает текущую проблему, ты просто смещаешь угол обзора. В моём рассказе не принципиально, по какому каналу Transfer Manager получил инфу о новом переводе - по grpc, из кафки и т.п. Суть не меняется.
Если хочешь обсудить подробней, приходи в наш чат Gopher Club, обсудим.
@@nikolay_tuzov Да, все верно. Спасибо за объяснение!
35:12 а почему бы не написать внутри второго case ?
Потому что "плоский" код легче читается. Я этот select воспринимаю как Guard Expression
шутка про Кафку засчитана
Вот они проблемы event driven architecture. Я бы взял оркестратор например Temporal, который бы делал retry
здорово, что говорится про graceful shutdown, но жаль, что только говорится, ибо код в main не имеет признаков graceful shutdown
Так ролик не о нём. Да и сам URL Shortener во многих местах упрощён, т.к. был написан в течении нескольких часов, для учебного видео.
на 25:00 метод tx.Commit() выплняется только и тогда (!) когда err == nil, то есть когда ошибок на выходе не было до выполнения коммита, то есть нет никакого либо либо и переменная commitErr не нужна, можно сразу записывать выхлоп в err = tx.Commit()
Да, вы правы, спасибо за замечание. Немного запутался в этом месте)
Вопрос: а разве сообщение об ошибке, которое возвращается в saveEvent, не будет перезаписано в SaveURL
Потеряется же op из saveEvent?
Нет, ничего не потеряется, мы просто будем врапить одно в другое. В итоге, получится цепочка из всех ошибок, что мы успели завернуть. Попробуй вернуть ошибку из saveEvent, и посмотри что получтся.
@@nikolay_tuzov понял, спасибо
Хотел уточнить, вот кейс когда говорится в начале что сервер упадет, не могу понять одно. У нас же сохранение в бд и отправка сообщения в кафку происходит в рамках одной транзакции? Просто если так, то по-идее при перезагрузке сервера, у нас не получится запродюсить сообщение в кафку и транзакция откатится, нет? Как тогда произойдет неконсистентность?
Не факт. А если получилось запродюсить данные в брокер? Тогда данные есть в брокере (и потенциально в консьюмере), а в БД их нет и наоборот. Идея паттерна в том и заключается, чтобы исключить момент когда в рамках транзакции пишем в разные места (бд и брокер).
То есть ты говоришь про кейс. Что сохранили в базу, запродюсили, а потом упал сервер и транзакция откатилась? И вот типо будет неконсистентность?
Если открыть транзакцию, добавить запись, отправить сообщение в кафку и только потом закоммитить транзакцию, то это существенно замедлит работу с базой.
У тебя тут главная ошибка в этом месте: "сохранение в бд и отправка сообщения в кафку происходит в рамках одной транзакции". Транзакции обеспечивают атомарность в рамках одной только БД, а кафка сама по себе. Поэтому, не очень понимаю, что ты имеешь ввиду.
Да, бывают распределнные транзакции, например 2-phase commit, о котором я упоминал, но это более сложная штука.
В любом случае, приходи лучше к нам в Gopher Club, и там обсудим. В комментах неудобно вести долгие дискуссии.
@@nikolay_tuzovизвиняюсь, я просто из-за того, что на спринге пишу, привык что у нас можно транзакции ролбэкать не только при ошибках связанных с бд. То есть я могу в рамках одной spring transactional сделать сохранение в бд и продюсить в кафку. И например первым делом я сохраню в бд, а брокер мог отвалится, то у меня при попытке запродюсить выбросится исключение и транзакция не отработает.
В идеале бы еще сделать sender на основе чтения wal из слота репликации)
Я похожим в Ламоде занимался, но тот проект так и не запустили(
Но вообще, это надо в сторону Debezium смотреть, например.
@@nikolay_tuzov да, если есть возможность им воспользоваться, оптимальный вариант чтобы свои костыли не делать!
так если сервис отваливается по панике, транзакция зависает?
Если мы откатываем транзакцию через defer, то не зависнет. Потому что defer выполняется даже в случае паники. Но тут надо быть осторожным, т.к. он не выполнится, к примеру, в случае os.Exit() или log.Fatal(). Или если сам сервер перезагрузился.
Было бы круто если бы выложил код или ссылку на репо
URL Shortener давно уже выложен на гитхабе, а вот ссылка конкретно на ветку с изменениями из этого ролика: github.com/GolangLessons/url-shortener/tree/outbox
У меня точно такой же маленький флаг висит над столом 🔵🟡
Брат ты в казахстан переехал ?
Я в Казахстан вернулся)
@@nikolay_tuzov а ты в Алмате или в Астане?
Микросеврвисов *
Спасибо, поправил
@@nikolay_tuzovждём видео про планировщик ;)
Капец ты похож на маскота GO 😂
это все классно, но каков шанс, что это принесет больше пользы, чем вреда? 99% вам нужен монолит
И зачем тут 30 сервисов с кафкой. Почему сервис истории не может просто опираться на таблицу транзакций, в которую пишется уже что зачислили, а что списали. Ну слать туда poll-select банальный. То есть быть асинхронным.
А то получается, хотим писать данные в 2 таблицы двух разных микросервисов. Чтобы первый через кафку во второй сервис пулял сообщения. И о ужас у нас несогласованная транзакция. Надо срочно пойти исправиться, и сделать 3й сервис, который лечит проблемы первого и второго.
Выкидываем отсюда кафку. Даём доступ второго сервиса до бд первого и нет проблем.
Они нам рассказывают что микро- сервисы не должны знать про данные друг друга. Мол на то они и микросервисы. Зато потом как манну небесную впаривают "паттерны проектирования", которые возникли просто потому что как костыль на костыль из-за парадигмы микросервисов
Лайк за флаг и годное знание❤
проблема воркера на тикере в том, что он увеличивает задержку обработки сообщения. Конкретно в вашем примере клиент будет зря триггерить поддержку: он отправил деньги, а в ленту сообщение попадает не сразу. Поэтому он сразу бежит жаловаться.
В данном случае что делать? Меньше интервал в тикере делать? Тогда база заспамится. Плюс будут лишние запросы в базу скорее всего
Меньше интервал, плюс обрабатывать батчами. База выдержит, это не проблема.
@@nikolay_tuzovв ролике ты сказал, что у батча свои проблемы, что это за проблемы и как их решить.
Я имел ввиду, что это чуть сложнее в реализации, но не критично. Просто чуть сложнее код получится, и его бует сложнее поддерживать.
Как вариант использовать cdc вместо поллинга