Обработка потокобезопасных общих значений в памяти с помощью Rust

Вопрос

Существуют ли в Rust какие-либо стратегии/рекомендации по обработке редко обновляемых, совместно используемых и изменяемых значений?

Пример

struct SharedMutableResource {
    some_value: String,

    // time at which value must be refreshed
    // probably daily or weekly
    expires_at: chrono::DateTime<chrono::Utc>, 
}

impl SharedMutableResource {
    pub fn get_some_value(&mut self) -> &str {
        if self.expires_at < Utc::now() {
            // some refresh method
            self.some_value = some_new_value;
        }

        self.some_value.as_str()
    }
}

struct AsyncRequiresResource {
    resource: Arc<SharedMutableResource>,
}

impl AsyncRequiresResource {
    pub fn borrow_resource(&self) -> Value {
        // is there any way to avoid borrowing as mut here for every request
        let some_value = self.resource.get_mut().get_some_value();
        
        // do something with the value
    }
}

Некоторый контекст

Я пытаюсь создать реализацию ротации ключей для подписи моих JWT, в идеале меняя ключи от ежедневного до еженедельного. Эти запросы будут поступать асинхронно и должны получить доступ к значению ключа для подписи и проверки токенов. Однако ключи будут чередоваться в зависимости от временных ограничений, поэтому их можно будет обновить по любому запросу.

Проблема

Помня о безопасности потоков, прямо сейчас я пытаюсь найти оптимальный способ обработки доступа к ключу. В идеале он должен храниться в памяти в стеке во время выполнения, так как я понимаю, что это самый быстрый метод извлечения (для большинства запросов потребуется JWT, и поэтому я хотел бы убедиться, что во время этого процесса я избегаю как можно больше замедлений, таких как подключение к db или операции ввода-вывода). Однако, поскольку это должно быть потокобезопасным, у меня есть две идеи, но ни одна из них не кажется очень хорошей.

  1. Используйте асинхронные дуги (Arc<Mutex> вместо Arc)
    Плюсы

    • Безопасность потока сохраняется
    • Очень небольшая модификация того, что у меня сейчас есть

    Минусы

    • Атомарные операции для каждого отдельного запроса
    • В основном все запросы становятся синхронными, поскольку они ожидают блокировки, чтобы запрос мог быть обработан.
  2. Используйте асинхронный поток (thread::spawn(task))
    Плюсы

    • Безопасность потока сохраняется
    • Позволяет улучшить асинхронную обработку запросов

    Минусы

    • Кажется расточительным создавать целый поток, который будет запускаться только один раз в день/неделю, чтобы внести одно изменение.

Резюме

По сути, оба способа кажутся мне довольно расточительными для изменений, которые случаются очень редко. Я бы не беспокоился о дополнительных накладных расходах для фактического изменения значения, поскольку это происходит так нечасто, но для того, чтобы поддерживать высокие скорости чтения в рамках моих (по общему признанию, наложенных мной) ограничений, мне трудно найти путь вперед. Есть ли какие-либо инструменты, стратегии или какие-то ограничения, которые мне нужно отбросить / иметь неправильное понимание, чтобы разрешить нечасто изменяемые данные с высокой скоростью чтения?

Что у вас есть на данный момент? Похоже, RwLock, вероятно, будет лучше, чем Mutex для вашего случая. Почему бы не поставить это в static?

PitaJ 18.04.2023 00:12

@PitaJ копирование ключа в статический вызовет случайные «полунеправильные» ключи из-за того, что операции копирования не являются атомарными, не так ли?

Sedat Kapanoglu 18.04.2023 00:14

Нет, статика RwLock не позволит этому случиться.

PitaJ 18.04.2023 00:14

То, что я предлагаю в своем ответе, arc-swap, семантически эквивалентно RwLock<Arc>, только быстрее.

Chayim Friedman 18.04.2023 00:15

@PitaJ о, хорошо, я думал, ты предложил это как альтернативу, а не поверх RwLock.

Sedat Kapanoglu 18.04.2023 00:15

@ChayimFriedman Я изо всех сил пытаюсь понять, зачем нужен Arc в случае static RwLock, но arc-swap, вероятно, все же предпочтительнее, поскольку запросы никогда не будут заблокированы

PitaJ 18.04.2023 00:19

@PitaJ Обычно в этом нет необходимости, просто более прямая замена ArcSwap. Хотя удержание блокировки только на время клонирования Arc происходит быстрее.

Chayim Friedman 18.04.2023 00:22
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
7
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Лучше всего использовать ящик arc-swap. Он предназначен именно для этого сценария: данные, которые обычно используются, но редко обновляются. Загрузка данных осуществляется без блокировки, и ящик используется очень часто (25 миллионов загрузок на момент написания статьи на crates.io).

Он предоставляет различные варианты, но самый простой — это ArcSwap.

Другие вопросы по теме