๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์™€ ๋‹จ์ผ ์Šค๋ ˆ๋“œ, ๋™๊ธฐ์™€ ๋น„๋™๊ธฐ ์ œ๋Œ€๋กœ ์•Œ๊ณ  ์“ฐ์‹œ๋‚˜์š”? (feat. ์„ฑ๋Šฅ ๊ฐœ์„ )

ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ค‘ ์ง์—… ์ถ”์ฒœ api์—์„œ ๋ฐ˜ํ™˜๊นŒ์ง€ ํ‰๊ท  6์ดˆ ์ด์ƒ์˜ ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ๋‹ค. 100๊ฐœ๊ฐ€ ๋„˜๋Š” ์ง์—… csv์— ๊ฐ๊ฐ์˜ csv๋งˆ๋‹ค 6000๊ฐœ๊ฐ€ ๋„˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”๋ฐ ์ด๊ฑธ ์ „๋ถ€ ์ฝ๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š”๊ฒŒ ๋‹น์—ฐํ–ˆ๋‹ค.

 

์„ฑ๋Šฅ ๊ฐœ์„ ์„ ์œ„ํ•ด ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ์ ์šฉํ–ˆ์—ˆ๋Š”๋ฐ ํ”„๋กœ์ ํŠธ ๋งˆ๊ฐ ๊ธฐํ•œ์ด ์žˆ์—ˆ๋˜์ง€๋ผ ์‚ฌ์šฉ๋ฒ•๋งŒ ์ตํ˜€ ์ ์šฉํ–ˆ์—ˆ๋‹ค. 

 

ํ”„๋กœ์ ํŠธ๊ฐ€ ๋๋‚˜๊ณ  ๋‹ค์‹œ ๋Œ์•„์™€ ๊ณต๋ถ€ํ•ด๋ณด๋‹ˆ ์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ค์›Œ ์ด๋ฒˆ ๊ธฐํšŒ์— ์ž์„ธํžˆ ๊ณต๋ถ€ํ•˜๊ณ  ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค. 

 

 

๋น„๋™๊ธฐ์™€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ๋Š” ๊ฐ™์€ ๋œป์ด ์•„๋‹™๋‹ˆ๋‹ค.

๋น„๋™๊ธฐ์™€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ๋Š” ๋น„์Šทํ•˜๋‹ค ์ฐฉ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๊ฐ™์€ ๋œป์ด ์•„๋‹ˆ๋‹ค.

 

 

์˜๋ฏธ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

๋™๊ธฐ=  ์ˆœ์„œ 

์Šค๋ ˆ๋“œ= ๊ณต๊ฐ„ or ์ผ๊พผ

 

๊ทธ๋Ÿผ ์ด์ œ, ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž

 

 

๋™๊ธฐ & ๋น„๋™๊ธฐ

๋™๊ธฐ: ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ๊ฒƒ

๋น„๋™๊ธฐ: ์š”์ฒญ์„ ๋ฐ›์€ ๋’ค ๋จผ์ € ์ž‘์—…์ด ๋๋‚œ ์ˆœ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ๊ฒƒ

 

 

์‹ค์ƒํ™œ์—์„œ์˜ ๋™๊ธฐ, ๋น„๋™๊ธฐ์˜ ์ฐจ์ด๋Š” ์•„๋ž˜ ์‚ฌ์ง„๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์ด๋‹ค. 

 

 

๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ & ๋‹จ์ผ ์Šค๋ ˆ๋“œ

๋‹จ์ผ์Šค๋ ˆ๋“œ: ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‹จ์ผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ

๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ: ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‹ค์ค‘์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ

 

 

 

 

 

๊ทธ๋Ÿผ ๊ฒฝ์šฐ์˜ ์ˆ˜๊ฐ€ ์ด 4๊ฐ€์ง€๊ฐ€ ์ƒ๊ธด๋‹ค

1. ๋‹จ์ผ ์Šค๋ ˆ๋“œ - ๋™๊ธฐ

2. ๋‹จ์ผ ์Šค๋ ˆ๋“œ - ๋น„๋™๊ธฐ

3. ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ - ๋™๊ธฐ

4. ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ - ๋น„๋™๊ธฐ

 

์œ„์˜ 4๊ฐ€์ง€๋ฅผ ์ผ์ƒ์ƒํ™œ์—์„œ์˜ ์˜ˆ์‹œ๋กœ ๋“ค๋ฉด ์ดํ•ด๊ฐ€ ์‰ฝ๋‹ค.

1. ๋‹จ์ผ ์Šค๋ ˆ๋“œ - ๋™๊ธฐ

- ๋ณธ์ ์—์„œ ์Œ์‹์„ ์ฃผ๋ฌธ -> ์กฐ๋ฆฌ ์™„๋ฃŒ -> ๋‹ค์Œ ์ฃผ๋ฌธ ๋ฐ›์Œ

 

2. ๋‹จ์ผ ์Šค๋ ˆ๋“œ - ๋น„๋™๊ธฐ

- ๋ณธ์ ์—์„œ ์Œ์‹์„ ์ฃผ๋ฌธ -> ์กฐ๋ฆฌ ๋ฐ ์ฃผ๋ฌธ ๋ฐ›๊ธฐ -> ์กฐ๋ฆฌ๊ฐ€ ๋น ๋ฅธ ์ˆœ์„œ๋Œ€๋กœ ์ œ๊ณต

 

3. ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ - ๋™๊ธฐ

- ๋ณธ์ , 1ํ˜ธ์ , 2ํ˜ธ์  .. ์ค‘ ํ•œ๊ณณ์—์„œ ์Œ์‹์„ ์ฃผ๋ฌธ -> ์กฐ๋ฆฌ ์™„๋ฃŒ -> ๋‹ค์Œ ์ฃผ๋ฌธ ๋ฐ›์Œ

 

4. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ - ๋น„๋™๊ธฐ

- ๋ณธ์ , 1ํ˜ธ์ , 2ํ˜ธ์  .. ์ค‘ ํ•œ ๊ณณ์—์„œ ์Œ์‹์„ ์ฃผ๋ฌธ -> ์กฐ๋ฆฌ ๋ฐ ์ฃผ๋ฌธ ๋ฐ›๊ธฐ -> ์กฐ๋ฆฌ๊ฐ€ ๋น ๋ฅธ ์ˆœ์„œ๋Œ€๋กœ ์ œ๊ณต

 

 

 

 

์ด์ œ ํ”„๋กœ์ ํŠธ์— ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ ์šฉํ•ด๋ณด์ž.

 

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์‚ฌ์šฉ์ž์˜ ์ด๋ ฅ์„œ๋กœ๋ถ€ํ„ฐ ๊ฐ€์žฅ ์ ํ•ฉํ•œ ์ง์—…์„ ๊ณ„์‚ฐํ•˜์—ฌ ์ •๋ ฌ ํ›„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋กœ์ง์ด๋‹ค.

๋‘๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ์‹œ์ผœ๋ณด์•˜๋‹ค.   

    public List<JobRecommendResponse> recommendJobWithSkill(Long memberId) throws IOException, CsvException {
        List<String> skillList = skillService.findAllSkill(memberId).stream()
                .map(SkillResponse::getSkillName)
                .collect(Collectors.toList());
        String text = String.join(",", skillList);

        Resource[] resources = resourcePatternResolver.getResources("classpath:_skillspr/*.csv");

        List<JobRecommendResponse> results = new ArrayList<>();
        Set<String> userSkills = new HashSet<>(); // ์ถ”๊ฐ€

        // csv ํŒŒ์ผ ์ˆœํšŒ
        for (Resource resource : resources) {
            List<String[]> records;
            try (CSVReader csvReader = new CSVReader(new InputStreamReader(resource.getInputStream()))) {
                records = csvReader.readAll();
            }
            double pr = 0;
            boolean skip = true;
            // ๊ฐœ๋ณ„ csv ๋ฐ์ดํ„ฐ ์ˆœํšŒ
            for (String[] record : records) {
                // 0๋ฒˆ์งธ ํ–‰ skip
                if (skip) {
                    skip = false;
                    continue;
                }

                // skill ์ถ”์ถœ
                String skill = record[1];
                double probability = Double.parseDouble(record[5]);

                // skill ์ด pdf ๋‚ด์šฉ์— ์žˆ๋Š” ๋‹จ์–ด์ผ ๋•Œ
                if (text.contains(skill) && (skill.length() > 1 || "C".equals(skill))) {
                    pr += Math.log(probability);
                    userSkills.add(skill); // ์ถ”๊ฐ€
                }
            }
            if (pr == 1) pr = 0;
            results.add(new JobRecommendResponse(resource.getFilename().substring(0, resource.getFilename().length() - 4), pr));
        }
        results.sort(Comparator.comparing(JobRecommendResponse::getProbability).reversed());
        return results;
    }

 

 

 

 

๊ตฌํ˜„ 1. ExecutorService ์‚ฌ์šฉ

 

์ฒ˜์Œ ์‹œ๋„ํ–ˆ๋˜ ๊ฑด java.util.concurrentํŒจํ‚ค์ง€์˜ ExecutorService ๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฉ€ํ‹ฐ์“ฐ๋ ˆ๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜์˜€๋‹ค. 

 

ExecutorService๋ž€?

ExecutorService๋Š” ์žฌ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ ThreadPool๋กœ Executor ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅํ•˜์—ฌ Thread์˜ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ์ œ์–ดํ•œ๋‹ค.

 

Thread๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹ค์ˆ˜์˜ ์ž‘์—…(Task)๋“ค์„ ๋น„๋™๊ธฐ๋กœ ์ˆ˜ํ–‰ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ฐ„๋‹จํ•˜์ง€ ์•Š๋‹ค. Thread์˜ ๋ผ์ดํ”„์‚ฌ์ดํด(์ƒ์„ฑ๊ณผ ์ œ๊ฑฐ ๋“ฑ)์ด๋‚˜ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€ low level์˜ ๊ณ ๋ ค์‚ฌํ•ญ๋“ค์ด ์กด์žฌํ•˜๋Š”๋ฐ ์ด๋ฅผ ๊ฐœ๋ฐœ์ž๊ฐ€ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๋„๋ก ํŽธ๋ฆฌํ•˜๊ฒŒ ์ถ”์ƒํ™”ํ•œ ๊ฒƒ์ด ExecutorService์ด๋‹ค.

 

 

์•„๋ž˜์ฝ”๋“œ๋Š” ExecutorService๋ฅผ ์‚ฌ์šฉํ•ด ๋ฉ€ํ‹ฐ์“ฐ๋ ˆ๋“œ๋ฅผ ์ ์šฉํ•œ ํ›„์˜ ์ฝ”๋“œ์ด๋‹ค. 

 

public List<JobRecommendResponse> recommendJobWithResume(InputStream pdf) throws IOException, CsvException {
    // pdf -> text ์ถ”์ถœ
    String text = pdfToText(pdf);
    text = text.toUpperCase();
    final String finalText = text;
    Resource[] resources = resourcePatternResolver.getResources("classpath:_skillspr/*.csv");

    List<JobRecommendResponse> results = new ArrayList<>();
    Set<String> userSkills = new HashSet<>(); // ์ถ”๊ฐ€

    ExecutorService executorService = Executors.newFixedThreadPool(resources.length);
    List<Future<JobRecommendResponse>> futures = new ArrayList<>();

    // csv ํŒŒ์ผ ์ˆœํšŒ
    for (Resource resource : resources) {

        Future<JobRecommendResponse> future = executorService.submit(() -> {
            List<String[]> records;
            try (CSVReader csvReader = new CSVReader(new InputStreamReader(resource.getInputStream()))) {
                records = csvReader.readAll();
            } catch (Exception e) {
                log.info("๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋”ฉ ์ž‘์—… ์ค‘ ์—๋Ÿฌ");
                throw new CustomException(ErrorCode.MULTI_THREADING_ERROR);
            }
            double pr = 0;
            boolean skip = true;
            // ๊ฐœ๋ณ„ csv ๋ฐ์ดํ„ฐ ์ˆœํšŒ
            for (String[] record : records) {
                // 0๋ฒˆ์งธ ํ–‰ skip
                if (skip) {
                    skip = false;
                    continue;
                }

                // skill ์ถ”์ถœ
                String skill = record[1];
                double probability = Double.parseDouble(record[5]);

                // skill ์ด pdf ๋‚ด์šฉ์— ์žˆ๋Š” ๋‹จ์–ด์ผ ๋•Œ
                if (finalText.contains(skill) && (skill.length() > 1 || "C".equals(skill))) {
                    pr += Math.log(probability);
                    userSkills.add(skill); // ์ถ”๊ฐ€
                }
            }
            if (pr == 1) pr = 0;
            return new JobRecommendResponse(resource.getFilename().substring(0, resource.getFilename().length() - 4), pr);
        });
        futures.add(future);
    }

 

 

- newFixedThreadPool ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ ์ • ํฌ๊ธฐ์˜ ์Šค๋ ˆ๋“œ ํ’€์„ ์ƒ์„ฑ

 

- executorService.submit() : ์Šค๋ ˆ๋“œํ’€์— ์ œ์ถœ. ์‘๋‹ต๊ฐ’์œผ๋กœ future์„ ๋ฐ›์Œ. 

 

- ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ future๋กœ๋ถ€ํ„ฐ ๊ฐ€์ ธ์˜ด.

for (Future<JobRecommendResponse> future : futures) {
    try {
        results.add(future.get());
    } catch (Exception e) {
        log.info("๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋”ฉ ์ž‘์—… ์ค‘ ์—๋Ÿฌ");
        throw new CustomException(ErrorCode.MULTI_THREADING_ERROR);
    }
}

 

 

 

๊ตฌํ˜„ 2. Async ์‚ฌ์šฉ

Async์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์ž.

 

@Async์˜ ๊ธฐ๋ณธ ์„ค์ •์€ SimpleAsyncTaskExecutor๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋˜์–ด ์žˆ๋Š”๋ฐ, ์ด๊ฒƒ์€ ์Šค๋ ˆ๋“œ ํ’€์ด ์•„๋‹ˆ๊ณ  ๋‹จ์ˆœํžˆ ์Šค๋ ˆ๋“œ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. 

๊ธฐ๋ณธ ์„ค์ •์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • corePoolSize : 1 
  • maxPoolSize : Integer.MAX_VALUE 
  • keepAliveSeconds : 60(second) 
  • QueueCapacity : Integer.MAX_VALUE 
  • AllowCoreThreadTimeOut : false 
  • WaitForTasksToCompleteOnShutdown : false 
  • RejectedExecutionHandler : AbortPolicy

์Šค๋ ˆ๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํ˜ธ์ถœ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์Šค๋ ˆ๋“œ๋ฅผ ์‹œ์ž‘ํ•˜๋ฉฐ, AbortPolicy ์ด๋ฏ€๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋Š” ์ˆ˜์ค€์ด ๋˜๋ฉด Exception์„ ๋ฐœ์ƒ์‹œํ‚ค๋ฉฐ ์ข…๋ฃŒ๋œ๋‹ค.

 

๋”ฐ๋ผ์„œ ๋”ฐ๋กœ ์ปค์Šคํ…€ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

@Configuration
@EnableAsync
public class AsyncConfig {
    private static int CORE_POOL_SIZE = 500; // ๋™์‹œ์— ์‹คํ–‰ํ•  ์“ฐ๋ ˆ๋“œ์˜ ๊ฐฏ์ˆ˜๋ฅผ ์˜๋ฏธ, default ๊ฐ’์€ 1์ด๋‹ค.
    private static int MAX_POOL_SIZE = 3000; // ์“ฐ๋ ˆ๋“œ ํ’€์˜ ์ตœ๋Œ€ ํฌ๊ธฐ๋ฅผ ์ง€์ •, default ๊ฐ’์€ Integer.MAX_VALUE
    private static int QUEUE_CAPACITY = 5000; // ํ์˜ ํฌ๊ธฐ๋ฅผ ์ง€์ •, default ๊ฐ’์€ Integer.MAX_VALUE ์ด๋‹ค.
    private static String THREAD_NAME_PREFIX = "async-task";

    @Bean
    public Executor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        executor.initialize();
        return executor;
    }
}
  • CorePoolSize : ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ์„ฑํ•ด๋‘๊ณ  ์‹คํ–‰ ๋Œ€๊ธฐํ•˜๋Š” Thread์˜ ์ˆ˜ 
  • MaxPoolSize : ๋™์‹œ ๋™์ž‘ํ•˜๋Š” ์ตœ๋Œ€ Thread์˜ ์ˆ˜ 
  • QueueCapacity MaxPoolSize ์ดˆ๊ณผ ์š”์ฒญ์—์„œ Thread ์ƒ์„ฑ ์š”์ฒญ์‹œ, ํ•ด๋‹น ์š”์ฒญ์„ Queue์— ์ €์žฅํ•˜๋Š”๋ฐ ์ด๋•Œ ์ตœ๋Œ€ ์ˆ˜์šฉ ๊ฐ€๋Šฅํ•œ Queue์˜ ํฌ๊ธฐ Queue์— ์ €์žฅ๋˜์–ด์žˆ๋‹ค๊ฐ€ Thread์— ์ž๋ฆฌ๊ฐ€ ์ƒ๊ธฐ๋ฉด ํ•˜๋‚˜์”ฉ ๋น ์ ธ๋‚˜๊ฐ€ ์Šค๋ ˆ๋“œ ํ• ๋‹น 
  • ThreadNamePrefix : ์ƒ์„ฑ๋˜๋Š” Thread ์ ‘๋‘์‚ฌ ์ง€์ • 
  • initialize() : ์ƒ์„ฑํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋กํ•˜๋Š” ์„ค์ •

 

๋ฐ˜ํ™˜๊ฐ’์— ๋”ฐ๋ฅธ ๋™์ž‘

@Async๊ฐ€ ๋ถ™์œผ๋ฉด ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ ๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜ ํƒ€์ž…์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. 

  • Future
  • Completable
  • FutureListenable
  • void

@Async ์• ๋„ˆํ…Œ์ด์…˜์œผ๋กœ ์„ ์–ธ๋œ ๋ฉ”์„œ๋“œ๋Š” ๋ฆฌํ„ด ํƒ€์ž…์— ๋”ฐ๋ผ ๋‚ด๋ถ€์ ์œผ๋กœ ์ƒ์ดํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.

 

void

@Slf4j
@Service
@RequiredArgsConstructor
public class CallService {

    private final UserService userService;

    public void call(){
        userService.hello();
        log.info("call");
    }
}
@Slf4j
@Service
public class UserService {

    @Async("HELLO_WORLD_EXECUTOR")
    public void hello(){
        try {
            Thread.sleep(1000);
            log.info("{}","hello");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 

CallService์—์„œ call ๋ฉ”์„œ๋“œ๊ฐ€ UserService์˜ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ hello๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์ด๋ฏ€๋กœ call์„ ํ˜ธ์ถœํ•œ ์Šค๋ ˆ๋“œ๋Š” call์˜ ์ˆ˜ํ–‰์„ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  (๋…ผ๋ธ”๋กœํ‚น) ๋ฐ”๋กœ log.info๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

 

Future

๋ฉ”์„œ๋“œ์˜ ๊ฒฐ๊ณผ๊ฐ’์€ ์ „๋‹ฌ๋ฐ›์•„์•ผ ํ•œ๋‹ค๋ฉด Future์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์•ž์„œ ์„ค๋ช…ํ•œ ๋‚˜๋จธ์ง€ ํƒ€์ž… ๋ชจ๋‘ Future ๊ณ„์—ด์— ํ•ด๋‹นํ•˜๊ณ  Future์ด ๊ธฐ๋ณธ์ด ๋œ๋‹ค. Spring์—์„œ ์ œ๊ณตํ•˜๋Š” AsyncResult๋Š” Future์˜ ๊ตฌํ˜„์ฒด์ด๋ฉฐ ์ด๋ฅผ ์‚ฌ์šฉํ•ด Future ํƒ€์ž…์œผ๋กœ ๋ฆฌํ„ดํ•  ์ˆ˜ ์žˆ๋‹ค.

@Slf4j
@Service
@RequiredArgsConstructor
public class CallService {

    private final UserService userService;

    public void futureCall() {
        Future<String> future = userService.returnFuture();
        try {
            log.info("{}",future.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("origin void call");
    }
}
@Slf4j
@Service
public class UserService {

    @Async
    public Future<String> returnFuture(){
        try {
            Thread.sleep(1000);
            log.info("{}","async return future");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return new AsyncResult<>("async return future");
    }
}

 

futureCall ๋ฉ”์„œ๋“œ์—์„œ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ returnFuture์„ ํ˜ธ์ถœํ•˜์—ฌ ๊ฒฐ๊ณผ๊ฐ’์„ get์œผ๋กœ ๊บผ๋‚ธ๋‹ค. ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ๋Š” ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•˜์ง€๋งŒ future์˜ get ๋ฉ”์„œ๋“œ๋Š” ๋ฉ”์„œ๋“œ์˜ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•  ๋•Œ๊นŒ์ง€ ๊ณ„์† ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ๋œ๋‹ค. ์ฆ‰, ๋ธ”๋กœํ‚น ํ˜„์ƒ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Future์˜ ๊ฒฝ์šฐ ๋น„๋™๊ธฐ ๋ธ”๋กœํ‚น ๋ฐฉ์‹์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ด ์ข‹์ง€ ์•Š์•„ ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

CompletableFuture

@Slf4j
@Service
@RequiredArgsConstructor
public class CallService {

    private final UserService userService;

    public void completableFutureCall() {
        CompletableFuture<String> future = userService.returnCompletableFuture();
        try {
            future.thenAccept(s -> log.info("{}",s));
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("origin completableFutureC call");
    }
}
@Slf4j
@Service
public class UserService {

    @Async
    public CompletableFuture<String> returnCompletableFuture(){
        try {
            Thread.sleep(1000);
            log.info("{}","async return CompletableFuture");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return CompletableFuture.completedFuture("async return CompletableFuture");
    }
}

 

๊ธฐ์กด์—๋Š” Future๋‚˜ ListenableFuture๋ฅผ ์ด์šฉํ•˜์—ฌ ํ•ด๊ฒฐํ–ˆ์ง€๋งŒ JAVA 8๋ฒ„์ „ ๋ถ€ํ„ฐ๋Š” CompletableFuture๋ฅผ ์ œ๊ณตํ•œ๋‹ค. 

thenAccept ๋ฉ”์„œ๋“œ๋กœ ๊ฒฐ๊ณผ๊ฐ’์ด ๋‚˜์˜ค๋ฉด ์ˆ˜ํ–‰ํ•  ์ผ์„ ์ •์˜ํ•ด์ฃผ์—ˆ๋‹ค. ์ฆ‰, thenAccept ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋‚˜์ค‘์— ๊ฒฐ๊ณผ๊ฐ’์ด ๋‚˜์˜ค๋ฉด ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๊ณ  ๋ฐ”๋กœ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Ÿฌ ๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๋…ผ๋ธ”๋กœํ‚น ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

 

 

 

์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์˜€๋‹ค.

public List<JobRecommendResponse> recommendJobWithResumeV2(InputStream pdf) throws IOException {
    long startTime = System.currentTimeMillis(); // ์‹œ์ž‘ ์‹œ๊ฐ„ ๊ธฐ๋ก

    // pdf -> text ์ถ”์ถœ
    String text = pdfToText(pdf);
    text = text.toUpperCase();
    final String finalText = text;
    Resource[] resources = resourcePatternResolver.getResources("classpath:_skillspr/*.csv");

    List<JobRecommendResponse> results = new ArrayList<>();
    Set<String> userSkills = new HashSet<>(); // ์ถ”๊ฐ€

    List<CompletableFuture<JobRecommendResponse>> futures = new ArrayList<>();

    // csv ํŒŒ์ผ ์ˆœํšŒ
    for (Resource resource : resources) {
        CompletableFuture<JobRecommendResponse> future = processCsvFileAsync(resource, finalText, userSkills);
        futures.add(future);
    }

    // ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
    for (CompletableFuture<JobRecommendResponse> future : futures) {
        try {
            results.add(future.join());
        } catch (Exception e) {
            log.info("๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋”ฉ ์ž‘์—… ์ค‘ ์—๋Ÿฌ");
            throw new CustomException(ErrorCode.MULTI_THREADING_ERROR);
        }
    }

    long endTime = System.currentTimeMillis(); // ์ข…๋ฃŒ ์‹œ๊ฐ„ ๊ธฐ๋ก
    long duration = endTime - startTime; // ์‹คํ–‰ ์‹œ๊ฐ„ ๊ณ„์‚ฐ
    System.out.println("์‹คํ–‰ ์‹œ๊ฐ„: " + duration + "ms");

    return results;
}
@Async
public CompletableFuture<JobRecommendResponse> processCsvFileAsync(Resource resource, String finalText, Set<String> userSkills) {
    CompletableFuture<JobRecommendResponse> completableFuture = CompletableFuture.supplyAsync(() -> {
        List<String[]> records;
        try (CSVReader csvReader = new CSVReader(new InputStreamReader(resource.getInputStream()))) {
            records = csvReader.readAll();
        } catch (Exception e) {
            log.info("๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋”ฉ ์ž‘์—… ์ค‘ ์—๋Ÿฌ");
            throw new CustomException(ErrorCode.MULTI_THREADING_ERROR);
        }
        double pr = 0;
        boolean skip = true;
        // ๊ฐœ๋ณ„ csv ๋ฐ์ดํ„ฐ ์ˆœํšŒ
        for (String[] record : records) {
            // 0๋ฒˆ์งธ ํ–‰ skip
            if (skip) {
                skip = false;
                continue;
            }

            // skill ์ถ”์ถœ
            String skill = record[1];
            double probability = Double.parseDouble(record[5]);

            // skill ์ด pdf ๋‚ด์šฉ์— ์žˆ๋Š” ๋‹จ์–ด์ผ ๋•Œ
            if (finalText.contains(skill) && (skill.length() > 1 || "C".equals(skill))) {
                pr += Math.log(probability);
                userSkills.add(skill); // ์ถ”๊ฐ€
            }
        }
        if (pr == 1) pr = 0;
        return new JobRecommendResponse(resource.getFilename().substring(0, resource.getFilename().length() - 4), pr);
    });

    return completableFuture;
}

 

 

๋น„๋™๊ธฐ ๋ฉ€ํ‹ฐ์“ฐ๋ ˆ๋“œ๋กœ ๋ณ€๊ฒฝ ํ›„ api ์‘๋‹ต ์†๋„ 87%๋ฅผ ๊ฐœ์„ ์‹œํ‚ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค.