Pull to refresh
3
0
Сергей @seriych

ClickHouse DBA

Send message

В общем есть в нём одна незадокументированная фишка

(процесс дедубликации не моментальный, но не 2 года же)

Именно это и задокументировано прямым текстом: "в неизвестный момент времени, на который вы не можете ориентироваться". Не надо додумывать и считать, что документация вам врёт, потому что вам кажется, что в этой фразе почему-то подразумевается "но не 2 года же".

(справедливости ради, документация иногда действительно врёт, но не в данном случае)

А вам стоит разобраться, почему у вас не происходит дедубликация (если вам это, конечно, нужно):

  • действительно ли данные на одном шарде

  • не лежат ли данные в разных партициях (так тоже не будет дедубликации)

  • настройки типа insert_deduplicate=0 + optimize_skip_merged_partitions=1 могут привести к неудалению дублей даже в одной партиции

  • насколько большие парты с дублями (есть лимиты на слияние слишком больших)

  • как именно вызываете optimize (с final или нет) и каких таблиц и партиций, на всех ли шардах...

  • не падает ли мердж

Может еще что-то забыл и проблема в чем-то еще. Но у нас почти всё на ReplicatedReplacingMergeTree, так что на 99% уверен, что проблема не в КХ (даже не смотря на не самую удачную версию у вас - 1% на это оставляю).

PS. В современных версиях есть настройки, задающие максимальное время до мерджа, что потенциально может избавить от optimize по cron/airflow, но мы до этого еще не обновились, так что не могу сказать, насколько оно норм и какие там грабли.

Можете сами придумать регулярку, которая слова вычленяет на ваш взгляд правильно, и заменить тут простую \w+ на нужную: https://fiddle.clickhouse.com/1a424911-2450-4668-82bc-c27a24b4161b

со \w+ и lowercase получается:

count():                                   32583
uniqExact(word):                           4650
uniq(word):                                4650
uniqCombined(word):                        4657
uniqHLL12(word):                           4663
length(toString(uniqExactState(word))):    74402
length(toString(uniqState(word))):         18603
length(toString(uniqCombinedState(word))): 98505
length(toString(uniqHLL12State(word))):    2651

Примерный uniq дает такой же результат, что и точный uniqExact, но занимает меньше памяти. Но тут данных мало, если найдется файлик пожирнее, разница будет больше.

Не так уж и много памяти сейчас. Вот есть у нас несколько юзеров БД, каждый запустил запрос на количество уникальных по нескольким колонкам. А среди них еще и строковые могут быть. И на практике это реально кучу памяти отъедает. Плюс это еще и медленно. Поэтому приблизительные алгоритмы рулят, особенно которые дают точный результат на маленьком числе групп, а неточность возникает только после тысяч.

Есть разница между тем, что запрос отожрал всю память и если повезет, то не упал и выполнился за время пока ты кофе пошел сделать, и тем, что запрос ничего не отожрал и выполнился за пару секунд (но может быть выдал 123456700 вместо 123456789, какой ужас).

В первом же абзаце статьи: "Главный критерий - нахождение адреса, если написано с ошибками или не дописан он в полной мере." Чуть опечатался и всё, разбиение на слова и точная проверка ломаются сразу. Ну и выше вам показывали про дом с квартирой. Тоже самое с улицей и населенным пунктом может быть.

ИВАНОВ ИВАН ИВАНОВИЧ
и
ИВАН ИВАНОВ
считаются совпадением т.к все уникальные элементы более коротого набора

Мне кажется, что ИВАНОВ ИВАН ИВАНОВИЧ и ИВАНОВ ИВАН ИВАНОВИЧ всё-таки совпадение получше, то есть логично меру для ИВАН ИВАНОВ и ИВАНОВ ИВАН ИВАНОВИЧ видеть хуже.

Возможно, вы не совсем поняли про "последовательности байт" (собственно я и не пояснял особо). Там ищется не максимально длинное совпадение. Там строки разбиваются на все возможные последовательности N байт (для фиксированного N), например: 'ИВАН', 'ВАН ', 'АН И', 'Н ИВ' и т.д. И вот число совпадающих этих наборов сравнивается. Это достаточно хорошо работает с перестановками слов, опечатками и.т.п. (поэтому я и предложил метод автору). Но пункт про нормализацию строк в той или иной степени может быть полезен в каких-то случаях и здесь.

P.S. Я ни разу не специалист в этой теме, не знаю, как лучше будет работать у автора статьи и как лучше будет работать у вас. Я лишь запостил еще один вариант. Кажется, что у каждого метода есть недостатки, выстреливающие в зависимости от ситуации. Собственно не было бы нескольких разных методов если бы это было не так.

В clickhouse для этого есть всякие готовые ngramDistance*(). Неплохо работает для приведенных примеров (даже явно получше, чем в статье для Нарт). Оно смотрит процент совпадающих последовательностей байт в строках - можно это реализовать на удобном вам языке.

Hidden text
with
  ['Эски сары', 'Нарты']
    as queries,
  ['Эски сары кёл', 'Хасаутская', 'Нартов', 'Новый Карачай', 'Мара-Аягъы', 'Кавказская']
  as target_vector
select
  arrayJoin(queries) as query,
  arrayJoin(target_vector) as target,
  ngramDistance(query, target) as similarity1,
  ngramDistanceCaseInsensitiveUTF8(query, target) as similarity2
order by query, similarity2, similarity1
format PrettyCompactMonoBlock
;
    +-query-----+-target--------+-similarity1-+-similarity2-+
 1. | Нарты     | Нартов        |       0.375 |  0.42857143 |
 2. | Нарты     | Эски сары кёл |  0.85714287 |           1 |
 3. | Нарты     | Мара-Аягъы    |   0.9130435 |           1 |
 4. | Нарты     | Новый Карачай |   0.9310345 |           1 |
 5. | Нарты     | Хасаутская    |           1 |           1 |
 6. | Нарты     | Кавказская    |           1 |           1 |
 7. | Эски сары | Эски сары кёл |         0.2 |  0.22222222 |
 8. | Эски сары | Хасаутская    |   0.7419355 |           1 |
 9. | Эски сары | Нартов        |  0.82608694 |           1 |
10. | Эски сары | Кавказская    |  0.87096775 |           1 |
11. | Эски сары | Мара-Аягъы    |  0.93333334 |           1 |
12. | Эски сары | Новый Карачай |   0.9444444 |           1 |
    +-----------+---------------+-------------+-------------+

https://fiddle.clickhouse.com/466ee362-dbff-4161-9553-ae6abcd565ec

А вообще, используйте WebP. Он лучше JPG и его уже везде принимают, кроме пары технически отсталых сайтов.

Из статьи:

Канал.jpg (Хабр не поддерживает формат WebP)

что-то мне подсказывает, что "парочка отсталых сайтов" в реальном мире - это 90+%. И по итогу будет двойное пережатие исходник -> webp -> jpeg, что врядли хорошо отразится на хорошем качестве.

Пишу это только из-за "В марте 2024 года мы снова проведём наше исследование", чтобы следующая статья была оформлена лучше.

Не надо указывать значения со 100500 значащими цифрами, если погрешность измерений не позволяет столько разрешить в действительности. То есть тут центы указывать - это только захламлять диаграммы, ухудшая их читаемость, при этом не добавляя никакой полезной информации. Самое простое до целых долларов округлить. Я бы вообще до десятков долларов округлил. Значения никак не пострадают, читабельность значительно улучшится.

"спокойно" - если у вас в 4 раза больше денег на сервера

snowflake id или sonyflake id почти всегда лучше, ибо 8 байт и монотонность. Возможно, хуже там где "Отсутствие предсказуемости" - это серьезный фактор. Ну или у вас ну ооочень много юников.

А что если убрать из рассмотрения исходно простые числа?

Странно, что в тест не включили просто сжатие ZSTD без кодеков.

От себя могу добавить:

  • в первую очередь подбираем ORDER BY таблиц, потом уже тестируем кодеки, если нужно

  • тестируйте на своих реальных данных для каждой конкретной таблицы

  • для целых чисел (включая Date, DateTime, Decimal и Enum) часто хорош вариант (T64, LZ4)

  • для строк не ошибкой будет всегда выбирать между двумя вариантами: ZSTD или LowCardinality

  • LowCardinality может быть выгоден и гораздо больше чем для 10000 уникальных значений, особенно если строки длинные

  • для коротких строк можно по умолчанию оставлять LZ4

  • LZ4HC очень медленный на вставку

Агрегация данных, вставляемых разными пачками решается через комбинатор State или SimpleState
В вашем примере в матвью вместо count(*) можно сделать sumSimpleState(1), и селектить потом через sumMerge.

"Данные были вставлены в одном запросе и это имеет значение."
Это имеет значение, если выставлена настройка insert_deduplicate = 1. Для более эффективной вставки ее выставляют в 0, тогда и в одной вставке данные не будут схлопываться сразу.

И у вас в примерах во всех таблицах в ключе сортировки стоит поменять порядок: ORDER BY (app, time). Для полного счастья что-то вроде такого:
PARTITION BY toYYYYMM(date)
ORDER BY (date, app, time)

(при желании можно не делать отдельную колонку с датой, но так удобнее в запросах, а сожмется она в ноль)
После этого может оказаться, что и без матвью скорости хватает.

Подмена понятий туда-сюда по ходу статьи. Сначала с пафосом заявляем: «ответ найден!», потом вводим сомнительные критерии, плюс определяем некоторые термины в математической модели задачи, в конце заменяем смысл введенных в математической модели терминов на бытовой аналог этих терминов и утверждаем, что задача решена.
Подробнее:
1. Пункт 4 про термин «Эффективный спуск» — не учитываем, из какого вагона нам надо будет выйти при следующей пересадке. Это как бы очень очень важно, но просто упоминается в «PS», якобы, не имеет особого значения. Но если мне надо в другой конец поезда, то пока я иду по платформе с одного конца до другого, придет аж следующий поезд. Таким образом, если я раньше оказываюсь на платформе, то или сажусь в предыдущий поезд и перехожу по вагонам на следующих остановках, или сразу оказываюсь в нужном вагоне, или в каком-то из промежуточных состояний, но в любом случае в лучшей ситуации, чем если бы не ускорился на эскалаторе. Кроме случаев, когда место пересадки совпадает с местом, куда приходит наш эскалатор.
2. В пункте 5 называем «целесообразным» спуск с вероятностью не менее 0.5. ОК, не вопрос, ввели понятие. Но внезапно в конце «целесообразный» становится целесообразным в житейском смысле. Что, это как вообще? То есть если я в 40% случаев попадаю в предыдущий поезд и приезжаю на 2-3 минуты раньше — это нецелесообразно?
Если честно, остальное между матмоделью и выводами промотал, но учитывая странную матмодель и подмену понятий в выводах, нет особого смысла рассматривать само решение, даже если оно идеально.
Не баг, но еще по поводу скачивания файлов. Есть в планах вернуть фичу из 12-й, когда она начинает скачивать файл еще до того, как указываешь место, куда сохранять? Очень удобная фича. Так невольно тыкаешь куда поближе, лишь бы быстрее файл начал скачиваться, а в 12-й спокойненько выбираешь конечное местоположение файла
В тему экспресс-панели. В 12-й всегда добавляю сайты следующим образом:
открываю нужный сайт, тыкаю новую вкладку (открывается экспресс-панель), тыкаем плюсик, видим наш сайт первым в списке, кликаем на него, всё. В вивальди же в списке сайтов нет открытых сейчас сайтов (только старые какие-то), приходится открывать его вкладку, копипастить урл и потом копипастить его в поле экспресс-панели. Неудобненько.
И да, спасибо за браузер.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Database Administrator, Data Engineer
ClickHouse
Grafana
JavaScript