Как записать большое количество записей в базу данных Xodus без утечек памяти? - PullRequest
0 голосов
/ 17 апреля 2020

Мне нужно вставить около 150 миллионов записей в базу данных Xodus, используя пакет xodus-dnq. Основываясь на примерах, я реализовал следующий метод:

        XdModel.registerNodes(
                XDTrip
        )
        val store = StaticStoreContainer.init(
                dbFolder = File(target),
                environmentName = "trips"
        )
        initMetaData(XdModel.hierarchy, store)
        store.use { store ->
            store.persistentStore.use {
                // TripLoader(File(src)) returns a stream with roughly 150M elements
                TripLoader(File(src)).forEach { databaseTrip ->
                    store.transactional {
                        XDTrip.new {
                            id = databaseTrip.id
                            start = databaseTrip.start.epochSecond
                            end = databaseTrip.end.epochSecond
                        }
                    }
                }
            }
        }

Это прекрасно работает, но утечки памяти. Предположительно мне нужно вручную зафиксировать / очистить / сохранить транзакцию?

На основании другого предложения я реорганизовал это для запуска меньших пакетов внутри транзакции:

    XdModel.registerNodes(
            XDTrip,
            XDPosition,
            XDPositionSource
    )
    val store = StaticStoreContainer.init(
            dbFolder = File(target),
            environmentName = "trips"
    )
    initMetaData(XdModel.hierarchy, store)
    store.use {
        it.use { store ->
            Sequence { TripLoader(File(src)).iterator() }.chunked(100).forEachIndexed { index, csvChunk ->
                store.transactional {
                    println("Chunk $index")
                    csvChunk.forEach { csvTrip ->
                        XDTrip.new {
                            id = csvTrip.id
                            start = csvTrip.start.epochSecond
                            end = csvTrip.end.epochSecond
                            // ...
                        }
                    }
                }
            }
        }
    }

Это, тем не менее, приводит к утечке памяти.

Я проанализировал память и нашел экземпляр jetbrains.exodus.core.dataStructures.ConcurrentLongObjectCache, занимающий большую часть кучи:

Один экземпляр "jetbrains.exodus.core.dataStructures.ConcurrentLongObjectCache", загруженный "sun.mis c .Launcher $ AppClassLoader @ 0x4c0481d58" занимает 45 075 952 (80,24%) байта. Память накапливается в одном экземпляре "jetbrains.exodus.core.dataStructures.ConcurrentLongObjectCache $ CacheEntry []", загруженном "sun.mis c .Launcher $ AppClassLoader @ 0x4c0481d58".

Keywords Jetbrains.ex .core.dataStructures.ConcurrentLongObjectCache sun.mis c .Launcher $ AppClassLoader @ 0x4c0481d58 jetbrains.exodus.core.dataStructures.ConcurrentLongObjectCache $ CacheEntry []

* * * * * * * * * * * * * * * * * *

1019 * 1019 пока программа работает, поэтому я не уверен, почему кэш заполняется здесь.

1 Ответ

2 голосов
/ 17 апреля 2020

Обработка тяжелых операций с партиями - рекомендуемый способ go. Если вы используете Kotlin Последовательности, есть функция расширения windowed. Поэтому ваш код будет выглядеть следующим образом:

TripLoader(File(src)).windowed(1000) { trips ->
                    store.transactional {
                    trips.forEach { databaseTrip ->
                        XDTrip.new {
                            id = databaseTrip.id
                            start = databaseTrip.start.epochSecond
                            end = databaseTrip.end.epochSecond
                        }
                    }
                }

Практически, если вы делаете что-то во время выполнения или целая операция занимает много времени, лучше go немного глубже и использовать низкоуровневый API. Код станет более сложным, но работает быстрее.

Например, если вы считаете, что данные непротиворечивы и вам не нужно проверять все ограничения, отношения и прочее, вы можете использовать что-то вроде этого:

val type =  XdTrip.entityType
TripLoader(File(src)).windowed(1000) { trips ->
        persistentStore.executeInExclusiveTransaction { txn -> 
               trips.forEach { databaseTrip ->
                        txn.newEntity(type).also {
                            it.setProperty("id", databaseTrip.id)
                            ...
                        }
               }
        }

Размер окна следует проверять в соответствии с к импортированным данным. В случае примитивных структур данных это может быть увеличено.

...