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

Use materialized view for vault pnl data. #2598

Merged
merged 4 commits into from
Nov 22, 2024

Conversation

vincentwschau
Copy link
Contributor

@vincentwschau vincentwschau commented Nov 22, 2024

Changelist

Vault PnL queries will scale with the number of vaults and the amount of data to query from the database. These queries fetch information that does not have to be updated often (pnl ticks are created once an hour). Instead of querying and computing this data each time, use a materialized view that is periodically refreshed (once every 5 minutes within the first 15 minutes of each hour / day).

Test Plan

Unit tests.

Author/Reviewer Checklist

  • If this PR has changes that result in a different app state given the same prior state and transaction list, manually add the state-breaking label.
  • If the PR has breaking postgres changes to the indexer add the indexer-postgres-breaking label.
  • If this PR isn't state-breaking but has changes that modify behavior in PrepareProposal or ProcessProposal, manually add the label proposal-breaking.
  • If this PR is one of many that implement a specific feature, manually label them all feature:[feature-name].
  • If you wish to for mergify-bot to automatically create a PR to backport your change to a release branch, manually add the label backport/[branch-name].
  • Manually add any of the following labels: refactor, chore, bug.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced materialized views for hourly and daily vault profit and loss (PnL) data.
    • Added functionality for refreshing PnL views to ensure up-to-date data.
    • Implemented a new task for managing refresh intervals of vault PnL views.
    • Expanded the API to include new methods for retrieving PnL data from the materialized views.
  • Tests

    • Established a comprehensive test suite for the PnL views and refresh tasks to ensure accuracy and performance.
    • Enhanced test setup to maintain data consistency across tests.
  • Configuration

    • Added new configuration options to control refresh intervals and enable/disable refresh loops for vault PnL data.

@vincentwschau vincentwschau requested a review from a team as a code owner November 22, 2024 18:46
Copy link
Contributor

coderabbitai bot commented Nov 22, 2024

Walkthrough

This pull request introduces a comprehensive testing framework for the PnlTicks store in the vault-pnl-ticks-view module, along with new database migrations for creating materialized views for hourly and daily PnL data. It also adds functionality for querying and refreshing these views, integrates them into the vault controller, and enhances the configuration for managing refresh intervals. The changes ensure that PnL data is accurately retrieved and maintained across various components of the application.

Changes

File Change Summary
indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts Introduced a new test suite for PnlTicks store, including setup and parameterized tests for retrieving PnL ticks.
indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts Added migration for creating vaults_hourly_pnl materialized view, including up and down methods.
indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts Added migration for creating vaults_daily_pnl materialized view, including up and down methods.
indexer/packages/postgres/src/index.ts Added export for VaultPnlTicksView from the vault-pnl-ticks-view module.
indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts Introduced functions for managing and querying PnL data, including refreshHourlyView, refreshDailyView, and getVaultsPnl.
indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts Updated tests to refresh PnL views after each test case, ensuring data integrity.
indexer/services/comlink/src/controllers/api/v4/vault-controller.ts Replaced PnlTicksTable.getPnlTicksAtIntervals with VaultPnlTicksView.getVaultsPnl for fetching PnL ticks.
indexer/services/roundtable/src/config.ts Added new configuration entries for managing PNL refresh intervals and enabling the refresh loop.
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts Introduced a task to refresh hourly and daily PnL views based on configured time windows.
indexer/services/roundtable/__tests__/tasks/refresh-vault-pnl.test.ts Added tests for refreshVaulPnlTask, verifying correct behavior under time constraints.

Possibly related PRs

Suggested reviewers

  • tqin7

Poem

🐇 In the vaults where numbers play,
PnL ticks dance both night and day.
With tests to guard and views to refresh,
Our data sings, a joyful mesh!
So hop along, let’s celebrate,
For every tick, we elevate! 🎉


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0fb0b97 and 0602679.

📒 Files selected for processing (1)
  • indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • indexer/services/comlink/tests/controllers/api/v4/vault-controller.test.ts

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Outside diff range and nitpick comments (12)
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (1)

30-44: Refactor to reduce duplication and improve consistency.

The daily view refresh implementation duplicates logic from the hourly view refresh and has inconsistent variable naming.

Consider extracting a common function for both refresh operations:

+async function refreshView(
+  viewType: 'hourly' | 'daily',
+  currentTime: DateTime,
+): Promise<void> {
+  const timeUnit = viewType === 'hourly' ? 'hour' : 'day';
+  const timeFromStart = currentTime.diff(
+    currentTime.startOf(timeUnit)
+  ).toMillis();
+
+  if (timeFromStart < config.TIME_WINDOW_FOR_REFRESH_MS) {
+    logger.info({
+      at: 'refresh-vault-pnl#runTask',
+      message: `Refreshing vault ${viewType} pnl view`,
+      currentTime,
+      timeFromStart,
+    });
+
+    const refreshStart: number = Date.now();
+    try {
+      await (viewType === 'hourly'
+        ? VaultPnlTicksView.refreshHourlyView()
+        : VaultPnlTicksView.refreshDailyView());
+      logger.info({
+        at: 'refresh-vault-pnl#runTask',
+        message: `Successfully refreshed ${viewType} view`,
+      });
+    } catch (error) {
+      logger.error({
+        at: 'refresh-vault-pnl#runTask',
+        message: `Failed to refresh ${viewType} view`,
+        error,
+      });
+      throw error;
+    }
+    stats.timing(
+      `${config.SERVICE_NAME}.refresh-vault-pnl.${viewType}-view.timing`,
+      Date.now() - refreshStart,
+    );
+  }
+}

 export default async function runTask(): Promise<void> {
   const currentTime: DateTime = DateTime.utc();
-  // ... existing hourly and daily refresh code ...
+  await refreshView('hourly', currentTime);
+  await refreshView('daily', currentTime);
 }
indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts (1)

45-47: Enhance down migration to clean up all artifacts

The down migration should clean up all created database objects, including the index.

Consider this implementation:

 export async function down(knex: Knex): Promise<void> {
+  return knex.transaction(async (trx) => {
+    try {
+      // Drop the index first to avoid dependency issues
+      await knex.raw('DROP INDEX IF EXISTS vaults_daily_pnl_id_idx;');
       await knex.raw('DROP MATERIALIZED VIEW IF EXISTS vaults_daily_pnl;');
+    } catch (error) {
+      console.error('Rollback failed:', error);
+      throw error;
+    }
+  });
 }
indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts (3)

35-35: Consider making the time window configurable.

The 7-day window (604800 seconds) is hardcoded. Consider making this configurable to allow for different retention periods based on environment needs.


3-38: Consider performance optimizations for the materialized view.

A few suggestions to optimize the view:

  1. Add an index on blockTime to improve the window function performance
  2. Consider adding indexes on frequently queried columns
  3. Define a concurrent refresh strategy to avoid blocking reads

Example index creation:

CREATE INDEX ON vaults_hourly_pnl (blockTime);
CREATE INDEX ON vaults_hourly_pnl (subaccountId, blockTime);

40-43: Consider adding error handling and logging.

While the migration functions are well-structured, they could benefit from try-catch blocks and logging for better operational visibility and error recovery.

 export async function up(knex: Knex): Promise<void> {
+  try {
     await knex.raw(RAW_VAULTS_PNL_HOURLY_QUERY);
     await knex.raw(`CREATE UNIQUE INDEX ON vaults_hourly_pnl (id);`)
+    console.log('Successfully created vaults_hourly_pnl materialized view');
+  } catch (error) {
+    console.error('Failed to create materialized view:', error);
+    throw error;
+  }
 }
indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts (2)

14-21: Consider consolidating the refresh functions to reduce duplication.

While the current implementation correctly uses CONCURRENT refresh and proper write connection, there's unnecessary code duplication between the two refresh functions.

Consider consolidating into a single function:

-export async function refreshHourlyView(): Promise<void> {
-  return await rawQuery(
-    `REFRESH MATERIALIZED VIEW CONCURRENTLY ${VAULT_HOURLY_PNL_VIEW}`,
-    {
-      readReplica: false,
-    }
-  );
-}
-
-export async function refreshDailyView(): Promise<void> {
-  return await rawQuery(
-    `REFRESH MATERIALIZED VIEW CONCURRENTLY ${VAULT_DAILY_PNL_VIEW}`,
-    {
-      readReplica: false,
-    }
-  );
-}
+export async function refreshView(interval: PnlTickInterval): Promise<void> {
+  const viewName = interval === PnlTickInterval.hour
+    ? VAULT_HOURLY_PNL_VIEW
+    : VAULT_DAILY_PNL_VIEW;
+  return await rawQuery(
+    `REFRESH MATERIALIZED VIEW CONCURRENTLY ${viewName}`,
+    {
+      readReplica: false,
+    }
+  );
+}

Also applies to: 23-30


32-65: Consider adding a caching layer for better performance.

Since the materialized views are only refreshed every 5 minutes, implementing a caching layer (e.g., Redis) with a TTL matching the refresh interval could further reduce database load and improve response times.

Example implementation approach:

  1. Check cache first
  2. If cache miss, query database
  3. Store results in cache with 5-minute TTL
  4. Return results

Would you like me to provide a detailed implementation example?

indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts (3)

23-50: Consider adding error handling to database operations.

The test setup is well-structured with proper isolation between tests. However, consider adding try-catch blocks around database operations in the lifecycle hooks to handle potential database errors gracefully.

 beforeEach(async () => {
+  try {
     await seedData();
     await WalletTable.create(defaultWallet2);
     await SubaccountTable.create(defaultSubaccountWithAlternateAddress);
     await Promise.all([
       VaultTable.create({
         ...defaultVault,
         address: defaultSubaccount.address,
       }),
       VaultTable.create({
         ...defaultVault,
         address: defaultSubaccountWithAlternateAddress.address,
       }),
     ]);
+  } catch (error) {
+    console.error('Failed to setup test data:', error);
+    throw error;
+  }
 });

52-93: Consider adding more test cases for edge scenarios.

While the current test cases cover the basic functionality, consider adding tests for:

  1. Empty result sets
  2. Edge cases around interval boundaries
  3. Error cases (e.g., invalid intervals)
  4. Cases with missing block data

Example test case for empty results:

it('should return empty array when no data exists', async () => {
  await clearData(); // Clear all data
  await VaultPnlTicksView.refreshDailyView();
  await VaultPnlTicksView.refreshHourlyView();
  
  const pnlTicks = await VaultPnlTicksView.getVaultsPnl(
    PnlTickInterval.hour,
    7 * 24 * 60 * 60,
    DateTime.utc(),
  );
  
  expect(pnlTicks).toEqual([]);
});

95-208: Improve maintainability of the test data setup.

Consider the following improvements to make the setup function more maintainable:

  1. Extract magic numbers into named constants
  2. Use TypeScript enums or const objects for block heights
  3. Create a type-safe factory function for PnL tick creation

Example refactor:

const TIME_OFFSETS = {
  TEN_MINUTES: { minute: 10 },
  TWO_HOURS: { hour: 2 },
  TWO_DAYS: { day: 2 },
  ONE_MONTH: { day: 30 },
} as const;

const BLOCK_HEIGHTS = {
  MONTH_AGO: '3',
  TWO_DAYS_AGO: '4',
  TWO_HOURS_AGO: '6',
  TEN_MIN_AGO: '8',
  ALMOST_TEN_MIN_AGO: '10',
} as const;

interface PnlTickCreationParams {
  subaccountId: string;
  equity: string;
  blockHeight: string;
  blockTime: string;
  totalPnl: string;
  netTransfers?: string;
}

function createPnlTick(params: PnlTickCreationParams): Partial<PnlTicksFromDatabase> {
  return {
    ...params,
    createdAt: params.blockTime,
    netTransfers: params.netTransfers ?? '50',
  };
}
indexer/services/roundtable/src/config.ts (1)

222-224: Consider grouping related configuration

The TIME_WINDOW_FOR_REFRESH_MS config should be grouped with other vault PnL related configurations for better maintainability. Consider moving it near LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL.

  LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL: parseInteger({
    default: 5 * ONE_MINUTE_IN_MILLISECONDS,
  }),
+ 
+ // Refresh vault pnl view
+ TIME_WINDOW_FOR_REFRESH_MS: parseInteger({ default: 15 * ONE_MINUTE_IN_MILLISECONDS }),

  // Start delay
  START_DELAY_ENABLED: parseBoolean({ default: true }),
-
- // Refresh vault pnl view
- TIME_WINDOW_FOR_REFRESH_MS: parseInteger({ default: 15 * ONE_MINUTE_IN_MILLISECONDS }),
indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts (1)

Line range hint 1-661: Consider adding tests for edge cases related to materialized views.

While the test coverage is comprehensive for the happy path and basic error cases, consider adding tests for:

  1. Behavior when view refresh fails
  2. Concurrent view refresh scenarios
  3. Performance with large datasets

Example test case structure:

it('handles view refresh failures gracefully', async () => {
  // Mock view refresh to fail
  jest.spyOn(VaultPnlTicksView, 'refreshDailyView').mockRejectedValueOnce(new Error('Refresh failed'));
  
  // Test that the system handles the failure appropriately
  await expect(createPnlTicks()).rejects.toThrow('Refresh failed');
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between fefd10e and 7f894a5.

📒 Files selected for processing (9)
  • indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts (1 hunks)
  • indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts (1 hunks)
  • indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts (1 hunks)
  • indexer/packages/postgres/src/index.ts (1 hunks)
  • indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts (1 hunks)
  • indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts (3 hunks)
  • indexer/services/comlink/src/controllers/api/v4/vault-controller.ts (3 hunks)
  • indexer/services/roundtable/src/config.ts (3 hunks)
  • indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (1 hunks)
🔇 Additional comments (16)
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (1)

11-45: Verify refresh interval configuration and concurrent execution handling.

Please ensure:

  1. The refresh interval configuration (LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL) aligns with the TIME_WINDOW_FOR_REFRESH_MS to avoid missing refresh windows.
  2. The materialized view refresh operations handle concurrent executions gracefully.

Let's verify the configuration and implementation:

✅ Verification successful

Based on the code analysis, I can now generate the final response:

Configuration and concurrent execution are properly handled

  1. The refresh interval configuration is correctly set:

    • LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL is set to 5 minutes by default
    • TIME_WINDOW_FOR_REFRESH_MS is set to 15 minutes by default
    • This provides a sufficient window to avoid missing refreshes
  2. Concurrent execution is properly handled:

    • The materialized views are refreshed using REFRESH MATERIALIZED VIEW CONCURRENTLY
    • Both hourly and daily views use concurrent refresh to prevent blocking reads
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check refresh interval configuration and concurrent execution handling

# Check refresh interval configuration
echo "Checking refresh interval configuration..."
rg -A 5 "LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL|TIME_WINDOW_FOR_REFRESH_MS" --type ts

# Check if materialized view refresh uses CONCURRENTLY option
echo "Checking materialized view refresh implementation..."
rg -A 10 "REFRESH MATERIALIZED VIEW" --type sql

Length of output: 2177


Script:

#!/bin/bash
# Let's check the materialized view implementation in VaultPnlTicksView
echo "Checking VaultPnlTicksView implementation..."
rg -p "class VaultPnlTicksView" -A 20 --type ts

# Check SQL queries for refresh operations
echo "Checking SQL queries for materialized view refresh..."
fd -e sql . | xargs cat

# Check if there are any locks or transaction handling
echo "Checking transaction and lock handling..."
rg -p "BEGIN|LOCK" --type sql

Length of output: 101158


Script:

#!/bin/bash
# Let's check the VaultPnlTicksView implementation specifically
echo "Checking VaultPnlTicksView implementation..."
rg -p "VaultPnlTicksView" -A 30 --type ts

# Check materialized view refresh queries
echo "Checking materialized view refresh queries..."
fd -e sql -e ts -g "*vault*pnl*" | xargs cat

Length of output: 21182

indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts (1)

3-38: Verify the impact on database performance

The materialized view creation might impact database performance during refresh operations. Let's verify the potential impact:

✅ Verification successful

Materialized view implementation follows best practices for performance

The implementation shows careful consideration for performance:

  • Uses REFRESH MATERIALIZED VIEW CONCURRENTLY in vault-pnl-ticks-view.ts, allowing reads during refresh
  • Time-bounds data with a 90-day window (7776000 seconds), limiting data volume
  • Shares similar structure with hourly view, suggesting consistent query patterns
  • Includes only necessary columns and appropriate filtering in the base query
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for existing indexes and table sizes

# Look for similar materialized views or tables
rg -l "CREATE MATERIALIZED VIEW" --type ts

# Check for other PnL-related queries that might be affected
rg "FROM\s+pnl_ticks" --type ts -A 5

# Look for refresh schedules or cron jobs
rg "REFRESH MATERIALIZED VIEW" --type ts

Length of output: 3078

indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts (1)

3-38: Verify refresh strategy implementation.

The PR objectives mention refreshing the view every 5 minutes during the first 15 minutes of each hour/day, but this refresh strategy isn't defined in the migration. Ensure this is implemented in the application layer.

✅ Verification successful

Refresh strategy is properly implemented

The refresh strategy is correctly implemented in the codebase:

  • The materialized view refresh is executed every 5 minutes (LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL = 5 minutes)
  • The refresh only occurs within the first 15 minutes of each hour (TIME_WINDOW_FOR_REFRESH_MS = 15 minutes)
  • The refresh is performed concurrently to avoid blocking reads
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for refresh implementation in the codebase
rg -l "vaults_hourly_pnl" | xargs rg "REFRESH MATERIALIZED VIEW"

Length of output: 327


Script:

#!/bin/bash
# Check the refresh implementation details in vault-pnl-ticks-view.ts
cat indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts

Length of output: 1674


Script:

#!/bin/bash
# Search for scheduler or cron implementation that calls the refresh functions
rg -l "refreshHourlyView" | xargs rg -A 10 "refreshHourlyView"

Length of output: 5135


Script:

#!/bin/bash
# Check the refresh task implementation in roundtable service
cat indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts

Length of output: 1381


Script:

#!/bin/bash
# Check the configuration for refresh window
cat indexer/services/roundtable/src/config.ts

Length of output: 9588

indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts (2)

1-12: LGTM! Imports and constants are well-organized.

The imports are appropriate for the functionality, and view names are properly defined as constants.


11-12: Verify materialized view performance and indexing.

Let's ensure the materialized views are properly indexed for optimal query performance.

✅ Verification successful

Let me generate another verification script to check for refresh strategies and view definitions since we need to examine the materialized view performance more thoroughly.

This will help us understand:

  1. How the views are refreshed
  2. Complete view definitions to analyze query complexity
  3. Any scheduled refresh mechanisms

Let me try a different approach to find the view definitions and refresh mechanisms.

This broader search should help us locate:

  1. Migration files containing view definitions
  2. Any TypeScript code handling view refreshes
  3. Task scheduler implementations that might handle view maintenance

Let me check the specific content of these identified files to understand the view implementations and refresh mechanisms.

This will show us:

  1. The view store implementation which likely contains refresh logic
  2. The complete view definitions from migrations
  3. Any refresh strategies defined in the migrations

Materialized views are properly optimized for performance

The implementation shows good performance optimization practices:

  • Concurrent refresh support using REFRESH MATERIALIZED VIEW CONCURRENTLY
  • Unique indexes on id column for both views
  • Efficient time-based filtering with blockTime window conditions
  • Row partitioning with ROW_NUMBER() to get single records per time interval
  • Views are scoped to vault subaccounts only
  • Time windows are appropriately sized (7 days for hourly, 90 days for daily)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check materialized view definitions and indexes

# Check materialized view definitions
echo "Checking materialized view definitions..."
rg -A 20 "CREATE MATERIALIZED VIEW.*${VAULT_HOURLY_PNL_VIEW}"
rg -A 20 "CREATE MATERIALIZED VIEW.*${VAULT_DAILY_PNL_VIEW}"

# Check indexes on the views
echo "Checking indexes..."
rg -A 5 "CREATE INDEX.*${VAULT_HOURLY_PNL_VIEW}"
rg -A 5 "CREATE INDEX.*${VAULT_DAILY_PNL_VIEW}"

Length of output: 41719


Script:

#!/bin/bash
# Check for materialized view refresh strategies and definitions

# Search for refresh related configurations
echo "Checking refresh configurations..."
rg -A 10 "REFRESH MATERIALIZED VIEW.*vaults_(hourly|daily)_pnl"

# Search for view definitions and dependencies
echo "Checking view definitions..."
rg -A 30 "CREATE MATERIALIZED VIEW.*vaults_(hourly|daily)_pnl" --type sql

# Check for any scheduled tasks or triggers
echo "Checking for scheduled refreshes..."
rg -A 10 "RefreshMaterializedViewTask|refreshMaterializedView"

Length of output: 433


Script:

#!/bin/bash
# Search for materialized view related code with broader patterns

echo "Checking view definitions in migrations..."
fd -e ts -e sql . indexer/packages/postgres/src/db/migrations | xargs rg -l "vaults_.*_pnl" 

echo "Checking for refresh mechanisms in TypeScript files..."
fd -e ts . indexer/packages/postgres/src | xargs rg -l "refresh.*view|materialized.*view" --ignore-case

echo "Checking task scheduler implementations..."
fd -e ts . indexer/packages/postgres/src | xargs rg -A 5 "class.*Task" --ignore-case

Length of output: 1066


Script:

#!/bin/bash

echo "Checking vault-pnl-ticks-view.ts implementation..."
cat indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts

echo -e "\nChecking hourly view migration..."
cat indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts

echo -e "\nChecking daily view migration..."
cat indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts

Length of output: 5337

indexer/packages/postgres/src/index.ts (2)

54-54: LGTM! Export follows established patterns

The new export for VaultPnlTicksView follows the consistent naming and organization patterns used throughout the file, and aligns with the PR objective of implementing materialized views for vault PnL data.


54-54: Verify the existence of the vault-pnl-ticks-view module

Let's ensure the referenced module exists to prevent potential runtime errors.

✅ Verification successful

The vault-pnl-ticks-view module exists and exports the expected functions

The verification confirms that:

  • The file exists at the expected location: indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts
  • The module exports three key functions:
    • refreshHourlyView
    • refreshDailyView
    • getVaultsPnl
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence of the vault-pnl-ticks-view module
# and its exported functions mentioned in the AI summary

# Check if the file exists
fd -t f "vault-pnl-ticks-view.ts" indexer/packages/postgres/src/stores

# Verify the module exports the expected functions
rg -A 5 "export (async )?function (refreshHourlyView|refreshDailyView|getVaultsPnl)" indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts

Length of output: 873

indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts (1)

1-22: LGTM!

The imports are well-organized and include all necessary dependencies for the test suite.

indexer/services/roundtable/src/config.ts (2)

63-63: LGTM: Loop enablement flag follows existing patterns

The LOOPS_ENABLED_REFRESH_VAULT_PNL flag follows the established pattern of other loop enablement flags and has a sensible default value.


144-146: Verify refresh interval timing

The 5-minute refresh interval might be too frequent since PnL ticks are generated hourly (as mentioned in PR objectives). Consider if this could lead to unnecessary database load.

Let's check the PnL tick generation interval in the codebase:

Consider adjusting the refresh interval based on:

  1. The actual PnL tick generation frequency
  2. The trade-off between data freshness and database load
✅ Verification successful

Let me gather more information about the vault PnL refresh implementation to understand its relationship with PnL ticks.


Let me search for the actual implementation of vault PnL refresh to understand its purpose and dependencies.


Let me search for the actual implementation of the refresh vault PnL task to understand how the data is used.


Let me check the task implementation in the roundtable service.


Based on the gathered information, I can now provide a final response:

5-minute refresh interval for vault PnL materialized views is appropriate

The 5-minute refresh interval for vault PnL views is justified because:

  • The materialized views (vaults_hourly_pnl and vaults_daily_pnl) are used to serve historical PnL data through API endpoints
  • While PnL ticks are generated hourly (PNL_TICK_UPDATE_INTERVAL_MS: ONE_HOUR_IN_MILLISECONDS), the views need to be refreshed more frequently to ensure timely data availability for API consumers
  • The refresh operation uses REFRESH MATERIALIZED VIEW CONCURRENTLY which doesn't block reads, minimizing database impact
  • The views are used by /vault/v1/megavault/historicalPnl and /vault/v1/vaults/historicalPnl endpoints which need fresh data for UI display
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify PnL tick generation interval
# Look for PNL_TICK_UPDATE_INTERVAL_MS usage and other timing configurations

rg -A 2 "PNL_TICK_UPDATE_INTERVAL_MS"

Length of output: 5749


Script:

#!/bin/bash
# Search for vault PnL refresh implementation and its dependencies
rg -A 5 "LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL" 

# Also search for any vault PnL related code
ast-grep --pattern 'function $FUNC_NAME($$$) {
  $$$
  refreshVaultPnl
  $$$
}'

Length of output: 558


Script:

#!/bin/bash
# Search for vault PnL refresh implementation
rg -A 10 "refreshVaultPnl" 

# Search for any loops or tasks related to vault PnL
rg -A 5 "vault.*pnl" -i

Length of output: 70945


Script:

#!/bin/bash
# Search for the refresh vault PnL task implementation
rg -A 10 "refresh-vault-pnl" src/tasks/

# Also search for any materialized view refresh scheduling
rg -A 5 "REFRESH MATERIALIZED VIEW" 

Length of output: 1094


Script:

#!/bin/bash
# Search for refresh vault PnL task in roundtable service
rg -A 15 "refresh-vault-pnl" indexer/services/roundtable/src/

# Also search for any PNL tick generation related code
rg -A 10 "PNL_TICK_UPDATE_INTERVAL_MS" indexer/services/roundtable/src/

Length of output: 8664

indexer/services/comlink/src/controllers/api/v4/vault-controller.ts (3)

32-32: LGTM: Import of VaultPnlTicksView

The addition of VaultPnlTicksView import aligns with the PR objective to implement materialized views for vault PnL data.


562-565: LGTM: Consistent use of materialized view for latest PnL tick

The implementation correctly uses the materialized view with appropriate interval and window configuration.

Verify the configuration for latest PnL tick window:

✅ Verification successful

Configuration for latest PnL tick window is properly defined

The configuration VAULT_LATEST_PNL_TICK_WINDOW_HOURS is properly defined in the config schema with a default value of 1 hour, which is a reasonable window size for latest PnL tick calculation. The test files demonstrate that this configuration can be overridden when needed (e.g., set to 48 hours in tests).

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the configuration for PnL tick windows
rg "VAULT_LATEST_PNL_TICK_WINDOW_HOURS.*=.*" 

Length of output: 335


Script:

#!/bin/bash
# Check the configuration file for PNL tick window definition
fd "config.ts|config.js" indexer/services/comlink/ --exec cat {}

Length of output: 3482


347-350: LGTM: Migration to materialized view for PnL data

The implementation correctly uses the new materialized view while maintaining the adjustment logic. This change should improve query performance as intended.

Verify that the materialized view refresh timing aligns with the PnL tick generation:

✅ Verification successful

LGTM: Materialized view refresh is properly configured

The materialized views for vault PnL data are refreshed through a dedicated service (roundtable) with appropriate timing:

  • Hourly view refresh interval: 5 minutes (LOOPS_INTERVAL_MS_REFRESH_VAULT_PNL)
  • Views are refreshed concurrently to avoid blocking reads
  • Both hourly and daily views are refreshed within a 15-minute window (TIME_WINDOW_FOR_REFRESH_MS)
  • Views have unique indices for efficient querying
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check the materialized view refresh configuration
rg -A 5 "CREATE MATERIALIZED VIEW.*vault_pnl" 

Length of output: 47


Script:

#!/bin/bash
# Let's try searching for SQL files and view definitions with different patterns
fd -e sql | xargs rg -l "vault.*pnl"

# Also search for view definitions in TypeScript files
rg -g "*.ts" "class.*VaultPnlTicksView" -A 10

Length of output: 86


Script:

#!/bin/bash
# Let's try a broader search for any PnL view or materialized view related code
rg -g "*.{ts,sql}" -i "materialized.*view|pnl.*view|vault.*pnl" -B 2 -A 2

# Also search for the specific class implementation
ast-grep --pattern 'class VaultPnlTicksView {
  $$$
  getVaultsPnl($_) {
    $$$
  }
  $$$
}'

Length of output: 51064

indexer/services/comlink/__tests__/controllers/api/v4/vault-controller.test.ts (3)

19-19: LGTM!

The import of VaultPnlTicksView is correctly placed and necessary for the materialized view functionality.


131-132: LGTM! Good test cleanup practice.

The refresh calls for both daily and hourly views in the afterEach block ensure proper test isolation and data consistency between test runs.


658-659: LGTM! Proper view refresh timing.

The refresh calls are correctly placed at the end of createPnlTicks to ensure the materialized views reflect all the created test data.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (5)
indexer/services/roundtable/__tests__/tasks/refresh-vault-pnl.test.ts (3)

41-47: Consider improving test isolation.

Refreshing views in afterEach might mask issues where tests depend on stale view data. Consider:

  1. Moving view refresh to beforeEach to ensure fresh state for each test
  2. Adding explicit assertions about view state where relevant

109-129: Consider parameterizing test data setup.

The helper function uses hardcoded values that could be made more meaningful or parameterized:

  1. Block height ('6') should be a named constant or parameter
  2. PnL values (equity: '1080', totalPnl: '1180') could be parameterized for different test scenarios

Example refactor:

- async function setupPnlTick(): Promise<PnlTicksFromDatabase> {
+ async function setupPnlTick(
+   params: {
+     blockHeight?: string,
+     equity?: string,
+     totalPnl?: string,
+     netTransfers?: string,
+   } = {},
+ ): Promise<PnlTicksFromDatabase> {
+   const {
+     blockHeight = '6',
+     equity = '1080',
+     totalPnl = '1180',
+     netTransfers = '50',
+   } = params;

18-130: Consider adding test cases for error conditions.

The test suite thoroughly covers the happy path and time window boundaries, but could benefit from additional test cases:

  1. Database errors during view refresh
  2. Invalid PnL data scenarios
  3. Concurrent refresh attempts
indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts (2)

52-93: Enhance test coverage with edge cases and clearer descriptions

While the parameterized tests cover basic functionality, consider:

  1. Adding edge cases for time boundary conditions
  2. Making test descriptions more explicit about what's being verified
  3. Adding test cases for empty results and error conditions

Example improvements:

it.each([
  {
    description: 'returns hourly PnL ticks excluding same-hour ticks except first',
    interval: PnlTickInterval.hour,
    // ... test params
  },
  {
    description: 'returns daily PnL ticks excluding same-day ticks except first',
    interval: PnlTickInterval.day,
    // ... test params
  },
  {
    description: 'returns empty array when no ticks exist',
    interval: PnlTickInterval.hour,
    // ... test params
  },
  {
    description: 'handles ticks exactly at hour/day boundaries',
    interval: PnlTickInterval.hour,
    // ... test params
  }
])

95-209: Improve test data setup with explicit time offsets and validation

The setup function creates comprehensive test data, but could be improved by:

  1. Using constants for time offsets to make the test data structure more explicit
  2. Adding validation to ensure test data was created correctly
  3. Using more descriptive variable names for time offsets

Example improvements:

const TIME_OFFSETS = {
  RECENT: { minutes: 10 },
  SAME_HOUR: { minutes: 50 },
  DIFFERENT_HOUR: { hours: 2 },
  DIFFERENT_DAY: { days: 2 },
  HISTORICAL: { days: 30 },
} as const;

async function setupIntervalPnlTicks(): Promise<PnlTicksFromDatabase[]> {
  const currentTime: DateTime = DateTime.utc().startOf('day');
  const timePoints = Object.entries(TIME_OFFSETS).reduce((acc, [key, offset]) => ({
    ...acc,
    [key]: currentTime.minus(offset).toISO(),
  }), {});
  
  // Create blocks and ticks using timePoints
  // Add validation
  const createdTicks = await PnlTicksTable.createMany([...]);
  expect(createdTicks).toHaveLength(9); // Validate creation
  return createdTicks;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7f894a5 and 1bde4f6.

📒 Files selected for processing (3)
  • indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts (1 hunks)
  • indexer/services/roundtable/__tests__/tasks/refresh-vault-pnl.test.ts (1 hunks)
  • indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts
🔇 Additional comments (2)
indexer/services/roundtable/__tests__/tasks/refresh-vault-pnl.test.ts (1)

1-17: LGTM! Import statements and setup are well-organized.

The imports are properly structured, and AWS helpers are correctly mocked for unit testing.

indexer/packages/postgres/__tests__/stores/vault-pnl-ticks-view.test.ts (1)

1-22: LGTM: Imports are well organized

The imports are logically grouped and all necessary dependencies are included for database operations, types, and test helpers.

indexer/services/roundtable/src/config.ts Outdated Show resolved Hide resolved
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts Outdated Show resolved Hide resolved
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts Outdated Show resolved Hide resolved
const result: {
rows: PnlTicksFromDatabase[],
} = await knexReadReplica.getConnection().raw(
`
Copy link
Contributor

Choose a reason for hiding this comment

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

sql!!

import * as Knex from "knex";

const RAW_VAULTS_PNL_HOURLY_QUERY: string = `
CREATE MATERIALIZED VIEW IF NOT EXISTS vaults_hourly_pnl AS WITH vault_subaccounts AS
Copy link
Contributor

Choose a reason for hiding this comment

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

niiice

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (3)
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (2)

9-11: Enhance the JSDoc documentation with more details.

The current documentation could be more descriptive about the task's purpose, timing windows, and behavior.

Consider expanding the documentation:

 /**
- * Refresh the vault pnl ticks views.
+ * Refreshes the vault PnL materialized views (hourly and daily) within a configured time window
+ * after the start of each hour/day. This task is designed to optimize query performance by
+ * maintaining pre-computed PnL data that updates periodically instead of computing it on-demand.
+ * 
+ * The refresh occurs only if the current time is within TIME_WINDOW_FOR_REFRESH_VAULT_PNL_MS
+ * milliseconds from the start of the hour (for hourly view) or day (for daily view).
  */

31-53: Refactor to reduce code duplication and improve maintainability.

The hourly and daily refresh logic shares similar structure, which could be abstracted into a reusable function.

Consider refactoring to reduce duplication:

+async function refreshView(
+  viewType: 'hourly' | 'daily',
+  currentTime: DateTime,
+  refreshFn: () => Promise<void>,
+): Promise<void> {
+  const timeStart = currentTime.startOf(viewType === 'hourly' ? 'hour' : 'day');
+  const timeFromStart = currentTime.diff(timeStart).toMillis();
+
+  if (timeFromStart < config.TIME_WINDOW_FOR_REFRESH_VAULT_PNL_MS) {
+    logger.info({
+      at: 'refresh-vault-pnl#runTask',
+      message: `Refreshing vault ${viewType} pnl view`,
+      currentTime,
+      timeFromStart,
+    });
+    const refreshStart: number = Date.now();
+    try {
+      await refreshFn();
+      logger.info({
+        at: 'refresh-vault-pnl#runTask',
+        message: `Successfully refreshed ${viewType} view`,
+      });
+      stats.timing(
+        `${config.SERVICE_NAME}.refresh-vault-pnl.${viewType}-view.timing`,
+        Date.now() - refreshStart,
+      );
+    } catch (error) {
+      logger.error({
+        at: 'refresh-vault-pnl#runTask',
+        message: `Failed to refresh ${viewType} view`,
+        error,
+      });
+      throw error;
+    }
+  }
+}

 export default async function runTask(): Promise<void> {
   try {
     const currentTime: DateTime = DateTime.utc();
-    if (currentTime.diff(...)
-      // ... hourly refresh logic
-    }
-
-    if (currentTime.diff(...)
-      // ... daily refresh logic
-    }
+    await refreshView('hourly', currentTime, VaultPnlTicksView.refreshHourlyView);
+    await refreshView('daily', currentTime, VaultPnlTicksView.refreshDailyView);
   } catch (error) {
     logger.error({
       at: 'refresh-vault-pnl#runTask',
       message: 'Failed to refresh vault pnl views',
       error,
     });
+    throw error; // Re-throw to ensure task failure is properly handled
   }
 }
indexer/services/roundtable/src/config.ts (1)

222-224: Improve configuration organization and naming consistency

Consider these improvements to better align with the codebase's established patterns:

  1. Move the configuration under an existing section (e.g., "Loop Timing") instead of creating a new section
  2. Rename the configuration to follow the established naming pattern
  3. Remove the unnecessary blank line
-
-  // Refresh vault pnl view
-  TIME_WINDOW_FOR_REFRESH_VAULT_PNL_MS: parseInteger({ default: 15 * ONE_MINUTE_IN_MILLISECONDS }),
+  // Loop Timing (continued)
+  LOOPS_WINDOW_MS_REFRESH_VAULT_PNL: parseInteger({ default: 15 * ONE_MINUTE_IN_MILLISECONDS }),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 1bde4f6 and 0fb0b97.

📒 Files selected for processing (6)
  • indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts (1 hunks)
  • indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts (1 hunks)
  • indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts (1 hunks)
  • indexer/services/roundtable/__tests__/tasks/refresh-vault-pnl.test.ts (1 hunks)
  • indexer/services/roundtable/src/config.ts (3 hunks)
  • indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • indexer/packages/postgres/src/db/migrations/migration_files/20241119162238_create_vault_hourly_view.ts
  • indexer/packages/postgres/src/db/migrations/migration_files/20241119163402_create_vault_daily_view.ts
  • indexer/packages/postgres/src/stores/vault-pnl-ticks-view.ts
  • indexer/services/roundtable/tests/tasks/refresh-vault-pnl.test.ts
🔇 Additional comments (3)
indexer/services/roundtable/src/tasks/refresh-vault-pnl.ts (1)

46-52: Verify error handling in the task scheduler.

The error is logged but not re-thrown, which might prevent the task scheduler from properly handling task failures.

Let's verify how task failures are handled:

indexer/services/roundtable/src/config.ts (2)

63-63: LGTM!

The loop enablement flag follows the established naming convention and its default value of true is appropriate for this core functionality.


144-146: LGTM!

The loop interval configuration follows the established pattern and its default value of 5 minutes aligns with the PR objectives for refreshing the materialized view.

@vincentwschau vincentwschau merged commit 92733ca into main Nov 22, 2024
16 checks passed
@vincentwschau vincentwschau deleted the vincentc/use-materialized-view branch November 22, 2024 23:06
@coderabbitai coderabbitai bot mentioned this pull request Nov 25, 2024
6 tasks
@vincentwschau
Copy link
Contributor Author

@Mergifyio backport release/indexer/v7.x

Copy link
Contributor

mergify bot commented Nov 25, 2024

backport release/indexer/v7.x

✅ Backports have been created

mergify bot pushed a commit that referenced this pull request Nov 25, 2024
vincentwschau added a commit that referenced this pull request Nov 25, 2024
Co-authored-by: vincentwschau <99756290+vincentwschau@users.noreply.github.com>
@coderabbitai coderabbitai bot mentioned this pull request Dec 17, 2024
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

2 participants