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

Проект называется vinyl. У него есть репозиторий. Пользоваться им можно и нужно! Про то, как это делать, можно узнать из README - я думаю, всё вполне соответствует ожиданиям от асинхронной версии. Здесь я хочу рассказать про то, что, как говорится, удалось узнать в процессе.

Сейчас vinyl - только асинхронный фреймворк, но первая версия такой не была, она поддерживала как синхронный, так и асинхронный I/O. API асинхронной версии было зеркальным отражением синхронной. Мне всегда было интересно, почему нельзя так сделать - и вот попробовал сам. Как оказалось, это возможно, и относительно нетрудно, особенно, если все операции выполняются последовательно, как в django orm.

Однако вскоре меня начали мучить сомнения, что поддержка сразу обеих версий может быть медвежьей услугой, и долгосрочно не оправдано. И, конечно, этих сомнений было достаточно, чтобы я простился со своим "универсальным" кодом и сделал фреймворк async-only.

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

Интересная, конечно, ситуация: лично я, например, не знаю, что лучше: только синхронный или только асинхронный django. Потому что эндпоинты/урлы, для которых нужен асинхронный I/O, обычно в явном меньшинстве. Но, тем не менее, примем, что асинхронный orm всем нужен как статистический факт.

Идём дальше. Я обнаружил один интересный факт, который существенно облегчает портирование orm на асинхронный I/O. Он касается операций записи в базу данных - тех, которые обычно приводят к серии insert/update/delete запросов. Так вот, в отличие от select-запросов, нам не очень важен результат в таких запросах. То есть, важно, чтобы они успешно выполнилось, а, скажем, сколько строк обновил UPDATE - не очень важно. Для INSERT может понадобиться первичный ключ сохранённого объекта, но это отдельный случай.

Решение напрашивается, как говорится: логику синхронного кода мы не меняем, но вместо выполнения запросов мы только сохраняем их в списке, а в самом конце уже асинхронно выполняем. Таким образом, логику многих CRUD операций, а также всяких related менеджеров, почти не пришлось менять. Этот трюк позволил сохранить высокоуровневый API django для операций записи - в противном случае, я думаю, он стал бы более минималистичным.

В общем, всё это было для затравки, гляньте на репозиторий и скажите, насколько он хорош.

Комментарии (6)


  1. turbidit
    29.08.2022 11:47
    +2

    А есть какие-то тесты для сравнения производительности?


    1. abetkin Автор
      29.08.2022 12:01

      разве что производительность драйверов бд имеет смысл сравнивать, например, я не могу выбрать между psycopg3 и asyncpg (пока используется первый)


  1. whoisking
    29.08.2022 18:19

    Не совсем понял плюсы этого, в джанге релизнули уже асинхронный орм в 4.1. Из минусов то, что какие-то миддлвари ещё могут не быть асинхронными и drf не умеет в асинк, что пока печаль(


    1. abetkin Автор
      29.08.2022 18:30

      ну это нативно асинхронный)


      1. whoisking
        29.08.2022 21:30
        +1

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


        1. abetkin Автор
          29.08.2022 22:03

          ~