[๐Ÿ”ฅTroubleShooting - TicToc๐Ÿ”ฅ] ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก ๊ด€๋ฆฌ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒโ€ผ๏ธโ“โ€ผ๏ธโ“

._mungยท2025๋…„ 3์›” 9์ผ
0

TicToc

๋ชฉ๋ก ๋ณด๊ธฐ
6/6

๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

ํŒ€์› : ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ
๊ธฐ๊ฐ„ : 2025.01 ~ ์ง„ํ–‰ ์ค‘
๋งํฌ : https://github.com/M-ung/TicToc_Server
์„œ๋น„์Šค ๋‚ด์šฉ : ๋‹น์‹ ์˜ ์‹œ๊ฐ„์— ๊ฐ€์น˜๋ฅผ ๋งค๊ธฐ๋‹ค, ์‹œ๊ฐ„ ๊ฑฐ๋ž˜ ๊ฒฝ๋งค ํ”Œ๋žซํผ


๐Ÿ”ฅTroubleShooting๐Ÿ”ฅ

Problems

"TicToc" ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์‹ค์ œ ๋ฐฐํฌ๋ฅผ ๋ชฉ์ ์œผ๋กœ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•˜๋Š” ์ž…์žฅ์—์„œ ์„ค๊ณ„๋ฅผ ํ•˜๊ฒŒ ๋๋‹ค. ๊ทธ๋ž˜์„œ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ ๊ฐ–๊ณ  ์žˆ๋Š”๊ฒŒ ์ข‹๋‹ค๊ณ  ํŒ๋‹จํ•˜์˜€๊ธฐ์— ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก(UserLoginHistory) ํ…Œ์ด๋ธ”์„ ๋”ฐ๋กœ ์ƒ์„ฑํ–ˆ๋‹ค.

๋กœ๊ทธ์ธ ๊ธฐ๋ก์€ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ํ•œ ์‹œ์ ์— ์ฆ‰ JWT ํ† ํฐ ๋ฐœ๊ธ‰ ์‹œ์ ์— ์ €์žฅ์„ ํ•˜๋„๋ก ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ ๋‹จ์ˆœํžˆ DB์— ์ €์žฅํ•˜๊ธฐ์—๋Š” ํ•œ ์‚ฌ์šฉ์ž๊ฐ€ ํ•˜๋ฃจ์— ๋กœ๊ทธ์ธ์„ 10๋ฒˆ๋งŒ ํ•ด๋„ 10๊ฐœ๊ฐ€ ์Œ“์ด๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ๋‹ค. ๊ทธ๋ ‡์ง€๋งŒ ํ•˜๋ฃจ์— ํ•œ ๋ช…๋งŒ ์ ‘์†ํ•  ์ผ์ด ์—†๋‹ค. 10๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ 10๋ฒˆ ๋กœ๊ทธ์ธ์„ ํ•˜๋ฉด ํ…Œ์ด๋ธ”์—๋Š” ํ•˜๋ฃจ์—๋งŒ 100๊ฐœ๊ฐ€ ๋„˜๊ฒŒ ์Œ“์ด๊ฒŒ ๋œ๋‹ค.


How

๊ทธ๋ž˜์„œ ์œ„ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹จ์ˆœํžˆ DB์— ๊ณ„์† ์ €์žฅํ•˜๊ธฐ ๋ณด๋‹จ ์‹œ๊ฐ„์„ ๋‘๊ณ  ํŠน์ • ๊ธฐ๊ฐ„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์šธ ํ•„์š”๊ฐ€ ์žˆ๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ Spring Batch๋ฅผ ์ ์šฉํ•ด ์ผ์ฃผ์ผ๋งˆ๋‹ค ์ผ์ฃผ์ผ ์น˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์šฐ๋Š” ์„ค๊ณ„๋ฅผ ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋‹จ์ˆœํžˆ ๋˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐ„์„ ๋‘๊ณ  ์ง€์šฐ๊ธฐ์—๋Š” ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•˜๋Š” ์ž…์žฅ์—์„œ ์ข‹์ง€ ์•Š์€ ๋ฐฉํ–ฅ์ผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋ชจ๋“  ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ ์ €์žฅํ•  ๊ณต๊ฐ„์ด ํ•„์š”ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ DB๊ฐ€ ์•„๋‹Œ, UserLoginHistory.log๋ผ๋Š” ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ DB์™€ log ํŒŒ์ผ์— ๋™์‹œ์— ์ €์žฅํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.


Process

Spring Batch ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

๐Ÿ“ UserLoginHistoryBatchScheduler.java

@Component
@RequiredArgsConstructor
public class UserLoginHistoryBatchScheduler {
    private final JobLauncher jobLauncher;
    private final Job userLoginHistoryJob;

    @Scheduled(cron = "0 0 0 * * SUN") 
    public void runBatchJob() throws JobExecutionException {
        JobParameters jobParameters = new JobParametersBuilder()
                .addLong("time", System.currentTimeMillis())
                .toJobParameters();
        jobLauncher.run(userLoginHistoryJob, jobParameters);
    }
}

๐Ÿ“ UserLoginHistoryBatchConfig.java

@Configuration
@RequiredArgsConstructor
public class UserLoginHistoryBatchConfig {
    private final JobRepository jobRepository;
    private final DataSource dataSource;
    private final PlatformTransactionManager transactionManager;

    @Bean
    public Job userLoginHistoryJob(Step step) {
        return new JobBuilder("userLoginHistoryJob", jobRepository)
                .start(step)
                .build();
    }

    @Bean
    public Step userLoginHistoryStep() {
        return new StepBuilder("userLoginHistoryStep", jobRepository)
                .<Long, Long>chunk(100, transactionManager)
                .reader(userLoginHistoryReader())
                .writer(userLoginHistoryItemWriter())
                .build();
    }

    @Bean
    public JdbcCursorItemReader<Long> userLoginHistoryReader() {
        LocalDateTime endDate = LocalDateTime.now();
        LocalDateTime startDate = endDate.minusDays(7);

        return new JdbcCursorItemReaderBuilder<Long>()
                .dataSource(dataSource)
                .name("userLoginHistoryReader")
                .sql("SELECT id FROM user_login_history WHERE login_at BETWEEN ? AND ?")
                .queryArguments(Timestamp.valueOf(startDate), Timestamp.valueOf(endDate))
                .rowMapper((rs, rowNum) -> rs.getLong("id"))
                .build();
    }

    @Bean
    public ItemWriter<Long> userLoginHistoryItemWriter() {
        return items -> {
            NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
            if (items.isEmpty()) return;
            String sql = "DELETE FROM user_login_history WHERE id IN (:ids)";
            Map<String, Object> params = new HashMap<>();
            params.put("ids", items);
            namedParameterJdbcTemplate.update(sql, params);
        };
    }
}

Spring Batch ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

ํ•˜์ง€๋งŒ Spring Batch๋ฅผ ๋™์ž‘ํ•  ๋•Œ ๋งŒ์•ฝ Spring ์„œ๋ฒ„๊ฐ€ ๋‹ค์šด๋˜๋ฉด Spring Batch ๋˜ํ•œ ์ œ๋•Œ ๋™์ž‘์„ ์ œ๋Œ€๋กœ ์ˆ˜ํ–‰ํ•˜์ง€ ๋ชป ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— Spring Batch๋ฅผ ๋™์ž‘ํ•  ์„œ๋ฒ„์™€ API ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ๋ฐฐํฌํ•ด์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค.

๋‹คํ–‰ํžˆ ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋Š” ๋ฉ€ํ‹ฐ๋ชจ๋“ˆ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ถ„๋ฆฌํ•˜๊ธฐ ์–ด๋ ต์ง€ ์•Š์•˜๋‹ค.

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ๋ฌธ์ œ๋Š” ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ DB์™€ log ํŒŒ์ผ์— ์ €์žฅํ•˜๋Š” ๋กœ์ง์„ Spring Batch ์„œ๋ฒ„์—์„œ ๊ด€๋ฆฌํ•˜๋ ค๊ณ  ํ–ˆ์ง€๋งŒ, API ์„œ๋ฒ„์™€ Spring Batch ์„œ๋ฒ„๋ฅผ ์ด์–ด์ค„ "๋ฌด์–ธ๊ฐ€" ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” WebFlux๋ฅผ ์ ์šฉํ•ด ๊ด€๋ฆฌํ•˜๋ ค ํ–ˆ์ง€๋งŒ, ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก์€ ์ˆœ์‹๊ฐ„์— ๋งŽ์€ ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๊ณ  ๋Š์ž„์—†์ด ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋Œ€์šฉ๋Ÿ‰์„ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ "์นดํ”„์นด" ๋ฅผ ์ ์šฉํ•ด ๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

์นดํ”„์นด ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

๐Ÿ“ UserLoginHistoryEventProducer.java

@Slf4j
@Component
@RequiredArgsConstructor
public class UserLoginHistoryEventProducer {
    private final HttpServletRequest request;
    private final KafkaTemplate<String, UserLoginHistoryEvent> kafkaTemplate;

    @Async
    public void produce(Long userId) {
        try {
            UserLoginHistoryEvent event = UserLoginHistoryEvent.of(userId, getClientIp(), getUserAgent());
            log.info("[INFO] ํ† ํ”ฝ์„ ๋ฐœํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.: {}", event);
            kafkaTemplate.send("user-login-history-topic", event);
        } catch (Exception e) {
            throw new KafkaPublishException(ErrorCode.KAFKA_PUBLISH_ERROR);
        }
    }

    private String getClientIp() {
        var ip = request.getHeader(UserLoginHistoryConstants.X_FORWARDED_FOR);
        if (ip == null || ip.isEmpty() ||UserLoginHistoryConstants.UNKNOWN.equalsIgnoreCase(ip))
            ip = request.getRemoteAddr();
        return ip;
    }

    private String getUserAgent() {
        return request.getHeader(UserLoginHistoryConstants.USER_AGENT);
    }
}

๐Ÿ“ UserLoginHistoryEventConsumer.java

@Slf4j
@Component
@RequiredArgsConstructor
public class UserLoginHistoryEventConsumer {
    private final UserLoginHistoryRepository userLoginHistoryRepository;
    private final KafkaTemplate<String, UserLoginHistoryEvent> kafkaTemplate;
    private static final String LOG_FILE_PATH = "/var/log/tictoc/user_login_history.log";
    private static final String LOG_MESSAGE_FORMAT = "%s - Id: %d, UserId: %d, IPAddress: %s, Device: %s%n";

    @KafkaListener(
            topics = "user-login-history-topic",
            groupId = "${spring.kafka.consumer.group-id}",
            containerFactory = "kafkaListenerContainerFactory"
    )
    public void consume(UserLoginHistoryEvent event) {
        try {
            log.info("[INFO] ํ† ํ”ฝ์„ ์†Œ๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค.: {}", event);
            saveUserLoginHistory(UserLoginHistory.of(event.userId(), event.loginAt(), event.ipAddress(), event.device()));
        } catch (Exception e) {
            kafkaTemplate.send("user-login-history-topic.DLT", event);
            throw new KafkaConsumeException(KAFKA_CONSUME_ERROR, e);
        }
    }

    public void saveUserLoginHistory(UserLoginHistory loginHistory) {
        userLoginHistoryRepository.save(loginHistory);
        writeLogToFile(loginHistory);
    }

    private void writeLogToFile(UserLoginHistory loginHistory) {
        File logFile = ensureLogFileExists();
        synchronized (this) {
            try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFile, true))) {
                writer.write(String.format(LOG_MESSAGE_FORMAT,
                        loginHistory.getLoginAt(), loginHistory.getId(), loginHistory.getUserId(), loginHistory.getIpAddress(), loginHistory.getDevice()));
            } catch (IOException e) {
                throw new LogFileWriteException(LOG_FILE_WRITE_ERROR, e);
            }
        }
    }

    private static File ensureLogFileExists() {
        File logFile = new File(LOG_FILE_PATH);
        File directory = logFile.getParentFile();
        if (!directory.exists() && directory.mkdirs()) {
            setFilePermissions(directory, "755");
        }
        if (!logFile.exists()) {
            try {
                if (logFile.createNewFile()) {
                    setFilePermissions(logFile, "666");
                }
            } catch (IOException e) {
                throw new LogFileWriteException(LOG_FILE_CREATION_ERROR, e);
            }
        }
        return logFile;
    }

    private static void setFilePermissions(File file, String permission) {
        try {
            Process process = new ProcessBuilder("chmod", permission, file.getAbsolutePath()).start();
            if (process.waitFor() == 0) {
                log.info("[INFO] chmod {} ๊ถŒํ•œ ์„ค์ • ์™„๋ฃŒ: {}", permission, file.getAbsolutePath());
            } else {
                log.error("[ERROR] chmod {} ์‹คํŒจ: {}", permission, file.getAbsolutePath());
            }
        } catch (Exception e) {
            log.error("[ERROR] chmod {} ์‹คํ–‰ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ: {}", permission, file.getAbsolutePath(), e);
        }
    }
}

์œ„ ๊ตฌํ˜„ ๊ฒฐ๊ณผ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ์„ ํ•œ๋‹ค.
2. API ์„œ๋ฒ„๋Š” ๋กœ๊ทธ์ธ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋ฉด JWT ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  ๋ฐœ๊ธ‰๊ณผ ๋™์‹œ์— ์นดํ”„์นด ์„œ๋ฒ„์— ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก ๋ฉ”์„ธ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค.
3. Kafka๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ํ์— ์ €์žฅํ•˜์—ฌ Spring Batch ์„œ๋ฒ„๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
4. Spring Batch ์„œ๋ฒ„๋Š” Kafka์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ˆ˜์‹ ํ•œ๋‹ค.
5. Spring Batch ์„œ๋ฒ„์—์„œ ๋ฐ›์€ ๋ฉ”์‹œ์ง€๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ DB ์ €์žฅ ๋ฐ ๋กœ๊ทธ ํŒŒ์ผ์— ๊ธฐ๋กํ•œ๋‹ค.


Result

๋ฌธ์ œ ํ•ด๊ฒฐ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๋ฅผ ์–ป์—ˆ๋‹ค.
1. Kafka๋กœ ์ด๋ฒคํŠธ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ โ†’ ์‘๋‹ต ์†๋„ ํ–ฅ์ƒ, DB ๋ถ€ํ•˜ ๊ฐ์†Œ.
2. Spring Batch ์„œ๋ฒ„์—์„œ ์ฃผ๊ธฐ์  DB ์‚ญ์ œ ๋ฐ ๋กœ๊ทธ ํŒŒ์ผ ๋ฐฑ์—… โ†’ ๋ฐ์ดํ„ฐ ์œ ์‹ค ๋ฐฉ์ง€.
3. API ์„œ๋ฒ„์™€ Batch ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ, ํ™•์žฅ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด์„ฑ ํ™•๋ณด.

์ตœ์ข…์ ์ธ ์•„ํ‚คํ…์ฒ˜ ํ๋ฆ„์„ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.


Thoughts

์ด๋ฒˆ ๊ฒฝํ—˜์„ ํ†ตํ•ด ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์„ ๊ณ ๋ คํ•œ ์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ๊ธฐ๋ก ์ €์žฅ ๋ฐฉ์‹์„ ๊ณ ๋ฏผํ•˜๋ฉฐ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ด ๋‚˜๊ฐˆ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ฒ˜์Œ์—๋Š” ๋‹จ์ˆœํžˆ DB์— ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ ค ํ–ˆ์ง€๋งŒ, ๋กœ๊ทธ์ธ ์š”์ฒญ์ด ๋งŽ์•„์งˆ์ˆ˜๋ก ๋ฐ์ดํ„ฐ๊ฐ€ ๊ธฐํ•˜๊ธ‰์ˆ˜์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๋Š” ๋ฌธ์ œ๋ฅผ ์ฒ˜์Œ ๋งˆ์ฃผํ–ˆ๋‹ค.
์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Spring Batch๋ฅผ ํ™œ์šฉํ•ด ์ฃผ๊ธฐ์  ๋ฐ์ดํ„ฐ ์‚ญ์ œ๋ฅผ ์ ์šฉํ•˜๋ ค ํ–ˆ์ง€๋งŒ, ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ ๋ชจ๋‘ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์€ ์šด์˜์ ์ธ ์ธก๋ฉด์—์„œ ๋ฌธ์ œ๋  ๊ฒƒ ๊ฐ™์•˜๋‹ค.

๊ทธ๋ž˜์„œ DB๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋กœ๊ทธ ํŒŒ์ผ์—๋„ ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์„ ์ฑ„ํƒํ–ˆ๋‹ค.
ํ•˜์ง€๋งŒ API ์„œ๋ฒ„์—์„œ ์ง์ ‘ Spring Batch ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋ ค๋ฉด ์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹  ๋น„์šฉ์ด ์ฆ๊ฐ€ํ•˜๊ณ , ์„œ๋ฒ„ ์žฅ์•  ๋ฐœ์ƒ ์‹œ ๋ฐ์ดํ„ฐ ์œ ์‹ค ์œ„ํ—˜์ด ์žˆ์—ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Kafka๋ฅผ ํ™œ์šฉํ•˜์—ฌ API ์„œ๋ฒ„์™€ Spring Batch ์„œ๋ฒ„๋ฅผ ๋ถ„๋ฆฌํ•˜์˜€๋‹ค.
Kafka๋ฅผ ๋„์ž…ํ•จ์œผ๋กœ์จ ๋กœ๊ทธ์ธ ๊ธฐ๋ก์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์—ˆ์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ ์œ ์‹ค์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋˜ Spring Batch ์„œ๋ฒ„๊ฐ€ API ์„œ๋ฒ„์™€ ๋ณ„๋„๋กœ ์šด์˜๋˜๋„๋ก ๊ตฌ์„ฑํ•˜์—ฌ, API ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ณ  ์•ˆ์ •์ ์ธ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค.

์ด๋ฒˆ ๊ฒฝํ—˜์—์„œ ๋‹จ์ˆœํ•œ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ์•„๋‹ˆ๋ผ, ์‹ค์ œ ์„œ๋น„์Šค ์šด์˜์„ ๊ณ ๋ คํ•œ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„์˜ ์ค‘์š”์„ฑ์„ ๋‹ค์‹œ ํ•œ ๋ฒˆ ๋Š๋‚„ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์•ž์œผ๋กœ๋„ ์šด์˜ ์ธก๋ฉด์—์„œ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์ข‹์€ ์„ค๊ณ„๋ฅผ ๊ณ ๋ฏผํ•˜๋ฉฐ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋กœ ์„ฑ์žฅํ•  ๊ฒƒ์ด๋‹ค.


profile
๐Ÿ’ป ๐Ÿ’ป ๐Ÿ’ป

0๊ฐœ์˜ ๋Œ“๊ธ€

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด