fix(trading): performance issues with many orders (#3944)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
This commit is contained in:
parent
e8ff1b0b60
commit
f2c70e6da4
@ -59,7 +59,7 @@ const cacheConfig: InMemoryCacheConfig = {
|
||||
keyFields: false,
|
||||
},
|
||||
TradableInstrument: {
|
||||
keyFields: ['instrument'],
|
||||
keyFields: false,
|
||||
},
|
||||
Product: {
|
||||
keyFields: ['settlementAsset', ['id']],
|
||||
@ -95,5 +95,10 @@ const cacheConfig: InMemoryCacheConfig = {
|
||||
Fees: {
|
||||
keyFields: false,
|
||||
},
|
||||
// Don't cache order update as this subscription result gets merged into the main order cache
|
||||
// We don't need to write these to the cache at all
|
||||
OrderUpdate: {
|
||||
keyFields: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ import { AppLoader, DynamicLoader } from '../components/app-loader';
|
||||
import { Navbar } from '../components/navbar';
|
||||
import { ENV } from '../lib/config';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeOrdersProvider, allOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { useTelemetryApproval } from '../lib/hooks/use-telemetry-approval';
|
||||
import {
|
||||
ProtocolUpgradeCountdownMode,
|
||||
@ -151,11 +151,6 @@ const PartyData = () => {
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
useDataProvider({
|
||||
dataProvider: allOrdersProvider,
|
||||
variables,
|
||||
skip,
|
||||
});
|
||||
return null;
|
||||
};
|
||||
|
||||
|
@ -235,6 +235,7 @@ function makeDataProviderInternal<
|
||||
// subscription is started before initial query, all deltas that will arrive before initial query response are put on queue
|
||||
const updateQueue: Delta[] = [];
|
||||
|
||||
let initialized = false;
|
||||
let resetTimer: ReturnType<typeof setTimeout>;
|
||||
let variables: Variables;
|
||||
let data: Data | null = null;
|
||||
@ -480,12 +481,10 @@ function makeDataProviderInternal<
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
if (subscription) {
|
||||
if (resetTimer) {
|
||||
clearTimeout(resetTimer);
|
||||
}
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
initialized = true;
|
||||
loading = true;
|
||||
error = undefined;
|
||||
notifyAll();
|
||||
@ -499,15 +498,12 @@ function makeDataProviderInternal<
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
if (!subscription) {
|
||||
return;
|
||||
}
|
||||
subscriptionUnsubscribe();
|
||||
initialized = false;
|
||||
data = null;
|
||||
error = undefined;
|
||||
loading = false;
|
||||
loaded = false;
|
||||
notifyAll();
|
||||
};
|
||||
|
||||
// remove callback from list, and unsubscribe if there is no more callbacks registered
|
||||
@ -524,13 +520,14 @@ function makeDataProviderInternal<
|
||||
|
||||
return (callback, c, v) => {
|
||||
callbacks.push(callback);
|
||||
if (callbacks.length === 1) {
|
||||
if (!initialized) {
|
||||
client = c;
|
||||
if (v) {
|
||||
variables = v;
|
||||
}
|
||||
initialize();
|
||||
} else {
|
||||
clearTimeout(resetTimer);
|
||||
notify(callback);
|
||||
}
|
||||
return {
|
||||
|
@ -170,6 +170,7 @@ export const DealTicket = ({
|
||||
marginAccountBalance || generalAccountBalance ? balance : undefined,
|
||||
},
|
||||
skip: !normalizedOrder,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
const assetSymbol =
|
||||
|
@ -34,6 +34,7 @@ export const useEstimateFees = (
|
||||
type: order.type,
|
||||
},
|
||||
skip: !pubKey || !order?.size || !order?.price,
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
return data?.estimateFees;
|
||||
};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import produce from 'immer';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import {
|
||||
@ -6,9 +5,9 @@ import {
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||
import { OrderStatus } from '@vegaprotocol/types';
|
||||
import type {
|
||||
OrderFieldsFragment,
|
||||
@ -80,6 +79,28 @@ const orderMatchFilters = (
|
||||
return true;
|
||||
};
|
||||
|
||||
const mapOrderUpdateToOrder = (
|
||||
orderUpdate: OrderUpdateFieldsFragment
|
||||
): OrderFieldsFragment => {
|
||||
const { marketId, liquidityProvisionId, ...order } = orderUpdate;
|
||||
// If there is a liquidity provision id add the object to the resulting order
|
||||
const liquidityProvision: OrderFieldsFragment['liquidityProvision'] | null =
|
||||
liquidityProvisionId
|
||||
? {
|
||||
__typename: 'LiquidityProvision',
|
||||
}
|
||||
: null;
|
||||
return {
|
||||
...order,
|
||||
liquidityProvision: liquidityProvision,
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
__typename: 'Order',
|
||||
};
|
||||
};
|
||||
|
||||
const getData = (
|
||||
responseData: OrdersQuery | null
|
||||
): Edge<OrderFieldsFragment>[] =>
|
||||
@ -93,27 +114,9 @@ const getDelta = (
|
||||
if (!subscriptionData.orders) {
|
||||
return [];
|
||||
}
|
||||
subscriptionData.orders.forEach((order) => {
|
||||
client.cache.modify({
|
||||
id: client.cache.identify({
|
||||
__typename: 'Order',
|
||||
id: order.id,
|
||||
}),
|
||||
fields: {
|
||||
price: () => order.price,
|
||||
size: () => order.size,
|
||||
remaining: () => order.remaining,
|
||||
updatedAt: () => order.updatedAt,
|
||||
status: () => order.status,
|
||||
},
|
||||
});
|
||||
});
|
||||
return subscriptionData.orders;
|
||||
};
|
||||
|
||||
const getPageInfo = (responseData: OrdersQuery): PageInfo | null =>
|
||||
responseData.party?.ordersConnection?.pageInfo || null;
|
||||
|
||||
export const update = (
|
||||
data: ReturnType<typeof getData> | null,
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
@ -132,49 +135,37 @@ export const update = (
|
||||
),
|
||||
'createdAt'
|
||||
);
|
||||
return produce(data, (draft) => {
|
||||
// Add or update incoming orders
|
||||
incoming.forEach((node) => {
|
||||
const index = draft.findIndex((edge) => edge.node.id === node.id);
|
||||
const newer =
|
||||
draft.length === 0 || node.createdAt >= draft[0].node.createdAt;
|
||||
const doesFilterPass = !variables || orderMatchFilters(node, variables);
|
||||
if (index !== -1) {
|
||||
if (doesFilterPass) {
|
||||
Object.assign(draft[index].node, node);
|
||||
} else {
|
||||
draft.splice(index, 1);
|
||||
}
|
||||
} else if (newer && doesFilterPass) {
|
||||
const { marketId, liquidityProvisionId, ...order } = node;
|
||||
|
||||
// If there is a liquidity provision id add the object to the resulting order
|
||||
const liquidityProvision:
|
||||
| OrderFieldsFragment['liquidityProvision']
|
||||
| null = liquidityProvisionId
|
||||
? {
|
||||
__typename: 'LiquidityProvision',
|
||||
}
|
||||
: null;
|
||||
|
||||
draft.unshift({
|
||||
node: {
|
||||
...order,
|
||||
liquidityProvision: liquidityProvision,
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
__typename: 'Order',
|
||||
},
|
||||
cursor: '',
|
||||
});
|
||||
const updatedData = [...data];
|
||||
incoming.forEach((orderUpdate) => {
|
||||
const index = data.findIndex((edge) => edge.node.id === orderUpdate.id);
|
||||
const newer =
|
||||
data.length === 0 || orderUpdate.createdAt >= data[0].node.createdAt;
|
||||
const doesFilterPass =
|
||||
!variables || orderMatchFilters(orderUpdate, variables);
|
||||
if (index !== -1) {
|
||||
if (doesFilterPass) {
|
||||
updatedData[index] = {
|
||||
...updatedData[index],
|
||||
node: mapOrderUpdateToOrder(orderUpdate),
|
||||
};
|
||||
} else {
|
||||
updatedData.splice(index, 1);
|
||||
}
|
||||
});
|
||||
} else if (newer && doesFilterPass) {
|
||||
updatedData.unshift({
|
||||
node: mapOrderUpdateToOrder(orderUpdate),
|
||||
cursor: '',
|
||||
});
|
||||
}
|
||||
});
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
const ordersProvider = makeDataProvider<
|
||||
const getPageInfo = (responseData: OrdersQuery): PageInfo | null =>
|
||||
responseData.party?.ordersConnection?.pageInfo || null;
|
||||
|
||||
export const ordersProvider = makeDataProvider<
|
||||
OrdersQuery,
|
||||
ReturnType<typeof getData>,
|
||||
OrdersUpdateSubscription,
|
||||
@ -189,39 +180,13 @@ const ordersProvider = makeDataProvider<
|
||||
pagination: {
|
||||
getPageInfo,
|
||||
append,
|
||||
first: 1000,
|
||||
first: 5000,
|
||||
},
|
||||
resetDelay: 1000,
|
||||
additionalContext: { isEnlargedTimeout: true },
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
|
||||
const allOrderMaxCount = 50000;
|
||||
|
||||
export const allOrdersProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
{ partyId: string; marketId?: string }
|
||||
>(
|
||||
[
|
||||
(callback, client, variables) =>
|
||||
ordersProvider(callback, client, { partyId: variables.partyId }),
|
||||
],
|
||||
(partsData, variables, prevData, parts, subscriptions) => {
|
||||
const orders = partsData[0] as ReturnType<typeof getData>;
|
||||
// load next pages until allOrderMaxCount reached
|
||||
if (
|
||||
!parts[0].isUpdate &&
|
||||
subscriptions &&
|
||||
subscriptions[0].load &&
|
||||
orders?.length < allOrderMaxCount
|
||||
) {
|
||||
subscriptions[0].load();
|
||||
}
|
||||
return variables.marketId
|
||||
? orders.filter((edge) => variables.marketId === edge.node.market.id)
|
||||
: orders;
|
||||
}
|
||||
);
|
||||
|
||||
export const activeOrdersProvider = makeDerivedDataProvider<
|
||||
ReturnType<typeof getData>,
|
||||
never,
|
||||
|
@ -76,12 +76,21 @@ export const OrderListManager = ({
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
const variables =
|
||||
filter === Filter.Open
|
||||
? { partyId, filter: { liveOnly: true } }
|
||||
: { partyId };
|
||||
|
||||
const { data, error, loading, reload } = useDataProvider({
|
||||
dataProvider: ordersWithMarketProvider,
|
||||
variables:
|
||||
filter === Filter.Open
|
||||
? { partyId, filter: { liveOnly: true } }
|
||||
: { partyId },
|
||||
variables,
|
||||
update: ({ data }) => {
|
||||
if (data && gridRef.current?.api) {
|
||||
gridRef.current.api.setRowData(data);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
@ -127,8 +136,13 @@ export const OrderListManager = ({
|
||||
[bottomPlaceholderOnFilterChanged]
|
||||
);
|
||||
|
||||
const onRowDataChanged = useCallback(() => {
|
||||
const rowCount = gridRef.current?.api?.getModel().getRowCount();
|
||||
setHasData((rowCount ?? 0) > 0);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setHasData((gridRef.current?.api?.getModel().getRowCount() ?? 0) > 0);
|
||||
setHasData(Boolean(data?.length));
|
||||
}, [data]);
|
||||
|
||||
const cancelAll = useCallback(() => {
|
||||
@ -150,8 +164,8 @@ export const OrderListManager = ({
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
onFilterChanged={onFilterChanged}
|
||||
onRowDataChanged={onRowDataChanged}
|
||||
isReadOnly={isReadOnly}
|
||||
blockLoadDebounceMillis={100}
|
||||
storeKey={storeKey}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
|
@ -49,8 +49,11 @@ export const OrderListTable = memo<
|
||||
{ onCancel, onEdit, onMarketClick, onOrderTypeClick, filter, ...props },
|
||||
ref
|
||||
) => {
|
||||
const showAllActions =
|
||||
filter === undefined || filter === Filter.Open ? true : false;
|
||||
const showAllActions = props.isReadOnly
|
||||
? false
|
||||
: filter === undefined || filter === Filter.Open
|
||||
? true
|
||||
: false;
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
|
@ -25,6 +25,7 @@ export const useVegaTransactionUpdater = () => {
|
||||
useOrderTxUpdateSubscription({
|
||||
variables,
|
||||
skip,
|
||||
fetchPolicy: 'no-cache',
|
||||
onData: ({ data: result }) =>
|
||||
result.data?.orders?.forEach((order) => {
|
||||
updateOrder(order);
|
||||
@ -34,6 +35,7 @@ export const useVegaTransactionUpdater = () => {
|
||||
useWithdrawalBusEventSubscription({
|
||||
variables,
|
||||
skip,
|
||||
fetchPolicy: 'no-cache',
|
||||
onData: ({ data: result }) =>
|
||||
result.data?.busEvents?.forEach((event) => {
|
||||
if (event.event.__typename === 'Withdrawal') {
|
||||
@ -48,6 +50,7 @@ export const useVegaTransactionUpdater = () => {
|
||||
useTransactionEventSubscription({
|
||||
variables,
|
||||
skip,
|
||||
fetchPolicy: 'no-cache',
|
||||
onData: ({ data: result }) =>
|
||||
result.data?.busEvents?.forEach((event) => {
|
||||
if (event.event.__typename === 'TransactionResult') {
|
||||
|
@ -174,6 +174,7 @@ const EditOrderDetails = ({
|
||||
}) => {
|
||||
const { data: orderById } = useOrderByIdQuery({
|
||||
variables: { orderId: data.orderId },
|
||||
fetchPolicy: 'no-cache',
|
||||
});
|
||||
const { data: markets } = useMarketList();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user