Redis через VPN здається повільним: скоротіть кількість обмінів на запит
VPN не робить віддалений виклик Redis локальним. Плануйте кількість обмінів на запит, повторно використовуйте з'єднання і тримайте pipeline подалі від транзакцій.
Сторінка, яка з локальним Redis відповідає за 200 мс, потребує чотирьох секунд, щойно клієнт опиняється за VPN, тоді як лічильники сервера простоюють. Slow Log не показує нічого понад мілісекунду, графік CPU рівний, а інстанс обслуговує інших клієнтів без скарг. Змінилася відстань, а те, що множить відстань, — це кількість разів, коли ваш код зупиняється й чекає. Пройдіть шлях запиту від клієнта назовні, і виправлення майже завжди полягає в меншій кількості послідовних очікувань, а не в швидшому сервері.
Почніть із клієнта, а не з метрик сервера
Спостерігайте за процесом клієнта між першою і останньою командою. Якщо трасування показує відповідь, паузу, а потім наступну команду, ви платите повний обмін за кожну команду, і жодне налаштування сервера цього не змінить. Slow Log тут — неправильний інструмент, бо він фіксує час виконання і навмисно виключає комунікаційний I/O. Чистий Slow Log поруч із повільним endpoint — це підпис проблеми з обмінами.
Спершу виміряйте сам шлях, а вже потім торкайтеся коду. redis-cli -h redis.example.internal --latency надсилає повторювані PING і друкує середній час запиту та відповіді, що є вашою ціною за один обмін. Помножте це на кількість послідовних команд в одній дії користувача, і ви отримаєте нижню межу для endpoint.
Порахуйте, скільки обмінів насправді потребує одна дія користувача
Візьміть один endpoint і запишіть у порядку кожен виклик Redis, включно з тими, що ховаються в ORM, session middleware чи пошуку feature flag. Більшість повільних endpoint роблять двадцять або тридцять викликів там, де вистачило б трьох, а приховані зазвичай становлять більшість.
Ланцюг залежностей не можна згорнути: якщо другій команді потрібен ключ, який повернула перша, очікування реальне, і єдині варіанти — інша модель даних або серверна операція, яка виражає весь крок одразу. Незалежні читання — інший випадок, бо ніщо не змушує вас чекати на першу відповідь, перш ніж надіслати другу. Позначте кожен виклик як залежний або незалежний, перш ніж щось редагувати, і форма виправлення стане очевидною.
Важливе число — послідовні очікування, а не загальна кількість команд. Тридцять команд в одному batch усе одно коштують одного очікування.
Об'єднуйте в batch лише команди, що не залежать одна від одної
Pipeline ставить незалежні команди в чергу і збирає відповіді по порядку, перетворюючи N очікувань на одне. Більшість клієнтів подають його як блок, callback або context manager, а не як окремий режим з'єднання, тож перевірте API, який надає встановлена версія. Виграш масштабується з часом обміну, і саме тому він важить на VPN і майже не помітний на localhost.
import redis
r = redis.Redis(host="redis.example.internal", port=6379, socket_timeout=2)
pipe = r.pipeline(transaction=False)
for key in ("user:1001", "user:1002", "user:1003"):
pipe.get(key)
print(pipe.execute())
Передайте transaction=False, коли потрібне лише пакетування. Із типовим налаштуванням клієнт загортає batch у MULTI і EXEC, що робить його атомарним і змінює те, що інші клієнти можуть спостерігати під час виконання. Атомарність — це рішення про коректність, а не важіль продуктивності, і pipeline не є транзакцією лише тому, що обидва зменшують кількість обмінів.
Читайте кожну відповідь, а не лише надсилання batch. Команда в черзі може повернути помилку, тоді як batch у цілому завершується, і клієнт, який відкидає список відповідей, перетворює невдалий запис на тихий. Порівняйте довжину списку відповідей із кількістю команд, які ви поставили в чергу.
Тримайте з'єднання живим між діями
Кожне нове з'єднання платить за розв'язання імені, TCP handshake, TLS handshake і AUTH, а за VPN кожне з них — це обмін, який не запакувати в batch. Створіть клієнта один раз на процес і використовуйте повторно, або скористайтеся пулом, який надає ваша бібліотека, з обмеженим розміром. Повторне підключення на кожен запит — друга за поширеністю причина цього симптому після очікування на кожну команду.
Тримайте приватний hostname у конфігурації, а не як буквальну адресу. Перевірка сертифіката й ідентичність служби діють і в приватній мережі, а прив'язка IP ламається тієї ж миті, коли провайдер перенумеровує subnet. Якщо повільним є саме розв'язання імені, виправте шлях до резолвера, а не рядок підключення.
Задайте явний socket timeout, щоб застряглий тунель швидко падав, а не підвішував worker. Потім підтвердьте, що пул справді використовується повторно, залогувавши створення з'єднань у клієнті.
Розрахуйте розмір batch, перш ніж він стане проблемою пам'яті
Redis тримає відповіді в черзі протягом усього batch, а клієнт потім тримає розібраний результат. Pipeline з десяти тисяч великих значень обмінює мережеві очікування на стрибки пам'яті й довгі паузи на обох кінцях.
Обирайте розмір batch за обсягом відповідей, а не за кількістю команд. Кілька сотень дрібних команд в одному batch зазвичай комфортні, а сотня payload по кілька кілобайт — ні. Діліть і за байтами, і за кількістю, та тримайте кожен batch у межах бюджету затримки, який endpoint справді має.
Після зміни вимірюйте від початку до кінця: redis-cli -h redis.example.internal --latency-history покаже, чи стабільний сам шлях, а ваш власний таймер запитів покаже, чи покращився endpoint. Якщо endpoint усе ще повільний, а історія затримки рівна, залишкова ціна — робота на сервері або ваша серіалізація, і наступний крок — профіль обробника, а не ще одна зміна batch.