-
Notifications
You must be signed in to change notification settings - Fork 266
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
Postgresql support #1249
Postgresql support #1249
Conversation
One of the main complications with using a remote db backend with eclair is how you prevent two instances of eclair running at the same time and using the same db, which would be catastrophic. Doable, but not trivial. There has to be a locking mechanism somewhere. |
Exactly! That's why is just a POC. We are in fact discussing the locking mechanism atm. Unfortunately, it's not that easy to implement it for PostgresSQL, comparing to the sqlite implementation. But we have some ideas, which I'm going to implement soon. |
private def acquireExclusiveTableLock(lockTimeout: FiniteDuration)(implicit connection: Connection): Unit = { | ||
using(connection.createStatement()) { statement => | ||
statement.executeUpdate(s"SET lock_timeout TO '${lockTimeout.toSeconds}s'") | ||
statement.executeUpdate(s"LOCK TABLE $LockTable IN ACCESS EXCLUSIVE MODE") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation: https://www.postgresql.org/docs/11/sql-lock.html
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
Few thoughts on this:
But in general would be great. Am running a node on AWS and so with this could have a synchronous postgres replica in another data centre. So even if there was total loss of an AWS data centre I could (theoretically assume everyone else is not doing the same ;)) restart the node in perfect condition in a few mins - and not lose any channels or money. And would mean we could move away from the "dump the whole database every 15mins" approach which is not very scale-able over time. |
case CURRENT_VERSION => | ||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS local_channels (channel_id TEXT NOT NULL PRIMARY KEY, data BYTEA NOT NULL, is_closed BOOLEAN NOT NULL DEFAULT FALSE)") | ||
statement.executeUpdate("CREATE TABLE IF NOT EXISTS htlc_infos (channel_id TEXT NOT NULL, commitment_number TEXT NOT NULL, payment_hash TEXT NOT NULL, cltv_expiry BIGINT NOT NULL, FOREIGN KEY(channel_id) REFERENCES local_channels(channel_id))") | ||
statement.executeUpdate("CREATE INDEX IF NOT EXISTS htlc_infos_idx ON htlc_infos(channel_id, commitment_number)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Index on is_closed? As used for listLocalChannels.
eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
Outdated
Show resolved
Hide resolved
Even though there are a bunch of security settings outside Eclair, there is always a way to shoot in the foot.
I think that's a generic problem unrelated to the database type. It should be addressed in a separate PR.
We are going to do some performance testing to see how slow Postgres is comparing to Sqlite.
It's already there. Probably the naming of the tests were misleading, so I renamed them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work.
I'd rather rename the psql
package to postgres
(or even pg
if we really want to make it short). psql
isn't a common abbreviation of postgres AFAIK. I'm aware the postgres shell is named psql (but it's even more confusing) and it's close to pl/sql which is also a different thing. Same for classes e.g. PsqlNetworkDb
could be PgNetworkDb
.
One thing that bothers me a little bit is using blocking db calls from within actors. That makes sense when using an embedded db such as sqlite, but is a bit more problematic if we are dealing with a remote db server with network latency etc. I'm not saying it is necessarily a big blocker (pun intended), but it's an important thing to keep in mind.
@@ -263,5 +273,11 @@ | |||
<version>1.5.9</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.opentable.components</groupId> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now use three different way of spawning binaries in the tests (exec call for bitcoind, docker for electrum, and this specialized lib). FWIW in an other project we do use docker to spawn a postgres instance in test. Well, probably doesn't matter much.
eclair-core/src/main/scala/fr/acinq/eclair/db/jdbc/JdbcUtils.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/psql/PsqlUtils.scala
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 💯
I'll let @pm47 add his review and if we're good to go, we'll include this in our next release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost there!
eclair-core/src/test/scala/fr/acinq/eclair/db/BackupHandlerSpec.scala
Outdated
Show resolved
Hide resolved
sealed trait TestDatabases { | ||
val connection: Connection | ||
def network(): NetworkDb | ||
def audit(): AuditDb | ||
def channels(): ChannelsDb | ||
def peers(): PeersDb | ||
def payments(): PaymentsDb | ||
def pendingRelay(): PendingRelayDb | ||
def getVersion(statement: Statement, db_name: String, currentVersion: Int): Int | ||
def close(): Unit | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you use the Databases
trait? You can mix-in the close
method if needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I needed a sealed trait for exhaustive pattern matching
- Some of the tests need a fresh connection every time
network
,audit
, etc gets called, but they are defined asval
inDatabases
Databases
requireobtainExclusiveLock()
to be implemented, but it's database specific and can't be tested in a generic tests.
eclair-core/src/main/scala/fr/acinq/eclair/db/BackupHandler.scala
Outdated
Show resolved
Hide resolved
eclair-core/src/main/scala/fr/acinq/eclair/db/BackupHandler.scala
Outdated
Show resolved
Hide resolved
I've made some fixes to the |
Done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @rorp!
🎉 |
The main goal of this PR is to improve fault tolerance of
eclair
nodes in enterprise settings, as well as to "standardize" backup procedures. As a bonus point it could simplify scaling outeclair
nodes in the future.