Skip to content

Commit a8d4e07

Browse files
pm47t-bast
andauthored
Use less strict isolation level for channel meta (#1790)
We don't need `SERIALIZABLE` consistency guarantees when all we do is updating timestamp columns. This happens concurrently to channel data update and raised serialization errors in postgres. Fixed #1786. Co-authored-by: Bastien Teinturier <[email protected]>
1 parent 3079cb4 commit a8d4e07

File tree

3 files changed

+26
-12
lines changed

3 files changed

+26
-12
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgChannelsDb.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package fr.acinq.eclair.db.pg
1818

19+
import com.zaxxer.hikari.util.IsolationLevel
1920
import fr.acinq.bitcoin.ByteVector32
2021
import fr.acinq.eclair.CltvExpiry
2122
import fr.acinq.eclair.channel.HasCommitments
@@ -102,7 +103,7 @@ class PgChannelsDb(implicit ds: DataSource, lock: PgLock) extends ChannelsDb wit
102103
* Helper method to factor updating timestamp columns
103104
*/
104105
private def updateChannelMetaTimestampColumn(channelId: ByteVector32, columnName: String): Unit = {
105-
inTransaction { pg =>
106+
inTransaction(IsolationLevel.TRANSACTION_READ_UNCOMMITTED) { pg =>
106107
using(pg.prepareStatement(s"UPDATE local_channels SET $columnName=? WHERE channel_id=?")) { statement =>
107108
statement.setTimestamp(1, Timestamp.from(Instant.now()))
108109
statement.setString(2, channelId.toHex)

eclair-core/src/main/scala/fr/acinq/eclair/db/pg/PgUtils.scala

+18-8
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616

1717
package fr.acinq.eclair.db.pg
1818

19+
import com.zaxxer.hikari.util.IsolationLevel
1920
import fr.acinq.eclair.db.Monitoring.Metrics._
2021
import fr.acinq.eclair.db.Monitoring.Tags
2122
import fr.acinq.eclair.db.jdbc.JdbcUtils
2223
import fr.acinq.eclair.db.pg.PgUtils.PgLock.LockFailureHandler.LockException
2324
import grizzled.slf4j.Logging
2425
import org.postgresql.util.{PGInterval, PSQLException}
2526

26-
import java.sql.{Connection, Statement, Timestamp}
27+
import java.sql.{Connection, Timestamp}
2728
import java.util.UUID
2829
import javax.sql.DataSource
2930
import scala.concurrent.duration._
@@ -242,11 +243,14 @@ object PgUtils extends JdbcUtils {
242243

243244
}
244245

245-
def inTransaction[T](connection: Connection)(f: Connection => T): T = {
246-
val autoCommit = connection.getAutoCommit
246+
/**
247+
* @param isolationLevel Be careful when changing the default value
248+
*/
249+
private def inTransactionInternal[T](isolationLevel: IsolationLevel)(connection: Connection)(f: Connection => T): T = {
250+
val previousAutoCommit = connection.getAutoCommit
247251
connection.setAutoCommit(false)
248-
val isolationLevel = connection.getTransactionIsolation
249-
connection.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE)
252+
val previousIsolationLevel = connection.getTransactionIsolation
253+
connection.setTransactionIsolation(isolationLevel.getLevelId)
250254
try {
251255
val res = f(connection)
252256
connection.commit()
@@ -256,14 +260,20 @@ object PgUtils extends JdbcUtils {
256260
connection.rollback()
257261
throw ex
258262
} finally {
259-
connection.setAutoCommit(autoCommit)
260-
connection.setTransactionIsolation(isolationLevel)
263+
connection.setAutoCommit(previousAutoCommit)
264+
connection.setTransactionIsolation(previousIsolationLevel)
261265
}
262266
}
263267

264268
def inTransaction[T](f: Connection => T)(implicit dataSource: DataSource): T = {
265269
withConnection { connection =>
266-
inTransaction(connection)(f)
270+
inTransactionInternal(IsolationLevel.TRANSACTION_SERIALIZABLE)(connection)(f)
271+
}
272+
}
273+
274+
def inTransaction[T](isolationLevel: IsolationLevel)(f: Connection => T)(implicit dataSource: DataSource): T = {
275+
withConnection { connection =>
276+
inTransactionInternal(isolationLevel)(connection)(f)
267277
}
268278
}
269279

eclair-core/src/test/scala/fr/acinq/eclair/db/ChannelsDbSpec.scala

+6-3
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,11 @@ class ChannelsDbSpec extends AnyFunSuite {
9898
val db = dbs.channels
9999
implicit val ec: ExecutionContextExecutor = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(8))
100100
val channel = ChannelCodecsSpec.normal
101-
val futures = for (_ <- 0 until 10000) yield {
102-
Future(db.addOrUpdateChannel(channel.modify(_.commitments.channelId).setTo(randomBytes32)))
101+
val channelIds = (0 until 10).map(_ => randomBytes32).toList
102+
val futures = for (i <- 0 until 10000) yield {
103+
val channelId = channelIds(i % channelIds.size)
104+
Future(db.addOrUpdateChannel(channel.modify(_.commitments.channelId).setTo(channelId)))
105+
Future(db.updateChannelMeta(channelId, ChannelEvent.EventType.PaymentSent))
103106
}
104107
val res = Future.sequence(futures)
105108
Await.result(res, 60 seconds)
@@ -375,4 +378,4 @@ object ChannelsDbSpec {
375378
rs.getTimestampNullable(columnName).map(_.getTime)
376379
}
377380
}
378-
}
381+
}

0 commit comments

Comments
 (0)