πŸ€” μ„œλ²„λŠ” 3λŒ€μΈλ° μ™œ 응닡이 10초 이상이 걸릴까?

JYΒ·2025λ…„ 10μ›” 20일

Spring

λͺ©λ‘ 보기
6/7
post-thumbnail

⚠️ Issue

Python-Ansible 기반으둜 λ¦¬λˆ…μŠ€ μ„œλ²„λ“€μ„ 일괄 κ΄€λ¦¬ν•˜λŠ” μ›Ή μ„œλΉ„μŠ€λ₯Ό κ°œλ°œν•˜λ˜ 쀑, E2E ν…ŒμŠ€νŠΈ λ‹¨κ³„μ—μ„œ 응닡 속도 λ¬Έμ œκ°€ 치λͺ…μ μœΌλ‘œ λ“œλŸ¬λ‚¬λ‹€.

μ„œλΉ„μŠ€λŠ” Ansible ν”Œλ ˆμ΄λΆμ„ μ‹€ν–‰ν•˜κΈ° 전에, λŒ€μƒ μ„œλ²„λ“€μ΄ μ‹€μ œλ‘œ μ‚΄μ•„ μžˆλŠ”μ§€(Port, SSH, Ping λ“±) 사전 μ κ²€ν•˜λŠ” λ‘œμ§μ„ ν¬ν•¨ν•˜κ³  μžˆμ—ˆλŠ”λ°, λ“±λ‘λœ μ„œλ²„κ°€ 단 3λŒ€λΏμž„μ—λ„ 전체 μƒνƒœ μ‘°νšŒμ— 10초 이상이 μ†Œμš”λ˜κ±°λ‚˜, μ‹¬ν•œ 경우 ν”„λ‘ νŠΈμ—”λ“œ λ Œλ”λ§μ΄ μ•„μ˜ˆ μ‹€νŒ¨ν•˜λŠ” μƒν™©κΉŒμ§€ λ°œμƒν–ˆλ‹€.

μ›Ή λŒ€μ‹œλ³΄λ“œ μž…μž₯μ—μ„œλŠ” μ‚¬μš©μžκ°€ β€œμƒνƒœ μƒˆλ‘œκ³ μΉ¨β€ ν•œ 번 ν–ˆμ„ 뿐인데, κ²°κ³Όκ°€ μ˜€κΈ°κΉŒμ§€ 지연이 λ°œμƒν•˜κ±°λ‚˜ νƒ€μž„μ•„μ›ƒμœΌλ‘œ 인해 빈 화면이 λ…ΈμΆœλ˜λŠ” μ…ˆμ΄μ—ˆλ‹€.
운영 ν™˜κ²½μ—μ„œλŠ” μˆ˜μ‹­~수백 λŒ€μ˜ μ„œλ²„κ°€ 등둝될 수 μžˆλ‹€λŠ” 점을 κ³ λ €ν•˜λ©΄, 3λŒ€μ—μ„œ 이미 10초라면 100λŒ€ μ΄μƒμ—μ„œλŠ” μ„œλΉ„μŠ€κ°€ 사싀상 λ™μž‘ν•  수 μ—†λŠ” κ΅¬μ‘°λΌλŠ” λƒ‰μ •ν•œ 결둠이 λ‚˜μ˜¨λ‹€.

쑰사 κ²°κ³Ό, 문제의 핡심 원인은 λ‹¨μˆœν–ˆλ‹€.

μ„œλ²„ 점검이 순차적으둜 μ§„ν–‰λ˜λ©΄μ„œ, 응닡이 μ—†λŠ” μ„œλ²„μ— λŒ€ν•΄ νƒ€μž„μ•„μ›ƒ(μ΅œλŒ€ 10초) 을 맀번 기닀리고 있기 λ•Œλ¬Έμ΄μ—ˆλ‹€.

즉, "μ„œλ²„ 개수 Γ— νƒ€μž„μ•„μ›ƒ" 만큼 λŒ€κΈ°ν•˜λŠ” ꡬ쑰가 μˆ¨μ–΄ μžˆμ—ˆκ³ , 이 방식은 μ„œλ²„ μˆ˜κ°€ λŠ˜μ–΄λ‚ μˆ˜λ‘ 응닡 μ‹œκ°„μ΄ μ„ ν˜•μ μœΌλ‘œ λŠλ €μ§€λŠ” 병λͺ© ꡬ쑰λ₯Ό λ§Œλ“€κ³  μžˆμ—ˆλ‹€.


βœ… 아킀텍쳐 및 병λͺ© λ°œμƒμ§€μ  - ꡬ쑰λ₯Ό μ΄ν•΄ν•˜λ©΄ λ¬Έμ œλŠ” 더 λͺ…ν™•ν•΄μ§„λ‹€.

ν•΄λ‹Ή μ„œλΉ„μŠ€λŠ” μ›Ήμ—μ„œ λͺ¨λ“  μ„œλ²„μ˜ ν˜„μž¬ μƒνƒœλ₯Ό ν•œ λ²ˆμ— μ‘°νšŒν•œ λ’€, 이상이 μ—†λŠ” μ„œλ²„λ“€μ— λŒ€ν•΄μ„œλ§Œ Ansible ν”Œλ ˆμ΄λΆμ„ μ‹€ν–‰ν•˜λ„λ‘ μ„€κ³„λ˜μ–΄ μžˆμ—ˆλ‹€. 즉, β€œμ„œλ²„ μƒνƒœ μ‘°νšŒβ€λŠ” Ansible μ‹€ν–‰ μ΄μ „μ˜ 사전 필터링 역할을 ν•˜λŠ” 핡심 λ‹¨κ³„μ˜€κ³ , 이 단계가 λŠλ €μ§€λŠ” μˆœκ°„ 전체 μ‹œλ‚˜λ¦¬μ˜€κ°€ λͺ¨λ‘ 느렀질 μˆ˜λ°–μ— μ—†λŠ” κ΅¬μ‘°μ˜€λ‹€.

μ„œλΉ„μŠ€ λ ˆμ΄μ–΄μ˜ μ˜μ‘΄μ„± κ΄€κ³„λŠ” μ•„λž˜μ™€ κ°™μ•˜λ‹€.

[LinuxServerService]
  β”œβ”€ linuxServerRepository            ← DB μ„œλ²„ λͺ©λ‘ 쑰회 (Mongo/SQL λ“± μ˜μ†μ„±)
  β”œβ”€ inspector : SshRemoteInspector   ← μ„œλ²„ μƒνƒœ 확인(Ping, SSH μ—°κ²°, 포트 체크 λ“± λ„€νŠΈμ›Œν¬ I/O)
  β”œβ”€ deployer : SshDeployer           ← μ‹€μ œ 배포/λͺ…λ Ή μ‹€ν–‰ μ±…μž„ (SSH λͺ…λ Ή, 슀크립트 μˆ˜ν–‰)
  β”œβ”€ props : AnsibleProperties        ← YAML/Properties μ„€μ • λ§€ν•‘ (ν™˜κ²½ λ³€μˆ˜/경둜/timeout λ“±)
  └─ mongoTemplate                    ← ν•„μš” μ‹œ μ»€μŠ€ν…€ 쿼리용


[SshRemoteInspector]
  β”œβ”€ CommandBuilder   ← nc/ssh λͺ…λ Ή 생성
  └─ ProcessRunner    ← λͺ…λ Ή μ‹€ν–‰ + νƒ€μž„μ•„μ›ƒ μ œμ–΄

문제의 흐름은 λ‹¨μˆœν–ˆλ‹€.

μ‚¬μš©μž μš”μ²­ β†’ Service β†’ Repositoryμ—μ„œ μ„œλ²„ λͺ©λ‘ 쑰회
               β†’ inspector.probNetwork(server1)
               β†’ inspector.probNetwork(server2)
               β†’ inspector.probNetwork(server3)
               β†’ ... λͺ¨λ“  μ„œλ²„ ν™•μ • ν›„ 응닡 λ°˜ν™˜

이 방식은 μ½”λ“œ μƒμœΌλ‘œλŠ” λ¬΄λ‚œν•΄ 보인닀. ν•˜μ§€λ§Œ β€œλ„€νŠΈμ›Œν¬ I/OλŠ” μ–Έμ œλ“  느릴 수 μžˆλ‹€β€λŠ” 단 ν•˜λ‚˜μ˜ ν˜„μ‹€μ μΈ 쑰건을 κ°„κ³Όν•˜κ³  μžˆμ—ˆλ‹€. 예λ₯Ό λ“€μ–΄, λ‹€μŒκ³Ό 같은 상황이 λ°œμƒν–ˆλ‹€κ³  ν•˜μž.

  • server1 : 200~300ms λ‚΄ 응닡
  • server2 : 200~300ms λ‚΄ 응닡
  • server3 : μ•„μ˜ˆ 응닡 μ—†μŒ (포트 νƒ€μž„μ•„μ›ƒ 8~10초)

순차 κ΅¬μ‘°μ—μ„œλŠ” server3의 νƒ€μž„μ•„μ›ƒμ΄ 끝날 λ•ŒκΉŒμ§€ 전체 둜직이 λ©ˆμΆ°μ„œ 기닀릴 μˆ˜λ°–μ— μ—†λ‹€. κ²°κ΅­ κ²°κ³ΌλŠ” μ•„λž˜μ™€ 같이 λœλ‹€.

0.3초 + 0.3초 + 10초 = μ•½ 10.6초

이건 λ”± 3λŒ€μΌ λ•Œ 이야기닀.

  • μ„œλ²„κ°€ 10λŒ€κ°€ 되고, 그쀑 3λŒ€λ§Œ 응닡이 μ—†μœΌλ©΄? β†’ μ΅œλŒ€ 30초
  • μ„œλ²„κ°€ 100λŒ€κ°€ 되면? β†’ 100초 ~ 수백 초

λ°”λ‘œ 이 μ§€μ μ—μ„œ β€œμˆœμ°¨ μ²˜λ¦¬β€λŠ” ν™•μž₯ λΆˆκ°€λŠ₯ν•œ ꡬ쑰(Non-Scalable Architecture) 둜 νŒμ •λœλ‹€.

즉, 이 λ¬Έμ œλŠ” μ†λ„μ˜ λ¬Έμ œκ°€ μ•„λ‹ˆκ³  β€œκ΅¬μ‘°μ  병λͺ©β€ 의 λ¬Έμ œμ˜€λ‹€.


β›” μˆœμ°¨μ‹ λ°©μ‹μ˜ ν•œκ³„

μ •λ¦¬ν•˜λ©΄, κΈ°μ‘΄ ꡬ쑰의 λ¬Έμ œλŠ” λ”± 두 κ°€μ§€λ‘œ μš”μ•½λœλ‹€.

ν•œκ³„μ„€λͺ…
병렬성이 μ—†μŒμ—¬λŸ¬ μ„œλ²„λ₯Ό λ™μ‹œμ— μ²΄ν¬ν•˜μ§€ λͺ»ν•˜κ³  β€œν•œ μ€„λ‘œ μˆœμ„œλŒ€λ‘œβ€ 검사
Fail-Fast λΆ€μž¬μ‘λ‹΅ μ—†λŠ” μ„œλ²„λ„ λκΉŒμ§€ 기닀리며 전체 응닡을 κ°€λ‘œλ§‰μŒ

이 κ΅¬μ‘°λŠ” μ„œλ²„κ°€ 3λŒ€μΌ λ•ŒλŠ” β€œμŒβ€¦ λŠλ¦¬μ§€λ§Œ λŒμ•„κ°€λ„€β€ μˆ˜μ€€μΌ 뿐,
운영 λ‹¨κ³„μ—μ„œ μ„œλ²„κ°€ 50λŒ€, 100λŒ€λ‘œ λŠ˜μ–΄λ‚˜λŠ” μˆœκ°„ μ‹œμŠ€ν…œ 전체가 λ¬΄λ„ˆμ§„λ‹€.

λ”°λΌμ„œ κ°œμ„  λ°©ν–₯은 μžμ—°μŠ€λŸ½κ²Œ λ„μΆœλœλ‹€.

βœ… β€œλ™μ‹œμ— κ²€μ‚¬ν•˜κ³ , 응닡 μ—†λŠ” μ„œλ²„λŠ” λΉ λ₯΄κ²Œ 버리고, 전체 μš”μ²­μ˜ μƒν•œμ„ μ œν•œν•œλ‹€.”


βœ… ν•΄κ²° μ „λž΅

μ—¬κΈ°μ„œ μ μš©ν•œ κ°œμ„  μ „λž΅μ€ λ‹€μŒ 3κ°€μ§€λ‹€.

μ „λž΅μ˜λ―Έ
Parallel(λ™μ‹œ μ‹€ν–‰)μ„œλ²„ μˆ˜μ™€ 상관없이 ν•œ λ²ˆμ— 점검
Fail-Fast(λΉ λ₯Έ 포기)응닡 μ—†λŠ” μ„œλ²„λŠ” 1~2초 λ‚΄ TIMEOUT 처리
Global Timeout(전체 ν•œλ„)μ„œλ²„κ°€ 100λŒ€μ—¬λ„ 전체 μ‘°νšŒλŠ” 2~3초 λ‚΄ μ™„λ£Œ

κ³ μž‘ β€œλΉ„λ™κΈ° + νƒ€μž„μ•„μ›ƒβ€ 같은 λ‹¨μˆœν•œ ν‚€μ›Œλ“œκ°€ μ•„λ‹ˆλ‹€.
이 μ„Έ κ°€μ§€λŠ” 운영 κ°€λŠ₯ν•œ μ‹œμŠ€ν…œμ΄ λ°˜λ“œμ‹œ κ°€μ Έμ•Ό ν•˜λŠ” λ„€νŠΈμ›Œν¬ I/O μ•ˆμ •μž₯μΉ˜λ‹€.


🎯 κ°œμ„ λœ μ½”λ“œ μŠ€λƒ…μƒ·

  • LinuxServerService.java
// 병렬 + Fail-Fast + Global Timebox
public NetworkStatusResponse probNetworkForServers(String groupName) throws Exception {
		List<LinuxServer> servers = linuxServerRepository.findByGroupName(groupName);
        if (servers.isEmpty()) return new NetworkStatusResponse(groupName, List.of());

        int poolSize = Math.min(
                Math.max(8, Runtime.getRuntime().availableProcessors() * 2),
                Math.max(8, servers.size()));
        ExecutorService ex = Executors.newFixedThreadPool(poolSize);

        try {
            List<CompletableFuture<NetworkStatusResponse.Item>> futures =
                    servers.stream()
                            .map(s -> CompletableFuture.supplyAsync(() -> {
                                                NetProbe np = inspector.probNetwork(
                                                        s.getUserId(),
                                                        s.getIpAddress(),
                                                        s.getPort()
                                                );

                                                return new NetworkStatusResponse.Item(
                                                        s.getId(),
                                                        s.getIpAddress(),
                                                        s.getUserId(),
                                                        np.state(),
                                                        np.detail()
                                                );
                                            }, ex)
                                            .orTimeout(2, TimeUnit.SECONDS) // κ°œλ³„ Fail-Fast
                                            .exceptionally(e -> new NetworkStatusResponse.Item(
                                                    s.getId(),
                                                    s.getIpAddress(),
                                                    s.getUserId(),
                                                    NetState.TIMEOUT,
                                                    "timeout/" + e.getClass().getSimpleName()
                                            ))
                            ).toList();
            CompletableFuture<Void> all =
                    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
            try {
                // 전체 νƒ€μž„λ°•μŠ€(예: 2500ms) β€” λ‹€ μ•ˆ λλ‚˜λ„ μ—¬κΈ°μ„œ μ’…λ£Œ
                all.get(2500, TimeUnit.MILLISECONDS);
            } catch (Exception ignored) {}

            List<NetworkStatusResponse.Item> items = futures.stream()
                    .map(f -> f.getNow(null))
                    .filter(Objects::nonNull)
                    .toList();
            return new NetworkStatusResponse(groupName, items);
        } finally {
            ex.shutdown();
    }
  • CommandBuilder.java

κ°œλ³„ 단계 νƒ€μž„μ•„μ›ƒμ„ 1–2초둜 κ³ μ • (CommandBuilderμ—μ„œ 이미 λ‹¨μΆ•λœ μ˜΅μ…˜ μ‚¬μš©)

nc -n -w 2, ssh -o ConnectTimeout=2 -o ConnectionAttempts=1).

βœ… κ°œμ„  ν›„ κ²°κ³Ό

ν•­λͺ©κ°œμ„  μ „κ°œμ„  ν›„
3λŒ€ μ„œλ²„ 쑰회10 ~ 12초2 ~ 2.5초
λ Œλ”λ§ μ‹€νŒ¨λΉˆλ²ˆμ—†μŒ
ꡬ쑰 ν™•μž₯μ„±μ·¨μ•½μš°μˆ˜ (100λŒ€ 이상 λŒ€μ‘ κ°€λŠ₯)
μ‚¬μš©μž κ²½ν—˜μ§€μ—°Β·λŠκΉ€μ¦‰μ‹œ 응닡

이번 이슈λ₯Ό 톡해 얻은 κ΅ν›ˆμ€ λͺ…ν™•ν–ˆλ‹€. 속도 μ΅œμ ν™”μ˜ λ¬Έμ œκ°€ μ•„λ‹Œ ꡬ쑰적 ν•œκ³„λΌλŠ” 점이닀.

β€œλ„€νŠΈμ›Œν¬ I/Oλ₯Ό λ‹€λ£¨λŠ” μ„œλΉ„μŠ€μ—μ„œ 순차 μ²˜λ¦¬μ™€ λ¬΄μ œν•œ νƒ€μž„μ•„μ›ƒμ€ λ°˜λ“œμ‹œ 병λͺ©μ„ λ§Œλ“ λ‹€. ν™•μž₯ κ°€λŠ₯ν•œ ꡬ쑰λ₯Ό μ›ν•œλ‹€λ©΄ 병렬성(Concurrency)κ³Ό νƒ€μž„λ°•μŠ€(Time-bound)κ°€ κΈ°λ³Έ μ „μ œκ°€ λ˜μ–΄μ•Ό ν•œλ‹€.”

μ„œλΉ„μŠ€λŠ” 이제 μ„œλ²„ μˆ˜μ™€ λ¬΄κ΄€ν•˜κ²Œ μ•ˆμ •μ μΈ 응닡 속도λ₯Ό μœ μ§€ν•˜λ©°,
μš΄μ˜μžκ°€ μ‹€μ‹œκ°„μœΌλ‘œ μ„œλ²„ μƒνƒœλ₯Ό λΉ λ₯΄κ²Œ νŒŒμ•…ν•  수 μžˆλŠ” ν˜•νƒœλ‘œ κ°œμ„ λ˜μ—ˆλ‹€.

0개의 λŒ“κΈ€