Skip to content

Commit

Permalink
fix: improve leaderboard queries performance (#455)
Browse files Browse the repository at this point in the history
  • Loading branch information
matiwinnetou authored Oct 13, 2023
1 parent 7f75d8e commit 91c9450
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
public interface VoteRepository extends JpaRepository<Vote, String> {

@Query("SELECT v.categoryId as categoryId, v.proposalId as proposalId FROM Vote v WHERE v.eventId = :eventId AND v.voterStakingAddress = :stakeAddress ORDER BY v.votedAtSlot, v.idNumericHash ASC")
List<CategoryProposalProjection> getVotesByStakeAddress(@Param("eventId") String eventId, @Param("stakeAddress") String stakeAddress);
List<CategoryProposalProjection> getVotesByStakeAddress(@Param("eventId") String eventId,
@Param("stakeAddress") String stakeAddress);

Optional<Vote> findByEventIdAndCategoryIdAndVoterStakingAddress(String eventId, String categoryId, String voterStakeAddress);
Optional<Vote> findByEventIdAndCategoryIdAndVoterStakingAddress(String eventId,
String categoryId,
String voterStakeAddress);

@Query("SELECT COUNT(v) AS totalVoteCount, SUM(v.votingPower) AS totalVotingPower FROM Vote v WHERE v.eventId = :eventId")
List<HighLevelEventVoteCount> getHighLevelEventStats(@Param("eventId") String eventId);
Expand All @@ -25,7 +28,8 @@ public interface VoteRepository extends JpaRepository<Vote, String> {
List<HighLevelCategoryLevelStats> getHighLevelCategoryLevelStats(@Param("eventId") String eventId);

@Query("SELECT v.categoryId as categoryId, v.proposalId AS proposalId, COUNT(v) AS totalVoteCount, SUM(v.votingPower) AS totalVotingPower FROM Vote v WHERE v.eventId = :eventId AND v.categoryId = :categoryId GROUP BY categoryId, proposalId ORDER BY totalVotingPower DESC, totalVoteCount DESC")
List<CategoryLevelStats> getCategoryLevelStats(@Param("eventId") String eventId, @Param("categoryId") String categoryId);
List<CategoryLevelStats> getCategoryLevelStats(@Param("eventId") String eventId,
@Param("categoryId") String categoryId);

interface CategoryProposalProjection {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cardano.foundation.voting.client.ChainFollowerClient;
import org.cardano.foundation.voting.client.ChainFollowerClient.CategoryDetailsResponse;
import org.cardano.foundation.voting.client.ChainFollowerClient.EventDetailsResponse;
import org.cardano.foundation.voting.domain.Leaderboard;
import org.cardano.foundation.voting.repository.CustomVoteRepository;
import org.cardano.foundation.voting.repository.VoteRepository;
Expand Down Expand Up @@ -58,7 +60,8 @@ public Either<Problem, Boolean> isHighLevelEventLeaderboardAvailable(String even

@Override
@Transactional(readOnly = true)
public Either<Problem, Boolean> isHighLevelCategoryLeaderboardAvailable(String event, boolean forceLeaderboard) {
public Either<Problem, Boolean> isHighLevelCategoryLeaderboardAvailable(String event,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
Expand All @@ -84,7 +87,8 @@ public Either<Problem, Boolean> isHighLevelCategoryLeaderboardAvailable(String e

@Override
@Transactional(readOnly = true)
public Either<Problem, Leaderboard.ByEventStats> getEventLeaderboard(String event, boolean forceLeaderboard) {
public Either<Problem, Leaderboard.ByEventStats> getEventLeaderboard(String event,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
Expand Down Expand Up @@ -168,8 +172,13 @@ public Either<Problem, Leaderboard.ByEventStats> getEventLeaderboard(String even
byCategoryStatsBuilder.votes(Optional.ofNullable(categoryLevelStats.getTotalVoteCount()).orElse(0L));

switch (eventDetails.votingEventType()) {
case BALANCE_BASED, STAKE_BASED ->
byCategoryStatsBuilder.votingPower(Optional.ofNullable(categoryLevelStats.getTotalVotingPower()).map(String::valueOf).orElse("0"));
case BALANCE_BASED, STAKE_BASED -> {
var votingPowerM = Optional.ofNullable(categoryLevelStats.getTotalVotingPower())
.map(String::valueOf)
.orElse("0");

byCategoryStatsBuilder.votingPower(votingPowerM);
}
}

return byCategoryStatsBuilder.build();
Expand All @@ -181,6 +190,7 @@ public Either<Problem, Leaderboard.ByEventStats> getEventLeaderboard(String even

var byCategoryStatsCopy = new ArrayList<>(byCategoryStats);
// pre init with empty if category not returned from db

eventDetails.categories().forEach(categoryDetails -> {
if (!byCategoryStatsMap.containsKey(categoryDetails.id())) {
var b = Leaderboard.ByCategoryStats.builder();
Expand All @@ -203,7 +213,9 @@ public Either<Problem, Leaderboard.ByEventStats> getEventLeaderboard(String even

@Override
@Transactional(readOnly = true)
public Either<Problem, Leaderboard.ByProposalsInCategoryStats> getCategoryLeaderboard(String event, String category, boolean forceLeaderboard) {
public Either<Problem, Leaderboard.ByProposalsInCategoryStats> getCategoryLeaderboard(String event,
String category,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
Expand Down Expand Up @@ -279,7 +291,8 @@ public Either<Problem, Leaderboard.ByProposalsInCategoryStats> getCategoryLeader

@Override
@Transactional(readOnly = true, isolation = SERIALIZABLE)
public Either<Problem, List<Leaderboard.WinnerStats>> getEventWinners(String event, boolean forceLeaderboard) {
public Either<Problem, List<Leaderboard.WinnerStats>> getEventWinners(String event,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);

if (eventDetailsE.isEmpty()) {
Expand Down Expand Up @@ -316,13 +329,15 @@ public Either<Problem, List<Leaderboard.WinnerStats>> getEventWinners(String eve

var categoryIds = eventDetails.categories()
.stream()
.map(ChainFollowerClient.CategoryDetailsResponse::id)
.map(CategoryDetailsResponse::id)
.toList();

return Either.right(customVoteRepository.getEventWinners(event, categoryIds));
}

private static HashMap<String, Leaderboard.Votes> calcProposalsResults(ChainFollowerClient.CategoryDetailsResponse categoryDetails, Map<String, Leaderboard.Votes> proposalResultsMap, ChainFollowerClient.EventDetailsResponse eventDetails) {
private static HashMap<String, Leaderboard.Votes> calcProposalsResults(CategoryDetailsResponse categoryDetails,
Map<String, Leaderboard.Votes> proposalResultsMap,
EventDetailsResponse eventDetails) {
var categoryProposals = categoryDetails.proposals();

var proposalResultsMapCopy = new HashMap<>(proposalResultsMap);
Expand All @@ -345,7 +360,9 @@ private static HashMap<String, Leaderboard.Votes> calcProposalsResults(ChainFoll

@Override
@Transactional(readOnly = true)
public Either<Problem, Boolean> isCategoryLeaderboardAvailable(String event, String category, boolean forceLeaderboard) {
public Either<Problem, Boolean> isCategoryLeaderboardAvailable(String event,
String category,
boolean forceLeaderboard) {
var eventDetailsE = chainFollowerClient.getEventDetails(event);
if (eventDetailsE.isEmpty()) {
return Either.left(Problem.builder()
Expand Down Expand Up @@ -380,7 +397,7 @@ public Either<Problem, Boolean> isCategoryLeaderboardAvailable(String event, Str
return isCategoryLeaderboardAvailable(eventDetails, forceLeaderboard);
}

private Either<Problem, Boolean> isHighLevelEventLeaderboardAvailable(ChainFollowerClient.EventDetailsResponse eventDetails,
private Either<Problem, Boolean> isHighLevelEventLeaderboardAvailable(EventDetailsResponse eventDetails,
boolean forceLeaderboard) {
if (forceLeaderboard) {
return Either.right(true);
Expand All @@ -393,7 +410,7 @@ private Either<Problem, Boolean> isHighLevelEventLeaderboardAvailable(ChainFollo
return Either.right(eventDetails.proposalsReveal());
}

private Either<Problem, Boolean> isHighLevelCategoryLeaderboardAvailable(ChainFollowerClient.EventDetailsResponse eventDetails,
private Either<Problem, Boolean> isHighLevelCategoryLeaderboardAvailable(EventDetailsResponse eventDetails,
boolean forceLeaderboard) {
if (forceLeaderboard) {
return Either.right(true);
Expand All @@ -406,7 +423,7 @@ private Either<Problem, Boolean> isHighLevelCategoryLeaderboardAvailable(ChainFo
return Either.right(eventDetails.proposalsReveal());
}

private Either<Problem, Boolean> isCategoryLeaderboardAvailable(ChainFollowerClient.EventDetailsResponse eventDetails,
private Either<Problem, Boolean> isCategoryLeaderboardAvailable(EventDetailsResponse eventDetails,
boolean forceLeaderboard) {
if (forceLeaderboard) {
return Either.right(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@ CREATE TABLE vote (
CREATE INDEX idx_vote_stake_key
ON vote (event_id, category_id, voter_stake_address);

-- index for the leaderboard query
CREATE INDEX idx_vote_event_id
ON vote (event_id);

-- index for the leaderboard query
CREATE INDEX idx_vote_event_id_category_id
ON vote (event_id, category_id);

-- index for the leaderboard query
CREATE INDEX idx_vote_event_id_category_id_proposal_id
ON vote (event_id, category_id, proposal_id);

DROP TABLE IF EXISTS vote_merkle_proof;

-- benefit of storing vote merkle proof is that upon restart of app voter's receipt can be served from local db
-- benefit of storing vote merkle proof is that upon restart of app voter's receipt can be served from a db
CREATE TABLE vote_merkle_proof (
vote_id VARCHAR(255) NOT NULL,
vote_id_numeric_hash BIGINT NOT NULL,
Expand All @@ -53,3 +59,4 @@ CREATE INDEX idx_vote_merkle_proof_vote_id_event_id
-- special index to help us find out all vote_merkle_proofs that took part in rolled back transaction
CREATE INDEX idx_vote_merkle_proof_transaction_rollback
ON vote_merkle_proof (absolute_slot);

Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,21 @@ CREATE TABLE vote (
CREATE INDEX idx_vote_stake_key
ON vote (event_id, category_id, voter_stake_address);

-- index for the leaderboard query
CREATE INDEX idx_vote_event_id
ON vote (event_id);

-- index for the leaderboard query
CREATE INDEX idx_vote_event_id_category_id
ON vote (event_id, category_id);

-- index for the leaderboard query
CREATE INDEX idx_vote_event_id_category_id_proposal_id
ON vote (event_id, category_id, proposal_id);

DROP TABLE IF EXISTS vote_merkle_proof;

-- benefit of storing vote merkle proof is that upon restart of app voter's receipt can be served from local db
-- benefit of storing vote merkle proof is that upon restart of app voter's receipt can be served from a db
CREATE TABLE vote_merkle_proof (
vote_id VARCHAR(255) NOT NULL,
vote_id_numeric_hash BIGINT NOT NULL,
Expand Down

0 comments on commit 91c9450

Please sign in to comment.