Skip to content

Commit

Permalink
[keyserver][lib] replace neynar client cache with redis cache
Browse files Browse the repository at this point in the history
Summary:
we're using redis because we have multiple keyserver nodes that will need access to this data in order to minimize the number of neynar client calls

instead of caching led channels by the lead user, we cache all channels we get from the fetchFollowed neynar client method by channel ID. we do this in the background since we don't need to block on this operation.

Depends on D13636

Test Plan:
tested this in a few different ways:

1. commented out the background operation that sets data in the cache. registered a user with j4ck.eth's fid and confirmed that user was made lead of `bromero` community, which they lead on farcaster
2. uncommented the background operation. deleted and registered j4ck.eth again and confirmed that the information was fetched from the cache using redis-cli `monitor`. once again, user was made lead of `bromero` community

Reviewers: ashoat

Reviewed By: ashoat

Subscribers: tomek

Differential Revision: https://phab.comm.dev/D13637
  • Loading branch information
vdhanan committed Oct 9, 2024
1 parent 757a380 commit a64fc62
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 42 deletions.
49 changes: 42 additions & 7 deletions keyserver/src/updaters/thread-updaters.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import {
import type { Viewer } from '../session/viewer.js';
import { neynarClient } from '../utils/fc-cache.js';
import { findUserIdentities } from '../utils/identity-utils.js';
import { redisCache } from '../utils/redis-cache.js';
import RelationshipChangeset from '../utils/relationship-changeset.js';

type UpdateRoleOptions = {
Expand Down Expand Up @@ -942,13 +943,11 @@ async function fetchUserRoleForThread(
return null;
}

const ledChannels =
await neynarClient?.fetchLedFarcasterChannels(farcasterID);

if (
!ledChannels ||
!ledChannels.some(channel => channel.id === communityFarcasterChannelTag)
) {
const leadsChannel = await userLeadsChannel(
communityFarcasterChannelTag,
farcasterID,
);
if (!leadsChannel) {
return null;
}

Expand All @@ -962,6 +961,42 @@ async function fetchUserRoleForThread(
return null;
}

async function userLeadsChannel(
communityFarcasterChannelTag: string,
farcasterID: string,
) {
const cachedChannelInfo = await redisCache.getChannelInfo(
communityFarcasterChannelTag,
);
if (cachedChannelInfo) {
return cachedChannelInfo.lead.fid === parseInt(farcasterID);
}

// In the background, we fetch and cache followed channels
ignorePromiseRejections(
(async () => {
const followedChannels =
await neynarClient?.fetchFollowedFarcasterChannels(farcasterID);
if (followedChannels) {
await Promise.allSettled(
followedChannels.map(followedChannel =>
redisCache.setChannelInfo(followedChannel.id, followedChannel),
),
);
}
})(),
);

const channelInfo = await neynarClient?.fetchFarcasterChannelByName(
communityFarcasterChannelTag,
);
if (channelInfo) {
return channelInfo.lead.fid === parseInt(farcasterID);
}

return false;
}

async function toggleMessagePinForThread(
viewer: Viewer,
request: ToggleMessagePinRequest,
Expand Down
36 changes: 1 addition & 35 deletions lib/utils/neynar-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,9 @@ const fetchChannelsLimit = 50;

class NeynarClient {
apiKey: string;
ledChannelsCache: Map<
string,
{ channels: NeynarChannel[], timestamp: number },
>;
cacheTTL: number;

constructor(apiKey: string, cacheTTL: number = 60000) {
constructor(apiKey: string) {
this.apiKey = apiKey;
this.ledChannelsCache = new Map();
this.cacheTTL = cacheTTL; // Default TTL of 60 seconds
}

// We're using the term "friend" for a bidirectional follow
Expand Down Expand Up @@ -160,33 +153,6 @@ class NeynarClient {
return this.fetchFollowedFarcasterChannelsWithFilter(fid, () => true);
}

cleanExpiredCacheEntries() {
const now = Date.now();
for (const [fid, { timestamp }] of this.ledChannelsCache.entries()) {
if (now - timestamp >= this.cacheTTL) {
this.ledChannelsCache.delete(fid);
}
}
}

async fetchLedFarcasterChannels(fid: string): Promise<NeynarChannel[]> {
this.cleanExpiredCacheEntries();

const cachedEntry = this.ledChannelsCache.get(fid);
if (cachedEntry) {
return cachedEntry.channels;
}

const channels = await this.fetchFollowedFarcasterChannelsWithFilter(
fid,
channel => channel.lead.fid === parseInt(fid),
);

this.ledChannelsCache.set(fid, { channels, timestamp: Date.now() });

return channels;
}

async fetchFarcasterChannelByName(
channelName: string,
): Promise<?NeynarChannel> {
Expand Down

0 comments on commit a64fc62

Please sign in to comment.