PostgreSQL 14: Часть 1 или «июльский разогрев» (Коммитфест 2020-07)

    Выход PostgreSQL 13, о возможностях которого мы уже писали, планируется только осенью. Ничего принципиально нового в нем уже не появится. Поэтому самое время перейти к PostgreSQL 14.

    Жизненный цикл нововведений 14 версии состоит из 5 коммитфестов. Первый из которых — июльский — уже завершился, а значит есть что обсудить.

    Принятых изменений было намного больше, чем будет описано. Рассмотрены в основном патчи с новым или измененным функционалом СУБД. Именно такие изменения представляют наибольший интерес для пользователей PostgreSQL. А то что не попало, как правило, относится к следующим категорям: исправления ошибок, рефакторинг, инфраструктура проекта, документация(почти всегда), комментирование кода, тестирование.

    Да и вообще, цель описать абсолютно всё не ставилась. Отслеживание изменений в точности — очень трудоемкий процесс. Поэтому к решению важных задач, таких как переход на новую версию, всегда нужно готовиться по замечаниям к выпуску. А сейчас можно просто расслабиться и почитать «вести с полей» разработки. Если появится желание самостоятельно попробовать новинки, то придется собирать PostgreSQL из исходных кодов. А вот документация на принятые патчи доступна онлайн.

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

    Итак, начинаем.

    Новости PostgreSQL 13


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

    pg_stat_activity.leader_pid IS NULL для ведущего процесса
    commit: 11a68e

    После заморозки кода 13 версии изменились правила заполнения нового столбца leader_pid в pg_stat_activity — номер ведущего процесса при выполнении параллельных запросов. Первоначально предполагалось заполнять этот столбец не только для всех рабочих процессов, участвующих в выполнении запроса, но и для самого ведущего процесса. Теперь у ведущего процесса leader_pid будет оставаться незаполненным.

    Документация: новое форматирование таблиц в главе «Функции и операторы»
    commit: e894c61

    Таблицы на официальном сайте для 13 и последней разрабатываемой версии уже выглядят в новом стиле. Такое оформление таблиц занимает больше места, но лучше подходит для вывода в другие форматы, например в PDF. Для сравнения можно посмотреть на форматирование математических функций и операторов в новом и старом стиле.

    Параметра wal_keep_segments больше нет
    commit: c3fe108

    Вместо него wal_keep_size. Теперь нужно оперировать не количеством сегментов в pg_wal, а их размером. Сделано для унификации с новым для 13 версии параметром max_slot_wal_keep_size — максимальный размер файлов WAL, сохраняемых для слотов репликации.

    Мимо этого изменения не смог пройти depesz в своей новой серии Waiting for PostgreSQL 14. Но изменение попадет и в 13 версию, начиная с beta3.

    pg_validatebackup → pg_verifybackup
    commit: dbc60c5

    Новая для 13 версии утилита pg_validatebackup после заморозки кода переименована и будет называться pg_verifybackup.

    sslminprotocolversion → ssl_min_protocol_version, sslmaxprotocolversion → ssl_max_protocol_version
    commit: 401aad6

    Новые для 13 версии параметры sslminprotocolversion и sslmaxprotocolversion переименованы: ssl_min_protocol_version и ssl_max_protocol_version.

    ALTER… NO DEPENDS ON EXTENSION ...
    commit: 5fc7039

    В версии 9.6 добавили команду ALTER… DEPENDS ON EXTENSION… для указания, что объект зависит от расширения. Но забыли реализовать обратный вариант команды на снятие зависимости. Упущение исправлено и портировано в 13 версию. Из-за отсутствия жалоб портировать в более ранние версии не стали.

    Новый параметр hash_mem_multiplier
    commit: d6c08e2

    Возможность не выходить за установленные пределы оперативной памяти(work_mem) при создании хеш-таблицы появилась еще на момент заморозки кода 13 версии. Если на этапе выполнения запроса окажется, что планировщик ошибся в оценках и work_mem не хватает — будут использоваться временные файлы. Это хорошо, т.к. нынешнее поведение — использовать столько памяти, сколько потребуется, а это не всегда приятное соревнование с OOM-killer.

    Для управления поведением планировщика предлагались параметры enable_groupingsets_hash_disk и enable_hashagg_disk. К моменту выпуска 13 beta2 оба параметра заменили на hashagg_avoid_disk_plan. Но и этого оказалось недостаточно. Теперь вместо hashagg_avoid_disk_plan появился hash_mem_multiplier.

    Название взято по аналогии с bgwriter_lru_multiplier и представляет собой множитель, на который планировщик умножает work_mem, чтобы получить объем памяти, который может использоваться для операции хеширования. Значение по умолчанию 1, что не меняет предыдущего поведения — память для хеширования не должна превышать work_mem.

    Управляя этим параметром, и не меняя work_mem, можно дать планировщику больше шансов выбрать варианты с хеш вместо менее требовательных к наличию свободной памяти сортировок (в частности hash join вместо merge join).

    EXPLAIN ANALYZE: изменения в формате вывода узла HashAggregate
    commit: 0e3e1c4

    А вот как будут выглядеть операции хеширования в плане запроса. Посмотрим на примере такого запроса к демо-базе:

    EXPLAIN (ANALYZE) SELECT tf.fare_conditions, count(*)
                        FROM ticket_flights tf GROUP BY tf.fare_conditions;
    

    Ограничимся лишь фрагментом плана с узлом HashAggregate.

    Версия 12:

    ->  Partial HashAggregate
          Group Key: fare_conditions
          ->  Parallel Seq Scan on ticket_flights tf
    

    Версия 13beta2:

    ->  Partial HashAggregate
          Group Key: fare_conditions
          Peak Memory Usage: 24 kB
          Worker 0:  Peak Memory Usage: 24 kB
          Worker 1:  Peak Memory Usage: 24 kB
          ->  Parallel Seq Scan on ticket_flights tf
    

    Теперь:

    ->  Partial HashAggregate
          Group Key: fare_conditions
          Batches: 1  Memory Usage: 24kB
          Worker 0:  Batches: 1  Memory Usage: 24kB
          Worker 1:  Batches: 1  Memory Usage: 24kB
          ->  Parallel Seq Scan on ticket_flights tf
    

    Чтобы не играть в игру «найди 10 отличий» вот они, и их 5:

    • счетчик пакетов (batches) начинается с 1, а не 0;
    • Batches выводится всегда, даже если пакет был всего 1;
    • для текстового формата выводится Batches вместо HashAgg Batches;
    • для текстового формата выводится Memory Usage вместо Peak Memory Usage;
    • Batches выводится перед Memory Usage

    Изменение особенно важно для тех, кто разрабатывает свои инструменты для анализа плана запросов.

    Отслеживание пауз в работе очистки
    commit: cedffbd

    Паузы в работе очистки ([autovacuum_]vacuum_cost_delay) теперь отображаются в pg_stat_activity.wait_event_type=Timeout и wait_event=VacuumDelay. До этого считалось что процесс ничего не ждет и столбцы wait_enent* были не заполнены.

    Хватит о 13 версии, начиная со следующего раздела переходим к эксклюзиву версии 14.

    Клиентские приложения


    vacuumdb --no-truncate --no-index-cleanup
    commit: 9550ea3

    Утилита vacuumdb получила два новых аргумента командной строки: --no-truncate и --no-index-cleanup. С их помощью можно отключать соответствующие параметры VACUUM, появившиеся в 12 версии: TRUNCATE (нужно ли уменьшать файл, если последние страницы полностью очищены) и INDEX_CLEANUP (нужно ли выполнять очистку индексов).

    Мониторинг и управление


    pg_stat_statements: отслеживание количества обработанных строк у некоторых служебных команд
    commit: 6023b7e

    Раньше для команд CREATE TABLE AS, SELECT INTO, CREATE MATERIALIZED VIEW, FETCH в pg_stat_statements.rows не фиксировалось количество обработанных записей. Теперь эта информация стала доступна:

    CREATE TABLE bookings_copy AS SELECT * FROM bookings;
    
    SELECT 262788
    SELECT query,rows FROM pg_stat_statements 
        WHERE query LIKE 'CREATE TABLE bookings_copy%';
    
                            query                         |  rows  
    ------------------------------------------------------+--------
     CREATE TABLE bookings_copy AS SELECT * FROM bookings | 262788
    

    Новые столбцы в pg_prepared_statements: generic_plans, custom_plans
    commit: d05b172

    В pg_prepared_statements появилась информация о количестве запросов, выполненных с общим и частным планом. Следующая цель — добавить такую же информацию в pg_stat_statements.

    По умолчанию (plan_cache_mode = auto), если после пяти планирований запроса планировщик видит, что общий план будет не хуже частного, то он переходит на общий план, минимизируя свою работу. В следующем примере подготовленный запрос выполняется семь раз и после пятого повторения последние два раза выполняются уже с общим планом:

    \d pg_prepared_statements
    
                      View "pg_catalog.pg_prepared_statements"
         Column      |           Type           | Collation | Nullable | Default
    -----------------+--------------------------+-----------+----------+---------
     name            | text                     |           |          |
     statement       | text                     |           |          |
     prepare_time    | timestamp with time zone |           |          |
     parameter_types | regtype[]                |           |          |
     from_sql        | boolean                  |           |          |
     generic_plans   | bigint                   |           |          |
     custom_plans    | bigint                   |           |          |
    
    PREPARE q(text) AS SELECT count(*) FROM bookings WHERE book_ref = $1;
    SELECT $$EXECUTE q('ABCDEF')$$ FROM generate_series(1,7)\gexec
    
    SELECT generic_plans, custom_plans FROM pg_prepared_statements WHERE name = 'q';
    
     generic_plans | custom_plans
    ---------------+--------------
                 2 |            5
    

    Доступ не суперпользователям к функциям, работающим с источниками репликации
    commit: cc07264

    Функциями, работающим с источниками репликации (pg_*replication_origin*), теперь могут пользоваться не только суперпользователи. Ранее проверка на суперпользователя была жестко зашита в коде. Проверку убрали и отобрали права на исполнение у public. Теперь суперпользователь может командой GRANT выдать разрешение на выполнение этих функций любым пользователям. Это в том числе дает возможность запрашивать данные из представления pg_replication_origin_status обычным пользователям.

    В начале переписки рассматривалась идея предоставить роли pg_monitor доступ к pg_replication_origin_status. Но это в настоящий момент не реализовано.

    Функция pgstattuple_approx поддерживает таблицы TOAST
    commit: ee0202d

    Функция pgstattuple_approx предоставляет приблизительную, но более быструю, альтернативу функции pgstattuple из одноименного расширения для оценки распухания объектов базы данных. Ускорение обеспечивается тем, что таблица сканируется не целиком, а с учетом карты видимости и карты свободного пространства. До 14 версии примерную оценку нельзя было получить для таблиц TOAST. Теперь это исправлено.

    Производительность


    У этой группы патчей есть очень хорошая особенность. Как правило, для получения лучшего быстродействия ничего не нужно делать. Просто переходим на новую версию и всё начинает работать быстрее. Хотя для некоторых изменений предусмотрены «ручки» (параметры), которые нужно научиться аккуратно подкручивать для получения наилучшего результата.

    Кажется, что для описания увеличения производительности хорошо бы привести результаты замеров. Но их не будет. Во-первых, это достаточно сложная работа, требующая очень тщательной подготовки, разработки методики и описания условий проведения тестов. А во-вторых, какие бы впечатляющие результаты не приводились в замерах, они всё равно получены на некой абстрактной системе. И полностью доверять им не стоит. Любую оптимизацию нужно проверять на своей собственной системе и нагрузке. Поэтому поверим разработчикам PostgreSQL, которые на своих тестах получили положительные результаты.

    Оптимизация добавления записей в каталоги pg_attribute и pg_shdepend
    commit: e3931d0

    Добавление набора записей в pg_attribute, например при создании таблицы, теперь выполняется за один раз. По словам разработчиков, в некоторых случаях это приводит к 15% сокращению записи в WAL. Такая же оптимизация для pg_shdepend.

    Оптимизировано выполнение операторов PL/pgSQL
    commit: 1f902d4, fe2e206

    PL/pgSQL стал чуть быстрее. Правда, это не особенно заметно на архитектуре x86_64, а вот aarch64 выиграла однозначно.

    Оптимизация RETURN QUERY в PL/pgSQL
    commit: 2f48ede

    Оптимизирована не только выдача результата запроса из функции, но и добавлена возможность параллельного выполнения запросов в RETURN QUERY, RETURN QUERY EXECUTE.

    Создадим функцию с оператором RETURN QUERY.

    CREATE FUNCTION get_fc(OUT fare_conditions varchar, OUT total bigint)
        RETURNS SETOF RECORD AS $$
    BEGIN
        RETURN QUERY SELECT tf.fare_conditions, count(*)
                       FROM ticket_flights tf GROUP BY tf.fare_conditions;
    END;
    $$ LANGUAGE plpgsql STABLE PARALLEL SAFE;
    

    Подключим auto_explain для получения плана запроса и выполним функцию:

    SET auto_explain.log_min_duration = 0;
    SET auto_explain.log_nested_statements = on;
    SELECT * FROM get_fc();

    В журнале видим, что использовался план с параллельным выполнением:

    \! tail -n18 logfile
    
    2020-08-06 23:23:34.825 MSK [17278] LOG:  duration: 80.701 ms  plan:
        Query Text: SELECT tf.fare_conditions, count(*)
                           FROM ticket_flights tf GROUP BY tf.fare_conditions
        Finalize GroupAggregate  (cost=16250.86..16251.62 rows=3 width=16)
          Group Key: fare_conditions
          ->  Gather Merge  (cost=16250.86..16251.56 rows=6 width=16)
                Workers Planned: 2
                ->  Sort  (cost=15250.84..15250.85 rows=3 width=16)
                      Sort Key: fare_conditions
                      ->  Partial HashAggregate  (cost=15250.79..15250.82 rows=3 width=16)
                            Group Key: fare_conditions
                            ->  Parallel Seq Scan on ticket_flights tf  (cost=0.00..13072.19 rows=435719 width=8)
    2020-08-06 23:23:34.825 MSK [17278] CONTEXT:  SQL statement "SELECT tf.fare_conditions, count(*)
                           FROM ticket_flights tf GROUP BY tf.fare_conditions"
        PL/pgSQL function get_fc() line 3 at RETURN QUERY
    2020-08-06 23:23:34.825 MSK [17278] LOG:  duration: 82.101 ms  plan:
        Query Text: SELECT * FROM get_fc();
        Function Scan on get_fc  (cost=0.25..10.25 rows=1000 width=40)
    

    Выделение «быстрой» динамической разделяемой памяти для параллельных запросов
    commit: 84b1c63

    Когда для общей разделяемой памяти используются огромные страницы, новым параметром min_dynamic_shared_memory при запуске сервера можно выделить область динамической разделяемой памяти для параллельно выполняемых запросов. Используя эту «быструю» память, параллельные запросы выполняются существенно эффективнее. Если этой области памяти им не хватит, то будет выделяться дополнительная память способом указанным в dynamic_shared_memory_type, но ускорения уже не получится.

    Параллельные запросы: данные в узле Gather обрабатываются более эффективно
    commit: cdc7169

    И этим всё сказано. Никаких настроек, параллельные запросы станут работать быстрее.

    Все функции в расширении btree_gist помечены как PARALLEL_SAFE
    commit: d98c08c

    Теперь запросы, использующие операторы этого расширения, могут выполняться в параллельном режиме.

    Оптимизация ввода-вывода для параллельного последовательного сканирования
    commit: 56788d2

    Раньше рабочие процессы получали на обработку по одному блоку за раз. Теперь получают порциями, размер которых зависит от размера таблицы. Тестирование патча показало хорошие результаты на дисках SSD для операционных систем Linux, FreeBSD, Windows. Однако для облачных провайдеров результаты не столь очевидны.

    Кеширование размера файлов отношений при восстановлении
    commit: c5315f4

    При восстановлении, за счет кеширования, существенно сократилось количество вызовов, вычисляющих размер файлов lseek(SEEK_END). А это сокращение дисковых операций. Реплики работают в режиме восстановления всегда только в один процесс. Любая оптимизация здесь к месту.

    COPY FROM в двоичном формате стала быстрее
    commit: 0a0727c

    Увеличена производительность загрузки данных командой COPY FROM в двоичном формате. В частности простые тесты показали ускорение на 30% в Linux, еще больше в MacOS.

    COPY FROM в двоичном формате расходует меньше памяти
    commit: cd22d3c

    Еще одно небольшое улучшение для COPY FROM в двоичном формате позволяет сэкономить ~ 65Кб оперативной памяти на запуск.

    Репликация


    Логическая репликация сможет передавать данные в двоичном формате
    commit: 9de77b5

    Это изменение делает логическую репликацию быстрее. При создании/изменении подписки можно запросить у сервера публикации передачу в двоичном формате:

    CREATE|ALTER SUBSCRIPTION … WITH (BINARY = ON)
    

    По умолчанию двоичный формат отключен. После включения двоичные данные будут передаваться для тех типов, у которых определены функции send/receive. В противном случае используются функции указанные для input/output.

    Сервер


    Системные функции получения информации и адресации объектов возвращают NULL для несуществующих объектов
    commit: 2a10fdc

    В предыдущих версиях вызов подобных функций приводил к ошибке:

    SELECT pg_describe_object('pg_class'::regclass, 0::oid, 0);
    
    ERROR:  cache lookup failed for relation 0
    

    Но ошибка cache lookup failed предназначена для внутреннего использования и не должна быть видна пользователям. Все видимые типы ошибок описаны в документации. Теперь запрос возвращает NULL.

    Изменение будет полезно тем, кто пишет собственные инструменты/скрипты для работы с системным каталогом.

    Управление размером огромных страниц
    commit: d2bddc2

    Новый параметр huge_page_size для управления размером огромных страниц. По умолчанию 0, размер огромных страниц определяется настройками ОС.

    У типа numeric появились значения «бесконечность» и «минус бесконечность»
    commit: a57d312

    Поведение такое же как и у соответствующих значений для типов с плавающей точкой. Это дает возможность конвертировать значения с плавающей точкой (и интерфейсы их использующие) в числа с произвольной точностью.

    Разное


    Добавлен метод amadjustmembers для проверки членов класса операторов
    commit: 9f96827

    Началось всё с того, что в расширении intarray оказалось невозможным средствами SQL удалить оператор <@ из класса операторов для GiST. В результате был доработан интерфейс для индексных методов доступа. В частности появилась функция amadjustmembers. Расширение intarray пока осталось без изменений.

    Операторы + и − для типа pg_lsn
    commit: 9bae7e4

    Еще не успели попробовать появившиеся в 13 версии агрегатные функции min и max для типа данных pg_lsn, как в 14-ю версию добавили арифметические операторы для добавления/вычитания байтов:

    SELECT w.lsn, w.lsn +1, w.lsn -1 FROM (SELECT pg_current_wal_lsn()) AS w(lsn);
    
        lsn    | ?column?  | ?column?  
    -----------+-----------+-----------
     0/EF04B78 | 0/EF04B79 | 0/EF04B77
    

    Получение информации об источнике репликации для транзакции
    commit: b1e48bb

    Источник репликации можно получить новой функцией pg_xact_commit_timestamp_origin. Эту же информацию добавили в pg_last_committed_xact.

    Чтобы получить информацию о времени фиксации транзакции и источнике репликации должен быть включен параметр track_commit_timestamp:

    SHOW track_commit_timestamp;
    
     track_commit_timestamp
    ------------------------
     on
    
    SELECT pg_current_xact_id();
    
     pg_current_xact_id 
    --------------------
                    721
    
    SELECT * FROM pg_xact_commit_timestamp_origin('721'::xid);
    
               timestamp           | roident
    -------------------------------+---------
     2020-08-10 13:57:18.103297+03 |       0
    
    SELECT * FROM pg_last_committed_xact();
    
     xid |           timestamp           | roident
    -----+-------------------------------+---------
     721 | 2020-08-10 13:57:18.103297+03 |       0
    

    На этом пока всё. Продолжение о сентябрьском коммитфесте.
    Postgres Professional
    Разработчик СУБД Postgres Pro

    Комментарии 14

      +2

      Таблицы в доке зря переиначили. С телефона искать глазами очень неудобно в новой версии.

        +1
        Ради форматирования и перевода в др.форматы, уничтожили читабельность. Молодцы, нечего сказать. Лучше бы комменты реализовали в документации.
          +2

          Комменты в документации — это один из столпов, на которых стоит невероятная популярность PHP, кстати. Странно, что еще не все их делают.

            0

            Вряд ли комментарии сыграли в этом хоть какую-то роль. Когда PHP появился, у него был самый низкий порог входа: на всех других языках программирования требовалось изучать какие-то интерфейсы типа CGI, часто писать самостоятельно значительную часть обработки HTTP запросов и генерации ответов, тогда как в PHP ничего этого делать не надо было. Объективно, сложность написания простейшего скрипта на PHP была на порядки меньше, чем на любом другом языке.


            Потом другие языки несколько догнали в плане лёгкости, появились различные фреймворки и упростилась процедура подключения приложения к веб-серверу. Но до сих пор это требует всё же несколько более усилий, чем просто забросить файл в почти любое место на веб-сервере. Пускай, это будет адская мешанина HTML и PHP, в которой через год уже не разобраться, но это не имеет значения для человека, который вообще не хочет читать документацию.


            Мне сложно понять зачем нужны комментарии в документации. Что такого можно написать в комментариях к CREATE TABLE или SELECT, ради чего стоило бы в них рыться?

            +2
            Возможно комментарии в документации реализованы не лучшим образом, но они есть. Для всех поддерживаемых версий (сейчас это с 9.5 по 12) внизу любой страницы документации есть раздел Submit correction. Заполняете форму и замечание после модерации приходит в список рассылки pgsql-docs. Там замечание обязательно рассматривают и если решат, что нужно внести изменения — вносят.
            Да, списки рассылки не самый современный инструмент совместной работы, но проект postgresql пользуется именно им.
            +1
            На мой взгляд и прежний вариант был далек от идеала. По крайней мере, когда я первый раз увидел форматирование функций в виде таблиц мне это показалось очень неудобным. Потом привык.

            Нынешний вариант не выглядит явным улучшением. Да, проще конвертировать в PDF, но к читаемости вопросы есть. Попробуем с этим поработать.

            А вообще приятно, что несколько первых комментариев именно по документации. Это к извечной шутке о том, «кто её читать будет». Читают, и это здорово!
              +1
              Еще как читаем. От качества документации напрямую зависит популярность проекта, у PG она на высоком уровне. Но глядя на изменения — таблица — это четкое структурирование, что, зачем, куда, пример. А новинка, 10х по времени на понимание где якорь. Про комменты: в основном люди туда пишут примеры. Смотрите тот же PHP, MYSQL, Python. И примеры это очень важно. Накопление базы вменяемых примеров — это годы… И желательно пропустить через рецензирование. Текущая схема «аля комментирования» от слова — не работоспособна (для масс).
                0
                Согласен, база примеров (именно вменяемых) была бы очень полезна.
            0
            а что там с undo на оракловый манер? vacum когда выпилят?
              +3

              Выпилят примерно никогда. Тенденция такая, что стандартный heap останется универсальным движком, а альтернативы будут целиться в узкие области применения, где у них есть шансы показать лучший результат.
              Конкретно насчет undo — это надо следить за успехами https://github.com/EnterpriseDB/zheap

              +2

              Для производительности полезен коммит 56788d2156 (выделение блоков группой для параллельных запросов) и стоило бы сказать, что убрали PGXACT (серия патчей snapshot scalability, в частности 73487a60fc).
              Также изменили системные лимиты для wraparound-а в cd5e82256d.

                0
                Виктор, спасибо!

                Кроме коммита 56788d2156 (в статье он называется «Оптимизация ввода-вывода для параллельного последовательного сканирования»), для производительности параллельных запросов еще интересен коммит cdc7169 (про Gather). На моих простых тестах на ноутбуке я получал стабильно 12% ускорение против 13beta2 на запросе с группировкой. Правда, выносить результаты таких «детских» экспериментов в статью не стал. А в комментариях, думаю, можно поделиться.

                Что касается коммитов о PGXACT и wraparound, то они случились уже в августе и формально под июльский коммитфест не попадают. А вот в следующей статье о сентябрьском коммитфесте будет рассмотрено всё принятое с 01.08 по 30.09 и они там будут.
                  0

                  Да, теперь увидел. Я искал по хэшу коммита, но он у вас обрезан, поэтому подумал что нет.
                  Спасибо!

                    0
                    Полное значения хеша слишком много места занимает, поэтому решил сократить. А для однозначной идентификации коммита вроде хватило.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое