Простая реализация S3-подобного сервиса для загрузки/скачивания файлов
Поднять окружение
docker compose up
Запустить функциональные тесты
make func-tests
Также можно потестировать вручную с помощью curl. Примеры:
Сервис состоит из 3 компонентов:
- REST API - обработка входящих запросов пользователей
- Meta Storage - хранение мета-информации о файлах
- File Storage - хранение партов файлов
В качестве http роутера используется Fiber.
Для простоты реализован в виде inmemory хранилища - map + RWMutex (без шардирования, бакетов и т.п.). Для сохранения стейта между рестартами приложения для MetaStorage реализовано простое сохранение/загрузка его состояния в файл/из файла.
(В реальности в качестве MetaStorage должно быть какое-то шардированное хранилище)
gRPC-сервер для работы с файлами. Передает файлы по стриму.
gRPC выбран за его простоту и кодогенерацию кода клиента/сервера.
В рамках тестового задания другие компоненты не прорабатывались. Но вполне желательно иметь еще кеш для "горячих" данных.
File -> имя бакета + ключ
Каждая загрузка создает новую версию файла FileVersion -> версия, статус и массив партов. Статус при начале загрузки Loading. По окончании загрузки статус меняется на Error или Ready.
Скачать можно только файл со статусом Ready.
В рамках тестового задания не было реализовано никакого "фонового" процесса по очистке не до конца загруженных файлов, а также, например, чистке старых версий файлов.
Состоит из номера парта и массива серверов, на который данный парт был загружен.
Для простоты каждый парт грузится только на 1 файловый сервер. Но можно сделать настройку, например,
Replication Factor для репликации файла на несколько серверов.
Отвечает за план распределения партов файлов по серверам при загрузке файла. Для каждого сервера задан вес. Чем больше вес, тем чаще он будет задействован при загрузке файлов.
Также конфигурируется еще 2 параметрами:
- минимальный размер парта
- максимальное число партов
К примеру, если минимальный размер парта 8Kb, а размер файла 15Kb - то он будет поделен всего лишь на 2 парта.
В рамках тестового задания не было реализовано никакого хранения стейта текущих серверов. В реальности алгоритм должен учитывать и этот, и многие другие факторы.
Добавление новых серверов можно обыграть заданием для них повышенных весов. В данный момент приложение не поддерживает динамическое добавление серверов (требуется перезапуск с обновленными настройками). В реальности эти данные можно как-то дискаверить.
Координирует работу всех логических компонентов в REST API.
При загрузке файла:
- получает план загрузки от PartDistributor
- создает новую версию файла в Meta Storage
- последовательно читает чанками файл, распределяя парты серверам из полученного плана. По готовности парта создает запись в Meta Storage
- По окончании загрузки помечает FileVersion как Ready в случае успеха, или Error в случае ошибки.
При скачивании файла:
- получает список партов (при условии существования файла)
- последовательно загружает парт за партом и чанками отправляет прочитанную информацию клиенту
В случае ошибки - оркестратор прерывает процесс загрузки/скачивания файлов - в данный момент нет никаких ретраев. В случае прерывания загрузки пользователем - запрос тоже завершается за счет использования контекста.
Полностью осуществляется за счет переменных окружения
Пример табличных unit тестов можно посмотреть тут. Полностью покрыт тестами метод GetVersion.
Другой код тестировался только в рамках функциональных тестов и в теории может содержать баги, которые не были замечены в ходе тестов.
В рамках тестового задания был реализован PoC и не рассматривались вопросы:
- авторизации
- разграничении доступов
- рейт-лимитов
- ограничения скорости
- кеширования
- High Availability
- метрик и др.