0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>На сегодняшний день любой бизнес активно использует аналитику, чтобы развивать свои продукты. На основе данных можно лучше понимать пользователей и их потребности. Одна из базовых метрик, использующаяся повсеместно, - это количество уникальных посетителей страницы. Представим, что есть необходимость хранить такие данные в Redis.</p>
1
<p>На сегодняшний день любой бизнес активно использует аналитику, чтобы развивать свои продукты. На основе данных можно лучше понимать пользователей и их потребности. Одна из базовых метрик, использующаяся повсеместно, - это количество уникальных посетителей страницы. Представим, что есть необходимость хранить такие данные в Redis.</p>
2
<p>Прежде всего, стоит определить, как данные будут использоваться. Важно эффективно выполнять 2 условия:</p>
2
<p>Прежде всего, стоит определить, как данные будут использоваться. Важно эффективно выполнять 2 условия:</p>
3
<ul><li>получать количество уникальных посетителей страницы</li>
3
<ul><li>получать количество уникальных посетителей страницы</li>
4
<li>проверять, что пользователь уже есть в списке посетителей, чтобы не записать дважды</li>
4
<li>проверять, что пользователь уже есть в списке посетителей, чтобы не записать дважды</li>
5
</ul><p>Прикинем несколько вариантов решения.</p>
5
</ul><p>Прикинем несколько вариантов решения.</p>
6
<h3>Обычные ключи</h3>
6
<h3>Обычные ключи</h3>
7
<p>Для начала можно попробовать хранить каждое посещение отдельным ключом с форматом<em>page:{page_path}:user:{user_id}</em>:</p>
7
<p>Для начала можно попробовать хранить каждое посещение отдельным ключом с форматом<em>page:{page_path}:user:{user_id}</em>:</p>
8
<p>Хотя проверка на существование пользователя в списке будет происходить быстро, этот вариант не подходит, потому что получение количества уникальных пользователей займет O(N) (N - количество ключей в Redis).</p>
8
<p>Хотя проверка на существование пользователя в списке будет происходить быстро, этот вариант не подходит, потому что получение количества уникальных пользователей займет O(N) (N - количество ключей в Redis).</p>
9
<h3>Redis Lists</h3>
9
<h3>Redis Lists</h3>
10
<p>Второй вариант - использовать тип данных Lists для хранения уникального списка ID пользователей:</p>
10
<p>Второй вариант - использовать тип данных Lists для хранения уникального списка ID пользователей:</p>
11
<p>В данном случае размер списка можно получить очень быстро за O(1), но вставка будет происходить неэффективно, так как перед вставкой необходимо делать поиск по всему списку для проверки существования.</p>
11
<p>В данном случае размер списка можно получить очень быстро за O(1), но вставка будет происходить неэффективно, так как перед вставкой необходимо делать поиск по всему списку для проверки существования.</p>
12
<h3>Redis Hashes</h3>
12
<h3>Redis Hashes</h3>
13
<p>Можно использовать структуру Hashes. Ключом будет страница, а значения будут в формате<em>{user_id}: 1</em>:</p>
13
<p>Можно использовать структуру Hashes. Ключом будет страница, а значения будут в формате<em>{user_id}: 1</em>:</p>
14
<p>Это решение удовлетворяет обоим требованиям:</p>
14
<p>Это решение удовлетворяет обоим требованиям:</p>
15
<ol><li>Получение списка уникальных пользователей происходит за O(1) с командой hlen</li>
15
<ol><li>Получение списка уникальных пользователей происходит за O(1) с командой hlen</li>
16
<li>Вставка происходит за O(1) с проверкой уникальности пользователя</li>
16
<li>Вставка происходит за O(1) с проверкой уникальности пользователя</li>
17
</ol><p>Но имеются и недостатки:</p>
17
</ol><p>Но имеются и недостатки:</p>
18
<ul><li>Хранится много лишних данных из-за единиц для каждого идентификатора юзера</li>
18
<ul><li>Хранится много лишних данных из-за единиц для каждого идентификатора юзера</li>
19
<li>Hashes не позволяют посчитать пересечения списков: например, количество пользователей, которые посетили 3 определенные страницы</li>
19
<li>Hashes не позволяют посчитать пересечения списков: например, количество пользователей, которые посетили 3 определенные страницы</li>
20
</ul><h2>Redis Sets</h2>
20
</ul><h2>Redis Sets</h2>
21
<p>Для решения задачи с уникальными посетителями в Redis есть структура данных Sets. Sets - это список уникальных элементов, поддерживающий быстрые функции вставки, проверки на уникальность и пересечений.</p>
21
<p>Для решения задачи с уникальными посетителями в Redis есть структура данных Sets. Sets - это список уникальных элементов, поддерживающий быстрые функции вставки, проверки на уникальность и пересечений.</p>
22
<h3>Запись</h3>
22
<h3>Запись</h3>
23
<p>Для записи значений используется команда sadd key member [member ...]:</p>
23
<p>Для записи значений используется команда sadd key member [member ...]:</p>
24
<p>Если набора не существовало, то он будет создан. После записи возвращается количество добавленных элементов. Если попытаться добавить существующий элемент, то вернется 0:</p>
24
<p>Если набора не существовало, то он будет создан. После записи возвращается количество добавленных элементов. Если попытаться добавить существующий элемент, то вернется 0:</p>
25
<h3>Чтение и проверка на уникальность</h3>
25
<h3>Чтение и проверка на уникальность</h3>
26
<p>Получить количество уникальных пользователей страницы можно с помощью команды scard key:</p>
26
<p>Получить количество уникальных пользователей страницы можно с помощью команды scard key:</p>
27
<p>Команда sismember key member проверяет, что пользователь посещал данную страницу:</p>
27
<p>Команда sismember key member проверяет, что пользователь посещал данную страницу:</p>
28
<h3>Удаление</h3>
28
<h3>Удаление</h3>
29
<p>Удалить пользователя из набора можно командой srem key member [member ...]:</p>
29
<p>Удалить пользователя из набора можно командой srem key member [member ...]:</p>
30
<h3>Пересечения</h3>
30
<h3>Пересечения</h3>
31
<p>Допустим, что есть 3 страницы:<em>/courses</em>,<em>/courses/java</em>и<em>/courses/php</em>. Нужно получить уникальных пользователей, которые посетили все 3 страницы. Задача решается одной командой sinter key [key ...]:</p>
31
<p>Допустим, что есть 3 страницы:<em>/courses</em>,<em>/courses/java</em>и<em>/courses/php</em>. Нужно получить уникальных пользователей, которые посетили все 3 страницы. Задача решается одной командой sinter key [key ...]:</p>
32
<h2>Резюме</h2>
32
<h2>Резюме</h2>
33
<ul><li>уникальные значения в Redis можно хранить с помощью Hashes, но тогда хранятся лишние данные и отсутствуют некоторые функции</li>
33
<ul><li>уникальные значения в Redis можно хранить с помощью Hashes, но тогда хранятся лишние данные и отсутствуют некоторые функции</li>
34
<li>структура данных Sets идеально подходит для списков уникальных значений. Вставка и проверка на уникальность происходят за O(1). Также присутствуют функции для расчета пересечений/разницы</li>
34
<li>структура данных Sets идеально подходит для списков уникальных значений. Вставка и проверка на уникальность происходят за O(1). Также присутствуют функции для расчета пересечений/разницы</li>
35
<li>добавить элемент в набор можно командой sadd</li>
35
<li>добавить элемент в набор можно командой sadd</li>
36
<li>с помощью sismember можно проверить существование элемента в наборе</li>
36
<li>с помощью sismember можно проверить существование элемента в наборе</li>
37
<li>для получения количества уникальных элементов используется команда scard</li>
37
<li>для получения количества уникальных элементов используется команда scard</li>
38
<li>для вычисления пересечений наборов используется команда sinter</li>
38
<li>для вычисления пересечений наборов используется команда sinter</li>
39
</ul>
39
</ul>