Есть ли возможность группировать запрос на выборку с помощью SPARQL и RDF4J?

Я работаю с довольно большим набором данных (около 500 миллионов троек), хранящимся в graphDB Free и работающим на моей локальной машине разработчика.

Я хочу выполнить некоторые операции с набором данных с помощью RDF4J и должен выбрать более или менее весь набор данных. Чтобы сделать тест, я просто ВЫБИРАЮ нужные кортежи. Код работает нормально для первых миллионов кортежей, после чего он становится очень медленным, так как graphDB продолжает выделять больше оперативной памяти.

Есть ли возможность выполнять SELECT-запрос для очень больших наборов данных и получать их партиями?

По сути, я хочу просто выполнить итерацию по некоторым выбранным тройкам, поэтому не должно быть необходимости использовать столько оперативной памяти из graphDB. Я вижу, что я уже получаю данные в RDF4J до того, как запрос завершится, поскольку он дает сбой (HeapSpaceError) только примерно при 1,4 миллионах прочитанных кортежей. К сожалению, почему-то graphDB не освобождает память уже прочитанных кортежей. Я что-то упускаю?

Большое спасибо за твою помощь.

пс. Я уже установил полезное пространство кучи для graphDB на 20 ГБ.

Код RDF4J (Java) выглядит следующим образом:

package ch.test;


import org.eclipse.rdf4j.query.*;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.http.HTTPRepository;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

public class RDF2RDF {

    public static void main(String[] args) {
        System.out.println("Running RDF2RDF");

        HTTPRepository sourceRepo = new HTTPRepository("http://localhost:7200/repositories/datatraining");
        try {
            String path = new File("").getAbsolutePath();
            String sparqlCommand= Files.readString(Paths.get(path + "/src/main/resources/sparql/select.sparql"), StandardCharsets.ISO_8859_1);

            int chunkSize = 10000;
            int positionInChunk = 0;
            long loadedTuples = 0;

            RepositoryConnection sourceConnection = sourceRepo.getConnection();
            TupleQuery query = sourceConnection.prepareTupleQuery(sparqlCommand);

            try (TupleQueryResult result = query.evaluate()) {
                for (BindingSet solution:result) {
                    loadedTuples++;
                    positionInChunk++;

                    if (positionInChunk >= chunkSize) {
                        System.out.println("Got " + loadedTuples + " Tuples");
                        positionInChunk = 0;
                    }
                }
            }

        } catch (IOException err) {
            err.printStackTrace();
        }
    }
}

выберите.sparql:

PREFIX XXX_meta_schema: <http://schema.XXX.ch/meta/>
PREFIX XXX_post_schema: <http://schema.XXX.ch/post/>
PREFIX XXX_post_tech_schema: <http://schema.XXX.ch/post/tech/>

PREFIX XXX_geo_schema: <http://schema.XXX.ch/geo/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX XXX_raw_schema: <http://schema.XXX.ch/raw/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT * WHERE {

    BIND(<http://data.XXX.ch/raw/Table/XXX.csv> as ?table).

    ?row XXX_raw_schema:isDefinedBy ?table.

    ?cellStreetAdress XXX_raw_schema:isDefinedBy ?row;
        XXX_raw_schema:ofColumn <http://data.XXX.ch/raw/Column/Objektadresse>;
        rdf:value ?valueStreetAdress.

    ?cellOrt mobi_raw_schema:isDefinedBy ?row;
        XXX_raw_schema:ofColumn <http://XXX.mobi.ch/raw/Column/Ort>;
        rdf:value ?valueOrt.

    ?cellPlz mobi_raw_schema:isDefinedBy ?row;
        XXX_raw_schema:ofColumn <http://XXX.mobi.ch/raw/Column/PLZ>;
        rdf:value ?valuePLZ.

    BIND (URI(concat("http://data.XXX.ch/post/tech/Adress/", MD5(STR(?cellStreetAdress)))) as ?iri_tech_Adress).
}

Мое решение: использование оператора подзапроса, который сначала получает все строки.

PREFIX mobi_post_schema: <http://schema.mobi.ch/post/>
PREFIX mobi_post_tech_schema: <http://schema.mobi.ch/post/tech/>

PREFIX mobi_geo_schema: <http://schema.mobi.ch/geo/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX mobi_raw_schema: <http://schema.mobi.ch/raw/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT * WHERE {

    {
        SELECT ?row WHERE
        {
            BIND(<http://data.mobi.ch/raw/Table/Gebaeudeobjekte_August2020_ARA_Post.csv> as ?table).

            ?row mobi_raw_schema:isDefinedBy ?table.
        }
    }


    ?cellStreetAdress mobi_raw_schema:isDefinedBy ?row;
        mobi_raw_schema:ofColumn <http://data.mobi.ch/raw/Column/Objektadresse>;
        rdf:value ?valueStreetAdress.

    ?cellOrt mobi_raw_schema:isDefinedBy ?row;
        mobi_raw_schema:ofColumn <http://data.mobi.ch/raw/Column/Ort>;
        rdf:value ?valueOrt.

    ?cellPlz mobi_raw_schema:isDefinedBy ?row;
        mobi_raw_schema:ofColumn <http://data.mobi.ch/raw/Column/PLZ>;
        rdf:value ?valuePLZ.

    BIND (URI(concat("http://data.mobi.ch/post/tech/Adress/", MD5(STR(?cellStreetAdress)))) as ?iri_tech_Adress).
}

person onew4y    schedule 06.01.2021    source источник
comment
Я не понимаю, почему вы даете GraphDB память. Я имею в виду, что вы загружаете миллионы кортежей в свое приложение, то есть в память, поэтому вам следует увеличивать пространство кучи вашего Java-приложения, но не GraphDB.   -  person UninformedUser    schedule 06.01.2021
comment
Более важный вопрос: что ваше приложение делает с данными? Какой у вас SPARQL-запрос?   -  person UninformedUser    schedule 06.01.2021
comment
И действительно, вы также можете просто получить результат порциями — просто используйте limit и offset``(and technically order by`)   -  person UninformedUser    schedule 06.01.2021
comment
Вопрос 1) Я выделяю памяти для graphDB, потому что, похоже, она нужна для graphDB. моему java-приложению не нужно много оперативной памяти. Таким образом, куча пространства Java-приложения не является проблемой. Кажется, проблема в куче графовой базы данных.   -  person onew4y    schedule 06.01.2021
comment
Вопрос 2) В данный момент приложение ничего не делает. Я его реализую, как только получу данные из триплстора. Запрос SPARQL — это просто оператор SELECT для выбора нескольких троек.   -  person onew4y    schedule 06.01.2021
comment
Вопрос 3) Спасибо за подсказку со смещением. Я уже пробовал это, но кажется, что graphDB по-прежнему выделяет слишком много памяти во время фазы смещения, прежде чем доставить результаты.   -  person onew4y    schedule 06.01.2021
comment
какой запрос? Действительно, очень сложный запрос может отнимать много времени и памяти. Но это зависит. И да, нумерация страниц может раздражать - особенно когда вы делаете это правильно, т.е. с order by это может быть слишком дорого, потому что order by сложнее, чем в SQL, где можно просто двигать курсор   -  person UninformedUser    schedule 06.01.2021
comment
Я добавил запрос к вопросу. Я действительно не понимаю, почему для graphDB так много памяти, так как я довольно быстро получаю первые кортежи. и после доставки, graphDB может снова освободить эти ресурсы памяти.   -  person onew4y    schedule 06.01.2021


Ответы (1)


Я не знаю сразу, почему заданный запрос будет таким дорогостоящим с точки зрения памяти для выполнения GraphDB Free, но в целом многое может зависеть от формы и размера вашего набора данных. Конечно, выполнение запроса, который в основном извлекает всю базу данных, не обязательно является мудрым решением с самого начала.

Сказав это, есть пара вещей, которые вы можете попробовать. Работа с LIMIT и OFFSET в качестве механизма разбиения на страницы — это один из способов.

Другой вариант, который вы можете попробовать, — разделить запрос на два: один запрос извлекает все идентификаторы интересующих вас ресурсов, а затем вы перебираете их и для каждого выполняете отдельный запрос, чтобы получить подробную информацию. (атрибуты и отношения) для этого конкретного ресурса.

В вашем примере вы можете разделить на ?row, поэтому вы сначала выполните запрос, чтобы получить все строки для данной таблицы:

SELECT ?row WHERE {
    VALUES ?table { <http://data.XXX.ch/raw/Table/XXX.csv> }
    ?row XXX_raw_schema:isDefinedBy ?table.
}

Затем вы перебираете этот результат, вводя каждое возвращаемое значение для ?row в запрос, который извлекает детали:

SELECT * WHERE {
    VALUES ?row { <http://data.XXX.ch/raw/Table/XXX.csv#row1> }

    ?cellStreetAdress XXX_raw_schema:isDefinedBy ?row;
        XXX_raw_schema:ofColumn <http://data.XXX.ch/raw/Column/Objektadresse>;
        rdf:value ?valueStreetAdress.

    ?cellOrt mobi_raw_schema:isDefinedBy ?row;
        XXX_raw_schema:ofColumn <http://XXX.mobi.ch/raw/Column/Ort>;
        rdf:value ?valueOrt.

    ?cellPlz mobi_raw_schema:isDefinedBy ?row;
        XXX_raw_schema:ofColumn <http://XXX.mobi.ch/raw/Column/PLZ>;
        rdf:value ?valuePLZ.

    BIND (URI(concat("http://data.XXX.ch/post/tech/Adress/", MD5(STR(?cellStreetAdress)))) as ?iri_tech_Adress).
}

В Java-коде примерно так:


String sparqlCommand1 = // the query for all rows of the table

// query for details of each row. Value of row will be injected via the API
String sparqlCommand2 = "SELECT * WHERE { \n"
                    + "    ?cellStreetAdress XXX_raw_schema:isDefinedBy ?row;\n"
                    + "        XXX_raw_schema:ofColumn <http://data.XXX.ch/raw/Column/Objektadresse>;\n"
                    + "        rdf:value ?valueStreetAdress.\n"
                    + "    ?cellOrt mobi_raw_schema:isDefinedBy ?row;\n"
                    + "        XXX_raw_schema:ofColumn <http://XXX.mobi.ch/raw/Column/Ort>;\n"
                    + "        rdf:value ?valueOrt.\n"
                    + "    ?cellPlz mobi_raw_schema:isDefinedBy ?row;\n"
                    + "        XXX_raw_schema:ofColumn <http://XXX.mobi.ch/raw/Column/PLZ>;\n"
                    + "        rdf:value ?valuePLZ.\n"
                    + "    BIND (URI(concat(\"http://data.XXX.ch/post/tech/Adress/\", MD5(STR(?cellStreetAdress)))) as ?iri_tech_Adress).\n"
                    + "}";

try(RepositoryConnection sourceConnection = sourceRepo.getConnection()) {
     TupleQuery rowQuery = sourceConnection.prepareTupleQuery(sparqlCommand1);     
     TupleQuery detailsQuery = sourceConnection.prepareTupleQuery(sparqlCommand2);

     try (TupleQueryResult result = rowQuery.evaluate()) {
         for (BindingSet solution: result) {
                // inject the current row identifier
                detailsQuery.setBinding("row", solution.getValue("row"));

                // execute the details query for the row and do something with 
                // the result
                detailsQuery.evaluate().forEach(System.out::println);
         }
     }
}

Конечно, таким образом вы выполняете больше запросов (N+1, где N — количество строк), но каждый отдельный результат запроса — это лишь небольшой фрагмент, и, вероятно, GraphDB Free (а также ваше собственное приложение) легче управлять .

person Jeen Broekstra    schedule 06.01.2021
comment
большое спасибо за ответ! это был бы хороший и общий способ решить эту проблему. Я знаю, что запрашивать весь набор данных на самом деле не очень хорошо. но мне нужно выполнить операцию, подобную преобразованию, для всех строк из импортированного CSV. Мне удалось уменьшить использование оперативной памяти из graphDB с помощью подвыборки, которая сначала получает все строки. Я добавил это к своему первоначальному вопросу. - person onew4y; 08.01.2021