From 80c7460b18e95fb278dea31227e5f0b837e73115 Mon Sep 17 00:00:00 2001 From: Alexis Hernandez Date: Tue, 25 Dec 2018 17:05:08 -0700 Subject: [PATCH] server: Precompute the available coins In order to speed up the available coins retrieval, the aggregated_amounts table is created, here we store the total available coins which can be retrieved fast. --- .../anorm/LedgerPostgresDataHandler.scala | 22 ++++++++++++++----- .../dao/AggregatedAmountPostgresDAO.scala | 22 +++++++++++++++++++ server/conf/evolutions/default/8.sql | 17 ++++++++++++++ .../data/LedgerPostgresDataHandlerSpec.scala | 5 +++-- .../TransactionPostgresDataHandlerSpec.scala | 5 +++-- .../LedgerSynchronizerServiceSpec.scala | 5 +++-- 6 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 server/app/com/xsn/explorer/data/anorm/dao/AggregatedAmountPostgresDAO.scala create mode 100644 server/conf/evolutions/default/8.sql diff --git a/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala b/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala index 7a0f922..9b2fa77 100644 --- a/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala +++ b/server/app/com/xsn/explorer/data/anorm/LedgerPostgresDataHandler.scala @@ -1,16 +1,16 @@ package com.xsn.explorer.data.anorm import java.sql.Connection -import javax.inject.Inject import com.alexitc.playsonify.core.ApplicationResult import com.alexitc.playsonify.models.ApplicationError import com.xsn.explorer.data.LedgerBlockingDataHandler -import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} +import com.xsn.explorer.data.anorm.dao.{AggregatedAmountPostgresDAO, BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} import com.xsn.explorer.errors.{PostgresForeignKeyViolationError, PreviousBlockMissingError, RepeatedBlockHeightError} import com.xsn.explorer.models.rpc.Block import com.xsn.explorer.models.{Address, Balance, Transaction} import com.xsn.explorer.util.Extensions.ListOptionExt +import javax.inject.Inject import org.scalactic.Good import play.api.db.Database @@ -18,7 +18,8 @@ class LedgerPostgresDataHandler @Inject() ( override val database: Database, blockPostgresDAO: BlockPostgresDAO, transactionPostgresDAO: TransactionPostgresDAO, - balancePostgresDAO: BalancePostgresDAO) + balancePostgresDAO: BalancePostgresDAO, + aggregatedAmountPostgresDAO: AggregatedAmountPostgresDAO) extends LedgerBlockingDataHandler with AnormPostgresDataHandler { @@ -73,10 +74,16 @@ class LedgerPostgresDataHandler @Inject() ( _ <- transactions.map(tx => transactionPostgresDAO.upsert(tx)).everything // balances - _ <- balances(transactions) + balanceList = balances(transactions) + + _ <- balanceList .map { b => balancePostgresDAO.upsert(b) } .toList .everything + + // compute aggregated amount + delta = balanceList.map(_.available).sum + _ = aggregatedAmountPostgresDAO.updateAvailableCoins(delta) } yield () // link previous block (if possible) @@ -96,11 +103,16 @@ class LedgerPostgresDataHandler @Inject() ( _ <- blockPostgresDAO.delete(block.hash) // balances - _ <- balances(deletedTransactions) + balanceList = balances(deletedTransactions) + _ <- balanceList .map { b => b.copy(spent = -b.spent, received = -b.received) } .map { b => balancePostgresDAO.upsert(b) } .toList .everything + + // compute aggregated amount + delta = balanceList.map(_.available).sum + _ = aggregatedAmountPostgresDAO.updateAvailableCoins(-delta) } yield () } diff --git a/server/app/com/xsn/explorer/data/anorm/dao/AggregatedAmountPostgresDAO.scala b/server/app/com/xsn/explorer/data/anorm/dao/AggregatedAmountPostgresDAO.scala new file mode 100644 index 0000000..1f18c55 --- /dev/null +++ b/server/app/com/xsn/explorer/data/anorm/dao/AggregatedAmountPostgresDAO.scala @@ -0,0 +1,22 @@ +package com.xsn.explorer.data.anorm.dao + +import java.sql.Connection + +import anorm._ + +class AggregatedAmountPostgresDAO { + + def updateAvailableCoins(delta: BigDecimal)(implicit conn: Connection): Unit = { + val affectedRows = SQL( + """ + |UPDATE aggregated_amounts + |SET value = value + {delta} + |WHERE name = 'available_coins' + """.stripMargin + ).on( + 'delta -> delta + ).executeUpdate() + + require(affectedRows == 1) + } +} diff --git a/server/conf/evolutions/default/8.sql b/server/conf/evolutions/default/8.sql new file mode 100644 index 0000000..e93ae67 --- /dev/null +++ b/server/conf/evolutions/default/8.sql @@ -0,0 +1,17 @@ + +# --- !Ups + +CREATE TABLE aggregated_amounts( + name TEXT NOT NULL, + value AMOUNT_TYPE NOT NULL, + -- constraints + CONSTRAINT aggregated_amounts_name_pk PRIMARY KEY (name) +); + +INSERT INTO aggregated_amounts +SELECT 'available_coins' AS name, COALESCE(SUM(received - spent), 0) AS value FROM balances; + + +# --- !Downs + +DROP TABLE aggregated_amounts; diff --git a/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala index d05b429..ee45c94 100644 --- a/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/LedgerPostgresDataHandlerSpec.scala @@ -2,7 +2,7 @@ package com.xsn.explorer.data import com.alexitc.playsonify.sql.FieldOrderingSQLInterpreter import com.xsn.explorer.data.anorm.LedgerPostgresDataHandler -import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} +import com.xsn.explorer.data.anorm.dao.{AggregatedAmountPostgresDAO, BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} import com.xsn.explorer.data.common.PostgresDataHandlerSpec import com.xsn.explorer.errors.{PreviousBlockMissingError, RepeatedBlockHeightError} import com.xsn.explorer.helpers.{BlockLoader, TransactionLoader} @@ -17,7 +17,8 @@ class LedgerPostgresDataHandlerSpec extends PostgresDataHandlerSpec with BeforeA database, new BlockPostgresDAO(new FieldOrderingSQLInterpreter), new TransactionPostgresDAO(new FieldOrderingSQLInterpreter), - new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) + new BalancePostgresDAO(new FieldOrderingSQLInterpreter), + new AggregatedAmountPostgresDAO) val blockList = List( BlockLoader.get("00000c822abdbb23e28f79a49d29b41429737c6c7e15df40d1b1f1b35907ae34"), diff --git a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala index e8f4f57..53c6d9f 100644 --- a/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala +++ b/server/test/com/xsn/explorer/data/TransactionPostgresDataHandlerSpec.scala @@ -3,7 +3,7 @@ package com.xsn.explorer.data import com.alexitc.playsonify.models.ordering.{FieldOrdering, OrderingCondition} import com.alexitc.playsonify.models.pagination._ import com.alexitc.playsonify.sql.FieldOrderingSQLInterpreter -import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} +import com.xsn.explorer.data.anorm.dao.{AggregatedAmountPostgresDAO, BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} import com.xsn.explorer.data.anorm.{BlockPostgresDataHandler, LedgerPostgresDataHandler, TransactionPostgresDataHandler} import com.xsn.explorer.data.common.PostgresDataHandlerSpec import com.xsn.explorer.errors.{BlockNotFoundError, TransactionNotFoundError} @@ -22,7 +22,8 @@ class TransactionPostgresDataHandlerSpec extends PostgresDataHandlerSpec with Be database, new BlockPostgresDAO(new FieldOrderingSQLInterpreter), new TransactionPostgresDAO(new FieldOrderingSQLInterpreter), - new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) + new BalancePostgresDAO(new FieldOrderingSQLInterpreter), + new AggregatedAmountPostgresDAO) lazy val blockDataHandler = new BlockPostgresDataHandler(database, new BlockPostgresDAO(new FieldOrderingSQLInterpreter)) val defaultOrdering = FieldOrdering(TransactionField.Time, OrderingCondition.DescendingOrder) diff --git a/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala b/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala index 9f23769..a175e61 100644 --- a/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala +++ b/server/test/com/xsn/explorer/services/LedgerSynchronizerServiceSpec.scala @@ -3,7 +3,7 @@ package com.xsn.explorer.services import com.alexitc.playsonify.core.FutureApplicationResult import com.alexitc.playsonify.sql.FieldOrderingSQLInterpreter import com.alexitc.playsonify.validators.PaginatedQueryValidator -import com.xsn.explorer.data.anorm.dao.{BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} +import com.xsn.explorer.data.anorm.dao.{AggregatedAmountPostgresDAO, BalancePostgresDAO, BlockPostgresDAO, TransactionPostgresDAO} import com.xsn.explorer.data.anorm.{BlockPostgresDataHandler, LedgerPostgresDataHandler, TransactionPostgresDataHandler} import com.xsn.explorer.data.async.{BlockFutureDataHandler, LedgerFutureDataHandler, TransactionFutureDataHandler} import com.xsn.explorer.data.common.PostgresDataHandlerSpec @@ -24,7 +24,8 @@ class LedgerSynchronizerServiceSpec extends PostgresDataHandlerSpec with BeforeA database, new BlockPostgresDAO(new FieldOrderingSQLInterpreter), new TransactionPostgresDAO(new FieldOrderingSQLInterpreter), - new BalancePostgresDAO(new FieldOrderingSQLInterpreter)) + new BalancePostgresDAO(new FieldOrderingSQLInterpreter), + new AggregatedAmountPostgresDAO) lazy val transactionDataHandler = new TransactionPostgresDataHandler( database,