-
Notifications
You must be signed in to change notification settings - Fork 752
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
Enrichment monitoring fixes #2682
Changes from 8 commits
7ace0d2
08677f0
2ffa4a4
f1e1532
5959585
03d8a43
594574c
9f08e83
e3a4fb3
3cb5ef3
1980bd9
3788cc0
877368b
50aedb7
761611b
d893c8f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -9,9 +9,12 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { refreshMaterializedView } from '@crowd/data-access-layer/src/utils' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { RedisCache } from '@crowd/redis' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
IEnrichableMember, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
IEnrichableMemberIdentityActivityAggregate, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
IMemberEnrichmentCache, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
MemberEnrichmentSource, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
MemberIdentityType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PlatformType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} from '@crowd/types' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { EnrichmentSourceServiceFactory } from '../factory' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -41,6 +44,51 @@ export async function getEnrichmentData( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function getEnrichmentInput( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
input: IEnrichableMember, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): Promise<IEnrichmentSourceInput> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const enrichmentInput: IEnrichmentSourceInput = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
memberId: input.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
email: input.identities.find((i) => i.verified && i.type === MemberIdentityType.EMAIL), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
linkedin: input.identities.find( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(i) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.verified && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.platform === PlatformType.LINKEDIN && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.type === MemberIdentityType.USERNAME, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
displayName: input.displayName || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
website: input.website || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
location: input.location || undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
activityCount: input.activityCount || 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// there can be multiple verified identities in github, we select the one with the most activities | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const verifiedGithubIdentities = input.identities.filter( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(i) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.verified && i.platform === PlatformType.GITHUB && i.type === MemberIdentityType.USERNAME, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (verifiedGithubIdentities.length > 1) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const ghIdentityWithTheMostActivities = await findMemberIdentityWithTheMostActivityInPlatform( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
input.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
PlatformType.GITHUB, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (ghIdentityWithTheMostActivities) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
enrichmentInput.github = input.identities.find( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(i) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.verified && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.platform === PlatformType.GITHUB && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.type === MemberIdentityType.USERNAME && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
i.value === ghIdentityWithTheMostActivities.username, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
enrichmentInput.github = verifiedGithubIdentities?.[0] || undefined | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return enrichmentInput | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function normalizeEnrichmentData( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
source: MemberEnrichmentSource, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data: IMemberEnrichmentData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -104,8 +152,13 @@ export async function findMemberEnrichmentCache( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function findMemberEnrichmentCacheForAllSources( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
memberId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
returnRowsWithoutData = false, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): Promise<IMemberEnrichmentCache<IMemberEnrichmentData>[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return findMemberEnrichmentCacheForAllSourcesDb(svc.postgres.reader.connection(), memberId) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return findMemberEnrichmentCacheForAllSourcesDb( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
svc.postgres.reader.connection(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
memberId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
returnRowsWithoutData, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function insertMemberEnrichmentCache( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -138,6 +191,20 @@ export async function findMemberIdentityWithTheMostActivityInPlatform( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return findMemberIdentityWithTheMostActivityInPlatformQuestDb(svc.questdbSQL, memberId, platform) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function getObsoleteSourcesOfMember( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
memberId: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
possibleSources: MemberEnrichmentSource[], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
): Promise<MemberEnrichmentSource[]> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const caches = await findMemberEnrichmentCacheForAllSources(memberId, true) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const obsoleteSources = possibleSources.filter((source) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
isCacheObsolete( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
source, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
caches.find((s) => s.source === source), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return obsoleteSources | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix async operation in Array.filter and add error handling. The function has a critical issue where Apply this fix to handle async operations correctly and add error handling: export async function getObsoleteSourcesOfMember(
memberId: string,
possibleSources: MemberEnrichmentSource[],
): Promise<MemberEnrichmentSource[]> {
- const caches = await findMemberEnrichmentCacheForAllSources(memberId, true)
- const obsoleteSources = possibleSources.filter((source) =>
- isCacheObsolete(
- source,
- caches.find((s) => s.source === source),
- ),
- )
- return obsoleteSources
+ try {
+ const caches = await findMemberEnrichmentCacheForAllSources(memberId, true);
+ const obsoleteSources = await Promise.all(
+ possibleSources.map(async (source) => {
+ const isObsolete = await isCacheObsolete(
+ source,
+ caches.find((s) => s.source === source),
+ );
+ return isObsolete ? source : null;
+ })
+ );
+ return obsoleteSources.filter((source): source is MemberEnrichmentSource => source !== null);
+ } catch (error) {
+ svc.log.error('Error checking obsolete sources', { error, memberId });
+ throw error;
+ }
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function refreshMemberEnrichmentMaterializedView(mvName: string): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await refreshMaterializedView(svc.postgres.writer.connection(), mvName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,6 +7,9 @@ import { | |||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
import { EnrichmentSourceServiceFactory } from '../factory' | ||||||||||||||||||||||||||||||||||||||
import { svc } from '../main' | ||||||||||||||||||||||||||||||||||||||
import { IEnrichmentService } from '../types' | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
import { getEnrichmentInput, getObsoleteSourcesOfMember } from './enrichment' | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
export async function getEnrichableMembers( | ||||||||||||||||||||||||||||||||||||||
limit: number, | ||||||||||||||||||||||||||||||||||||||
|
@@ -27,3 +30,43 @@ export async function getEnrichableMembers( | |||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return rows | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
// Get the most strict parallelism among existing and enrichable sources | ||||||||||||||||||||||||||||||||||||||
// If current members are enrichable by multiple sources, we will use the min(maxConcurrentRequests) among sources | ||||||||||||||||||||||||||||||||||||||
export async function getMaxConcurrentRequests( | ||||||||||||||||||||||||||||||||||||||
members: IEnrichableMember[], | ||||||||||||||||||||||||||||||||||||||
possibleSources: MemberEnrichmentSource[], | ||||||||||||||||||||||||||||||||||||||
): Promise<number> { | ||||||||||||||||||||||||||||||||||||||
const serviceMap: Partial<Record<MemberEnrichmentSource, IEnrichmentService>> = {} | ||||||||||||||||||||||||||||||||||||||
const distinctEnrichableSources = new Set<MemberEnrichmentSource>() | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
for (const source of possibleSources) { | ||||||||||||||||||||||||||||||||||||||
serviceMap[source] = EnrichmentSourceServiceFactory.getEnrichmentSourceService(source, svc.log) | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
for (const member of members) { | ||||||||||||||||||||||||||||||||||||||
const enrichmentInput = await getEnrichmentInput(member) | ||||||||||||||||||||||||||||||||||||||
const obsoleteSources = await getObsoleteSourcesOfMember(member.id, possibleSources) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Object.keys(serviceMap).forEach(async (source) => { | ||||||||||||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||||||||||||
(await serviceMap[source].isEnrichableBySource(enrichmentInput)) && | ||||||||||||||||||||||||||||||||||||||
(obsoleteSources as string[]).includes(source) | ||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||
distinctEnrichableSources.add(source as MemberEnrichmentSource) | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logical error: Exclude obsolete sources instead of including them In the Apply this diff to fix the logical condition: - (obsoleteSources as string[]).includes(source)
+ !(obsoleteSources as string[]).includes(source) 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Fix async/await usage in forEach loop The current implementation has two issues:
Apply this fix: - Object.keys(serviceMap).forEach(async (source) => {
- if (
- (await serviceMap[source].isEnrichableBySource(enrichmentInput)) &&
- (obsoleteSources as string[]).includes(source)
- ) {
- distinctEnrichableSources.add(source as MemberEnrichmentSource)
- }
- })
+ for (const source of Object.keys(serviceMap)) {
+ if (
+ (await serviceMap[source].isEnrichableBySource(enrichmentInput)) &&
+ !(obsoleteSources as string[]).includes(source)
+ ) {
+ distinctEnrichableSources.add(source as MemberEnrichmentSource)
+ }
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
let smallestMaxConcurrentRequests = Infinity | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
Array.from(distinctEnrichableSources).forEach(async (source) => { | ||||||||||||||||||||||||||||||||||||||
smallestMaxConcurrentRequests = Math.min( | ||||||||||||||||||||||||||||||||||||||
smallestMaxConcurrentRequests, | ||||||||||||||||||||||||||||||||||||||
serviceMap[source].maxConcurrentRequests, | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid unnecessary The Apply this diff: - Array.from(distinctEnrichableSources).forEach(async (source) => {
+ Array.from(distinctEnrichableSources).forEach((source) => { 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
svc.log.info('Setting max concurrent requests', { smallestMaxConcurrentRequests }) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
return smallestMaxConcurrentRequests | ||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -31,6 +31,8 @@ export default class EnrichmentServiceClearbit extends LoggerBase implements IEn | |||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
public enrichableBySql = `"membersGlobalActivityCount".total_count > ${this.enrichMembersWithActivityMoreThan} AND mi.type = 'email' and mi.verified` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
public maxConcurrentRequests = 15 | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// bust cache after 120 days | ||||||||||||||||||||||||||||||||||
public cacheObsoleteAfterSeconds = 60 * 60 * 24 * 120 | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
|
@@ -57,7 +59,11 @@ export default class EnrichmentServiceClearbit extends LoggerBase implements IEn | |||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
async isEnrichableBySource(input: IEnrichmentSourceInput): Promise<boolean> { | ||||||||||||||||||||||||||||||||||
return !!input.email?.value && input.email?.verified | ||||||||||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||||||||||
input.activityCount > this.enrichMembersWithActivityMoreThan && | ||||||||||||||||||||||||||||||||||
!!input.email?.value && | ||||||||||||||||||||||||||||||||||
input.email?.verified | ||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||
Comment on lines
+62
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Remove unnecessary async and add input validation. The method is marked as async but contains no asynchronous operations. Additionally, consider adding input validation: - async isEnrichableBySource(input: IEnrichmentSourceInput): Promise<boolean> {
+ isEnrichableBySource(input: IEnrichmentSourceInput): boolean {
+ if (!input?.activityCount || !input?.email) {
+ return false;
+ }
+
return (
input.activityCount > this.enrichMembersWithActivityMoreThan &&
!!input.email?.value &&
input.email?.verified
)
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
async getData(input: IEnrichmentSourceInput): Promise<IMemberEnrichmentDataClearbit | null> { | ||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,8 @@ export default class EnrichmentServiceProgAILinkedinScraper | |
|
||
public cacheObsoleteAfterSeconds = 60 * 60 * 24 * 90 | ||
|
||
public maxConcurrentRequests = 1000 | ||
|
||
Comment on lines
+40
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Based on the comprehensive search results, I can now provide a final response: The concurrent request limit of 1000 is too high and should be reduced The high limit of 1000 concurrent requests for the ProgAI LinkedIn scraper service is concerning because:
Consider:
🔗 Analysis chainReview the concurrent request limit value. The
Let's verify if there are any rate limiting configurations or documentation: Consider:
Based on the search results, I can see that:
Let me verify the ProgAI service configuration and documentation: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Search for rate limiting related configurations or documentation
rg -i "rate.?limit|concurrent.?requests" --type md
rg "CROWD_ENRICHMENT_PROGAI" .env*
Length of output: 135 Script: #!/bin/bash
# Let's search for rate limiting configurations and usage across the codebase
rg -i "maxconcurrentrequests" -A 3 -B 3
# Search for any ProgAI configuration or documentation
fd -e md -e ts -e js -x rg -l "progai|prog.?ai"
# Look for similar enrichment services to compare configurations
ast-grep --pattern 'maxConcurrentRequests = $_'
Length of output: 79520 Script: #!/bin/bash
# Check for ProgAI configuration files and documentation
rg -i "progai|prog.?ai" -A 5 -B 5 services/apps/premium/members_enrichment_worker/src/sources/progai-linkedin-scraper/
# Look for any rate limiting documentation in README files
fd -e md -x rg -l "rate.?limit|concurrent.?requests|progai"
Length of output: 12715 |
||
constructor(public readonly log: Logger) { | ||
super(log) | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -33,11 +33,13 @@ export default class EnrichmentServiceProgAI extends LoggerBase implements IEnri | |||||||
public source: MemberEnrichmentSource = MemberEnrichmentSource.PROGAI | ||||||||
public platform = `enrichment-${this.source}` | ||||||||
|
||||||||
enrichableBySql = `mi.verified and ((mi.type = 'username' AND mi.platform = 'github') OR (mi.type = 'email'))` | ||||||||
public enrichableBySql = `mi.verified and ((mi.type = 'username' AND mi.platform = 'github') OR (mi.type = 'email'))` | ||||||||
|
||||||||
// bust cache after 90 days | ||||||||
public cacheObsoleteAfterSeconds = 60 * 60 * 24 * 90 | ||||||||
|
||||||||
public maxConcurrentRequests = 1000 | ||||||||
|
||||||||
Comment on lines
+41
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify the high concurrent request limit. The
Please verify:
Consider:
- public maxConcurrentRequests = 1000
+ public maxConcurrentRequests = parseInt(process.env.PROGAI_MAX_CONCURRENT_REQUESTS || '100', 10) 📝 Committable suggestion
Suggested change
|
||||||||
public attributeSettings: IMemberEnrichmentAttributeSettings = { | ||||||||
[MemberAttributeName.AVATAR_URL]: { | ||||||||
fields: ['profile_pic_url'], | ||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -14,13 +14,12 @@ import { chunkArray } from '../utils/common' | |||||||||||||||||||
|
||||||||||||||||||||
import { enrichMember } from './enrichMember' | ||||||||||||||||||||
|
||||||||||||||||||||
const { getEnrichableMembers } = proxyActivities<typeof activities>({ | ||||||||||||||||||||
const { getEnrichableMembers, getMaxConcurrentRequests } = proxyActivities<typeof activities>({ | ||||||||||||||||||||
startToCloseTimeout: '2 minutes', | ||||||||||||||||||||
}) | ||||||||||||||||||||
|
||||||||||||||||||||
export async function getMembersToEnrich(args: IGetMembersForEnrichmentArgs): Promise<void> { | ||||||||||||||||||||
const QUERY_FOR_ENRICHABLE_MEMBERS_PER_RUN = 1000 | ||||||||||||||||||||
const PARALLEL_ENRICHMENT_WORKFLOWS = 5 | ||||||||||||||||||||
const afterCursor = args?.afterCursor || null | ||||||||||||||||||||
const sources = [ | ||||||||||||||||||||
MemberEnrichmentSource.PROGAI, | ||||||||||||||||||||
|
@@ -40,7 +39,9 @@ export async function getMembersToEnrich(args: IGetMembersForEnrichmentArgs): Pr | |||||||||||||||||||
return | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
const chunks = chunkArray<IEnrichableMember>(members, PARALLEL_ENRICHMENT_WORKFLOWS) | ||||||||||||||||||||
const parallelEnrichmentWorkflows = await getMaxConcurrentRequests(members, sources) | ||||||||||||||||||||
|
||||||||||||||||||||
const chunks = chunkArray<IEnrichableMember>(members, parallelEnrichmentWorkflows) | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add validation for concurrent requests value The code assumes - const parallelEnrichmentWorkflows = await getMaxConcurrentRequests(members, sources)
+ const parallelEnrichmentWorkflows = await getMaxConcurrentRequests(members, sources)
+ if (!parallelEnrichmentWorkflows || parallelEnrichmentWorkflows < 1) {
+ throw new Error('Invalid concurrent requests value. Must be a positive number.')
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
|
||||||||||||||||||||
for (const chunk of chunks) { | ||||||||||||||||||||
await Promise.all( | ||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling and input validation.
The function lacks error handling for the
findMemberIdentityWithTheMostActivityInPlatform
call and doesn't validate if verified identities exist before accessing them.Consider applying these improvements: