ClickHouse Tool — Как повысить скорость расчета Retention

база данных

Эта статья была впервые опубликована на:Уокер ИИ

Удержание пользователей является важной функцией основных платформ анализа данных. Предприятия обычно используют коэффициент удержания для измерения активности пользователей, что также является прямым показателем, который может напрямую отражать ценность функций продукта. Коэффициент удержания является одним из наиболее важных показателей для измерения. качество пользователя, поэтому расчет различных коэффициентов удержания является самым основным навыком анализа данных. Итак, вот несколько практических примеров анализа удержания пользователей.

1. Подготовьте

Понимать несколько традиционных методов расчета текущего коэффициента удержания и понимать, что ClickHouse предоставляет функцию удержания (cond1, cond2, ...) для расчета коэффициента удержания.

Создать таблицу: таблица основной информации о пользователе: login_event

CREATE TABLE login_event --用户登录事件
(
    `accountId` String COMMENT '账号的ID', --用户唯一ID
    `ds` Date COMMENT '日期' --用户登录日期
)
ENGINE = MergeTree
PARTITION BY accountId
ORDER BY accountId

Производная: вставить данные для входа пользователя за август.

--插入数据
insert into login_event values (10001,toDate('2020-08-01'), (10001,toDate('2020-08-08')), (10001,toDate('2020-08-09')), (10001,toDate('2020-08-10')), (10001,toDate('2020-08-12')),
(10001,toDate('2020-08-13')), (10001,toDate('2020-08-14')), (10001,toDate('2020-08-15')), (10001,toDate('2020-08-16')), (10001,toDate('2020-08-17')), (10001,toDate('2020-08-18')),
(10001,toDate('2020-08-20')), (10001,toDate('2020-08-22')), (10001,toDate('2020-08-23')), (10001,toDate('2020-08-24')), (10002,toDate('2020-08-20')), (10002,toDate('2020-08-22')), (10002,toDate('2020-08-23')), (10002,toDate('2020-08-01')), (10002,toDate('2020-08-11')), (10002,toDate('2020-08-12')), (10002,toDate('2020-08-13')), (10002,toDate('2020-08-20')),
(10002,toDate('2020-08-15')), (10002,toDate('2020-08-30')), (10002,toDate('2020-08-20')), (10002,toDate('2020-08-01')), (10002,toDate('2020-08-06')), (10002,toDate('2020-08-24')), (10003,toDate('2020-08-05')), (10003,toDate('2020-08-08')), (10003,toDate('2020-08-09')), (10003,toDate('2020-08-10')), (10003,toDate('2020-08-11')), (10003,toDate('2020-08-13')),
(10003,toDate('2020-08-15')), (10003,toDate('2020-08-16')), (10003,toDate('2020-08-18')), (10003,toDate('2020-08-20')), (10003,toDate('2020-08-01')), (10003,toDate('2020-08-21')),
(10003,toDate('2020-08-22')), (10003,toDate('2020-08-24')), (10003,toDate('2020-08-26')), (10003,toDate('2020-08-25')), (10003,toDate('2020-08-27')), (10003,toDate('2020-08-28')),
(10003,toDate('2020-08-29')), (10003,toDate('2020-08-30')), (10004,toDate('2020-08-01')), (10004,toDate('2020-08-02')), (10004,toDate('2020-08-03')), (10004,toDate('2020-08-04')),
(10004,toDate('2020-08-05')), (10004,toDate('2020-08-08')), (10004,toDate('2020-08-09')), (10004,toDate('2020-08-10')), (10004,toDate('2020-08-11')), (10004,toDate('2020-08-14')),
(10004,toDate('2020-08-15')), (10004,toDate('2020-08-16')), (10004,toDate('2020-08-17')), (10004,toDate('2020-08-19')), (10004,toDate('2020-08-20')), (10004,toDate('2020-08-21')),
(10004,toDate('2020-08-22')), (10004,toDate('2020-08-23')), (10004,toDate('2020-08-24')), (10004,toDate('2020-08-23')),(10004,toDate('2020-08-23')), (10004,toDate('2020-08-25')),
(10004,toDate('2020-08-27')), (10004,toDate('2020-08-30'));

2. Тематический анализ

Чтобы рассчитать второй, 3-й, 7-й, 14-й и 30-й уровень удержания активных пользователей в определенный день, мы разделим решение проблемы на три этапа:

  • Найдите ежедневных активных пользователей

  • Найдите статус входа активных пользователей в определенный день 2, 3, 6, 13 и 29 числа.

  • Подсчитайте количество активных пользователей, вошедших в систему 2-го, 3-го, 6-го, 13-го и 29-го числа в определенный день, и рассчитайте коэффициент удержания за N дней.

Решение первое:


--计算出2020-08-01活跃用户在第2361329日的留存数,计算出留存率
SELECT
    ds,
    count(accountIdD0) AS activeAccountNum,
    count(accountIdD1) / count(accountIdD0) AS `次留`,
    count(accountIdD3) / count(accountIdD0) AS `3留`,
    count(accountIdD7) / count(accountIdD0) AS `7留`,
    count(accountIdD14) / count(accountIdD0) AS `14留`,
    count(accountIdD30) / count(accountIdD0) AS `30留`
FROM
( --使用LEFT JOIN 找到2020-08-01当日活跃用户在第2361329日的登录用户
    SELECT DISTINCT
        a.ds AS ds,
        a.accountIdD0 AS accountIdD0,
        IF(b.accountId = '', NULL, b.accountId) AS accountIdD1,
        IF(c.accountId = '', NULL, c.accountId) AS accountIdD3,
        IF(d.accountId = '', NULL, d.accountId) AS accountIdD7,
        IF(e.accountId = '', NULL, e.accountId) AS accountIdD14,
        IF(f.accountId = '', NULL, f.accountId) AS accountIdD30
    FROM
    (--找出2020-08-01当日活跃用户
        SELECT DISTINCT
            ds,
            accountId AS accountIdD0
        FROM login_event
        WHERE ds = '2020-08-01'
        ORDER BY ds ASC
    ) AS a
    LEFT JOIN test.login3_event AS b ON (b.ds = addDays(a.ds, 1)) AND (a.accountIdD0 = b.accountId)
    LEFT JOIN test.login3_event AS c ON (c.ds = addDays(a.ds, 2)) AND (a.accountIdD0 = c.accountId)
    LEFT JOIN test.login3_event AS d ON (d.ds = addDays(a.ds, 6)) AND (a.accountIdD0 = d.accountId)
    LEFT JOIN test.login3_event AS e ON (e.ds = addDays(a.ds, 13)) AND (a.accountIdD0 = e.accountId)
    LEFT JOIN test.login3_event AS f ON (f.ds = addDays(a.ds, 29)) AND (a.accountIdD0 = f.accountId)
) AS temp
GROUP BY ds

结果:
-----------------------------------------
┌─────────ds─┬─activeAccountNum─┬─次留─┬──3留─┬─7留─┬─14留─┬─30留─┐
 2020-08-01                 4  0.25  0.25    0   0.5  0.75 
└────────────┴──────────────────┴──────┴──────┴─────┴──────┴──────┘

1 rows in set. Elapsed: 0.022 sec.

Решение второе:


--判断2020-08-01活跃用户在第2361329日的留存数,计算出留存率,计算出留存率
SELECT DISTINCT
    b.ds AS ds,
    ifnull(countDistinct(if(a.ds = b.ds, a.accountId, NULL)), 0) AS activeAccountNum,
    ifnull(countDistinct(if(a.ds = addDays(b.ds, 1), b.accountId, NULL)) / activeAccountNum, 0) AS `次留`,
    ifnull(countDistinct(if(a.ds = addDays(b.ds, 2), b.accountId, NULL)) / activeAccountNum, 0) AS `3留`,
    ifnull(countDistinct(if(a.ds = addDays(b.ds, 6), b.accountId, NULL)) / activeAccountNum, 0) AS `7留`,
    ifnull(countDistinct(if(a.ds = addDays(b.ds, 13), b.accountId, NULL)) / activeAccountNum, 0) AS `14留`,
    ifnull(countDistinct(if(a.ds = addDays(b.ds, 29), b.accountId, NULL)) / activeAccountNum, 0) AS `30留`
FROM
  --使用INNER JOIN找出2020-08-01活跃用户在后续130日登录情况
(
    SELECT
        ds,
        accountId
    FROM login_event
    WHERE (ds <= addDays(toDate('2020-08-01'), 29)) AND (ds >= '2020-08-01')
) AS a
INNER JOIN
--找出2020-08-01当日活跃用户
(
    SELECT DISTINCT
        accountId,
        ds
    FROM test.login3_event
    WHERE ds = '2020-08-01'
) AS b ON a.accountId = b.accountId
GROUP BY ds

结果:
-----------------------------------------
┌─────────ds─┬─activeAccountNum─┬─次留─┬──3留─┬─7留─┬─14留─┬─30留─┐
 2020-08-01                 4  0.25  0.25    0   0.5  0.75 
└────────────┴──────────────────┴──────┴──────┴─────┴──────┴──────┘

1 rows in set. Elapsed: 0.019 sec.

Решение третье:


--根据数组下标SUM(r[index])获取2020-08-01活跃用户在第2361329日的留存数,计算出留存率
SELECT
    toDate('2020-08-01') AS ds,
    SUM(r[1]) AS activeAccountNum,
    SUM(r[2]) / SUM(r[1]) AS `次留`,
    SUM(r[3]) / SUM(r[1]) AS `3留`,
    SUM(r[4]) / SUM(r[1]) AS `7留`,
    SUM(r[5]) / SUM(r[1]) AS `14留`,
    SUM(r[6]) / SUM(r[1]) AS `30留`   
FROM
--找到2020-08-01活跃用户在第2361329日的登录情况,1/0 => 登录/未登录
(
    WITH toDate('2020-08-01') AS tt   
SELECT
    accountId,
    retention(
      toDate(ds) = tt, 
      toDate(subtractDays(ds, 1)) = tt, 
      toDate(subtractDays(ds, 2)) = tt, 
      toDate(subtractDays(ds, 6)) = tt,
      toDate(subtractDays(ds, 13)) = tt,
      toDate(subtractDays(ds, 29)) = tt
    ) AS r
  --找出2020-08-01活跃用户在后续130日登录数据
FROM login_event
WHERE (ds >= '2020-08-01') AND (ds <= addDays(toDate('2020-08-01'), 29))
GROUP BY accountId
)
GROUP BY ds


结果:
-----------------------------------------
┌─────────ds─┬─activeAccountNum─┬─次留─┬──3留─┬─7留─┬─14留─┬─30留─┐
 2020-08-01                 4  0.25  0.25    0   0.5  0.75 
└────────────┴──────────────────┴──────┴──────┴─────┴──────┴──────┘

1 rows in set. Elapsed: 0.009 sec.

3. Резюме

  • Первый метод заключается в использовании традиционного метода многотабличной ассоциации.Программисты, знакомые с ClickHouse, знают, что многотабличная ассоциация является естественным врагом ClickHouse, а скорость работы относительно низкая.

  • Второй метод использует ассоциацию таблиц, оценивает разницу дат с помощью функции ЕСЛИ и находит требуемые пользовательские данные даты.По сравнению с первым методом, ассоциация нескольких таблиц уменьшается, а скорость работы повышается.

  • Третий метод заключается в использовании собственной функции удержания ClickHouse. Функция удержания является расширенной функцией агрегирования в ClickHouse. Эта функция может принимать несколько условий. На основе результата первого условия следующие условия выполняются как 1, если не выполняются, это 0 и, наконец, возвращает массив из 1 и 0. Коэффициент удержания можно рассчитать, подсчитав количество соответствующих единиц в массиве.

Сравнивая три метода расчета, использование собственной функции удержания ClickHouse работает быстрее и эффективнее при больших объемах данных. Проблема медленной скорости и низкой эффективности в способе вычисления коэффициента удержания пользователя в известном уровне техники решается, тем самым достигается эффект повышения скорости вычисления и эффективности вычисления.


PS: Для получения дополнительной технической галантереи, пожалуйста, обратите внимание на [Публичный аккаунт | xingzhe_ai] и обсудите с ходоками!