Pull to refresh

Comments 13

Спасибо за статью. А разве не стоит работу с кэшом и бдь(очистка, например) делать не асинхронно-последовательно (.await; .await;), а полностью асинхронно (future запускать через join! например)?

Думаю, это могло бы быть справедливо, если бы результаты вызовов этих `await`'ов были логически независимы. Но, например, в этом случае:

pub async fn update_planet(
    &self,
    planet_id: &str,
    planet: Planet,
) -> Result<Planet, CustomError> {
    let updated_planet = self
        .mongodb_client
        .update_planet(ObjectId::from_str(planet_id)?, planet)
        .await?;

    let cache_key = self.get_planet_cache_key(planet_id);
    self.redis_connection_manager.clone().del(cache_key).await?;

    Ok(updated_planet)
}

на мой взгляд, очистка кэша должна выполняться только после завершения обновления в БД.

Благодарю за статью. Интересно смотреть примеры обычных прикладных задач на Rust.

Насколько сейчас Rust готов для полноценной разработки backend? Есть ли места, где не хватает crates?

Насколько сложно было переключиться с другого языка (если был такой опыт)? Или в целом гладко пошло и много боли не возникает на стандартных задачах?

Насколько сейчас Rust готов для полноценной разработки backend? Есть ли места, где не хватает crates?

Вцелом хватает, некоторые крейты еще сыроваты но это понятно.

Насколько сложно было переключиться с другого языка (если был такой опыт)? Или в целом гладко пошло и много боли не возникает на стандартных задачах?

Поначалу достаточно тяжело т.к. программирование на Rust требует немного другого мышления и соответственно первое время постоянно возникают проблемы связанные с владением данными и неспособностью выразить свои идеи так чтобы компилятор их принял. Через 2-3 месяца привыкаешь и пишешь уже вполне свободно.

Вполне возможно, что не для всех backend задач есть библиотеки или существующие недостаточно развиты. Пока что я сталкивался только со вторым, при этом в части случаев мейнтейнеры были готовы доработать крейты. Кроме того, для многих задач (например, GraphQL, ORM) есть 2 или более поддерживаемых библиотек.

По поводу переключения (в моём случае основными языками были Java/Kotlin), в первые месяцы действительно сложно.

Не корректно реализована работа с кешом в редиске - в два запроса. Просто классический race-condition. Первым запросом вы проверяете наличие ключа в редиске, вторым читаете его значение. Только вы совсем проигнорировали то, что чтение может вернуть ошибку в том числе и потому, что ключа нет в базе, т.к. его удалили в промежутке между первым и вторым запросом. В итоге приложение выкинет ошибку на ровном месте. Кроме того, в остальное время, будет оверхед на запрос проверки наличия ключа. Надо сразу запрашивать значение ключа одним запросом и обрабатывать ошибку его отсутствия.

С очисткой кеша, при обновлении записи в монге, тоже вопрос не простой. А что если приложение навернётся сразу после обновления документа в монге, но до того как кеш в редиске будет удалён? Очевидно не хватает "авто-протухания" кеша через какое-то время.

Первым запросом вы проверяете наличие ключа в редиске, вторым читаете его значение. Только вы совсем проигнорировали то, что чтение может вернуть ошибку в том числе и потому, что ключа нет в базе, т.к. его удалили в промежутке между первым и вторым запросом.

Справедливое замечание, поменял

С очисткой кеша, при обновлении записи в монге, тоже вопрос не простой. А что если приложение навернётся сразу после обновления документа в монге, но до того как кеш в редиске будет удалён? Очевидно не хватает "авто-протухания" кеша через какое-то время.

Конкретно здесь:

pub async fn update_planet(
    &self,
    planet_id: &str,
    planet: Planet,
) -> Result<Planet, CustomError> {
    let updated_planet = self
        .mongodb_client
        .update_planet(ObjectId::from_str(planet_id)?, planet)
        .await?;

    let cache_key = self.get_planet_cache_key(planet_id);
    self.redis_connection_manager.clone().del(cache_key).await?;

    Ok(updated_planet)
}

маловероятно, что что-то может пойти не так. Для порядка можно перенести формирование строки ключа в начало метода. Для "продакшн" приложения, возможно, и стоит рассмотреть вариант с автоэкспирацией.

маловероятно, что что-то может пойти не так.

"Ты не поверишь" :)

Первый вариант - моргнёт сеть и конект с редиской пропадёт на короткое время. В результате:

  • кеш содержит неактульное значение

  • запрос клиента завершится ошибкой из-за проблем с вспомогательным сервисом (кешем), несмотря на то что полезная работа была выполнена без ошибок.

Второй вариант - приложение жёстко перезапустили ровно в момент между запросом к монге и запросом к редиске.

Конечно, эти варианты возможны, но не считаю, что

  • утверждение о маловероятности опровергнуто

  • обязательно стоит предусматривать их обработку в учебном проекте

Вы недооцениваете вероятности сбоя. Всё, что может поломаться обязательно поломается. В пятницу. Вечером. :)

Для учебного проекта хорошо бы тогда озвучить о возможных ситуациях. Хотя бы в комментариях. :)

Как раз именно в учебном проекте и стоит. А то потом джуны копируют этот код и оно пролазит в продакшен. Т.к. им никто не объяснил как же именно делать правильно.

А то потом джуны копируют этот код и оно пролазит в продакшен.

И? Баг? Так и поделом такой компании, без синьоров и тестеров :)

Мужики, добавил автоэкспирацию записей в кэше с пояснением в статье :) Джуны могут спать спокойно :)

Sign up to leave a comment.

Articles