[{"data":1,"prerenderedAt":1563},["ShallowReactive",2],{"content:\u002F09-redis\u002F02-data-structures-commands":3},{"title":4,"description":5,"path":6,"body":7},"Redis data structures и моделирование данных","В Redis проектирование начинается не с таблиц, а с операций. Нужно спросить: какие запросы будут частыми, какие должны быть атомарными, какие данные можно хранить с TTL, а какие нельзя потерять.","\u002F09-redis\u002F02-data-structures-commands",{"type":8,"value":9,"toc":1541},"minimark",[10,14,17,20,31,34,37,42,48,54,57,90,93,174,177,179,183,188,191,282,285,291,294,296,300,303,309,393,396,402,405,407,411,414,420,492,495,497,501,504,510,593,596,602,605,607,611,614,620,706,709,723,729,731,735,738,744,747,753,756,770,773,775,779,782,788,791,797,800,802,806,809,815,818,820,824,827,833,836,838,842,845,859,862,868,871,875,886,903,906,910,913,919,922,943,945,949,1038,1049,1065,1067,1071,1109,1111,1115,1133,1135,1139,1173,1365,1537],[11,12,4],"h1",{"id":13},"redis-data-structures-и-моделирование-данных",[15,16,5],"p",{},[15,18,19],{},"Плохая модель Redis обычно выглядит так:",[21,22,28],"pre",{"className":23,"code":25,"language":26,"meta":27},[24],"language-text","key: user:42\nvalue: огромный JSON со всем профилем, настройками, прогрессом и статистикой\n","text","",[29,30,25],"code",{"__ignoreMap":27},[15,32,33],{},"Любое изменение требует прочитать весь JSON, распарсить, изменить поле, сериализовать обратно и записать. Хорошая модель использует структуры Redis так, чтобы команда меняла ровно нужную часть данных.",[35,36],"hr",{},[38,39,41],"h2",{"id":40},"ключи-и-naming-convention","Ключи и naming convention",[15,43,44,45,47],{},"Redis не знает схемы, но схему должны знать вы. Обычно ключи строят через ",[29,46,47],{},":",[21,49,52],{"className":50,"code":51,"language":26,"meta":27},[24],"user:42\nuser:42:settings\ncourse:go-basics:lessons\nlesson:redis-overview:views\nrate:user:42:login:2026-04-30T10:35\n",[29,53,51],{"__ignoreMap":27},[15,55,56],{},"Правила:",[58,59,60,64,67,70,73,80,87],"ul",{},[61,62,63],"li",{},"включайте тип сущности и идентификатор;",[61,65,66],{},"не делайте ключи слишком длинными, но и не экономьте до нечитабельности;",[61,68,69],{},"для multi-tenant систем добавляйте tenant prefix;",[61,71,72],{},"держите TTL-ключи отдельно от долговременных;",[61,74,75,76,79],{},"не полагайтесь на возможность \"найти всё по prefix\" через ",[29,77,78],{},"KEYS",".",[61,81,82,83,86],{},"версионируйте ключи или payload, если формат меняется: ",[29,84,85],{},"course:v2:42",";",[61,88,89],{},"документируйте owner ключа: какой use case пишет, кто читает, кто инвалидирует.",[15,91,92],{},"Минимальный key contract для production обычно включает:",[94,95,96,109],"table",{},[97,98,99],"thead",{},[100,101,102,106],"tr",{},[103,104,105],"th",{},"Поле",[103,107,108],{},"Пример",[110,111,112,123,131,139,150,158,166],"tbody",{},[100,113,114,118],{},[115,116,117],"td",{},"key pattern",[115,119,120],{},[29,121,122],{},"ratedesk:rate:{pair}:latest",[100,124,125,128],{},[115,126,127],{},"type",[115,129,130],{},"String или Hash",[100,132,133,136],{},[115,134,135],{},"TTL",[115,137,138],{},"5 минут плюс jitter",[100,140,141,144],{},[115,142,143],{},"source of truth",[115,145,146,147],{},"PostgreSQL ",[29,148,149],{},"rates",[100,151,152,155],{},[115,153,154],{},"invalidation",[115,156,157],{},"delete\u002Fupdate после commit обновления курса",[100,159,160,163],{},[115,161,162],{},"outage policy",[115,164,165],{},"miss\u002Fread Redis error -> читать source of truth",[100,167,168,171],{},[115,169,170],{},"max expected size",[115,172,173],{},"до 2 KB payload, не список истории",[15,175,176],{},"Такой контракт не заменяет проектное задание, но помогает ревьюеру понять, что ключ не появился случайно в середине handler'а.",[35,178],{},[38,180,182],{"id":181},"структуры-данных","Структуры данных",[184,185,187],"h3",{"id":186},"string","String",[15,189,190],{},"String - самый универсальный тип. Это byte array до 512 MB, но на практике такие размеры почти всегда ошибка.",[94,192,193,206],{},[97,194,195],{},[100,196,197,200,203],{},[103,198,199],{},"Команда",[103,201,202],{},"Смысл",[103,204,205],{},"Сложность",[110,207,208,223,239,253,268],{},[100,209,210,215,218],{},[115,211,212],{},[29,213,214],{},"GET key",[115,216,217],{},"получить значение",[115,219,220],{},[29,221,222],{},"O(1)",[100,224,225,233,236],{},[115,226,227,228,232],{},"`SET key value ",[229,230,231],"span",{},"EX seconds"," [NX",[115,234,235],{},"XX]`",[115,237,238],{},"записать значение",[100,240,241,246,249],{},[115,242,243],{},[29,244,245],{},"INCR key",[115,247,248],{},"увеличить integer",[115,250,251],{},[29,252,222],{},[100,254,255,260,263],{},[115,256,257],{},[29,258,259],{},"MGET key...",[115,261,262],{},"получить несколько ключей",[115,264,265],{},[29,266,267],{},"O(N)",[100,269,270,275,278],{},[115,271,272],{},[29,273,274],{},"SETNX key value",[115,276,277],{},"записать только если нет ключа",[115,279,280],{},[29,281,222],{},[15,283,284],{},"Примеры:",[21,286,289],{"className":287,"code":288,"language":26,"meta":27},[24],"SETEX lesson:redis-overview:html 600 \"\u003Ch1>...\u003C\u002Fh1>\"\nINCR lesson:redis-overview:views\nSET lock:billing:42 token-abc NX PX 10000\n",[29,290,288],{"__ignoreMap":27},[15,292,293],{},"String подходит для кеша сериализованных объектов, счётчиков, feature flags, tokens. Если нужно часто менять отдельные поля объекта, лучше смотреть на Hash.",[35,295],{},[184,297,299],{"id":298},"hash","Hash",[15,301,302],{},"Hash хранит набор field-value внутри одного ключа.",[21,304,307],{"className":305,"code":306,"language":26,"meta":27},[24],"HSET user:42 name \"Pavel\" role \"student\" timezone \"Europe\u002FMoscow\"\nHGET user:42 name\nHINCRBY user:42 solved_tasks 1\n",[29,308,306],{"__ignoreMap":27},[94,310,311,321],{},[97,312,313],{},[100,314,315,317,319],{},[103,316,199],{},[103,318,202],{},[103,320,205],{},[110,322,323,337,351,365,379],{},[100,324,325,330,333],{},[115,326,327],{},[29,328,329],{},"HGET key field",[115,331,332],{},"получить поле",[115,334,335],{},[29,336,222],{},[100,338,339,344,347],{},[115,340,341],{},[29,342,343],{},"HSET key field value",[115,345,346],{},"записать поле",[115,348,349],{},[29,350,222],{},[100,352,353,358,361],{},[115,354,355],{},[29,356,357],{},"HMGET key f1 f2",[115,359,360],{},"получить несколько полей",[115,362,363],{},[29,364,267],{},[100,366,367,372,375],{},[115,368,369],{},[29,370,371],{},"HGETALL key",[115,373,374],{},"получить весь hash",[115,376,377],{},[29,378,267],{},[100,380,381,386,389],{},[115,382,383],{},[29,384,385],{},"HINCRBY key field n",[115,387,388],{},"увеличить числовое поле",[115,390,391],{},[29,392,222],{},[15,394,395],{},"Hash удобен для объектов среднего размера:",[21,397,400],{"className":398,"code":399,"language":26,"meta":27},[24],"user:42\n  id=42\n  name=Pavel\n  avatar_url=https:\u002F\u002F...\n  solved_tasks=17\n",[29,401,399],{"__ignoreMap":27},[15,403,404],{},"Но не нужно складывать в один hash миллион полей. Большой hash становится big key: он плохо удаляется, реплицируется и может создавать задержки.",[35,406],{},[184,408,410],{"id":409},"list","List",[15,412,413],{},"List - двусвязный список строк. Быстрые операции по краям:",[21,415,418],{"className":416,"code":417,"language":26,"meta":27},[24],"LPUSH notifications:user:42 \"new-comment\"\nLTRIM notifications:user:42 0 99\nLRANGE notifications:user:42 0 9\n",[29,419,417],{"__ignoreMap":27},[94,421,422,432],{},[97,423,424],{},[100,425,426,428,430],{},[103,427,199],{},[103,429,202],{},[103,431,205],{},[110,433,434,448,462,477],{},[100,435,436,441,444],{},[115,437,438],{},[29,439,440],{},"LPUSH\u002FRPUSH",[115,442,443],{},"добавить слева\u002Fсправа",[115,445,446],{},[29,447,222],{},[100,449,450,455,458],{},[115,451,452],{},[29,453,454],{},"LPOP\u002FRPOP",[115,456,457],{},"удалить слева\u002Fсправа",[115,459,460],{},[29,461,222],{},[100,463,464,469,472],{},[115,465,466],{},[29,467,468],{},"LRANGE key start stop",[115,470,471],{},"диапазон",[115,473,474],{},[29,475,476],{},"O(S+N)",[100,478,479,484,487],{},[115,480,481],{},[29,482,483],{},"BLPOP key timeout",[115,485,486],{},"blocking pop",[115,488,489],{},[29,490,491],{},"O(N keys)",[15,493,494],{},"List подходит для простых очередей и \"последних N элементов\". Но для событий с подтверждением обработки лучше использовать Streams: List не хранит consumer group state и pending entries.",[35,496],{},[184,498,500],{"id":499},"set","Set",[15,502,503],{},"Set - неупорядоченное множество уникальных строк.",[21,505,508],{"className":506,"code":507,"language":26,"meta":27},[24],"SADD course:redis:students user:42 user:100\nSISMEMBER course:redis:students user:42\nSCARD course:redis:students\n",[29,509,507],{"__ignoreMap":27},[94,511,512,522],{},[97,513,514],{},[100,515,516,518,520],{},[103,517,199],{},[103,519,202],{},[103,521,205],{},[110,523,524,538,552,566,580],{},[100,525,526,531,534],{},[115,527,528],{},[29,529,530],{},"SADD",[115,532,533],{},"добавить элементы",[115,535,536],{},[29,537,267],{},[100,539,540,545,548],{},[115,541,542],{},[29,543,544],{},"SREM",[115,546,547],{},"удалить элементы",[115,549,550],{},[29,551,267],{},[100,553,554,559,562],{},[115,555,556],{},[29,557,558],{},"SISMEMBER",[115,560,561],{},"проверить наличие",[115,563,564],{},[29,565,222],{},[100,567,568,573,576],{},[115,569,570],{},[29,571,572],{},"SMEMBERS",[115,574,575],{},"получить все",[115,577,578],{},[29,579,267],{},[100,581,582,587,590],{},[115,583,584],{},[29,585,586],{},"SINTER\u002FSUNION\u002FSDIFF",[115,588,589],{},"операции множеств",[115,591,592],{},"зависит от размеров",[15,594,595],{},"Set хорошо моделирует связи \"пользователь состоит в группе\", \"урок отмечен избранным\", \"роль назначена пользователю\".",[21,597,600],{"className":598,"code":599,"language":26,"meta":27},[24],"user:42:favorites = {lesson:redis-overview, lesson:docker-basics}\nlesson:redis-overview:liked_by = {user:42, user:77}\n",[29,601,599],{"__ignoreMap":27},[15,603,604],{},"Если нужен порядок или score, используйте Sorted Set.",[35,606],{},[184,608,610],{"id":609},"sorted-set","Sorted Set",[15,612,613],{},"Sorted Set хранит уникальные members и числовой score. Элементы упорядочены по score.",[21,615,618],{"className":616,"code":617,"language":26,"meta":27},[24],"ZADD leaderboard:weekly 150 user:42\nZINCRBY leaderboard:weekly 10 user:42\nZREVRANGE leaderboard:weekly 0 9 WITHSCORES\nZRANK leaderboard:weekly user:42\n",[29,619,617],{"__ignoreMap":27},[94,621,622,632],{},[97,623,624],{},[100,625,626,628,630],{},[103,627,199],{},[103,629,202],{},[103,631,205],{},[110,633,634,649,663,678,692],{},[100,635,636,641,644],{},[115,637,638],{},[29,639,640],{},"ZADD",[115,642,643],{},"добавить\u002Fобновить score",[115,645,646],{},[29,647,648],{},"O(log N)",[100,650,651,656,659],{},[115,652,653],{},[29,654,655],{},"ZINCRBY",[115,657,658],{},"увеличить score",[115,660,661],{},[29,662,648],{},[100,664,665,670,673],{},[115,666,667],{},[29,668,669],{},"ZRANGE\u002FZREVRANGE",[115,671,672],{},"диапазон по рангу",[115,674,675],{},[29,676,677],{},"O(log N + M)",[100,679,680,685,688],{},[115,681,682],{},[29,683,684],{},"ZRANGEBYSCORE",[115,686,687],{},"диапазон по score",[115,689,690],{},[29,691,677],{},[100,693,694,699,702],{},[115,695,696],{},[29,697,698],{},"ZREM",[115,700,701],{},"удалить",[115,703,704],{},[29,705,648],{},[15,707,708],{},"Сценарии:",[58,710,711,714,717,720],{},[61,712,713],{},"leaderboard;",[61,715,716],{},"топ популярных уроков;",[61,718,719],{},"delayed jobs, где score - timestamp;",[61,721,722],{},"sliding window rate limit, где score - время запроса.",[21,724,727],{"className":725,"code":726,"language":26,"meta":27},[24],"rate:user:42\n  score=1714471200123 member=req-1\n  score=1714471201440 member=req-2\n",[29,728,726],{"__ignoreMap":27},[35,730],{},[184,732,734],{"id":733},"streams","Streams",[15,736,737],{},"Stream - append-only структура для событий. Каждый entry имеет ID и набор полей.",[21,739,742],{"className":740,"code":741,"language":26,"meta":27},[24],"XADD submissions * user_id 42 lesson redis-overview status accepted\nXREAD COUNT 10 STREAMS submissions 0\n",[29,743,741],{"__ignoreMap":27},[15,745,746],{},"С consumer groups:",[21,748,751],{"className":749,"code":750,"language":26,"meta":27},[24],"XGROUP CREATE submissions workers $ MKSTREAM\nXREADGROUP GROUP workers worker-1 COUNT 10 BLOCK 5000 STREAMS submissions >\nXACK submissions workers 1714471200000-0\n",[29,752,750],{"__ignoreMap":27},[15,754,755],{},"Streams дают:",[58,757,758,761,764,767],{},[61,759,760],{},"хранение событий в Redis;",[61,762,763],{},"чтение несколькими consumer'ами;",[61,765,766],{},"pending entries для сообщений, которые выданы, но не подтверждены;",[61,768,769],{},"replay старых сообщений в пределах retention.",[15,771,772],{},"Это не Kafka, но для локальных backend-задач и небольших event pipelines Streams часто достаточно.",[35,774],{},[184,776,778],{"id":777},"bitmap","Bitmap",[15,780,781],{},"Bitmap - операции над битами внутри String. Удобно для компактных boolean-флагов.",[21,783,786],{"className":784,"code":785,"language":26,"meta":27},[24],"SETBIT attendance:2026-04-30 42 1\nGETBIT attendance:2026-04-30 42\nBITCOUNT attendance:2026-04-30\n",[29,787,785],{"__ignoreMap":27},[15,789,790],{},"Если user ID плотные и числовые, можно хранить посещаемость или активность очень компактно:",[21,792,795],{"className":793,"code":794,"language":26,"meta":27},[24],"1_000_000 пользователей = примерно 125 KB на один день активности\n",[29,796,794],{"__ignoreMap":27},[15,798,799],{},"Недостаток: sparse ID вроде UUID плохо подходят, потому что индекс должен быть integer offset.",[35,801],{},[184,803,805],{"id":804},"hyperloglog","HyperLogLog",[15,807,808],{},"HyperLogLog даёт приближённое количество уникальных элементов с маленькой памятью.",[21,810,813],{"className":811,"code":812,"language":26,"meta":27},[24],"PFADD unique:lesson:redis-overview user:42 user:77\nPFCOUNT unique:lesson:redis-overview\nPFMERGE unique:course:redis unique:lesson:1 unique:lesson:2\n",[29,814,812],{"__ignoreMap":27},[15,816,817],{},"Сценарий: \"сколько уникальных пользователей открыли урок\". Если точность до одного пользователя критична, нужен Set или основная БД. Если нужна оценка с небольшой погрешностью - HyperLogLog экономит память.",[35,819],{},[184,821,823],{"id":822},"geospatial","Geospatial",[15,825,826],{},"Redis умеет хранить координаты и искать объекты рядом:",[21,828,831],{"className":829,"code":830,"language":26,"meta":27},[24],"GEOADD mentors:geo 37.6173 55.7558 mentor:moscow\nGEOSEARCH mentors:geo FROMLONLAT 37.6 55.7 BYRADIUS 10 km WITHDIST\n",[29,832,830],{"__ignoreMap":27},[15,834,835],{},"Под капотом используется Sorted Set с geohash-like score. Это удобно для простого nearby search, но не заменяет полноценную гео-БД, если нужны сложные полигоны, маршруты и геоаналитика.",[35,837],{},[38,839,841],{"id":840},"моделирование-объект-или-индексы","Моделирование: объект или индексы",[15,843,844],{},"Допустим, есть курс и нужно быстро:",[58,846,847,850,853,856],{},[61,848,849],{},"получить карточку курса;",[61,851,852],{},"узнать количество студентов;",[61,854,855],{},"показать топ студентов;",[61,857,858],{},"проверить, записан ли пользователь.",[15,860,861],{},"Модель может быть такой:",[21,863,866],{"className":864,"code":865,"language":26,"meta":27},[24],"course:redis                         Hash    title, level, description\ncourse:redis:students                Set     user ids\ncourse:redis:leaderboard             ZSet    user id -> points\ncourse:redis:stats                   Hash    views, completed_count\n",[29,867,865],{"__ignoreMap":27},[15,869,870],{},"Так Redis хранит не \"таблицу course\", а несколько структур под конкретные операции. Это нормально, но увеличивает ответственность приложения: нужно поддерживать согласованность между ключами.",[184,872,874],{"id":873},"согласованность-между-ключами","Согласованность между ключами",[15,876,877,878,881,882,885],{},"Redis не поддерживает foreign keys и не знает, что ",[29,879,880],{},"course:redis:students"," связан с ",[29,883,884],{},"course:redis:leaderboard",". Если запись меняет несколько структур, нужно выбрать стратегию:",[58,887,888,891,897,900],{},[61,889,890],{},"одна атомарная команда, если структура позволяет;",[61,892,893,896],{},[29,894,895],{},"MULTI\u002FEXEC"," или Lua, если нужно обновить несколько ключей вместе;",[61,898,899],{},"eventual consistency с периодическим repair job, если небольшая рассинхронизация допустима;",[61,901,902],{},"source of truth в PostgreSQL и Redis как производный read model\u002Fcache.",[15,904,905],{},"Для кешей чаще всего выигрывает последний вариант: Redis-ключ можно удалить и восстановить из основной БД. Для счётчиков и рейтингов нужно заранее решить, что происходит при потере последних секунд данных, eviction или failover.",[184,907,909],{"id":908},"cardinality-и-tenant-boundaries","Cardinality и tenant boundaries",[15,911,912],{},"Проектируя ключи, оценивайте не только один пример, а количество ключей и элементов:",[21,914,917],{"className":915,"code":916,"language":26,"meta":27},[24],"tenants * users * courses * days = потенциальный key count\n",[29,918,916],{"__ignoreMap":27},[15,920,921],{},"Ошибки, которые часто всплывают поздно:",[58,923,924,927,930,933,936],{},[61,925,926],{},"ключ на каждый request без TTL;",[61,928,929],{},"tenant prefix забыли, и данные разных клиентов смешались;",[61,931,932],{},"Set растёт годами без retention;",[61,934,935],{},"ZSet использует user id как label в метриках или как бесконечный high-cardinality namespace;",[61,937,938,939,942],{},"в Redis Cluster все ключи получили один hash tag вроде ",[29,940,941],{},"{global}"," и перегрели один slot.",[35,944],{},[38,946,948],{"id":947},"команды-которые-требуют-осторожности","Команды, которые требуют осторожности",[94,950,951,960],{},[97,952,953],{},[100,954,955,957],{},[103,956,199],{},[103,958,959],{},"Почему опасна",[110,961,962,972,982,992,1002,1012,1027],{},[100,963,964,969],{},[115,965,966],{},[29,967,968],{},"KEYS pattern",[115,970,971],{},"блокирует event loop при большом keyspace",[100,973,974,979],{},[115,975,976],{},[29,977,978],{},"SMEMBERS huge_set",[115,980,981],{},"возвращает всё множество целиком",[100,983,984,989],{},[115,985,986],{},[29,987,988],{},"HGETALL huge_hash",[115,990,991],{},"возвращает все поля большого hash",[100,993,994,999],{},[115,995,996],{},[29,997,998],{},"LRANGE key 0 -1",[115,1000,1001],{},"может вытащить огромный список",[100,1003,1004,1009],{},[115,1005,1006],{},[29,1007,1008],{},"DEL big_key",[115,1010,1011],{},"удаление большого ключа может занять заметное время",[100,1013,1014,1024],{},[115,1015,1016,1019,1020,1023],{},[29,1017,1018],{},"SORT",", ",[29,1021,1022],{},"SUNION"," по большим множествам",[115,1025,1026],{},"CPU и память на одну команду могут заблокировать соседей",[100,1028,1029,1035],{},[115,1030,1031,1034],{},[29,1032,1033],{},"XREAD"," без retention-плана",[115,1036,1037],{},"stream может расти без ограничения",[15,1039,1040,1041,1044,1045,1048],{},"В production обычно используют ",[29,1042,1043],{},"SCAN",", лимитированные диапазоны, ",[29,1046,1047],{},"UNLINK"," для асинхронного удаления и метрики big keys.",[15,1050,1051,1052,1019,1055,1019,1058,1019,1061,1064],{},"Отдельная ловушка - administrative команды в приложении. ",[29,1053,1054],{},"FLUSHALL",[29,1056,1057],{},"CONFIG",[29,1059,1060],{},"DEBUG",[29,1062,1063],{},"MONITOR"," и похожие команды не должны быть доступны runtime-пользователю сервиса через ACL.",[35,1066],{},[38,1068,1070],{"id":1069},"вопросы-на-собеседовании","Вопросы на собеседовании",[58,1072,1073,1076,1079,1085,1091,1094,1097,1100,1103,1106],{},[61,1074,1075],{},"Чем Redis Hash отличается от JSON в String?",[61,1077,1078],{},"Когда выбрать Set, а когда Sorted Set?",[61,1080,1081,1082,1084],{},"Почему ",[29,1083,572],{}," может быть проблемой?",[61,1086,1087,1088,1090],{},"Какая сложность у ",[29,1089,640],{}," и почему leaderboard обычно делают на Sorted Set?",[61,1092,1093],{},"Для чего нужен HyperLogLog и чем он хуже Set?",[61,1095,1096],{},"Почему List не всегда хорошая очередь?",[61,1098,1099],{},"Что такое Stream consumer group?",[61,1101,1102],{},"Как бы вы смоделировали лайки, подписки и топ пользователей в Redis?",[61,1104,1105],{},"Что должно быть в key contract перед добавлением Redis-ключа в production?",[61,1107,1108],{},"Как понять, что выбранная модель создаст big key или hotspot?",[35,1110],{},[38,1112,1114],{"id":1113},"практика","Практика",[1116,1117,1118,1121,1124,1127,1130],"ol",{},[61,1119,1120],{},"Спроектируйте Redis-ключи для прогресса пользователя по курсу: завершённые уроки, текущий lesson slug, количество решённых задач, дата последней активности.",[61,1122,1123],{},"Выберите типы Redis для задач: уникальные просмотры урока, последние 20 уведомлений, рейтинг недели, поиск менторов рядом с городом.",[61,1125,1126],{},"Для каждого ключа из практики укажите команды чтения и записи, а также команды, которых стоит избегать при росте данных.",[61,1128,1129],{},"Оцените, какие ключи могут стать big keys, и предложите способ разбиения.",[61,1131,1132],{},"Для одного ключа RateDesk опишите owner, TTL, source of truth, invalidation и max expected size.",[35,1134],{},[38,1136,1138],{"id":1137},"интерактивная-практика","Интерактивная практика",[1140,1141,1145,1148,1161],"quiz",{"answer":1142,"id":1143,"xp":1144},"3","redis-structures-q1","10",[15,1146,1147],{},"Какую структуру Redis чаще всего выбирают для leaderboard с score?",[1149,1150,1151],"template",{"v-slot:options":27},[58,1152,1153,1155,1157,1159],{},[61,1154,187],{},[61,1156,299],{},[61,1158,610],{},[61,1160,805],{},[1149,1162,1163],{"v-slot:explanation":27},[15,1164,1165,1166,1169,1170,79],{},"Sorted Set хранит member и score, умеет быстро обновлять рейтинг и получать диапазоны через ",[29,1167,1168],{},"ZRANGE","\u002F",[29,1171,1172],{},"ZREVRANGE",[1174,1175,1179,1182,1360],"predict",{"answer":1176,"id":1177,"xp":1178},"danger\\nbounded","redis-structures-p1","15",[15,1180,1181],{},"Что выведет программа?",[1149,1183,1184],{"v-slot:code":27},[21,1185,1189],{"className":1186,"code":1187,"language":1188,"meta":27,"style":27},"language-go shiki shiki-themes github-dark","package main\n\nimport \"fmt\"\n\nfunc readPattern(fullKeyspace bool) string {\n    if fullKeyspace {\n        return \"danger\"\n    }\n    return \"bounded\"\n}\n\nfunc main() {\n    fmt.Println(readPattern(true))\n    fmt.Println(readPattern(false))\n}\n","go",[29,1190,1191,1203,1210,1226,1231,1259,1268,1277,1283,1292,1298,1303,1314,1337,1355],{"__ignoreMap":27},[229,1192,1195,1199],{"class":1193,"line":1194},"line",1,[229,1196,1198],{"class":1197},"snl16","package",[229,1200,1202],{"class":1201},"svObZ"," main\n",[229,1204,1206],{"class":1193,"line":1205},2,[229,1207,1209],{"emptyLinePlaceholder":1208},true,"\n",[229,1211,1213,1216,1220,1223],{"class":1193,"line":1212},3,[229,1214,1215],{"class":1197},"import",[229,1217,1219],{"class":1218},"sU2Wk"," \"",[229,1221,1222],{"class":1201},"fmt",[229,1224,1225],{"class":1218},"\"\n",[229,1227,1229],{"class":1193,"line":1228},4,[229,1230,1209],{"emptyLinePlaceholder":1208},[229,1232,1234,1237,1240,1244,1248,1251,1254,1256],{"class":1193,"line":1233},5,[229,1235,1236],{"class":1197},"func",[229,1238,1239],{"class":1201}," readPattern",[229,1241,1243],{"class":1242},"s95oV","(",[229,1245,1247],{"class":1246},"s9osk","fullKeyspace",[229,1249,1250],{"class":1197}," bool",[229,1252,1253],{"class":1242},") ",[229,1255,186],{"class":1197},[229,1257,1258],{"class":1242}," {\n",[229,1260,1262,1265],{"class":1193,"line":1261},6,[229,1263,1264],{"class":1197},"    if",[229,1266,1267],{"class":1242}," fullKeyspace {\n",[229,1269,1271,1274],{"class":1193,"line":1270},7,[229,1272,1273],{"class":1197},"        return",[229,1275,1276],{"class":1218}," \"danger\"\n",[229,1278,1280],{"class":1193,"line":1279},8,[229,1281,1282],{"class":1242},"    }\n",[229,1284,1286,1289],{"class":1193,"line":1285},9,[229,1287,1288],{"class":1197},"    return",[229,1290,1291],{"class":1218}," \"bounded\"\n",[229,1293,1295],{"class":1193,"line":1294},10,[229,1296,1297],{"class":1242},"}\n",[229,1299,1301],{"class":1193,"line":1300},11,[229,1302,1209],{"emptyLinePlaceholder":1208},[229,1304,1306,1308,1311],{"class":1193,"line":1305},12,[229,1307,1236],{"class":1197},[229,1309,1310],{"class":1201}," main",[229,1312,1313],{"class":1242},"() {\n",[229,1315,1317,1320,1323,1325,1328,1330,1334],{"class":1193,"line":1316},13,[229,1318,1319],{"class":1242},"    fmt.",[229,1321,1322],{"class":1201},"Println",[229,1324,1243],{"class":1242},[229,1326,1327],{"class":1201},"readPattern",[229,1329,1243],{"class":1242},[229,1331,1333],{"class":1332},"sDLfK","true",[229,1335,1336],{"class":1242},"))\n",[229,1338,1340,1342,1344,1346,1348,1350,1353],{"class":1193,"line":1339},14,[229,1341,1319],{"class":1242},[229,1343,1322],{"class":1201},[229,1345,1243],{"class":1242},[229,1347,1327],{"class":1201},[229,1349,1243],{"class":1242},[229,1351,1352],{"class":1332},"false",[229,1354,1336],{"class":1242},[229,1356,1358],{"class":1193,"line":1357},15,[229,1359,1297],{"class":1242},[1149,1361,1362],{"v-slot:hint":27},[15,1363,1364],{},"Команды, которые вытаскивают весь keyspace или огромную структуру целиком, опасны для single-threaded Redis.",[1366,1367,1371,1388,1518],"code-task",{"expected":1368,"id":1369,"xp":1370},"Set\\nSortedSet\\nStream","redis-structures-ct1","20",[15,1372,1373,1374,1377,1378,1380,1381,1384,1385,79],{},"Реализуй ",[29,1375,1376],{},"StructureFor",": уникальные элементы — ",[29,1379,500],{},", рейтинг — ",[29,1382,1383],{},"SortedSet",", durable очередь событий — ",[29,1386,1387],{},"Stream",[1149,1389,1390],{"v-slot:template":27},[21,1391,1393],{"className":1186,"code":1392,"language":1188,"meta":27,"style":27},"package main\n\nimport \"fmt\"\n\nfunc StructureFor(usecase string) string {\n    return \"todo\"\n}\n\nfunc main() {\n    fmt.Println(StructureFor(\"unique\"))\n    fmt.Println(StructureFor(\"leaderboard\"))\n    fmt.Println(StructureFor(\"events\"))\n}\n",[29,1394,1395,1401,1405,1415,1419,1440,1447,1451,1455,1463,1480,1497,1514],{"__ignoreMap":27},[229,1396,1397,1399],{"class":1193,"line":1194},[229,1398,1198],{"class":1197},[229,1400,1202],{"class":1201},[229,1402,1403],{"class":1193,"line":1205},[229,1404,1209],{"emptyLinePlaceholder":1208},[229,1406,1407,1409,1411,1413],{"class":1193,"line":1212},[229,1408,1215],{"class":1197},[229,1410,1219],{"class":1218},[229,1412,1222],{"class":1201},[229,1414,1225],{"class":1218},[229,1416,1417],{"class":1193,"line":1228},[229,1418,1209],{"emptyLinePlaceholder":1208},[229,1420,1421,1423,1426,1428,1431,1434,1436,1438],{"class":1193,"line":1233},[229,1422,1236],{"class":1197},[229,1424,1425],{"class":1201}," StructureFor",[229,1427,1243],{"class":1242},[229,1429,1430],{"class":1246},"usecase",[229,1432,1433],{"class":1197}," string",[229,1435,1253],{"class":1242},[229,1437,186],{"class":1197},[229,1439,1258],{"class":1242},[229,1441,1442,1444],{"class":1193,"line":1261},[229,1443,1288],{"class":1197},[229,1445,1446],{"class":1218}," \"todo\"\n",[229,1448,1449],{"class":1193,"line":1270},[229,1450,1297],{"class":1242},[229,1452,1453],{"class":1193,"line":1279},[229,1454,1209],{"emptyLinePlaceholder":1208},[229,1456,1457,1459,1461],{"class":1193,"line":1285},[229,1458,1236],{"class":1197},[229,1460,1310],{"class":1201},[229,1462,1313],{"class":1242},[229,1464,1465,1467,1469,1471,1473,1475,1478],{"class":1193,"line":1294},[229,1466,1319],{"class":1242},[229,1468,1322],{"class":1201},[229,1470,1243],{"class":1242},[229,1472,1376],{"class":1201},[229,1474,1243],{"class":1242},[229,1476,1477],{"class":1218},"\"unique\"",[229,1479,1336],{"class":1242},[229,1481,1482,1484,1486,1488,1490,1492,1495],{"class":1193,"line":1300},[229,1483,1319],{"class":1242},[229,1485,1322],{"class":1201},[229,1487,1243],{"class":1242},[229,1489,1376],{"class":1201},[229,1491,1243],{"class":1242},[229,1493,1494],{"class":1218},"\"leaderboard\"",[229,1496,1336],{"class":1242},[229,1498,1499,1501,1503,1505,1507,1509,1512],{"class":1193,"line":1305},[229,1500,1319],{"class":1242},[229,1502,1322],{"class":1201},[229,1504,1243],{"class":1242},[229,1506,1376],{"class":1201},[229,1508,1243],{"class":1242},[229,1510,1511],{"class":1218},"\"events\"",[229,1513,1336],{"class":1242},[229,1515,1516],{"class":1193,"line":1316},[229,1517,1297],{"class":1242},[1149,1519,1520],{"v-slot:hints":27},[58,1521,1522,1527,1532],{},[61,1523,1524,1526],{},[29,1525,500],{}," хранит уникальность.",[61,1528,1529,1531],{},[29,1530,1383],{}," хранит score.",[61,1533,1534,1536],{},[29,1535,1387],{}," хранит log сообщений и supports consumer groups.",[1538,1539,1540],"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 .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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);}",{"title":27,"searchDepth":1205,"depth":1205,"links":1542},[1543,1544,1555,1559,1560,1561,1562],{"id":40,"depth":1205,"text":41},{"id":181,"depth":1205,"text":182,"children":1545},[1546,1547,1548,1549,1550,1551,1552,1553,1554],{"id":186,"depth":1212,"text":187},{"id":298,"depth":1212,"text":299},{"id":409,"depth":1212,"text":410},{"id":499,"depth":1212,"text":500},{"id":609,"depth":1212,"text":610},{"id":733,"depth":1212,"text":734},{"id":777,"depth":1212,"text":778},{"id":804,"depth":1212,"text":805},{"id":822,"depth":1212,"text":823},{"id":840,"depth":1205,"text":841,"children":1556},[1557,1558],{"id":873,"depth":1212,"text":874},{"id":908,"depth":1212,"text":909},{"id":947,"depth":1205,"text":948},{"id":1069,"depth":1205,"text":1070},{"id":1113,"depth":1205,"text":1114},{"id":1137,"depth":1205,"text":1138},1781022064368]