Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-3416 LMDB-based SAIL. #3413

Merged
merged 24 commits into from
Dec 11, 2021
Merged

GH-3416 LMDB-based SAIL. #3413

merged 24 commits into from
Dec 11, 2021

Conversation

kenwenzel
Copy link
Contributor

@kenwenzel kenwenzel commented Nov 10, 2021

  • based on NativeStore
    • uses similar encodings for triples and values
    • uses (varint encoded) long IDs everywhere
  • uses LWJGL interfaces to LMDB
    • fast implementation that is available for multiple platforms
  • uses one db for indexes and one db for values

GitHub issue resolved: #3416

Briefly describe the changes proposed in this PR:

This change adds a full LMDB-based SAIL that stores values and triples in two LMDB databases.


PR Author Checklist (see the contributor guidelines for more details):

  • my pull request is self-contained
  • I've added tests for the changes I made
  • I've applied code formatting (you can use mvn process-resources to format from the command line)
  • I've squashed my commits where necessary
  • every commit message starts with the issue number (GH-xxxx) followed by a meaningful description of the change

@kenwenzel
Copy link
Contributor Author

kenwenzel commented Nov 10, 2021

# LMDB (with Varints and improved memory management)
Benchmark                                                       Mode  Cnt      Score      Error  Units
TransactionsPerSecondBenchmark.largerTransaction               thrpt    5     24.096 ±    1.903  ops/s
TransactionsPerSecondBenchmark.largerTransactionLevelNone      thrpt    5     23.398 ±   33.358  ops/s
TransactionsPerSecondBenchmark.mediumTransactionsLevelNone     thrpt    5  13477.604 ± 8788.563  ops/s
TransactionsPerSecondBenchmark.transactions                    thrpt    5  15810.539 ± 3486.354  ops/s
TransactionsPerSecondBenchmark.transactionsLevelNone           thrpt    5  28134.420 ± 5945.131  ops/s
TransactionsPerSecondBenchmark.veryLargerTransactionLevelNone  thrpt    5      0.276 ±    0.052  ops/s

Benchmark                                               Mode  Cnt     Score     Error  Units
QueryBenchmark.complexQuery                             avgt    5    30.823 ±   2.769  ms/op
QueryBenchmark.distinctPredicatesQuery                  avgt    5   815.920 ±  80.725  ms/op
QueryBenchmark.groupByQuery                             avgt    5    13.679 ±   0.898  ms/op
QueryBenchmark.removeByQuery                            avgt    5   240.115 ±  25.557  ms/op
QueryBenchmark.removeByQueryReadCommitted               avgt    5   641.684 ±  43.026  ms/op
QueryBenchmark.simpleUpdateQueryIsolationNone           avgt    5   497.786 ±  92.203  ms/op
QueryBenchmark.simpleUpdateQueryIsolationReadCommitted  avgt    5  1042.506 ± 148.653  ms/op
# Native Store
Benchmark                                                       Mode  Cnt    Score     Error  Units
TransactionsPerSecondBenchmark.largerTransaction               thrpt    5    8.552 ±   3.788  ops/s
TransactionsPerSecondBenchmark.largerTransactionLevelNone      thrpt    5   10.301 ±   0.960  ops/s
TransactionsPerSecondBenchmark.mediumTransactionsLevelNone     thrpt    5  324.496 ±  72.688  ops/s
TransactionsPerSecondBenchmark.transactions                    thrpt    5  331.587 ±  42.337  ops/s
TransactionsPerSecondBenchmark.transactionsLevelNone           thrpt    5  298.569 ± 167.852  ops/s
TransactionsPerSecondBenchmark.veryLargerTransactionLevelNone  thrpt    5    0.080 ±   0.014  ops/s

Benchmark                                               Mode  Cnt     Score     Error  Units
QueryBenchmark.complexQuery                             avgt    5    35.331 ±  10.004  ms/op
QueryBenchmark.distinctPredicatesQuery                  avgt    5  1721.104 ± 578.482  ms/op
QueryBenchmark.groupByQuery                             avgt    5    26.727 ±   3.426  ms/op
QueryBenchmark.removeByQuery                            avgt    5  1507.656 ± 805.813  ms/op
QueryBenchmark.removeByQueryReadCommitted               avgt    5  5348.434 ± 650.709  ms/op
QueryBenchmark.simpleUpdateQueryIsolationNone           avgt    5  5422.728 ± 894.426  ms/op
QueryBenchmark.simpleUpdateQueryIsolationReadCommitted  avgt    5  5360.098 ± 750.562  ms/op

@hmottestad
Copy link
Contributor

hmottestad commented Nov 10, 2021

That query performance is pretty impressive!

Edit: Any chance it's too good to be true? It's miles ahead of the MemoryStore.

@kenwenzel
Copy link
Contributor Author

kenwenzel commented Nov 10, 2021

That query performance is pretty impressive!

Edit: Any chance it's too good to be true? It's miles ahead of the MemoryStore.

I'm also not sure if the numbers are correct. I've re-run the benchmarks several times with the same results.
To get more confidence I've also added corresponding tests in compliance/sparql. Those tests pass - at least on my computer.

BTW: I've used MDB_NOSYNC and MDB_NOMETASYNC for the benchmarks. Maybe most of the data is cached in (off-heap) memory.

I will dig into it.

* byte 12 - A : the UTF-8 encoded the encoded context identifer
* </pre>
*
* @author Jeen Broekstra
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this Author field should be different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some files like ContextStore.java are just (almost) verbatim copies from NativeStore. Therefore I didn't touch the author fields.
Should I change or extend the field even for small changes?

@JervenBolleman
Copy link
Contributor

I think this is fantastic. I would love to have it with longs as identifiers. Making it usable for large datasets like UniProt or Wikidata.

I put some small issues in already from a brief first look. But to me it looks like good quality code.

@kenwenzel
Copy link
Contributor Author

I think this is fantastic. I would love to have it with longs as identifiers. Making it usable for large datasets like UniProt or Wikidata.

I put some small issues in already from a brief first look. But to me it looks like good quality code.

I also think that longs are the way to go. To keep storage space efficiency also VarInts can be used.

Copy link
Contributor

@abrokenjester abrokenjester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very impressvie @kenwenzel , thanks! I'll try and find time soon to do a more comprehensive review as soon as possible.

I've noticed you have copy-pasted a lot of existing code into this new module to be able to reuse it (can tell by the use of old copyright headers and author tags). Two things:

  1. I'd prefer treating these as "new" (so with a new copyright header and author) even if they are derived from existing code.
  2. I haven't looked in detail but do we need code duplication in this fashion, or is there some way we can organize this to make code reuse a little easier?

Comment on lines 16 to 84
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-lmdb</artifactId>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-lmdb</artifactId>
<classifier>natives-linux</classifier>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-lmdb</artifactId>
<classifier>natives-macos</classifier>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl-lmdb</artifactId>
<classifier>natives-windows</classifier>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>natives-linux</classifier>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>natives-macos</classifier>
<version>${lwjgl.version}</version>
</dependency>
<dependency>
<groupId>org.lwjgl</groupId>
<artifactId>lwjgl</artifactId>
<classifier>natives-windows</classifier>
<version>${lwjgl.version}</version>
</dependency>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to to check the IP status of these and if necessary file CQs for them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to file CQs against this: they are not sufficiently documented in ClearlyDefined, and not previously registered for an IP check with Eclipse Foundation either. I'll start filing some CQs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gotten CQs for the two main libraries (lwjgl and lwjgl-lmdb) approved. If we include the native extensions as optional runtimes only, we won't need to file additional CQs for those (especially since they're just different compilations of essentially the same code).

@@ -0,0 +1,41 @@
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This copyright header needs to be changed - even if you copied it from an existing file, it's essentially a new contribution I'd say.

@abrokenjester abrokenjester linked an issue Nov 13, 2021 that may be closed by this pull request
@abrokenjester abrokenjester added the ✋ CQ-Pending requires a CQ to be approved label Nov 14, 2021
@abrokenjester
Copy link
Contributor

abrokenjester commented Nov 14, 2021

@kenwenzel can you please create an issue in our issue tracker that describes the proposed improvement, and link this PR to it? And if you have them, I'd appreciate some pointers to the source of the LMDB libraries that you're using, and any licensing information for them - I'll need that info to log them for IP review.

@hmottestad
Copy link
Contributor

I'm on OS X with the new ARM based M1 processor. Seems that support is coming in the next release (3.3.0).

LWJGL/lwjgl3#601

I'm currently not able to run the tests or benchmarks. We could consider trying out the 3.3.0 snapshot version.

@kenwenzel
Copy link
Contributor Author

Short update from my side: I'm going to switch to varint encoded long ids and I'm planning to push this change within the next few days.
I'm also working on a zero-copy design for the record iterators on another branch.

@kenwenzel
Copy link
Contributor Author

kenwenzel commented Nov 15, 2021

@kenwenzel can you please create an issue in our issue tracker that describes the proposed improvement, and link this PR to it? And if you have them, I'd appreciate some pointers to the source of the LMDB libraries that you're using, and any licensing information for them - I'll need that info to log them for IP review.

@jeenbroekstra You mean besides #3416? So I should create an LMDB-specific issue with some explanation?

@abrokenjester
Copy link
Contributor

Ah, no, you can use that issue, but it was not clear to me that was what you were using. Can you link them up by mentioning the issue number in your commit messages and the PR description please?

@kenwenzel kenwenzel changed the title Initial LMDB-based SAIL. GH-3416 Initial LMDB-based SAIL. Nov 16, 2021
@kenwenzel
Copy link
Contributor Author

kenwenzel commented Nov 18, 2021

I'm on OS X with the new ARM based M1 processor. Seems that support is coming in the next release (3.3.0).

LWJGL/lwjgl3#601

I'm currently not able to run the tests or benchmarks. We could consider trying out the 3.3.0 snapshot version.

@hmottestad I've pushed a commit with an update to LWJGL 3.3.0.

@kenwenzel
Copy link
Contributor Author

A short update: I have some open issues with this.

  • drop custom comparator and use natural sort order for indexes (callbacks from native to Java are slow with LWJGL)
  • change group varint encoding to be compatible with natural sort order of the numbers (prerequisite for the above)
  • use varints everywhere in value store

@hmottestad
Copy link
Contributor

hmottestad commented Nov 19, 2021

I got it running on my laptop :) Thanks!

I took a look at the benchmarks and changed the complex query one to this:

	@Benchmark
	public long complexQuery() {

		try (SailRepositoryConnection connection = repository.getConnection()) {
			long count = connection
				.prepareTupleQuery(query4)
				.evaluate()
				.stream()
				.count();
			System.out.println(count);
			return count;
		}
	}

It prints 0 which is wrong. You can change the one in the MemoryStore and see what it's supposed to be.

core/sail/lmdb/pom.xml Outdated Show resolved Hide resolved
@kenwenzel
Copy link
Contributor Author

I got it running on my laptop :) Thanks!

I took a look at the benchmarks and changed the complex query one to this:

	@Benchmark
	public long complexQuery() {

		try (SailRepositoryConnection connection = repository.getConnection()) {
			long count = connection
				.prepareTupleQuery(query4)
				.evaluate()
				.stream()
				.count();
			System.out.println(count);
			return count;
		}
	}

It prints 0 which is wrong. You can change the one in the MemoryStore and see what it's supposed to be.

Thank you! I will dig into this.

@abrokenjester
Copy link
Contributor

Minor remark - you're using isse number 3415 in several commits, but the actual related issue is #3416.

@kenwenzel
Copy link
Contributor Author

@jeenbroekstra The SPARQL compliance tests for LMDB fail now due to the optional dependencies to LWJGL native libraries.
Should I add those dependencies to the compliance tests or just remove the compliance tests for LMDB?

@abrokenjester
Copy link
Contributor

@kenwenzel Ah, yes, adding them as runtime dependence in the compliance test module should fix that

@kenwenzel
Copy link
Contributor Author

# LMDB
Benchmark                                                       Mode  Cnt      Score      Error  Units
TransactionsPerSecondBenchmark.largerTransaction               thrpt    5     31.845 ±    0.861  ops/s
TransactionsPerSecondBenchmark.largerTransactionLevelNone      thrpt    5     44.121 ±    2.161  ops/s
TransactionsPerSecondBenchmark.mediumTransactionsLevelNone     thrpt    5  19986.561 ±  774.509  ops/s
TransactionsPerSecondBenchmark.transactions                    thrpt    5  21565.831 ±  815.203  ops/s
TransactionsPerSecondBenchmark.transactionsLevelNone           thrpt    5  38754.785 ± 1597.893  ops/s
TransactionsPerSecondBenchmark.veryLargerTransactionLevelNone  thrpt    5      0.268 ±    0.016  ops/s

Benchmark                                               Mode  Cnt     Score     Error  Units
QueryBenchmark.complexQuery                             avgt    5    33.494 ±   2.694  ms/op
QueryBenchmark.distinctPredicatesQuery                  avgt    5  1231.341 ± 120.133  ms/op
QueryBenchmark.groupByQuery                             avgt    5    18.464 ±   1.735  ms/op
QueryBenchmark.removeByQuery                            avgt    5   231.470 ±  14.651  ms/op
QueryBenchmark.removeByQueryReadCommitted               avgt    5   683.210 ±  25.924  ms/op
QueryBenchmark.simpleUpdateQueryIsolationNone           avgt    5   514.449 ±  20.417  ms/op
QueryBenchmark.simpleUpdateQueryIsolationReadCommitted  avgt    5  1095.385 ± 216.371  ms/op

Findings:

  • insertion speed also improved for non-hashed values (benchmarks only generate very short literals)
  • degraded query perfomance because of 2 hops for lookups of hashed values (id -> hash and hash+id -> value)
  • TODO: rework storing of hashed values again

@kenwenzel kenwenzel changed the title GH-3416 Initial LMDB-based SAIL. GH-3416 LMDB-based SAIL. Nov 29, 2021
@kenwenzel kenwenzel marked this pull request as ready for review November 29, 2021 11:51
@kenwenzel
Copy link
Contributor Author

This is now feature complete in comparison to the native store. Two open points are:

  1. add a perfomant GC algorithm for values (Which was the initial motivation for this PR)
  2. maybe drop the complex logic for transaction isolation and solely rely on LMDB's transactions (problem: only one writer at a time is allowed)

Point 1 is really necessary while point 2 would be nice to have.

How should we proceed? Do you want to merge this PR first and then we add GC and maybe other features?

@hmottestad
Copy link
Contributor

Previously we've marked features as experimental or for internal use only. That way we can merge early while still allowing for large changes later on.

@JervenBolleman
Copy link
Contributor

As the work is stand alone as a new sail, I agree with @hmottestad to merge it with an experimental tag.

@abrokenjester
Copy link
Contributor

Great work @kenwenzel , thanks!

@abrokenjester abrokenjester merged commit bd2f136 into eclipse-rdf4j:develop Dec 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement alternative embedded persistent backend
4 participants