doFetchSubCount: batch support; fetch interval gap;

1. The API supports batching -- updated the code to use that. Retained string as the parameter (instead of changing it to array) so that existing clients won't be affected.

2. Make `doFetchSubCount` a batched command by default through an idle timer. This way, none of the clients need to collect IDs -- it's all done behind the scenes.

3. Added minimum of 5 minutes between each sub-count fetch for a claim ID.
This commit is contained in:
infinite-persistence 2021-10-28 12:50:17 +08:00
parent cbedc4b933
commit 5dd5826b33
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
2 changed files with 75 additions and 13 deletions

View file

@ -2,6 +2,9 @@
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
const FETCH_SUB_COUNT_MIN_INTERVAL_MS = 5 * 60 * 1000;
const FETCH_SUB_COUNT_IDLE_FIRE_MS = 100;
export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) => { export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED }); dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_STARTED });
@ -10,23 +13,56 @@ export const doFetchViewCount = (claimIdCsv: string) => (dispatch: Dispatch) =>
const viewCounts = result; const viewCounts = result;
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } }); dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_COMPLETED, data: { claimIdCsv, viewCounts } });
}) })
.catch(error => { .catch((error) => {
dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error }); dispatch({ type: ACTIONS.FETCH_VIEW_COUNT_FAILED, data: error });
}); });
}; };
export const doFetchSubCount = (claimId: string) => (dispatch: Dispatch) => { const executeFetchSubCount = (claimIdCsv: string) => (dispatch: Dispatch, getState: GetState) => {
const state = getState();
const subCountLastFetchedById = state.stats.subCountLastFetchedById;
const now = Date.now();
const claimIds = claimIdCsv.split(',').filter((id) => {
const prev = subCountLastFetchedById[id];
return !prev || now - prev > FETCH_SUB_COUNT_MIN_INTERVAL_MS;
});
if (claimIds.length === 0) {
return;
}
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_STARTED }); dispatch({ type: ACTIONS.FETCH_SUB_COUNT_STARTED });
return Lbryio.call('subscription', 'sub_count', { claim_id: claimId }) return Lbryio.call('subscription', 'sub_count', { claim_id: claimIds.join(',') })
.then((result: Array<number>) => { .then((result: Array<number>) => {
const subCount = result[0]; const subCounts = result;
dispatch({ dispatch({
type: ACTIONS.FETCH_SUB_COUNT_COMPLETED, type: ACTIONS.FETCH_SUB_COUNT_COMPLETED,
data: { claimId, subCount }, data: { claimIdCsv, subCounts, fetchDate: now },
}); });
}) })
.catch(error => { .catch((error) => {
dispatch({ type: ACTIONS.FETCH_SUB_COUNT_FAILED, data: error }); dispatch({ type: ACTIONS.FETCH_SUB_COUNT_FAILED, data: error });
}); });
}; };
let fetchSubCountTimer;
let fetchSubCountQueue = '';
export const doFetchSubCount = (claimIdCsv: string) => (dispatch: Dispatch) => {
if (fetchSubCountTimer) {
clearTimeout(fetchSubCountTimer);
}
if (fetchSubCountQueue && !fetchSubCountQueue.endsWith(',')) {
fetchSubCountQueue += ',';
}
fetchSubCountQueue += claimIdCsv;
fetchSubCountTimer = setTimeout(() => {
dispatch(executeFetchSubCount(fetchSubCountQueue));
fetchSubCountQueue = '';
}, FETCH_SUB_COUNT_IDLE_FIRE_MS);
};

View file

@ -8,15 +8,18 @@ const defaultState = {
fetchingSubCount: false, fetchingSubCount: false,
subCountError: undefined, subCountError: undefined,
subCountById: {}, subCountById: {},
subCountLastFetchedById: {},
}; };
export const statsReducer = handleActions( export const statsReducer = handleActions(
{ {
[ACTIONS.FETCH_VIEW_COUNT_STARTED]: state => ({ ...state, fetchingViewCount: true }), [ACTIONS.FETCH_VIEW_COUNT_STARTED]: (state) => ({ ...state, fetchingViewCount: true }),
[ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({ [ACTIONS.FETCH_VIEW_COUNT_FAILED]: (state, action) => ({
...state, ...state,
viewCountError: action.data, viewCountError: action.data,
}), }),
[ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => { [ACTIONS.FETCH_VIEW_COUNT_COMPLETED]: (state, action) => {
const { claimIdCsv, viewCounts } = action.data; const { claimIdCsv, viewCounts } = action.data;
@ -35,20 +38,43 @@ export const statsReducer = handleActions(
viewCountById, viewCountById,
}; };
}, },
[ACTIONS.FETCH_SUB_COUNT_STARTED]: state => ({ ...state, fetchingSubCount: true }),
[ACTIONS.FETCH_SUB_COUNT_STARTED]: (state) => ({ ...state, fetchingSubCount: true }),
[ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({ [ACTIONS.FETCH_SUB_COUNT_FAILED]: (state, action) => ({
...state, ...state,
subCountError: action.data, subCountError: action.data,
}), }),
[ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => {
const { claimId, subCount } = action.data;
const subCountById = { ...state.subCountById, [claimId]: subCount }; [ACTIONS.FETCH_SUB_COUNT_COMPLETED]: (state, action) => {
return { const { claimIdCsv, subCounts, fetchDate } = action.data;
const subCountById = Object.assign({}, state.subCountById);
const subCountLastFetchedById = Object.assign({}, state.subCountLastFetchedById);
const claimIds = claimIdCsv.split(',');
let dataChanged = false;
if (claimIds.length === subCounts.length) {
claimIds.forEach((claimId, index) => {
if (subCountById[claimId] !== subCounts[index]) {
subCountById[claimId] = subCounts[index];
dataChanged = true;
}
subCountLastFetchedById[claimId] = fetchDate;
});
}
const newState = {
...state, ...state,
fetchingSubCount: false, fetchingSubCount: false,
subCountById, subCountLastFetchedById,
}; };
if (dataChanged) {
newState.subCountById = subCountById;
}
return newState;
}, },
}, },
defaultState defaultState