tangled
alpha
login
or
join now
dollspace.gay
/
Aurora-Prism
4
fork
atom
A third party ATProto appview
4
fork
atom
overview
issues
pulls
pipelines
display bug fix
dollspacegay.tngl.sh
4 months ago
f8266bfa
bc9f456b
+83
-81
1 changed file
expand all
collapse all
unified
split
server
storage.ts
+83
-81
server/storage.ts
···
713
713
private statsCache: { data: unknown; timestamp: number } | null = null;
714
714
private readonly STATS_CACHE_TTL = 60000;
715
715
private statsQueryInProgress = false;
716
716
+
private statsQueryPromise: Promise<any> | null = null;
716
717
private backgroundRefreshInterval: NodeJS.Timeout | null = null;
717
718
718
719
constructor(dbConnection?: DbConnection) {
···
3873
3874
console.log('[STORAGE] Clearing stats cache');
3874
3875
this.statsCache = null;
3875
3876
this.statsQueryInProgress = false;
3877
3877
+
this.statsQueryPromise = null;
3876
3878
}
3877
3879
3878
3880
private async refreshStatsInBackground() {
···
3946
3948
console.log('[STORAGE] No Redis counts, falling back to PostgreSQL COUNT queries');
3947
3949
3948
3950
// No Redis counts - fallback to PostgreSQL query (only on first load)
3949
3949
-
if (this.statsQueryInProgress) {
3950
3950
-
// Another request is already fetching, return zeros
3951
3951
-
return {
3952
3952
-
totalUsers: 0,
3953
3953
-
totalPosts: 0,
3954
3954
-
totalLikes: 0,
3955
3955
-
totalReposts: 0,
3956
3956
-
totalFollows: 0,
3957
3957
-
totalBlocks: 0,
3958
3958
-
};
3951
3951
+
if (this.statsQueryInProgress && this.statsQueryPromise) {
3952
3952
+
// Another request is already fetching, wait for it instead of returning zeros
3953
3953
+
console.log('[STORAGE] Stats query already in progress, waiting for result...');
3954
3954
+
return this.statsQueryPromise;
3959
3955
}
3960
3956
3961
3957
this.statsQueryInProgress = true;
3962
3958
3963
3963
-
try {
3964
3964
-
// Use accurate COUNT(*) queries instead of pg_stat_user_tables estimates
3965
3965
-
// Run all counts in a single query for better performance
3966
3966
-
const statsPromise = this.db.execute<{
3967
3967
-
users: string;
3968
3968
-
posts: string;
3969
3969
-
likes: string;
3970
3970
-
reposts: string;
3971
3971
-
follows: string;
3972
3972
-
blocks: string;
3973
3973
-
}>(sql`
3974
3974
-
SELECT
3975
3975
-
(SELECT COUNT(*)::text FROM users) as users,
3976
3976
-
(SELECT COUNT(*)::text FROM posts) as posts,
3977
3977
-
(SELECT COUNT(*)::text FROM likes) as likes,
3978
3978
-
(SELECT COUNT(*)::text FROM reposts) as reposts,
3979
3979
-
(SELECT COUNT(*)::text FROM follows) as follows,
3980
3980
-
(SELECT COUNT(*)::text FROM blocks) as blocks
3981
3981
-
`);
3959
3959
+
// Create a promise for this query so concurrent requests can wait for it
3960
3960
+
this.statsQueryPromise = (async () => {
3961
3961
+
try {
3962
3962
+
// Use accurate COUNT(*) queries instead of pg_stat_user_tables estimates
3963
3963
+
// Run all counts in a single query for better performance
3964
3964
+
const statsPromise = this.db.execute<{
3965
3965
+
users: string;
3966
3966
+
posts: string;
3967
3967
+
likes: string;
3968
3968
+
reposts: string;
3969
3969
+
follows: string;
3970
3970
+
blocks: string;
3971
3971
+
}>(sql`
3972
3972
+
SELECT
3973
3973
+
(SELECT COUNT(*)::text FROM users) as users,
3974
3974
+
(SELECT COUNT(*)::text FROM posts) as posts,
3975
3975
+
(SELECT COUNT(*)::text FROM likes) as likes,
3976
3976
+
(SELECT COUNT(*)::text FROM reposts) as reposts,
3977
3977
+
(SELECT COUNT(*)::text FROM follows) as follows,
3978
3978
+
(SELECT COUNT(*)::text FROM blocks) as blocks
3979
3979
+
`);
3980
3980
+
3981
3981
+
const timeoutPromise = new Promise<never>((_, reject) => {
3982
3982
+
setTimeout(() => reject(new Error('Stats query timeout')), 10000);
3983
3983
+
});
3982
3984
3983
3983
-
const timeoutPromise = new Promise<never>((_, reject) => {
3984
3984
-
setTimeout(() => reject(new Error('Stats query timeout')), 10000);
3985
3985
-
});
3985
3985
+
const result = await Promise.race([statsPromise, timeoutPromise]);
3986
3986
3987
3987
-
const result = await Promise.race([statsPromise, timeoutPromise]);
3987
3987
+
const row = result.rows[0] as {
3988
3988
+
users?: string;
3989
3989
+
posts?: string;
3990
3990
+
likes?: string;
3991
3991
+
reposts?: string;
3992
3992
+
follows?: string;
3993
3993
+
blocks?: string;
3994
3994
+
};
3988
3995
3989
3989
-
const row = result.rows[0] as {
3990
3990
-
users?: string;
3991
3991
-
posts?: string;
3992
3992
-
likes?: string;
3993
3993
-
reposts?: string;
3994
3994
-
follows?: string;
3995
3995
-
blocks?: string;
3996
3996
-
};
3996
3996
+
const data = {
3997
3997
+
totalUsers: parseInt(row.users || '0'),
3998
3998
+
totalPosts: parseInt(row.posts || '0'),
3999
3999
+
totalLikes: parseInt(row.likes || '0'),
4000
4000
+
totalReposts: parseInt(row.reposts || '0'),
4001
4001
+
totalFollows: parseInt(row.follows || '0'),
4002
4002
+
totalBlocks: parseInt(row.blocks || '0'),
4003
4003
+
};
3997
4004
3998
3998
-
const data = {
3999
3999
-
totalUsers: parseInt(row.users || '0'),
4000
4000
-
totalPosts: parseInt(row.posts || '0'),
4001
4001
-
totalLikes: parseInt(row.likes || '0'),
4002
4002
-
totalReposts: parseInt(row.reposts || '0'),
4003
4003
-
totalFollows: parseInt(row.follows || '0'),
4004
4004
-
totalBlocks: parseInt(row.blocks || '0'),
4005
4005
-
};
4005
4005
+
console.log('[STORAGE] PostgreSQL COUNT query result:', data);
4006
4006
4007
4007
-
console.log('[STORAGE] PostgreSQL COUNT query result:', data);
4007
4007
+
// Cache the result
4008
4008
+
this.statsCache = { data, timestamp: Date.now() };
4008
4009
4009
4009
-
// Cache the result
4010
4010
-
this.statsCache = { data, timestamp: Date.now() };
4011
4011
-
this.statsQueryInProgress = false;
4010
4010
+
// Update Redis counters with accurate counts
4011
4011
+
const { redisQueue: redisQueueUpdate } = await import('./services/redis-queue');
4012
4012
+
await Promise.all([
4013
4013
+
redisQueueUpdate.setRecordCount('users', data.totalUsers),
4014
4014
+
redisQueueUpdate.setRecordCount('posts', data.totalPosts),
4015
4015
+
redisQueueUpdate.setRecordCount('likes', data.totalLikes),
4016
4016
+
redisQueueUpdate.setRecordCount('reposts', data.totalReposts),
4017
4017
+
redisQueueUpdate.setRecordCount('follows', data.totalFollows),
4018
4018
+
redisQueueUpdate.setRecordCount('blocks', data.totalBlocks),
4019
4019
+
]).catch((err) => {
4020
4020
+
console.error('[STORAGE] Failed to update Redis counters:', err);
4021
4021
+
});
4012
4022
4013
4013
-
// Update Redis counters with accurate counts
4014
4014
-
const { redisQueue: redisQueueUpdate } = await import('./services/redis-queue');
4015
4015
-
await Promise.all([
4016
4016
-
redisQueueUpdate.setRecordCount('users', data.totalUsers),
4017
4017
-
redisQueueUpdate.setRecordCount('posts', data.totalPosts),
4018
4018
-
redisQueueUpdate.setRecordCount('likes', data.totalLikes),
4019
4019
-
redisQueueUpdate.setRecordCount('reposts', data.totalReposts),
4020
4020
-
redisQueueUpdate.setRecordCount('follows', data.totalFollows),
4021
4021
-
redisQueueUpdate.setRecordCount('blocks', data.totalBlocks),
4022
4022
-
]).catch((err) => {
4023
4023
-
console.error('[STORAGE] Failed to update Redis counters:', err);
4024
4024
-
});
4023
4023
+
return data;
4024
4024
+
} catch (error) {
4025
4025
+
console.error('[STORAGE] Error getting stats:', error);
4025
4026
4026
4026
-
return data;
4027
4027
-
} catch (error) {
4028
4028
-
this.statsQueryInProgress = false;
4029
4029
-
console.error('[STORAGE] Error getting stats:', error);
4027
4027
+
// If query times out or fails, return cached data if available, otherwise zeros
4028
4028
+
if (this.statsCache) {
4029
4029
+
return this.statsCache.data;
4030
4030
+
}
4030
4031
4031
4031
-
// If query times out or fails, return cached data if available, otherwise zeros
4032
4032
-
if (this.statsCache) {
4033
4033
-
return this.statsCache.data;
4032
4032
+
return {
4033
4033
+
totalUsers: 0,
4034
4034
+
totalPosts: 0,
4035
4035
+
totalLikes: 0,
4036
4036
+
totalReposts: 0,
4037
4037
+
totalFollows: 0,
4038
4038
+
totalBlocks: 0,
4039
4039
+
};
4040
4040
+
} finally {
4041
4041
+
this.statsQueryInProgress = false;
4042
4042
+
this.statsQueryPromise = null;
4034
4043
}
4044
4044
+
})();
4035
4045
4036
4036
-
return {
4037
4037
-
totalUsers: 0,
4038
4038
-
totalPosts: 0,
4039
4039
-
totalLikes: 0,
4040
4040
-
totalReposts: 0,
4041
4041
-
totalFollows: 0,
4042
4042
-
totalBlocks: 0,
4043
4043
-
};
4044
4044
-
}
4046
4046
+
return this.statsQueryPromise;
4045
4047
}
4046
4048
4047
4049
// Post aggregations operations