Skip to content

refactor: VM 할당 개선#72

Open
ga111o wants to merge 5 commits into
easy-cloud-Knet:stagingfrom
ga111o:refactor/core-vm-allocation-v2
Open

refactor: VM 할당 개선#72
ga111o wants to merge 5 commits into
easy-cloud-Knet:stagingfrom
ga111o:refactor/core-vm-allocation-v2

Conversation

@ga111o

@ga111o ga111o commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

  • 기존 first-fit -> 부하 분산
  • race condition 제거
  • 삭제 시 자원 회수

Motivation

기존 SelectCore는 그저 앞에서부터 조건을 만족하는 첫 코어를 고르는 문제가 있었음.
동시 생성 시 over subscription이 발생 가능했으며, 삭제 시 인메모리쪽 되돌리지 않았음.

Approach

부하분산 스케줄링

  • first-fit 대신 적합한 코어 전체를 스캔해 loadScore가 가장 작은 코어 선택.
  • 스코어링
    • 포화 페널티: 한 차원이라도 사용률이 100%에 근접하면 점수가 폭증하도록.
    • 메모리, 디스크, 시퓨 중 하나라도 포화된 코어는 선택되지 않음.
    • 자원 우선순위 가중치 mem 0.6 > disk 0.3 > cpu 0.1.
      • 우선은 하드코딩. 추후 data fetching 등으로 관리하도록 하는 것이 좋아보임.

init CPU 정보

  • 기존 cpu 9999 하드코딩 제거. 코어의 GetCoreMachineCpuInfo(vcpu_status)로
    CoreInfoIdx.Cpu(total)·FreeCPU(idle)를 실제 값으로 설정.
  • CPU 정보 조회 실패 시 해당 코어를 IsAlive = false 처리.

VM 삭제 시 인메모리 회수

  • ReleaseVM(core, uuid) 추가, Reserve+Attach+Register atomic하게 역연산.
    Free* 복구 + VMInfoIdx/VMLocation/AliveVM 정리를 한 lock 안에서 수행.

Type of Change

  • Bug fix
  • New feature
  • Refactoring
  • Docs / Config
  • CI/CD

Related Issue

Testing

  • Tested locally
  • No regression in existing functionality

Checklist

  • Reviewers assigned
  • Related issue linked

추후 개선 필요.

  • 아래 내용들 매우 치명적인 문제가 아니며, 현재 pr 범위가 아니기에 진행하지는 않았습니다.
    • 코어 삭제 성공 후 DB DeleteInstance 실패 시 DB에 레코드가 남아 서버 재부팅 시 다시 생겨버릴 수도 있음. 코어 삭제를 idempotent하게 만들거나 할 필요가 있음.
    • init은 코어 실측 available 기반, 런타임은 예약 기반.

Summary by CodeRabbit

  • New Features

    • Added detailed vCPU status to CPU information responses, including total, allocated, sleeping, and idle counts.
  • Bug Fixes

    • Improved VM provisioning/deletion rollback to more reliably restore reserved resources after failures, reducing risk of resource over-allocation.
    • Updated core health/CPU availability initialization to rely on live CPU idle counts rather than prior approximations, keeping reported capacity more accurate.
    • Improved core selection to use scored, balanced reservations to better spread resource usage.

ga111o added 4 commits July 7, 2026 14:55
- Introduced loadScore function to calculate core load based on resource utilization.
- Added utilRatio and saturationPenalty functions for improved resource management.
- Updated SelectCore to select the core with the lowest load score that meets resource requirements.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e946cd98-b140-4c2c-91cf-3d8296829093

📥 Commits

Reviewing files that changed from the base of the PR and between c2f311c and 996fab5.

📒 Files selected for processing (1)
  • service/vm.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • service/vm.go

📝 Walkthrough

Walkthrough

This PR adds vCPU status to CPU responses, replaces first-fit allocation with scored reservation and unified release APIs, and updates VM lifecycle handling and core initialization to use reserved resources and idle-based CPU tracking.

Changes

vCPU status and resource manager rework

Layer / File(s) Summary
vCPU status data contract and API documentation
client/model/vm.go, client/vm.go
Adds VcpuStatus, exposes it on CPU info responses, and documents total and idle vCPU values.
Scored core reservation and release
structure/resource_manager.go
Adds load-scored ReserveCore, metadata-only AttachVMInfo, and state-restoring ReleaseVM, replacing the previous allocation methods.
VM lifecycle reservation and cleanup
service/vm.go
Updates CreateVM to reserve resources and use centralized rollback, and updates DeleteVM to release resource state after core deletion.
Core initialization from vCPU status
startup/init.go
Fetches CPU status, rejects missing status, and sets total and free CPU values from VcpuStatus.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CreateVM
  participant ResourceManager
  participant Core
  CreateVM->>ResourceManager: reserveCoreOrFail(req)
  ResourceManager->>Core: score cores and reserve Free*
  ResourceManager-->>CreateVM: reserved core
  CreateVM->>ResourceManager: AttachVMInfo(core, uuid, vm)
  alt setup failure
    CreateVM->>ResourceManager: cleanup.run()
    ResourceManager->>Core: ReleaseVM and restore Free*
  else core VM creation succeeds
    CreateVM->>Core: CreateVM
  end
  CreateVM->>ResourceManager: ReleaseVM(core, uuid) on deletion
Loading

Possibly related PRs

Suggested reviewers: wind5052, kwonkwonn

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: improving VM allocation and resource handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
structure/resource_manager.go (1)

36-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Solid scoring model — consider adding unit tests for the new helpers.

The weighting, saturation curve, and variance-balance terms are well thought out and internally consistent with their doc comments. Since this directly drives placement decisions, table-driven tests for utilRatio/saturationPenalty/loadScore (boundary cases: total==0, free==total, free==req) would guard against regressions when the weights are tuned later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@structure/resource_manager.go` around lines 36 - 95, Add table-driven unit
tests for the new scoring helpers in resource_manager.go, covering utilRatio,
saturationPenalty, and loadScore. Focus on boundary cases like total==0,
free==total, and free==req, plus a few representative weighting/variance
scenarios so future tuning of loadWeightMemory, loadWeightDisk, loadWeightCPU,
and loadBalanceGain won’t silently change placement behavior. Use the function
names utilRatio, saturationPenalty, and loadScore to locate the logic.
startup/init.go (1)

207-225: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated fetch/error-handling pattern across Memory, Disk, and CPU info.

The three info-fetch blocks (memory, disk, now CPU) repeat the same if err != nil { currentCore.IsAlive = false; return fmt.Errorf(...) } shape. Extracting a small helper (e.g., a generic wrapper taking a fetch func and a resource-name string) would reduce duplication and keep future resource additions consistent.

♻️ Example helper sketch
func fetchOrFail[T any](currentCore *structure.Core, resourceName, ip string, port uint16, fetch func() (T, error)) (T, error) {
	val, err := fetch()
	if err != nil {
		currentCore.IsAlive = false
	}
	return val, err
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@startup/init.go` around lines 207 - 225, The Memory, Disk, and CPU fetch
blocks in initCoreInfo repeat the same error-handling pattern, so factor that
logic into a small helper around the coreClient calls. Add a reusable function
(for example, a fetchOrFail-style helper near startup/init.go) that takes
currentCore, a resource name, and a fetch callback, sets currentCore.IsAlive to
false on failure, and returns the wrapped error message; then use it for
GetCoreMachineMemoryInfo, GetCoreMachineDiskInfo, and GetCoreMachineCpuInfo to
keep the behavior identical while removing duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/vm.go`:
- Around line 35-39: The rollback registered in the cleanup chain is only
releasing scheduler resources, but after core VM creation it can leave an
existing VM and Redis state behind if a later DB step fails. Update the failure
handling around core VM creation and the cleanup path in vm.go so that post-core
rollback first deletes the VM from the core and clears Redis state before
calling Resources.DeallocateResources. If needed, split the pre-core reservation
rollback from the post-core compensating rollback to ensure Free* capacity is
only restored after the core-side delete succeeds.
- Around line 247-252: The cleanup flow in ReleaseVM is removing
VMLocation/AliveVM too early, before VMRepo.DeleteInstance can still fail
afterward. Update the VM deletion path in ReleaseVM and its caller so the
in-memory VM mapping is only cleared after the fatal DB cleanup succeeds, or
otherwise make the later DeleteInstance failure non-fatal by preserving enough
state for a retry/reconciliation path. Use the ReleaseVM and
VMRepo.DeleteInstance flow to keep FindCoreByVmUUID retryable and avoid leaving
stale DB state with no recovery path.
- Line 174: The debug log in the core selection path is using the wrong units
for the request values. Update the log message in the core selection flow that
uses `log.DebugInfo` so it reports `HardwareRequirement.Memory` and `Disk` in
MiB, not GiB, while keeping `CPU` unchanged. Make sure the message in the
selection logic reflects the actual units used by `req.Memory`, `req.CPU`, and
`req.Disk`.

In `@startup/init.go`:
- Around line 217-225: Handle GetCoreMachineCpuInfo failures locally inside the
per-core goroutine in InitializeCoreData instead of returning the error to
errgroup and aborting g.Wait(). In the block that sets currentCore.IsAlive and
calls coreClient.GetCoreMachineCpuInfo, keep marking the specific core as not
alive and then continue processing the other cores; only return from the
goroutine for truly fatal setup errors. Use the existing InitializeCoreData,
GetCoreMachineCpuInfo, and currentCore.IsAlive flow to keep the failure scoped
to that core.

---

Nitpick comments:
In `@startup/init.go`:
- Around line 207-225: The Memory, Disk, and CPU fetch blocks in initCoreInfo
repeat the same error-handling pattern, so factor that logic into a small helper
around the coreClient calls. Add a reusable function (for example, a
fetchOrFail-style helper near startup/init.go) that takes currentCore, a
resource name, and a fetch callback, sets currentCore.IsAlive to false on
failure, and returns the wrapped error message; then use it for
GetCoreMachineMemoryInfo, GetCoreMachineDiskInfo, and GetCoreMachineCpuInfo to
keep the behavior identical while removing duplication.

In `@structure/resource_manager.go`:
- Around line 36-95: Add table-driven unit tests for the new scoring helpers in
resource_manager.go, covering utilRatio, saturationPenalty, and loadScore. Focus
on boundary cases like total==0, free==total, and free==req, plus a few
representative weighting/variance scenarios so future tuning of
loadWeightMemory, loadWeightDisk, loadWeightCPU, and loadBalanceGain won’t
silently change placement behavior. Use the function names utilRatio,
saturationPenalty, and loadScore to locate the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0c3f3fba-8be2-4cac-b200-7eca6f6ecee5

📥 Commits

Reviewing files that changed from the base of the PR and between 5a96e73 and c2f311c.

📒 Files selected for processing (5)
  • client/model/vm.go
  • client/vm.go
  • service/vm.go
  • startup/init.go
  • structure/resource_manager.go

Comment thread service/vm.go
Comment thread service/vm.go
func reserveCoreOrFail(contextStruct *vms.ControlContext, req vms.HardwareRequirement) (*vms.Core, int, error) {
log := util.GetLogger()

log.DebugInfo("core selection process. req: memory=%d GiB, cpu=%d, disk=%d", req.Memory, req.CPU, req.Disk)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the diagnostic units.

HardwareRequirement.Memory and Disk are MiB, but this log prints GiB, which makes capacity failures look 1024x different from the actual request.

Suggested fix
-	log.DebugInfo("core selection process. req: memory=%d GiB, cpu=%d, disk=%d", req.Memory, req.CPU, req.Disk)
+	log.DebugInfo("core selection process. req: memory=%d MiB, cpu=%d, disk=%d MiB", req.Memory, req.CPU, req.Disk)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
log.DebugInfo("core selection process. req: memory=%d GiB, cpu=%d, disk=%d", req.Memory, req.CPU, req.Disk)
log.DebugInfo("core selection process. req: memory=%d MiB, cpu=%d, disk=%d MiB", req.Memory, req.CPU, req.Disk)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/vm.go` at line 174, The debug log in the core selection path is using
the wrong units for the request values. Update the log message in the core
selection flow that uses `log.DebugInfo` so it reports
`HardwareRequirement.Memory` and `Disk` in MiB, not GiB, while keeping `CPU`
unchanged. Make sure the message in the selection logic reflects the actual
units used by `req.Memory`, `req.CPU`, and `req.Disk`.

Comment thread service/vm.go
Comment on lines +247 to +252
// Core 삭제 성공 -> 인메모리 자원 회수 (Free* 복구 + VMInfoIdx/VMLocation/AliveVM 정리)
if contextStruct.Resources.ReleaseVM(core, uuid) {
log.DebugInfo("released in-memory resources for VM %s on core %s: FreeMemory=%d, FreeCPU=%d, FreeDisk=%d",
uuid, core.IP, core.FreeMemory, core.FreeCPU, core.FreeDisk)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Don’t remove the in-memory VM location before later fatal cleanup can fail.

ReleaseVM deletes VMLocation/AliveVM, but VMRepo.DeleteInstance can still return an error afterward. A retry then may fail at FindCoreByVmUUID, leaving stale DB state without a normal retry path. Keep enough in-memory deletion state until fatal cleanup succeeds, or make the later DB failure non-fatal with a reconciliation/retry path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@service/vm.go` around lines 247 - 252, The cleanup flow in ReleaseVM is
removing VMLocation/AliveVM too early, before VMRepo.DeleteInstance can still
fail afterward. Update the VM deletion path in ReleaseVM and its caller so the
in-memory VM mapping is only cleared after the fatal DB cleanup succeeds, or
otherwise make the later DeleteInstance failure non-fatal by preserving enough
state for a retry/reconciliation path. Use the ReleaseVM and
VMRepo.DeleteInstance flow to keep FindCoreByVmUUID retryable and avoid leaving
stale DB state with no recovery path.

Comment thread startup/init.go
Comment on lines +217 to +225
cpuResp, err := coreClient.GetCoreMachineCpuInfo(ctx)
if err != nil {
currentCore.IsAlive = false
return fmt.Errorf("failed to get CPU info for core %s:%d: %w", currentCore.IP, currentCore.Port, err)
}
if cpuResp == nil || cpuResp.VcpuStatus == nil {
currentCore.IsAlive = false
return fmt.Errorf("core %s:%d returned no vcpu_status in CPU info", currentCore.IP, currentCore.Port)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== startup/init.go around InitializeCoreData ==\n'
nl -ba startup/init.go | sed -n '140,280p'

printf '\n== client/model/vm.go around VcpuStatus ==\n'
nl -ba client/model/vm.go | sed -n '70,120p'

printf '\n== client/vm.go around GetCoreMachineCpuInfo ==\n'
nl -ba client/vm.go | sed -n '80,140p'

printf '\n== search for errgroup usage in startup/init.go ==\n'
rg -n "errgroup|Wait\(\)|Go\(" startup/init.go

Repository: easy-cloud-Knet/KWS_Control

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== startup/init.go around InitializeCoreData ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path("startup/init.go")
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 140 <= i <= 280:
        print(f"{i:4d}\t{line}")
PY

printf '\n== client/model/vm.go around VcpuStatus ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path("client/model/vm.go")
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 70 <= i <= 120:
        print(f"{i:4d}\t{line}")
PY

printf '\n== client/vm.go around GetCoreMachineCpuInfo ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path("client/vm.go")
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 80 <= i <= 140:
        print(f"{i:4d}\t{line}")
PY

printf '\n== search for errgroup usage in startup/init.go ==\n'
python3 - <<'PY'
from pathlib import Path
for i, line in enumerate(Path("startup/init.go").read_text().splitlines(), 1):
    if "errgroup" in line or "Wait()" in line or ".Go(" in line:
        print(f"{i:4d}\t{line}")
PY

Repository: easy-cloud-Knet/KWS_Control

Length of output: 9872


Single core CPU lookup still aborts startup for all cores. GetCoreMachineCpuInfo errors are returned from the errgroup goroutine, so g.Wait() fails InitializeCoreData instead of just marking that core IsAlive = false. If this is meant to be a per-core health update, keep the failure local and continue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@startup/init.go` around lines 217 - 225, Handle GetCoreMachineCpuInfo
failures locally inside the per-core goroutine in InitializeCoreData instead of
returning the error to errgroup and aborting g.Wait(). In the block that sets
currentCore.IsAlive and calls coreClient.GetCoreMachineCpuInfo, keep marking the
specific core as not alive and then continue processing the other cores; only
return from the goroutine for truly fatal setup errors. Use the existing
InitializeCoreData, GetCoreMachineCpuInfo, and currentCore.IsAlive flow to keep
the failure scoped to that core.

kwonkwonn
kwonkwonn previously approved these changes Jul 9, 2026

@kwonkwonn kwonkwonn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

눈에 띄는 에러가 안보이기도 했고, 기존 방식보다는 훨씬 나아 보입니다.
테스트 후 큰 문제가 없으면 머지해도 될 거 같아요

Comment thread service/vm.go
@ga111o

ga111o commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

눈에 띄는 에러가 안보이기도 했고, 기존 방식보다는 훨씬 나아 보입니다. 테스트 후 큰 문제가 없으면 머지해도 될 거 같아요

엥 commit하나 붙이니 리뷰가 사라져버렸네요.
@kwonkwonn 님께서 말씀 주신대로 삭제 요청정도 추가했습니다.
그 외 정합성 문제 날 수 있는 부분들은 코어-컨트롤 건들 때 싹 갈아엎어질 거 같아서 굳이 당장 해놓지는 않았습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants