Запись запросов в ClickHouse с использованием данных GitHub
Этот набор данных содержит все коммиты и изменения для репозитория ClickHouse. Его можно сгенерировать с помощью встроенного инструмента git-import
, распространяемого с ClickHouse.
Сгенерированные данные предоставляют файл tsv
для каждой из следующих таблиц:
commits
- коммиты со статистикой.file_changes
- файлы, измененные в каждом коммите с информацией об изменении и статистикой.line_changes
- каждая измененная строка в каждом измененном файле в каждом коммите с полной информацией об строке и информацией о предыдущем изменении этой строки.
На 8 ноября 2022 года каждый TSV имеет приблизительно следующий размер и количество строк:
commits
- 7.8M - 266,051 строкfile_changes
- 53M - 266,051 строкline_changes
- 2.7G - 7,535,157 строк
Генерация данных
Это необязательно. Мы распространяем данные бесплатно - смотрите Скачивание и вставка данных.
Это займет около 3 минут (на 8 ноября 2022 года на MacBook Pro 2021) для завершения для репозитория ClickHouse.
Полный список доступных опций можно получить из встроенной справки инструмента.
Эта справка также предоставляет DDL для каждой из вышеуказанных таблиц, например:
Эти запросы должны работать на любом репозитории. Не стесняйтесь исследовать и сообщать о своих находках. Некоторые рекомендации относительно времени выполнения (на ноябрь 2022 года):
- Linux -
~/clickhouse git-import
- 160 минут
Скачивание и вставка данных
Следующие данные можно использовать для воспроизведения рабочей среды. В качестве альтернативы, этот набор данных доступен в play.clickhouse.com - смотрите Запросы для получения дополнительной информации.
Сгенерированные файлы для следующих репозиториев можно найти ниже:
- ClickHouse (8 ноября 2022 года)
- https://datasets-documentation.s3.amazonaws.com/github/commits/clickhouse/commits.tsv.xz - 2.5 MB
- https://datasets-documentation.s3.amazonaws.com/github/commits/clickhouse/file_changes.tsv.xz - 4.5MB
- https://datasets-documentation.s3.amazonaws.com/github/commits/clickhouse/line_changes.tsv.xz - 127.4 MB
- Linux (8 ноября 2022 года)
Чтобы вставить эти данные, подготовьте базу данных, выполнив следующие запросы:
Вставьте данные, используя INSERT INTO SELECT
и функцию s3. Например, ниже мы вставляем файлы ClickHouse в каждую из соответствующих таблиц:
commits
file_changes
line_changes
Запросы
Инструмент предлагает несколько запросов через свой вывод справки. Мы ответили на них, а также на некоторые дополнительные вспомогательные вопросы, представляющие интерес. Эти запросы имеют примерно возрастающую сложность по сравнению с произвольным порядком инструмента.
Этот набор данных доступен в play.clickhouse.com в базах данных git_clickhouse
. Мы предоставляем ссылку на эту среду для всех запросов, адаптируя имя базы данных по мере необходимости. Обратите внимание, что результаты в play могут отличаться от представленных здесь из-за различий во времени сбора данных.
История одного файла
Самый простой из запросов. Здесь мы смотрим на все сообщения коммитов для StorageReplicatedMergeTree.cpp
. Поскольку эти сообщения, вероятно, более интересны, мы сортируем по самым последним сообщениям сначала.
Мы также можем просмотреть изменения строк, исключая переименования, т.е. мы не покажем изменения до события переименования, когда файл существовал под другим именем:
Обратите внимание, что существует более сложный вариант этого запроса, где мы находим историю коммитов по строкам файла, учитывая переименования.
Найти текущие активные файлы
Это важно для дальнейшего анализа, когда мы хотим учитывать только текущие файлы в репозитории. Мы оцениваем этот набор как файлы, которые не были переименованы или удалены (а затем заново добавлены/переименованы).
Обратите внимание, что, похоже, была нарушена история коммитов в отношении файлов в директориях dbms
, libs
, tests/testflows/
во время их переименования. Поэтому мы также исключаем их.
Обратите внимание, что это позволяет файлам быть переименованными, а затем переименованными обратно в их оригинальные значения. Сначала мы агрегистрируем old_path
для списка удаленных файлов в результате переименования. Мы объединяем это с последней операцией для каждого path
. Наконец, мы фильтруем этот список, оставляя те, где последнее событие не является Delete
.
Обратите внимание, что мы пропустили импорт нескольких директорий во время импорта, т.е.
--skip-paths 'generated\.cpp|^(contrib|docs?|website|libs/(libcityhash|liblz4|libdivide|libvectorclass|libdouble-conversion|libcpuid|libzstd|libfarmhash|libmetrohash|libpoco|libwidechar_width))/'
Применение этого шаблона к git list-files
сообщает о 18155.
Наше текущее решение, таким образом, является оценкой текущих файлов.
Разница здесь вызвана несколькими факторами:
- Переименование может происходить одновременно с другими изменениями файла. Эти события перечислены как отдельные события в
file_changes
, но с одинаковым временем. ФункцияargMax
не может различать их - она выбирает первое значение. Естественный порядок вставок (единственный способ узнать правильный порядок) не сохраняется в объединении, поэтому могут быть выбраны измененные события. Например, ниже файлsrc/Functions/geometryFromColumn.h
имеет несколько модификаций перед тем, как его переименовали вsrc/Functions/geometryConverters.h
. Наше текущее решение может выбрать событие Modify как последнее изменение, в результате чегоsrc/Functions/geometryFromColumn.h
будет сохранен.
- Нарушенная история коммитов - отсутствуют события удаления. Источник и причина TBD.
Эти различия не должны существенно влиять на наш анализ. Мы приветствуем улучшенные версии этого запроса.
Список файлов с наибольшим количеством изменений
Ограничивая текущие файлы, мы рассматриваем количество изменений как сумму удалений и добавлений.
В какой день недели обычно происходят коммиты?
Это имеет смысл с некоторым падением продуктивности по пятницам. Приятно видеть, как люди вносят код в выходные! Огромное спасибо нашим участникам!
История подкаталога/файла - количество строк, коммитов и авторов с течением времени
Это производит большой результат запроса, который нереалистично показать или визуализировать без фильтрации. Мы, следовательно, разрешаем фильтровать файл или подкаталог в следующем примере. Здесь мы группируем по неделям, используя функцию toStartOfWeek
- адаптируйте по мере необходимости.
Эти данные хорошо визуализируются. Ниже мы используем Superset.
Для добавленных и удаленных строк:

Для коммитов и авторов:

Список файлов с максимальным количеством авторов
Ограничить только текущими файлами.
Самые старые строки кода в репозитории
Ограничено только текущими файлами.
Файлы с самой длинной историей
Ограничено только текущими файлами.
Наша основная структура данных, Merge Tree, очевидно, постоянно эволюционирует с долгой историей правок!
Распределение участников по документам и коду за месяц
Во время захвата данных изменения в папке docs/
были отфильтрованы из-за очень грязной истории коммитов. Поэтому результаты этого запроса не точны.
Пишем ли мы больше документов в определенные моменты месяца, например, около дат релизов? Мы можем использовать функцию countIf
, чтобы вычислить простое соотношение, визуализируя результат с помощью функции bar
.
Может, немного больше в конце месяца, но в целом мы сохраняем хорошее равномерное распределение. Опять же, это ненадежно из-за фильтрации документов во время вставки данных.
Авторы с самым разнообразным вкладом
Мы считаем разнообразием здесь количество уникальных файлов, к которым автор внес вклад.
Давайте посмотрим, у кого самые разнообразные коммиты в недавней работе. Вместо ограничения по дате мы ограничим последними N коммитами автора (в данном случае использовано 3, но вы можете изменить):
Любимые файлы для автора
Здесь мы выбираем нашего основателя Alexey Milovidov и ограничиваем наш анализ текущими файлами.
Это имеет смысл, потому что Алексей отвечает за поддержание журнала изменений. Но что если мы используем базовое имя файла для идентификации его популярных файлов - это позволяет учитывать переименования и должно сосредоточиться на вкладках кода.
Это может быть более отражательным для его областей интереса.
Самые большие файлы с наименьшим количеством авторов
Для этого сначала необходимо определить самые большие файлы. Оценка этого с помощью полной реконструкции файла для каждого файла из истории коммитов будет очень затратной!
Чтобы оценить, предположим, что мы ограничиваемся текущими файлами, мы суммируем добавления строк и вычитаем удаления. Затем мы можем вычислить соотношение длины к количеству авторов.
Текстовые словари могут быть не очень реалистичными, поэтому давайте ограничимся только кодом через фильтр по расширению файла!
В этом есть некоторый смещенный процесс предпочтения - более новые файлы имеют меньше возможностей для коммитов. Что, если мы ограничим файлы от 1 года и старше?
Распределение коммитов и строк кода по времени; по дням недели, по авторам; для конкретных подсистем
Мы интерпретируем это как количество добавленных и удалённых строк по дням недели. В данном случае мы сосредоточимся на каталоге Функций
И по времени суток,
Это распределение имеет смысл, учитывая, что большая часть нашей команды разработчиков находится в Амстердаме. Функция bar
помогает нам визуализировать эти распределения:
Матрица авторов, показывающая, какие авторы склонны переписывать код других авторов
sign = -1
указывает на удаление кода. Мы исключаем знаки препинания и вставку пустых строк.
Диаграмма Санке (SuperSet) позволяет это визуализировать. Обратите внимание, что мы увеличиваем наш LIMIT BY
до 3, чтобы получить тройку основных удалителей кода для каждого автора, что улучшает разнообразие в визуализации.

Алексей явно любит удалять код других. Давайте исключим его для более сбалансированного взгляда на удаление кода.

Кто является самым значительным Contributor по дням недели?
Если мы рассматриваем только количество коммитов:
Хорошо, есть некоторые возможные преимущества у самого продолжительного Contributors - нашего основателя Алексея. Давайте ограничим наш анализ последним годом.
Это всё ещё немного просто и не отражает труд людей.
Лучшим метрикой может быть, кто является основным contributor каждый день как доля от общего объёма работы, выполненного за последний год. Обратите внимание, что мы рассматриваем удаление и добавление кода одинаково.
Распределение возраста кода по репозиторию
Мы ограничиваем анализ текущими файлами. Для краткости мы ограничиваем результаты до глубины 2 с 5 файлами на корневую папку. Настройте по необходимости.
Какой процент кода для автора был удален другими авторами?
Для этого вопроса нам нужно количество строк, написанных автором, разделить на общее количество строк, которые были удалены другим контрибьютором.
Список файлов, которые были переписаны наибольшее количество раз?
Самый простой подход к этому вопросу может состоять в том, чтобы просто подсчитать наибольшее количество изменений строк по пути (с ограничением на текущие файлы), например:
Это не учитывает понятие "перезаписи", однако, когда большая часть файла изменяется в любом коммите. Это требует более сложного запроса. Если мы будем считать перепиской, когда более 50% файла удалено, и 50% добавлено. Вы можете настроить запрос в соответствии с вашим собственным пониманием того, что такое переписывание.
Запрос ограничивается только текущими файлами. Мы перечисляем все изменения файлов, группируя по path
и commit_hash
, возвращая число добавленных и удалённых строк. Используя оконную функцию, мы оцениваем общий размер файла в любой момент времени, выполняя кумулятивную сумму и оценивая влияние любых изменений на размер файла как lines added - lines removed
. Используя эту статистику, мы можем рассчитать процент файла, который был добавлен или удалён для каждого изменения. Наконец, мы считаем количество изменений файла, которые являются перепиской по файлу, т.е. (percent_add >= 0.5) AND (percent_delete >= 0.5) AND current_size > 50
. Обратите внимание, что мы требуем, чтобы файлы содержали более 50 строк, чтобы избежать учета ранних вкладов в файл как переписывания. Это также предотвращает предвзятость к очень маленьким файлам, которые могут быть чаще переписаны.
В какой день недели код имеет наибольшую вероятность остаться в репозитории?
Для этого нам нужно уникально идентифицировать строку кода. Мы оцениваем это (так как одна и та же строка может появляться несколько раз в файле) с использованием пути и содержимого строки.
Мы запрашиваем добавленные строки, соединяя их с удалёнными строками - фильтруя случаи, когда последние происходят позже первых. Это даёт нам удалённые строки, из которых мы можем вычислить время между этими двумя событиями.
Наконец, мы агрегируем по этому набору данных, чтобы вычислить среднее количество дней, которые строки остаются в репозитории по дню недели.
Файлы, отсортированные по среднему возрасту кода
Этот запрос использует тот же принцип, что и В какой день недели код имеет наибольшую вероятность остаться в репозитории - стремится уникально идентифицировать строку кода, используя путь и содержимое строки. Это позволяет нам определить время между добавлением строки и её удалением. Мы фильтруем только текущие файлы и код, а также усредняем время для каждого файла по строкам.
Кто, как правило, пишет больше тестов / кода C++ / комментариев?
Существует несколько способов, как можно подойти к этому вопросу. Сосредоточившись на соотношении кода и тестов, этот запрос относительно прост - подсчитать количество изменений в папках, содержащих tests
, и вычислить соотношение к общему количеству изменений.
Обратите внимание, что мы ограничиваем пользователей с более чем 20 изменениями, чтобы сосредоточиться на регулярных коммитерах и избежать предвзятости к единичным взносам.
Мы можем представить это распределение в виде гистограммы.
Большинство участников пишут больше кода, чем тестов, как и следовало ожидать.
Что насчет тех, кто добавляет больше всего комментариев при внесении кода?
Обратите внимание, что мы сортируем по вкладам кода. Удивительно высокий % для всех наших крупнейших участников и часть того, что делает наш код настолько читабельным.
Как меняется коммит автора со временем в зависимости от процента кода/комментариев?
Для вычисления этого по автору это тривиально,
Идеально, однако, мы хотим увидеть, как это изменяется в совокупности по всем авторам с первого дня их коммитов. Снижают ли они постепенно количество комментариев, которые они пишут?
Для вычисления этого мы сначала работаем над коэффициентом комментариев каждого автора с течением времени - аналогично Кто, как правило, пишет больше тестов / кода C++ / комментариев?. Это объединяется с датой начала каждого автора, что позволяет нам вычислять коэффициент комментариев по неделям.
После вычисления среднего значения по неделям для всех авторов мы получаем эти результаты, выбирая каждую 10-ю неделю.
Обнадеживает, что % комментариев довольно стабилен и не ухудшается, чем дольше авторы вносят изменения.
Каково среднее время, прежде чем код будет переписан, и медиана (период полураспада кода)?
Мы можем использовать тот же принцип, что и Список файлов, которые были переписаны наибольшее количество раз или наиболее авторами, чтобы определить переписывания, но рассмотреть все файлы. Используется оконная функция для вычисления времени между переписываниями для каждого файла. Из этого мы можем вычислить среднее и медиану по всем файлам.
В какое время хуже всего писать код с точки зрения того, что код имеет наибольшую вероятность быть переписанным?
Аналогично Каково среднее время, прежде чем код будет переписан, и медиана (период полураспада кода)? и Список файлов, которые были переписаны наибольшее количество раз или наиболее авторами, за исключением того, что мы агрегация по дням недели. Корректируйте по мере необходимости, например, по месяцам.
Код каких авторов наиболее "липкий"?
Мы определяем "липкий" как то, как долго код автора остается перед его переписыванием. Аналогично предыдущему вопросу Каково среднее время, прежде чем код будет переписан, и медиана (период полураспада кода)? - используя тот же критерий для переписываний, т.е. 50% добавлений и 50% удалений в файл. Мы вычисляем среднее время переписывания для каждого автора и учитываем только участников с более чем двумя файлами.
Наибольшее количество подряд дней коммитов от автора
Этот запрос сначала требует от нас расчета дней, когда автор делал коммиты. Используя оконную функцию, разбивая по авторам, мы можем вычислить дни между их коммитами. Для каждого коммита, если время с последнего коммита составило 1 день, мы помечаем его как последовательный (1), в противном случае - 0, сохраняя этот результат в consecutive_day
.
Наши последующие функции массива вычисляют самую длинную последовательность подряд единиц для каждого автора. Сначала используется функция groupArray
для сбора всех значений consecutive_day
для автора. Этот массив из 1-иц и 0, затем делится по значениям 0 на подмассивы. Наконец, мы вычисляем самую длинную подпоследовательность.
История коммитов файла построчно
Файлы могут быть переименованы. Когда это происходит, мы получаем событие переименования, при этом в колонке path
указывается новый путь к файлу, а old_path
представляет собой предыдущее местоположение, например:
Это делает просмотр полной истории файла затруднительным, поскольку у нас нет единого значения, связывающего все изменения строк или файлов.
Чтобы решить эту проблему, мы можем использовать Пользовательские Функции (UDF). В настоящее время они не могут быть рекурсивными, поэтому для определения истории файла нам необходимо определить серию UDF, которые будут явно вызывать друг друга.
Это означает, что мы можем отслеживать переименования только до максимальной глубины - приведенный пример имеет глубину 5. Маловероятно, что файл будет переименован большее количество раз, поэтому на данный момент этого достаточно.
Вызывая file_path_history('src/Storages/StorageReplicatedMergeTree.cpp')
, мы рекурсивно проходим через историю переименований, при этом каждая функция вызывает следующий уровень с old_path
. Результаты объединяются с помощью arrayConcat
.
Например,
Мы можем использовать эту возможность, чтобы теперь собрать коммиты для всей истории файла. В этом примере мы показываем один коммит для каждого из значений path
.
Нерешенные вопросы
Git blame
Это особенно сложно получить точный результат из-за невозможности в настоящее время поддерживать состояние в массивных функциях. Это будет возможно с помощью arrayFold
или arrayReduce
, которые позволяют удерживать состояние на каждой итерации.
Приблизительное решение, достаточное для анализа на высоком уровне, может выглядеть примерно так:
Мы приветствуем точные и улучшенные решения здесь.