Дисклеймер
Всем привет! В данной статье я поделюсь своим опытом написания сервиса. Я не являюсь опытным или профессиональным разработчиком, я пишу свой проект и мои решения могут быть не самыми оптимальными. Эта статья состоит в основном из ошибок, которые я совершил. Мой путь не является правильным и потому — судите «строго».
Создание структуры проекта
Первое, что приходит в голову при создании своего проекта, это как правильно организовать работу всего приложения, что с чем должно взаимодействовать, и как вообще это должно работать. Поэтому первым делом я решил накидать какую-то структуру проекта.
На картинке выше, я привел схему, которая появилась у меня в голове, когда я только начинал, и эта схема очень сильно напоминает MVC паттерн (По крайней мере мне так кажется). Что из этого выходит. Весь сервис можно разделить на 3 большие части:
-
Frontend — часть, которую видят пользователи, и где они могут загружать свои аудио/видео файлы
-
Controller — предоставляет api для frontend, чтобы можно было получать данные из бд и хранилище, писать в них и ставить задачи для бэка и получать некий callback, для обновления задач
-
Backend — выполняет сложные задачи, диаризации, транскрибации и т.д.
Давайте быстро пробежимся по тому, что это за сервис. Автопротоколирование — это некоторое приложение в которое ты можешь загрузить свою видео/аудио-запись, а на выходе получишь протокол и транскрипцию этого файла.
Так как проект домашний, для базы данных выбрал Postgres, для хранилища файлов — minio. Накидал docker-compose, запустил, всё супер, работает.
Почему я решил работу с базой оставить контроллеру а не бэку. Всё просто, задачи, которые обрабатывает бэк — крайне ресурсоёмкие, и в моём случае, распараллелить их не удается (один whisper кушает 6-8ГБ ОЗУ, а если брать нормальную модельку, то там минимум гигов 40). В совокупности, если бы бэку еще нужно было общаться с базой и отвечать на запросы фронта, это стоило бы ооооочень дорого.)
Вот тут я понял, что бэк я в любом случаю оставлю на Python, потому что там уже есть удобные библиотеки для работы с разными моделями, на чем писать контроллер и фронт.
Выбор языков для проекта
Для контроллера я выбрал то, что давно хотел начать изучать — Go, что касается фронта, на рандоме тыкнул в React с TypeScript, что в целом оказалось не плохо (Если бы я, конечно, умел писать фронт нормально)
Выбор сделан, работа закипела, но только по контроллеру и бэкенду.
Сделал простую структуру для конфигурации бэка и контроллера, и запихнул конфигурацию в yaml
Контроллер:
pg: host: 127.0.0.1 port: 5432 user: virus password: postgres database: db server: host: 0.0.0.0 port: 8080 s3: endpoint: http://127.0.0.1:9000 access_key: virus secret_key: password bucket: bucket region: ru-central1 minio: true
Бэк:
tokens: hf_token: ${HF_TOKEN} ollama: url: localhost model: llama3.3 num_context: 5000
Ну и сделал им структуры. (Не знаю, стоит ли их приводить, всегда можно глянуть на гитхабе, тут, пожалуй, будет кода минимум)
Структура баз данных
Как видно из конфигурации, у меня есть подключение к базе данных (у бэкэнда она тоже была, но я уже не найду). Ну и раз мне нужна база, сразу же сделал структуру базы.
Идея была тупой простой, у меня есть контроллер, который записывает всё необходимое в базу данных. Все таблицы задач (diarize, convert, transcribe, report) связаны с таблицей с Совещаниями, плюс еще несколько вспомогательных таблиц. Как это должно было работать:
-
Приходит пользователь и загружает свою аудио/видео-запись
-
Контроллер обрабатывает и кладет в S3 файл, а в бд добавляет запись
-
Пользователь нажимает кнопочку
-
Контроллер достает данные по совещанию и по порядку ставит задачи в бэк
-
Бэк получает задачу и записывает её в свою базу, как задачу в очереди
-
Параллельно с этим бэк читает поставленные задачи из бд, и отдает ответ контроллеру по API
-
Контроллер пишет полученные результаты задачи и ставит новую по необходимости
-
Вы великолепны
Вроде бы всё идеально. Позже до меня дойдет, что я делаю какой-то бред, что я реализую очередь через дополнительную базу данных, но это где-то в будущем.)
Что делаем дальше, простой клиент для базы в бэке и в контроллере (я выбрал базу pg, так как в целом с ней знаком). Показывать буду на примере Go, но в целом в питоне было сделано так же стандартно.
type DBConfig struct { Host string `yaml:"host"` Port int `yaml:"port"` User string `yaml:"user"` Password string `yaml:"password"` Database string `yaml:"database"` } type PGdatabase struct { Config DBConfig Pool *pgxpool.Pool } func NewDatabase(ctx context.Context, cfg DBConfig) (*PGdatabase, error) { connectionInfo := fmt.Sprintf( "postgres://%s:%s@%s:%d/%s?sslmode=disable", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Database) pool, err := pgxpool.New(ctx, connectionInfo) if err != nil { return nil, fmt.Errorf("Database connection error: %s", err) } if err = pool.Ping(ctx); err != nil { return nil, fmt.Errorf("Database ping error: %s", err) } return &PGdatabase{ Pool: pool, Config: cfg, }, nil } func (d *PGdatabase) CloseConnection() { if d.Pool != nil { d.Pool.Close() } }
То есть я создал структуру конфигурации подключения к базе, проверку, всё ли нормально работает, есть ли база и т.п, и создал свою структуру в которую писал pool и конфигурацию, чтобы не нужно было перечитывать (просто так захотелось, окей).
То же самое проделал на бэке, всё завелось, добавил ручку чтобы можно было покидать запросы, посмотреть, что пишется в базу, что читается. Для запросов к базе использовал sqlc, крайне удобная вещь, где ты просто пишешь свой запрос и получаешь готовый код (спасибо тем людям, которые пишут штуки для генерации кода)
-- name: GetConversations :many SELECT * FROM Conversations;
Привел пример простого запроса, он сам генерирует нужные типы данных, сам запрос на Go и всё что к нему прилагается, но нужно освоить конфиг, чтобы типы были нормальными а не pg.string и т.п. (с этим я тоже не мало пострадал, но 1 раз настроил и забыл)
Ну вот, первый запуск, все стартовало успешно, кинул запрос, записал в базу, прочитал из базы (писал curl’ом). Всё отлично, а что дальше…
Что же делать дальше…
А дальше нужно было делать постановку задач для бэка. И вот тут я понял свои ошибки. Использовать базу данных как очередь — плохо. Когда у тебя две базы данных — а какой смысл. Бэк принимает запросы по API? Вообще ужас. Callback с выполненной задачей по API? Тоже тяжело и бред, задачи могут потеряться. Но в тот момент я не видел этих проблем.
Ручки
Поэтому что я решил сделать? Правильно, сделать CRUD (Create, Read, Update, Delete) ручки для работы с базой. Это было просто, пишешь запрос в sqlc, генерируешь код, пишешь функцию, засовываешь эту функцию в ручку, которую потом дергаешь curl’ом по необходимости.) Сделал ручку для каждой таблички, для каждого значения, для создания строк, для изменения каждого значения.
Классно, супер, но зачем. Задача же стоит сделать общение с бэком. Что делать в этом случае. Правильно, отрефакторить бэк. В итоге переделал бэк, сделал там на fastAPI уже новые ручки, больше ручек. Я мог через swagger поставить разные задачи, закинуть туда файл по ссылке на S3. Всё так классно работает, но как это связать…
Рефакторинг
Я начинаю сам себе закапывать, пытаюсь понять, как сделать лучше, пишу доп функции, пишу генерики, чтобы было меньше повторений кода. Пишу пишу пишу пишу… А до результата ближе не оказываюсь. Переписываю, переписываю, переписываю, и вновь, я ухожу от главного, как поставить задачу в бэк.
В итоге на бессмысленное переписывание я убил неделю времени. Чтобы как-то сдвинуться с места, я решил спросить у друга, что я делаю не так. В ответ получаю, что почти всё. Но самое тупое — очередь через базу данных, бесконечное количество ручек и бесполезное улучшение того, что просто никак не будет использоваться при работе приложения.
Вывод
Для чего я это написал. Я описал то, как я начинал писать своё приложение. Описал решения, которые принимал, куда потратил много времени. Написал то, на какие грабли я наступил, этот этап, именно начала своего сервиса кроме как хождение по граблям — охарактеризовать никак не могу.
Если есть возможность, всегда лучше спросить у более опытных друзей.
Скоро напишу продолжение, про то, как я исправлял ошибки, менял базу, и многое другое.
ссылка на оригинал статьи https://habr.com/ru/articles/911166/
Добавить комментарий