[{"data":1,"prerenderedAt":1859},["ShallowReactive",2],{"content:\u002F09-redis\u002F04-expiration-eviction-memory":3},{"title":4,"description":5,"path":6,"body":7},"TTL, eviction и память Redis","Redis быстро работает, пока рабочий набор помещается в память и команды остаются маленькими. Поэтому production Redis - это не только GET и SET, а ещё дисциплина вокруг TTL, eviction policy, размера ключей, hot keys, fragmentation и безопасного обхода keyspace.","\u002F09-redis\u002F04-expiration-eviction-memory",{"type":8,"value":9,"toc":1845},"minimark",[10,14,27,37,40,43,48,51,57,60,96,99,101,105,108,181,184,219,222,225,227,231,241,335,344,350,353,363,369,371,375,378,384,387,404,411,413,417,420,437,444,450,461,463,467,470,484,487,515,518,524,527,533,546,549,575,577,581,584,590,593,604,607,624,627,633,636,638,642,648,654,660,666,686,689,970,973,975,979,982,988,990,1378,1381,1383,1387,1440,1442,1446,1464,1466,1470,1505,1671,1841],[11,12,4],"h1",{"id":13},"ttl-eviction-и-память-redis",[15,16,17,18,22,23,26],"p",{},"Redis быстро работает, пока рабочий набор помещается в память и команды остаются маленькими. Поэтому production Redis - это не только ",[19,20,21],"code",{},"GET"," и ",[19,24,25],{},"SET",", а ещё дисциплина вокруг TTL, eviction policy, размера ключей, hot keys, fragmentation и безопасного обхода keyspace.",[28,29,35],"pre",{"className":30,"code":32,"language":33,"meta":34},[31],"language-text","RAM Redis\n  ├─ данные пользователей\n  ├─ служебные структуры Redis\n  ├─ memory overhead ключей и объектов\n  ├─ allocator fragmentation\n  └─ временная память: AOF rewrite, replication buffers, client buffers\n","text","",[19,36,32],{"__ignoreMap":34},[15,38,39],{},"Ошибка новичков: считать только размер value. В Redis память тратится на ключи, структуры, указатели, metadata, allocator и буферы. Миллион маленьких ключей может занимать гораздо больше, чем кажется по сумме payload.",[41,42],"hr",{},[44,45,47],"h2",{"id":46},"ttl-как-ключи-истекают","TTL: как ключи истекают",[15,49,50],{},"TTL можно задать при записи:",[28,52,55],{"className":53,"code":54,"language":33,"meta":34},[31],"SET session:abc user:42 EX 3600\nSET otp:user:42 123456 PX 300000\nEXPIRE course:42 600\nTTL course:42\n",[19,56,54],{"__ignoreMap":34},[15,58,59],{},"Redis удаляет истёкшие ключи двумя способами:",[61,62,63,76],"table",{},[64,65,66],"thead",{},[67,68,69,73],"tr",{},[70,71,72],"th",{},"Механизм",[70,74,75],{},"Что происходит",[77,78,79,88],"tbody",{},[67,80,81,85],{},[82,83,84],"td",{},"Lazy expiration",[82,86,87],{},"ключ проверяется при обращении; если TTL истёк, Redis удаляет его и возвращает miss",[67,89,90,93],{},[82,91,92],{},"Active expiration",[82,94,95],{},"Redis периодически выбирает ключи с TTL и удаляет просроченные",[15,97,98],{},"Это значит, что истёкший ключ не всегда исчезает ровно в момент TTL. Обычно это нормально: Redis гарантирует логическую истёкшесть при доступе, а физическое освобождение памяти происходит с задержкой.",[41,100],{},[44,102,104],{"id":103},"ttl-как-часть-доменной-модели","TTL как часть доменной модели",[15,106,107],{},"TTL не должен быть случайной цифрой. Он зависит от смысла данных:",[61,109,110,124],{},[64,111,112],{},[67,113,114,117,121],{},[70,115,116],{},"Данные",[70,118,120],{"align":119},"right","Пример TTL",[70,122,123],{},"Почему",[77,125,126,137,148,159,170],{},[67,127,128,131,134],{},[82,129,130],{},"OTP-код",[82,132,133],{"align":119},"3-5 минут",[82,135,136],{},"короткое окно безопасности",[67,138,139,142,145],{},[82,140,141],{},"access\u002Fsession state",[82,143,144],{"align":119},"15-60 минут",[82,146,147],{},"зависит от auth-модели",[67,149,150,153,156],{},[82,151,152],{},"карточка курса",[82,154,155],{"align":119},"5-30 минут",[82,157,158],{},"можно обновить не мгновенно",[67,160,161,164,167],{},[82,162,163],{},"negative cache",[82,165,166],{"align":119},"10-60 секунд",[82,168,169],{},"объект может скоро появиться",[67,171,172,175,178],{},[82,173,174],{},"rate limit bucket",[82,176,177],{"align":119},"размер окна",[82,179,180],{},"ключ не нужен после окна",[15,182,183],{},"Для кеша важно различать:",[185,186,187,195,201,207,213],"ul",{},[188,189,190,194],"li",{},[191,192,193],"strong",{},"freshness"," - насколько данные могут быть старыми;",[188,196,197,200],{},[191,198,199],{},"load shedding"," - насколько кеш защищает БД;",[188,202,203,206],{},[191,204,205],{},"invalidation"," - кто и когда удаляет устаревший ключ.",[188,208,209,212],{},[191,210,211],{},"retention"," - сколько времени данные нужны для восстановления или повторной обработки;",[188,214,215,218],{},[191,216,217],{},"security window"," - как долго токен, OTP или blacklist-запись должны оставаться проверяемыми.",[15,220,221],{},"Часто используют комбинацию: короткий TTL + явная инвалидация при записи.",[15,223,224],{},"Production-ошибка: использовать одинаковый длинный TTL \"на всякий случай\" для всех ключей. Так Redis превращается в склад устаревших данных, а eviction начинает удалять случайные бизнес-сущности. TTL должен быть записан в cache contract рядом с причиной, а не только в коде.",[41,226],{},[44,228,230],{"id":229},"eviction-что-делает-redis-при-нехватке-памяти","Eviction: что делает Redis при нехватке памяти",[15,232,233,236,237,240],{},[19,234,235],{},"maxmemory"," ограничивает память Redis. Когда лимит достигнут, Redis применяет ",[19,238,239],{},"maxmemory-policy",".",[61,242,243,253],{},[64,244,245],{},[67,246,247,250],{},[70,248,249],{},"Policy",[70,251,252],{},"Что удаляется",[77,254,255,265,275,285,295,305,315,325],{},[67,256,257,262],{},[82,258,259],{},[19,260,261],{},"noeviction",[82,263,264],{},"ничего; write-команды получают ошибку",[67,266,267,272],{},[82,268,269],{},[19,270,271],{},"allkeys-lru",[82,273,274],{},"примерно least recently used среди всех ключей",[67,276,277,282],{},[82,278,279],{},[19,280,281],{},"volatile-lru",[82,283,284],{},"LRU только среди ключей с TTL",[67,286,287,292],{},[82,288,289],{},[19,290,291],{},"allkeys-lfu",[82,293,294],{},"примерно least frequently used среди всех ключей",[67,296,297,302],{},[82,298,299],{},[19,300,301],{},"volatile-lfu",[82,303,304],{},"LFU только среди ключей с TTL",[67,306,307,312],{},[82,308,309],{},[19,310,311],{},"allkeys-random",[82,313,314],{},"случайный ключ",[67,316,317,322],{},[82,318,319],{},[19,320,321],{},"volatile-random",[82,323,324],{},"случайный ключ с TTL",[67,326,327,332],{},[82,328,329],{},[19,330,331],{},"volatile-ttl",[82,333,334],{},"ключ с ближайшим истечением TTL",[15,336,337,338,340,341,343],{},"Для чистого кеша часто выбирают ",[19,339,271],{}," или ",[19,342,291],{},". Для Redis, где есть ключи, которые нельзя потерять, eviction нужно включать очень осторожно: Redis не знает, какой ключ \"важен для бизнеса\".",[28,345,348],{"className":346,"code":347,"language":33,"meta":34},[31],"Redis как cache only:\n  maxmemory-policy allkeys-lfu\n\nRedis хранит locks\u002Fsessions\u002Fqueues:\n  noeviction или отдельный Redis под cache\n",[19,349,347],{"__ignoreMap":34},[15,351,352],{},"Практическое правило: разные классы нагрузки лучше разделять по отдельным Redis instance\u002FDB\u002Fcluster, особенно если eviction допустим только для части данных.",[15,354,355,356,358,359,362],{},"Если выбран ",[19,357,261],{},", Redis не станет магически стабильнее: write-команды начнут возвращать ошибки. Приложение должно знать, что делать с ",[19,360,361],{},"OOM command not allowed when used memory > 'maxmemory'",": деградировать, отклонить запрос, сбросить менее важный слой или поднять alert.",[15,364,365,366,368],{},"Для смешанных сценариев лучше не класть cache, sessions, locks и streams в один Redis с одной eviction policy. Кеш может жить с ",[19,367,291],{},", а lock\u002Fsession state обычно требует отдельного лимита памяти, короткого TTL и более строгой политики.",[41,370],{},[44,372,374],{"id":373},"capacity-planning","Capacity planning",[15,376,377],{},"Перед запуском Redis полезно посчитать грубый бюджет:",[28,379,382],{"className":380,"code":381,"language":33,"meta":34},[31],"expected_keys * (avg_key_size + avg_value_size + overhead) + buffers + fragmentation + persistence_headroom\n",[19,383,381],{"__ignoreMap":34},[15,385,386],{},"Что обязательно оставить в запасе:",[185,388,389,392,395,398,401],{},[188,390,391],{},"copy-on-write память во время RDB\u002FAOF rewrite;",[188,393,394],{},"replication backlog и client output buffers;",[188,396,397],{},"memory fragmentation;",[188,399,400],{},"рост key cardinality после релиза новой функции;",[188,402,403],{},"headroom для failover, когда replica догоняет primary.",[15,405,406,407,410],{},"Если Redis работает с persistence, память и диск связаны: большой dataset может требовать дополнительной RAM во время ",[19,408,409],{},"BGSAVE","\u002Frewrite и достаточно быстрого диска, чтобы persistence не создавала latency spikes.",[41,412],{},[44,414,416],{"id":415},"fragmentation","Fragmentation",[15,418,419],{},"Redis может показывать, что использует больше памяти, чем сумма данных. Причины:",[185,421,422,425,428,431,434],{},[188,423,424],{},"allocator fragmentation;",[188,426,427],{},"freed memory не сразу возвращается ОС;",[188,429,430],{},"copy-on-write при RDB\u002FAOF rewrite;",[188,432,433],{},"большие объекты разного размера;",[188,435,436],{},"частые create\u002Fdelete паттерны.",[15,438,439,440,443],{},"В ",[19,441,442],{},"INFO memory"," смотрят:",[28,445,448],{"className":446,"code":447,"language":33,"meta":34},[31],"used_memory\nused_memory_rss\nmem_fragmentation_ratio\nallocator_frag_ratio\n",[19,449,447],{"__ignoreMap":34},[15,451,452,453,456,457,460],{},"Если ",[19,454,455],{},"used_memory_rss"," сильно больше ",[19,458,459],{},"used_memory",", память занята процессом, но не полезными данными. Это не всегда авария, но повод смотреть workload, активный defrag и размеры ключей.",[41,462],{},[44,464,466],{"id":465},"big-keys","Big keys",[15,468,469],{},"Big key - это ключ, значение которого слишком большое для быстрых операций. Это может быть:",[185,471,472,475,478,481],{},[188,473,474],{},"String на десятки мегабайт;",[188,476,477],{},"Hash с сотнями тысяч полей;",[188,479,480],{},"Set\u002FZSet\u002FList с миллионами элементов;",[188,482,483],{},"Stream без retention.",[15,485,486],{},"Проблемы big keys:",[185,488,489,492,506,509,512],{},[188,490,491],{},"медленная передача по сети;",[188,493,494,495,498,499,498,502,505],{},"блокировка event loop на операциях ",[19,496,497],{},"HGETALL",", ",[19,500,501],{},"SMEMBERS",[19,503,504],{},"DEL",";",[188,507,508],{},"долгий replication;",[188,510,511],{},"latency spikes у соседних запросов;",[188,513,514],{},"неравномерное распределение в Cluster.",[15,516,517],{},"Плохой пример:",[28,519,522],{"className":520,"code":521,"language":33,"meta":34},[31],"user:42:events = List на 5_000_000 элементов\nLRANGE user:42:events 0 -1\n",[19,523,521],{"__ignoreMap":34},[15,525,526],{},"Лучше:",[28,528,531],{"className":529,"code":530,"language":33,"meta":34},[31],"user:42:events:2026-04 = Stream\u002FList с retention\nuser:42:events:2026-05 = отдельный ключ\nчитать страницами, старые ключи архивировать\n",[19,532,530],{"__ignoreMap":34},[15,534,535,536,539,540,542,543,545],{},"Для удаления больших ключей лучше использовать ",[19,537,538],{},"UNLINK",", а не ",[19,541,504],{},": ",[19,544,538],{}," отдаёт освобождение памяти фоновому потоку.",[15,547,548],{},"Для регулярной эксплуатации полезны отдельные проверки:",[185,550,551,557,560,566,572],{},[188,552,553,556],{},[19,554,555],{},"redis-cli --bigkeys"," или безопасный аналог на maintenance-окне;",[188,558,559],{},"slowlog и latency monitor;",[188,561,562,563,505],{},"алерт на рост ",[19,564,565],{},"evicted_keys",[188,567,568,569,505],{},"алерт на ",[19,570,571],{},"used_memory \u002F maxmemory",[188,573,574],{},"dashboard по hit ratio и Redis command latency.",[41,576],{},[44,578,580],{"id":579},"hot-keys","Hot keys",[15,582,583],{},"Hot key - ключ, на который приходится непропорционально много запросов.",[28,585,588],{"className":586,"code":587,"language":33,"meta":34},[31],"all users -> GET config:public\nall users -> ZREVRANGE leaderboard:global 0 99\n",[19,589,587],{"__ignoreMap":34},[15,591,592],{},"Проблемы:",[185,594,595,598,601],{},[188,596,597],{},"один Redis shard становится bottleneck;",[188,599,600],{},"p99 latency растёт даже при небольшом общем RPS;",[188,602,603],{},"в Cluster один slot может быть перегрет.",[15,605,606],{},"Решения:",[185,608,609,612,615,618,621],{},[188,610,611],{},"локальный in-process cache на 1-5 секунд;",[188,613,614],{},"read replicas для read-heavy данных;",[188,616,617],{},"sharding ключа вручную для счётчиков;",[188,619,620],{},"precompute и кеширование готового ответа;",[188,622,623],{},"ограничение частоты обновления hot ключей.",[15,625,626],{},"Для счётчиков иногда используют распределённые бакеты:",[28,628,631],{"className":629,"code":630,"language":33,"meta":34},[31],"views:lesson:42:bucket:0\nviews:lesson:42:bucket:1\n...\nviews:lesson:42:bucket:15\n",[19,632,630],{"__ignoreMap":34},[15,634,635],{},"Запись идёт в случайный bucket, чтение суммирует 16 ключей. Это снижает конкуренцию на один ключ ценой более сложного чтения.",[41,637],{},[44,639,641],{"id":640},"scan-vs-keys","SCAN vs KEYS",[15,643,644,647],{},[19,645,646],{},"KEYS pattern"," проходит весь keyspace за один вызов и блокирует Redis на время выполнения. В production это опасно.",[15,649,650,653],{},[19,651,652],{},"SCAN"," возвращает данные порциями через cursor:",[28,655,658],{"className":656,"code":657,"language":33,"meta":34},[31],"SCAN 0 MATCH course:* COUNT 100\nSCAN 18432 MATCH course:* COUNT 100\n",[19,659,657],{"__ignoreMap":34},[15,661,662,663,665],{},"Свойства ",[19,664,652],{},":",[185,667,668,671,674,677,683],{},[188,669,670],{},"не блокирует Redis надолго;",[188,672,673],{},"может вернуть один ключ несколько раз;",[188,675,676],{},"не гарантирует snapshot;",[188,678,679,682],{},[19,680,681],{},"COUNT"," - подсказка, а не строгий лимит;",[188,684,685],{},"полный обход всё равно создаёт нагрузку.",[15,687,688],{},"Go-пример:",[28,690,695],{"className":691,"code":692,"language":693,"meta":694,"style":34},"language-go shiki shiki-themes github-dark","func scanKeys(ctx context.Context, rdb *redis.Client, pattern string, handle func(string) error) error {\n    var cursor uint64\n    for {\n        keys, next, err := rdb.Scan(ctx, cursor, pattern, 100).Result()\n        if err != nil {\n            return err\n        }\n        for _, key := range keys {\n            if err := handle(key); err != nil {\n                return err\n            }\n        }\n        if next == 0 {\n            return nil\n        }\n        cursor = next\n    }\n}\n","go","no-run",[19,696,697,776,788,796,827,844,853,859,876,898,906,912,917,933,941,946,958,964],{"__ignoreMap":34},[698,699,702,706,710,714,718,721,723,726,728,731,734,737,739,742,744,747,750,752,755,758,760,763,766,769,771,773],"span",{"class":700,"line":701},"line",1,[698,703,705],{"class":704},"snl16","func",[698,707,709],{"class":708},"svObZ"," scanKeys",[698,711,713],{"class":712},"s95oV","(",[698,715,717],{"class":716},"s9osk","ctx",[698,719,720],{"class":708}," context",[698,722,240],{"class":712},[698,724,725],{"class":708},"Context",[698,727,498],{"class":712},[698,729,730],{"class":716},"rdb",[698,732,733],{"class":704}," *",[698,735,736],{"class":708},"redis",[698,738,240],{"class":712},[698,740,741],{"class":708},"Client",[698,743,498],{"class":712},[698,745,746],{"class":716},"pattern",[698,748,749],{"class":704}," string",[698,751,498],{"class":712},[698,753,754],{"class":716},"handle",[698,756,757],{"class":704}," func",[698,759,713],{"class":712},[698,761,762],{"class":704},"string",[698,764,765],{"class":712},") ",[698,767,768],{"class":704},"error",[698,770,765],{"class":712},[698,772,768],{"class":704},[698,774,775],{"class":712}," {\n",[698,777,779,782,785],{"class":700,"line":778},2,[698,780,781],{"class":704},"    var",[698,783,784],{"class":712}," cursor ",[698,786,787],{"class":704},"uint64\n",[698,789,791,794],{"class":700,"line":790},3,[698,792,793],{"class":704},"    for",[698,795,775],{"class":712},[698,797,799,802,805,808,811,814,818,821,824],{"class":700,"line":798},4,[698,800,801],{"class":712},"        keys, next, err ",[698,803,804],{"class":704},":=",[698,806,807],{"class":712}," rdb.",[698,809,810],{"class":708},"Scan",[698,812,813],{"class":712},"(ctx, cursor, pattern, ",[698,815,817],{"class":816},"sDLfK","100",[698,819,820],{"class":712},").",[698,822,823],{"class":708},"Result",[698,825,826],{"class":712},"()\n",[698,828,830,833,836,839,842],{"class":700,"line":829},5,[698,831,832],{"class":704},"        if",[698,834,835],{"class":712}," err ",[698,837,838],{"class":704},"!=",[698,840,841],{"class":816}," nil",[698,843,775],{"class":712},[698,845,847,850],{"class":700,"line":846},6,[698,848,849],{"class":704},"            return",[698,851,852],{"class":712}," err\n",[698,854,856],{"class":700,"line":855},7,[698,857,858],{"class":712},"        }\n",[698,860,862,865,868,870,873],{"class":700,"line":861},8,[698,863,864],{"class":704},"        for",[698,866,867],{"class":712}," _, key ",[698,869,804],{"class":704},[698,871,872],{"class":704}," range",[698,874,875],{"class":712}," keys {\n",[698,877,879,882,884,886,889,892,894,896],{"class":700,"line":878},9,[698,880,881],{"class":704},"            if",[698,883,835],{"class":712},[698,885,804],{"class":704},[698,887,888],{"class":708}," handle",[698,890,891],{"class":712},"(key); err ",[698,893,838],{"class":704},[698,895,841],{"class":816},[698,897,775],{"class":712},[698,899,901,904],{"class":700,"line":900},10,[698,902,903],{"class":704},"                return",[698,905,852],{"class":712},[698,907,909],{"class":700,"line":908},11,[698,910,911],{"class":712},"            }\n",[698,913,915],{"class":700,"line":914},12,[698,916,858],{"class":712},[698,918,920,922,925,928,931],{"class":700,"line":919},13,[698,921,832],{"class":704},[698,923,924],{"class":712}," next ",[698,926,927],{"class":704},"==",[698,929,930],{"class":816}," 0",[698,932,775],{"class":712},[698,934,936,938],{"class":700,"line":935},14,[698,937,849],{"class":704},[698,939,940],{"class":816}," nil\n",[698,942,944],{"class":700,"line":943},15,[698,945,858],{"class":712},[698,947,949,952,955],{"class":700,"line":948},16,[698,950,951],{"class":712},"        cursor ",[698,953,954],{"class":704},"=",[698,956,957],{"class":712}," next\n",[698,959,961],{"class":700,"line":960},17,[698,962,963],{"class":712},"    }\n",[698,965,967],{"class":700,"line":966},18,[698,968,969],{"class":712},"}\n",[15,971,972],{},"Лучший вариант - вообще не сканировать Redis в request path. Если нужно знать список объектов, храните индекс явно: Set, ZSet или таблица в основной БД.",[41,974],{},[44,976,978],{"id":977},"pipelining","Pipelining",[15,980,981],{},"Pipelining отправляет несколько команд без ожидания ответа после каждой.",[28,983,986],{"className":984,"code":985,"language":33,"meta":34},[31],"Без pipeline:\n  SET a -> wait\n  SET b -> wait\n  SET c -> wait\n\nС pipeline:\n  SET a\n  SET b\n  SET c\n  wait all responses\n",[19,987,985],{"__ignoreMap":34},[15,989,688],{},[28,991,993],{"className":691,"code":992,"language":693,"meta":694,"style":34},"func loadCourses(ctx context.Context, rdb *redis.Client, ids []int64) (map[int64]string, error) {\n    pipe := rdb.Pipeline()\n\n    cmds := make(map[int64]*redis.StringCmd, len(ids))\n    for _, id := range ids {\n        key := fmt.Sprintf(\"course:%d:title\", id)\n        cmds[id] = pipe.Get(ctx, key)\n    }\n\n    _, err := pipe.Exec(ctx)\n    if err != nil && !errors.Is(err, redis.Nil) {\n        return nil, err\n    }\n\n    result := make(map[int64]string, len(ids))\n    for id, cmd := range cmds {\n        title, err := cmd.Result()\n        if errors.Is(err, redis.Nil) {\n            continue\n        }\n        if err != nil {\n            return nil, err\n        }\n        result[id] = title\n    }\n    return result, nil\n}\n",[19,994,995,1058,1072,1078,1116,1130,1158,1174,1178,1182,1197,1223,1233,1237,1241,1268,1282,1296,1307,1313,1318,1331,1340,1345,1356,1361,1373],{"__ignoreMap":34},[698,996,997,999,1002,1004,1006,1008,1010,1012,1014,1016,1018,1020,1022,1024,1026,1029,1032,1035,1038,1041,1044,1046,1049,1051,1053,1055],{"class":700,"line":701},[698,998,705],{"class":704},[698,1000,1001],{"class":708}," loadCourses",[698,1003,713],{"class":712},[698,1005,717],{"class":716},[698,1007,720],{"class":708},[698,1009,240],{"class":712},[698,1011,725],{"class":708},[698,1013,498],{"class":712},[698,1015,730],{"class":716},[698,1017,733],{"class":704},[698,1019,736],{"class":708},[698,1021,240],{"class":712},[698,1023,741],{"class":708},[698,1025,498],{"class":712},[698,1027,1028],{"class":716},"ids",[698,1030,1031],{"class":712}," []",[698,1033,1034],{"class":704},"int64",[698,1036,1037],{"class":712},") (",[698,1039,1040],{"class":704},"map",[698,1042,1043],{"class":712},"[",[698,1045,1034],{"class":704},[698,1047,1048],{"class":712},"]",[698,1050,762],{"class":704},[698,1052,498],{"class":712},[698,1054,768],{"class":704},[698,1056,1057],{"class":712},") {\n",[698,1059,1060,1063,1065,1067,1070],{"class":700,"line":778},[698,1061,1062],{"class":712},"    pipe ",[698,1064,804],{"class":704},[698,1066,807],{"class":712},[698,1068,1069],{"class":708},"Pipeline",[698,1071,826],{"class":712},[698,1073,1074],{"class":700,"line":790},[698,1075,1077],{"emptyLinePlaceholder":1076},true,"\n",[698,1079,1080,1083,1085,1088,1090,1092,1094,1096,1098,1101,1103,1105,1108,1110,1113],{"class":700,"line":798},[698,1081,1082],{"class":712},"    cmds ",[698,1084,804],{"class":704},[698,1086,1087],{"class":708}," make",[698,1089,713],{"class":712},[698,1091,1040],{"class":704},[698,1093,1043],{"class":712},[698,1095,1034],{"class":704},[698,1097,1048],{"class":712},[698,1099,1100],{"class":704},"*",[698,1102,736],{"class":708},[698,1104,240],{"class":712},[698,1106,1107],{"class":708},"StringCmd",[698,1109,498],{"class":712},[698,1111,1112],{"class":708},"len",[698,1114,1115],{"class":712},"(ids))\n",[698,1117,1118,1120,1123,1125,1127],{"class":700,"line":829},[698,1119,793],{"class":704},[698,1121,1122],{"class":712}," _, id ",[698,1124,804],{"class":704},[698,1126,872],{"class":704},[698,1128,1129],{"class":712}," ids {\n",[698,1131,1132,1135,1137,1140,1143,1145,1149,1152,1155],{"class":700,"line":846},[698,1133,1134],{"class":712},"        key ",[698,1136,804],{"class":704},[698,1138,1139],{"class":712}," fmt.",[698,1141,1142],{"class":708},"Sprintf",[698,1144,713],{"class":712},[698,1146,1148],{"class":1147},"sU2Wk","\"course:",[698,1150,1151],{"class":816},"%d",[698,1153,1154],{"class":1147},":title\"",[698,1156,1157],{"class":712},", id)\n",[698,1159,1160,1163,1165,1168,1171],{"class":700,"line":855},[698,1161,1162],{"class":712},"        cmds[id] ",[698,1164,954],{"class":704},[698,1166,1167],{"class":712}," pipe.",[698,1169,1170],{"class":708},"Get",[698,1172,1173],{"class":712},"(ctx, key)\n",[698,1175,1176],{"class":700,"line":861},[698,1177,963],{"class":712},[698,1179,1180],{"class":700,"line":878},[698,1181,1077],{"emptyLinePlaceholder":1076},[698,1183,1184,1187,1189,1191,1194],{"class":700,"line":900},[698,1185,1186],{"class":712},"    _, err ",[698,1188,804],{"class":704},[698,1190,1167],{"class":712},[698,1192,1193],{"class":708},"Exec",[698,1195,1196],{"class":712},"(ctx)\n",[698,1198,1199,1202,1204,1206,1208,1211,1214,1217,1220],{"class":700,"line":908},[698,1200,1201],{"class":704},"    if",[698,1203,835],{"class":712},[698,1205,838],{"class":704},[698,1207,841],{"class":816},[698,1209,1210],{"class":704}," &&",[698,1212,1213],{"class":704}," !",[698,1215,1216],{"class":712},"errors.",[698,1218,1219],{"class":708},"Is",[698,1221,1222],{"class":712},"(err, redis.Nil) {\n",[698,1224,1225,1228,1230],{"class":700,"line":914},[698,1226,1227],{"class":704},"        return",[698,1229,841],{"class":816},[698,1231,1232],{"class":712},", err\n",[698,1234,1235],{"class":700,"line":919},[698,1236,963],{"class":712},[698,1238,1239],{"class":700,"line":935},[698,1240,1077],{"emptyLinePlaceholder":1076},[698,1242,1243,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266],{"class":700,"line":943},[698,1244,1245],{"class":712},"    result ",[698,1247,804],{"class":704},[698,1249,1087],{"class":708},[698,1251,713],{"class":712},[698,1253,1040],{"class":704},[698,1255,1043],{"class":712},[698,1257,1034],{"class":704},[698,1259,1048],{"class":712},[698,1261,762],{"class":704},[698,1263,498],{"class":712},[698,1265,1112],{"class":708},[698,1267,1115],{"class":712},[698,1269,1270,1272,1275,1277,1279],{"class":700,"line":948},[698,1271,793],{"class":704},[698,1273,1274],{"class":712}," id, cmd ",[698,1276,804],{"class":704},[698,1278,872],{"class":704},[698,1280,1281],{"class":712}," cmds {\n",[698,1283,1284,1287,1289,1292,1294],{"class":700,"line":960},[698,1285,1286],{"class":712},"        title, err ",[698,1288,804],{"class":704},[698,1290,1291],{"class":712}," cmd.",[698,1293,823],{"class":708},[698,1295,826],{"class":712},[698,1297,1298,1300,1303,1305],{"class":700,"line":966},[698,1299,832],{"class":704},[698,1301,1302],{"class":712}," errors.",[698,1304,1219],{"class":708},[698,1306,1222],{"class":712},[698,1308,1310],{"class":700,"line":1309},19,[698,1311,1312],{"class":704},"            continue\n",[698,1314,1316],{"class":700,"line":1315},20,[698,1317,858],{"class":712},[698,1319,1321,1323,1325,1327,1329],{"class":700,"line":1320},21,[698,1322,832],{"class":704},[698,1324,835],{"class":712},[698,1326,838],{"class":704},[698,1328,841],{"class":816},[698,1330,775],{"class":712},[698,1332,1334,1336,1338],{"class":700,"line":1333},22,[698,1335,849],{"class":704},[698,1337,841],{"class":816},[698,1339,1232],{"class":712},[698,1341,1343],{"class":700,"line":1342},23,[698,1344,858],{"class":712},[698,1346,1348,1351,1353],{"class":700,"line":1347},24,[698,1349,1350],{"class":712},"        result[id] ",[698,1352,954],{"class":704},[698,1354,1355],{"class":712}," title\n",[698,1357,1359],{"class":700,"line":1358},25,[698,1360,963],{"class":712},[698,1362,1364,1367,1370],{"class":700,"line":1363},26,[698,1365,1366],{"class":704},"    return",[698,1368,1369],{"class":712}," result, ",[698,1371,1372],{"class":816},"nil\n",[698,1374,1376],{"class":700,"line":1375},27,[698,1377,969],{"class":712},[15,1379,1380],{},"Pipeline не делает команды атомарными. Он уменьшает сетевые round-trip'ы. Для атомарности нужны Lua, transactions или одна Redis-команда, которая уже атомарна.",[41,1382],{},[44,1384,1386],{"id":1385},"вопросы-на-собеседовании","Вопросы на собеседовании",[185,1388,1389,1392,1395,1402,1411,1414,1417,1426,1429,1437],{},[188,1390,1391],{},"Как Redis удаляет ключи с истёкшим TTL?",[188,1393,1394],{},"Чем expiration отличается от eviction?",[188,1396,1397,1398,1401],{},"Что будет при ",[19,1399,1400],{},"maxmemory-policy noeviction","?",[188,1403,1404,1405,1407,1408,1410],{},"Почему ",[19,1406,291],{}," может быть лучше ",[19,1409,271],{}," для кеша?",[188,1412,1413],{},"Что такое big key и чем он опасен?",[188,1415,1416],{},"Что такое hot key и как его разгрузить?",[188,1418,1404,1419,1421,1422,1425],{},[19,1420,652],{}," безопаснее ",[19,1423,1424],{},"KEYS",", но всё равно не бесплатен?",[188,1427,1428],{},"Чем pipeline отличается от transaction?",[188,1430,1431,1432,22,1435,1401],{},"Что произойдёт с приложением при Redis ",[19,1433,1434],{},"OOM",[19,1436,261],{},[188,1438,1439],{},"Почему cache, locks и streams лучше разделять при разных требованиях к eviction?",[41,1441],{},[44,1443,1445],{"id":1444},"практика","Практика",[1447,1448,1449,1452,1455,1458,1461],"ol",{},[188,1450,1451],{},"Для кеша уроков выберите TTL, eviction policy и объясните, что произойдёт при нехватке памяти.",[188,1453,1454],{},"Найдите в своей модели Redis потенциальные big keys и предложите схему разбиения.",[188,1456,1457],{},"Напишите функцию на Go, которая через pipeline получает заголовки 20 уроков по slug.",[188,1459,1460],{},"Спроектируйте hot-key защиту для публичной конфигурации сайта, которую читают все запросы.",[188,1462,1463],{},"Для Redis в RateDesk оцените, какие ключи можно потерять при eviction, а какие нужно вынести в отдельный instance или вообще не хранить в Redis.",[41,1465],{},[44,1467,1469],{"id":1468},"интерактивная-практика","Интерактивная практика",[1471,1472,1476,1479,1498],"quiz",{"answer":1473,"id":1474,"xp":1475},"2","redis-memory-q1","10",[15,1477,1478],{},"Чем eviction отличается от expiration?",[1480,1481,1482],"template",{"v-slot:options":34},[185,1483,1484,1487,1490,1495],{},[188,1485,1486],{},"Eviction удаляет ключи только по TTL, expiration — только при нехватке памяти",[188,1488,1489],{},"Expiration удаляет ключи по TTL, eviction — при нехватке памяти и maxmemory policy",[188,1491,1492,1493],{},"Это одно и то же название для ",[19,1494,504],{},[188,1496,1497],{},"Eviction работает только для Streams",[1480,1499,1500],{"v-slot:explanation":34},[15,1501,1502,1503,240],{},"Expiration связана с TTL ключа. Eviction включается при давлении памяти и зависит от ",[19,1504,239],{},[1506,1507,1511,1514,1663],"predict",{"answer":1508,"id":1509,"xp":1510},"reject\\nevict","redis-memory-p1","15",[15,1512,1513],{},"Что выведет программа?",[1480,1515,1516],{"v-slot:code":34},[28,1517,1519],{"className":691,"code":1518,"language":693,"meta":34,"style":34},"package main\n\nimport \"fmt\"\n\nfunc memoryBehavior(policy string) string {\n    if policy == \"noeviction\" {\n        return \"reject\"\n    }\n    return \"evict\"\n}\n\nfunc main() {\n    fmt.Println(memoryBehavior(\"noeviction\"))\n    fmt.Println(memoryBehavior(\"allkeys-lfu\"))\n}\n",[19,1520,1521,1529,1533,1547,1551,1571,1585,1592,1596,1603,1607,1611,1621,1642,1659],{"__ignoreMap":34},[698,1522,1523,1526],{"class":700,"line":701},[698,1524,1525],{"class":704},"package",[698,1527,1528],{"class":708}," main\n",[698,1530,1531],{"class":700,"line":778},[698,1532,1077],{"emptyLinePlaceholder":1076},[698,1534,1535,1538,1541,1544],{"class":700,"line":790},[698,1536,1537],{"class":704},"import",[698,1539,1540],{"class":1147}," \"",[698,1542,1543],{"class":708},"fmt",[698,1545,1546],{"class":1147},"\"\n",[698,1548,1549],{"class":700,"line":798},[698,1550,1077],{"emptyLinePlaceholder":1076},[698,1552,1553,1555,1558,1560,1563,1565,1567,1569],{"class":700,"line":829},[698,1554,705],{"class":704},[698,1556,1557],{"class":708}," memoryBehavior",[698,1559,713],{"class":712},[698,1561,1562],{"class":716},"policy",[698,1564,749],{"class":704},[698,1566,765],{"class":712},[698,1568,762],{"class":704},[698,1570,775],{"class":712},[698,1572,1573,1575,1578,1580,1583],{"class":700,"line":846},[698,1574,1201],{"class":704},[698,1576,1577],{"class":712}," policy ",[698,1579,927],{"class":704},[698,1581,1582],{"class":1147}," \"noeviction\"",[698,1584,775],{"class":712},[698,1586,1587,1589],{"class":700,"line":855},[698,1588,1227],{"class":704},[698,1590,1591],{"class":1147}," \"reject\"\n",[698,1593,1594],{"class":700,"line":861},[698,1595,963],{"class":712},[698,1597,1598,1600],{"class":700,"line":878},[698,1599,1366],{"class":704},[698,1601,1602],{"class":1147}," \"evict\"\n",[698,1604,1605],{"class":700,"line":900},[698,1606,969],{"class":712},[698,1608,1609],{"class":700,"line":908},[698,1610,1077],{"emptyLinePlaceholder":1076},[698,1612,1613,1615,1618],{"class":700,"line":914},[698,1614,705],{"class":704},[698,1616,1617],{"class":708}," main",[698,1619,1620],{"class":712},"() {\n",[698,1622,1623,1626,1629,1631,1634,1636,1639],{"class":700,"line":919},[698,1624,1625],{"class":712},"    fmt.",[698,1627,1628],{"class":708},"Println",[698,1630,713],{"class":712},[698,1632,1633],{"class":708},"memoryBehavior",[698,1635,713],{"class":712},[698,1637,1638],{"class":1147},"\"noeviction\"",[698,1640,1641],{"class":712},"))\n",[698,1643,1644,1646,1648,1650,1652,1654,1657],{"class":700,"line":935},[698,1645,1625],{"class":712},[698,1647,1628],{"class":708},[698,1649,713],{"class":712},[698,1651,1633],{"class":708},[698,1653,713],{"class":712},[698,1655,1656],{"class":1147},"\"allkeys-lfu\"",[698,1658,1641],{"class":712},[698,1660,1661],{"class":700,"line":943},[698,1662,969],{"class":712},[1480,1664,1665],{"v-slot:hint":34},[15,1666,1667,1668,1670],{},"При ",[19,1669,261],{}," Redis начинает отклонять записи, а eviction policies удаляют подходящие ключи.",[1672,1673,1677,1691,1820],"code-task",{"expected":1674,"id":1675,"xp":1676},"avoid\\nok\\nuse-unlink","redis-memory-ct1","20",[15,1678,1679,1680,542,1683,1685,1686,1688,1689,240],{},"Реализуй ",[19,1681,1682],{},"CommandReview",[19,1684,1424],{}," избегаем, ",[19,1687,652],{}," допустим для bounded iteration, удаление большого ключа лучше делать через ",[19,1690,538],{},[1480,1692,1693],{"v-slot:template":34},[28,1694,1696],{"className":691,"code":1695,"language":693,"meta":34,"style":34},"package main\n\nimport \"fmt\"\n\nfunc CommandReview(command string) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(CommandReview(\"KEYS\"))\n    fmt.Println(CommandReview(\"SCAN\"))\n    fmt.Println(CommandReview(\"DEL-big\"))\n}\n",[19,1697,1698,1704,1708,1718,1722,1742,1749,1753,1757,1765,1782,1799,1816],{"__ignoreMap":34},[698,1699,1700,1702],{"class":700,"line":701},[698,1701,1525],{"class":704},[698,1703,1528],{"class":708},[698,1705,1706],{"class":700,"line":778},[698,1707,1077],{"emptyLinePlaceholder":1076},[698,1709,1710,1712,1714,1716],{"class":700,"line":790},[698,1711,1537],{"class":704},[698,1713,1540],{"class":1147},[698,1715,1543],{"class":708},[698,1717,1546],{"class":1147},[698,1719,1720],{"class":700,"line":798},[698,1721,1077],{"emptyLinePlaceholder":1076},[698,1723,1724,1726,1729,1731,1734,1736,1738,1740],{"class":700,"line":829},[698,1725,705],{"class":704},[698,1727,1728],{"class":708}," CommandReview",[698,1730,713],{"class":712},[698,1732,1733],{"class":716},"command",[698,1735,749],{"class":704},[698,1737,765],{"class":712},[698,1739,762],{"class":704},[698,1741,775],{"class":712},[698,1743,1744,1746],{"class":700,"line":846},[698,1745,1366],{"class":704},[698,1747,1748],{"class":1147}," \"todo\"\n",[698,1750,1751],{"class":700,"line":855},[698,1752,969],{"class":712},[698,1754,1755],{"class":700,"line":861},[698,1756,1077],{"emptyLinePlaceholder":1076},[698,1758,1759,1761,1763],{"class":700,"line":878},[698,1760,705],{"class":704},[698,1762,1617],{"class":708},[698,1764,1620],{"class":712},[698,1766,1767,1769,1771,1773,1775,1777,1780],{"class":700,"line":900},[698,1768,1625],{"class":712},[698,1770,1628],{"class":708},[698,1772,713],{"class":712},[698,1774,1682],{"class":708},[698,1776,713],{"class":712},[698,1778,1779],{"class":1147},"\"KEYS\"",[698,1781,1641],{"class":712},[698,1783,1784,1786,1788,1790,1792,1794,1797],{"class":700,"line":908},[698,1785,1625],{"class":712},[698,1787,1628],{"class":708},[698,1789,713],{"class":712},[698,1791,1682],{"class":708},[698,1793,713],{"class":712},[698,1795,1796],{"class":1147},"\"SCAN\"",[698,1798,1641],{"class":712},[698,1800,1801,1803,1805,1807,1809,1811,1814],{"class":700,"line":914},[698,1802,1625],{"class":712},[698,1804,1628],{"class":708},[698,1806,713],{"class":712},[698,1808,1682],{"class":708},[698,1810,713],{"class":712},[698,1812,1813],{"class":1147},"\"DEL-big\"",[698,1815,1641],{"class":712},[698,1817,1818],{"class":700,"line":919},[698,1819,969],{"class":712},[1480,1821,1822],{"v-slot:hints":34},[185,1823,1824,1829,1834],{},[188,1825,1826,1828],{},[19,1827,1424],{}," блокирует Redis на обход всего keyspace.",[188,1830,1831,1833],{},[19,1832,652],{}," итеративный, но его всё равно нужно лимитировать.",[188,1835,1836,1838,1839,240],{},[19,1837,538],{}," удаляет key асинхроннее, чем тяжёлый ",[19,1840,504],{},[1842,1843,1844],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}",{"title":34,"searchDepth":778,"depth":778,"links":1846},[1847,1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858],{"id":46,"depth":778,"text":47},{"id":103,"depth":778,"text":104},{"id":229,"depth":778,"text":230},{"id":373,"depth":778,"text":374},{"id":415,"depth":778,"text":416},{"id":465,"depth":778,"text":466},{"id":579,"depth":778,"text":580},{"id":640,"depth":778,"text":641},{"id":977,"depth":778,"text":978},{"id":1385,"depth":778,"text":1386},{"id":1444,"depth":778,"text":1445},{"id":1468,"depth":778,"text":1469},1781022066804]