Проект курса "NoSQL"
Форкните проект, склонируйте и добавьте upstream:
$ git clone [email protected]:<username>/2023-nosql-lsm.git
Cloning into '2023-nosql-lsm'...
...
$ git remote add upstream [email protected]:polis-vk/2023-nosql-lsm.git
$ git fetch upstream
From github.com:polis-vk/2023-nosql-lsm
* [new branch] main -> upstream/main
Так можно запустить тесты (ровно то, что делает CI):
$ ./gradlew clean test
Откройте в IDE -- IntelliJ IDEA Community Edition нам будет достаточно.
ВНИМАНИЕ! При запуске тестов или сервера в IDE необходимо передавать Java опцию -Xmx64m.
Сделать имплементацию интерфейса DAO, заставив пройти все тесты. Для этого достаточно реализовать две операции: get и upsert, при этом достаточно реализации "в памяти".
Продолжайте запускать тесты и исправлять ошибки, не забывая подтягивать новые тесты и фиксы из upstream. Если заметите ошибку в upstream, заводите баг и присылайте pull request ;)
Когда всё будет готово, присылайте pull request в ветку main со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
Приведите код в состояние, удовлетворяющее новым тестам. А именно: при конструировании DAO следует восстановить состояние, персистентно сохраненное в методе close().
Нужно реализовать метод get(key), который будет возвращать Entry, сохраненной в памяти или в хипе.
В DaoFactory.Factory появился конструктор createDao(Config config), который нужно переопределить в своей реализации.
Config Содержит в себе basePath - директория для сохранения состояния DAO.
В новых тестах не предполагается использование полноценной реализации метода get(from, to).
Когда всё будет готово, присылайте pull request со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
Приведите код в состояние, удовлетворяющее данным запросам:
- Бинарный поиск по файлу теперь является обязательным для реализации.
- Метод
get(from, to)должен работать полноценно и полностью поддерживаться DAO даже при наличии в ней данных, записанных в файл. - Стоит учитывать, что в диапазоне от
fromдоto, некая часть данных может уже находиться в файле на диске, в то время, как другая часть будет все ещеInMemory. Несмотря на это, необходимо уметь последовательно отдавать все данные в правильно отсортированном порядке. - Запрошенный диапазон значений может быть слишком велик, чтобы держать его полностью в памяти, нужно динамически подгружать лишь необходимую в данный момент часть этого диапазона.
- За один тест метод
closeу конкретного инстанса DAO может быть вызван только один раз, однако после этого имеется возможность создать новый инстанс с тем жеconfig(т.е будет произведено переоткрытие DAO). В отличие от прошлого этапа, в этот раз в DAO можно будет писать новую информацию, а не только читать старую из него. Количество таких переоткрытий за один тест неограниченно. Следовательно, должна поддерживаться возможность создания соответствующего количества файлов, содержащих данные нашей БД - по одному на каждыйclose, а так же возможность поиска значений по всем этим файлам. - После использования метода
close,Entryс уже записанным до этого в файл ключом может быть добавлено в последующие DAO еще несколько раз, т.е текущее значение, соответствующее этому ключу, будет таким образом перезаписано, однако старые значения, соответствующие этому же ключу, все еще будут находиться в более ранних файлах. База данных должна суметь выдать самое свежее для запрошенного ключа значение при поиске из всех имеющихся. - На данный момент метод
flushбудет вызываться только из методаclose. Таким образом,flushбудет гарантированно выполняться однопоточно, также на данном этапе можно рассчитывать на то, чтоgetиupsertв процессе выполнения методаflushвызываться не будут, однако во все остальное времяgetиupsertмогут работать в многопоточном режиме - старые тесты наconcurrencyникуда не пропадают, но могут появиться и новые. Считаем, что поведениеgetиupsertпосле отработки методаclose-undefined upsertсvalue == nullподразумевает удаление: такие записи не должны возвращаться вget.
Когда всё будет готово, присылайте pull request в ветку main со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
Требуется реализовать метод compact() в DAO:
- Все SSTable'ы должны быть слиты в один, включая как таблицы на диске, так и таблицу в памяти
- Старые должны быть удалены
- Должна сохраниться только самая свежая версия, старые записи должны быть удалены
- Удалённые записи (
upsertсnull-значением) не должны сохраняться присompact() - В рамках данного задания гарантируется, что сразу после
compact()будет вызван методclose(), а значит работа с этим DAO больше производиться не будет. Но в последствии может быть открыто новоеDAOc тем же конфигом. И на нём тоже может быть вызванcompact. close()обязан быть идемпотентным -- повторныйclose()не должен приводить к отрицательным последствиям, включая потерю производительности. На текущем этапе можно считать, что конкуретных вызововclose()нет.- Тесты будут появляться и дополняться
Когда всё будет готово, присылайте pull request в ветку main со своей реализацией на review. Не забывайте отвечать на комментарии в PR и исправлять замечания!
Требуется обеспечить потокобезопаность всех методов Dao, т.е. любые методы могут вызываться одновременно из разных потоков и поведение должно быть корректным, включая итерирование по данным (кроме как после вызова close(), о чём см. ниже).
NB! Под "не блокирует" ниже понимается отсутствие блокировок длительностью более нескольких миллисекунд.
- В классе
Configпоявился порогflushThresholdBytes - При превышении размера таблицы в памяти указанного порога должен запускаться автоматический фоновый flush (не блокирующий другие методы
Dao) - В случаях, когда flush не поспевает за вставкой данных (одна полная таблица всё ещё пишется на диск, в то время как заполнилась следующая таблица), параллельные
upsert()ы должны бросать исключение -- тем самым мы сигнализируем клиенту о перегрузке и избегаем падения поOutOfMemory - Запускается в фоновом режиме (не блокирует другие методы
Dao) - В процессе flush все данные должны быть доступны на чтение
- Вызывается вручную, но из любого потока и сколько угодно раз на протяжении жизни
Dao - Компактит данные только на диске (в отличие от предыдущего этапа)
- Запускается в фоновом режиме (не блокирует другие методы
Dao) - В процессе compact все данные должны быть доступны на чтение
- Блокируется до окончания фоновых операций
flush()/compact(), если таковые имеются - Корректно освобождает все ресурсы, в т.ч. пулы потоков
- Обязан быть идемпотентным -- повторный
close()не должен приводить к отрицательным последствиям - Поведение всех других методов
Daoпосле вызоваclose()не определено, включая итерирование по предварительно полученномуIterator
Когда всё будет готово, присылайте pull request в ветку main со своей реализацией на review.
Не забывайте подтягивать новые тесты и изменения, отвечать на комментарии в PR и исправлять замечания!
После реализации всех предыдущих этапов, необходимо выбрать одну из незанятых фич, описанных ниже, и предварительно обсудить с преподавателем предполагаемый способ реализации.
При реализации фичи допускается изменение API Dao.
Добавление тестов, демонстрирующих работоспособность реализации, является обязательным.
Развёрнутая конструктивная обратная связь по курсу: достоинства и недостатки курса, сложность тем, предложения по улучшению.
Регулярный автоматический фоновый compaction.
Гарантирует durability (отсутствие потерь подтверджённых записей/удалений) даже в случае "падения" процесса за счёт того, что операции модификации подтверждаются только после того, как попадут в write-ahead-log. При инициализации стораджа обнаруженные записи WAL "проигрываются" перед началом обслуживания новых операций. Устаревшие WAL должны ротироваться, чтобы не занимать лишнее место на диске.
Существуют разные подходы к реализации sync() на диск.
Текущий интерфейс DAO позволяет итерироваться по данным только в лексикографическом порядке.
Требуется реализовать возможность корректной итерации в обратном порядке, т.е. добавить метод descendingGet(from, to).
Операция upsert() должна поддерживать опциональный параметр Time-To-Live (TTL) или время, после которого ячейка должна "пропасть".
"Протухшие" ячейки не должны отдаваться клиентам и должны вычищаться при compaction.
Необходимо реализовать блочную компрессию в файлах на диске (используя готовые реализации LZ4, Snappy или zstd) и не забыть про compaction.
Необходимо реализовать механизм для записи и чтения значений больше чем Java Heap, например, принимая InputStream и выдавая OutputStream в качестве значения.
Необходимо реализовать возможность атомарного применения набора модификаций (upsert или remove).
Например, можно принимать от клиента список модификаций, писать их в сериализованном виде в отдельную таблицу и удалять после успешного применения. При инициализации стораджа должны "проигрываться" недоприменённые батчи.
Необходимо обеспечить возможность транзакционного выполнения набора любых операций (upsert/remove/get).
При возникновении конфликта (любой другой транзакции, работающей с теми же ключами несовместимым способом, т.е. не read/read конфликта) клиент должен получать ConcurrentModificationException.
Пример реализации -- NewSQL.
Для каждой таблицы на диске необходимо поддерживать Bloom Filter для содержащихся в ней ключей, чтобы пропускать таблицы, гарантированно не содержащие запрашиваемых ключей.
Очевидно, что это будет работать только в случае "точечных", а не range-запросов.
Поддержка независимых таблиц/keyspace/database/namespace/whatever.
Получение слепка БД на текущий момент времени с возможностью чтения из него вне зависимости от "развития" основной БД.
Здесь могут помочь hard links.
Возможность указания клиентом пользовательского Comparator вместо лексикографического.
- Поддержка файлов больше 2ГБ
- Модульные тесты для ключей/значений больше 2ГБ