해결: 결과를 변수에 저장하지 말고 대신 파이프로 전달하세요.
I’m happy to translate the article for you, but I’ll need the actual text of the post. Could you please paste the content you’d like translated (excluding the source link you’ve already provided)? Once I have the text, I’ll translate it into Korean while preserving the original formatting, markdown, and any code blocks.
요약
TL;DR: 명령 출력 결과를 변수에 저장하면 모든 데이터를 한 번에 RAM에 로드하게 되어 메모리가 고갈되고 서버가 충돌할 수 있습니다. 대신 PowerShell 파이프라인을 활용해 데이터를 객체‑단위로 스트리밍하면 대용량 데이터셋도 효율적이고 메모리 사용량을 최소화할 수 있습니다.
- 명령 결과를 “버킷” 변수에 저장하면 모든 객체가 메모리에 적재되어 대용량 데이터셋에서 메모리 고갈이 발생합니다.
- PowerShell 파이프라인은 컨베이어 벨트처럼 동작하여 데이터를 객체‑단위로 처리하고 작고 일정하며 예측 가능한 메모리 사용량을 유지합니다.
- “Filter‑Left” 원칙: 가능한 한 빨리 필터링합니다(예: 네이티브
-Filter매개변수 사용) → 데이터 전송량과 메모리 사용을 최소화합니다. - 방대한 데이터셋이거나 재처리가 필요할 경우 디스크에 스풀합니다(예:
Export‑Csv→Import‑Csv) → 메모리 사용량을 거의 0에 가깝게 만들 수 있습니다. - 파이프된 입력과 함께
ForEach-Object(또는 별칭%)를 직접 사용하면 사전 로드된 변수에 대한foreach루프보다 한 번에 하나씩 처리되어 오버헤드가 줄어듭니다.
핵심: 대용량 명령 출력을 변수에 저장하는 것을 중단하세요. 파이프라인을 사랑하세요; 파이프라인은 데이터를 객체‑단위로 스트리밍하여 메모리 고갈과 프로덕션 서버에서의 치명적인 스크립트 실패를 방지합니다.
실제 사례
어제 일어난 일처럼 생생히 기억합니다: 금요일 오후 3시. 한 주니어 엔지니어에게 “간단한” 정리 스크립트를 맡겼습니다—우리 웹 팜 전체에서 30일 이상 된 모든 임시 파일을 찾아 로그에 기록하는 작업이었습니다. 그는 한 줄짜리 코드를 작성했습니다:
$files = Get-ChildItem -Path \\web-cluster-*-c$\temp -Recurse
10분 뒤, 내 페이지가 울렸습니다. prod-web-01 … prod-web-20까지 전체 프로덕션 웹 서버가 하나씩 메모리 압박 알림을 발생시키며 다운되기 시작했습니다.
왜일까요? 이 스크립트는 20대 서버에서 수백만 개의 파일 객체를 관리 박스의 단일 변수에 로드하려 했기 때문에 자원 재앙이 발생한 것입니다.
우리 모두 겪어본 일입니다—스트리밍 대신 절차적 사고에서 비롯된 고전적인 실수죠. Reddit 스레드에서 이렇게 요약했습니다:
“우리는 결과를 변수에 저장하는 것을 권장하지 않습니다.”
변수 vs. 파이프라인
변수 기반 접근 방식
$myBigList = Get-ADUser -Filter *
PowerShell는 모든 사용자를 가져와 각각에 대해 객체를 만들고, 전체를 $myBigList에 보관합니다.
- 50 000명의 사용자 → RAM에 50 000개의 객체.
- 수십 개 또는 수백 개 정도는 잘 동작하지만, 수천 개 또는 수백만 개가 되면 재앙이 됩니다.
파이프라인 기반 접근 방식
Get-ADUser -Filter * | Where-Object {$_.Enabled -eq $false}
파이프라인은 컨베이어 벨트와 같습니다:
Get-ADUser가 첫 번째 사용자 객체를 내보냅니다.Where-Object가 이를 평가하여 유지할지 버릴지를 결정합니다.- 다음 객체가 내보내지고, 이 과정이 반복됩니다.
결과: 메모리 사용량은 작고, 일정하며, 예측 가능하게 유지됩니다. 처리 대상이 100개이든 1천만 개이든 관계없습니다.
Pro Tip: 변수는 동작하기 전에 모든 것을 수집합니다. 파이프라인은 아이템이 도착하는 즉시 동작할 수 있게 합니다. 대규모 자동화에서는 파이프라인이 유일한 확장 가능한 접근 방식입니다.
Source: …
세 가지 실용적인 접근법 (빠른 해결 → “긴급 차단”)
1️⃣ ForEach-Object 로 직접 파이프라인 연결 (가장 직접적인 해결책)
변수를 저장한 뒤 foreach 로 반복하는 대신, 출력을 바로 ForEach-Object(별칭 %) 로 파이프하세요. 이렇게 하면 한 번에 하나씩 처리됩니다.
나쁜 방법 (메모리 과다 사용)
# WARNING: Loads ALL VMs into memory first!
$allVMs = Get-VM -ComputerName prod-hyperv-cluster
foreach ($vm in $allVMs) {
if ($vm.State -eq 'Off') {
Write-Host "$($vm.Name) is currently off. Removing snapshot."
Get-VMSnapshot -VMName $vm.Name | Remove-VMSnapshot
}
}
좋은 방법 (스트리밍)
# Processes one VM at a time. Beautiful.
Get-VM -ComputerName prod-hyperv-cluster | ForEach-Object {
if ($_.State -eq 'Off') {
Write-Host "$($_.Name) is currently off. Removing snapshot."
# $_ represents the current object in the pipeline
Get-VMSnapshot -VMName $_.Name | Remove-VMSnapshot
}
}
2️⃣ 조기 필터링 – “왼쪽 필터링” 원칙
가능한 한 명령 체인의 가장 왼쪽(가장 초기)에서 필터링을 수행하세요. 대부분을 로컬에서 버리는 대량 데이터를 가져오는 일을 피합니다.
비효율적인 방법 (늦게 필터링)
# Pulls ALL users, then filters. Bad for network & memory.
Get-ADUser -Filter * -Properties LastLogonDate |
Where-Object {
$_.Enabled -eq $false -and $_.LastLogonDate -lt (Get-Date).AddDays(-90)
} |
Select-Object Name
효율적인 방법 (왼쪽 필터링)
# Let the Domain Controller do the heavy lifting.
$ninetyDays = (Get-Date).AddDays(-90).ToFileTime()
Get-ADUser -Filter {
Enabled -eq $false -and LastLogonTimestamp -lt $ninetyDays
} -Properties LastLogonTimestamp |
Select-Object Name
-Filter 매개변수를 사용하면 AD 서버가 일치하는 사용자만 반환하도록 요청하게 되며, 파이프라인에 들어가는 객체 수가 크게 줄어듭니다.
3️⃣ 대용량 재처리 가능한 데이터 세트를 위한 디스크 스풀링
데이터 세트가 정말 크고 여러 번 처리해야 하거나(또는 소스 API가 느린 경우) 메모리에 보관하지 마세요. 파일에 덤프하고 필요할 때 다시 스트리밍합니다.
# Export once (near‑zero memory)
Get-ADUser -Filter * -Properties * | Export-Csv -Path 'AllUsers.csv' -NoTypeInformation
# Later, stream it back for each processing pass
Import-Csv -Path 'AllUsers.csv' | Where-Object { $_.Enabled -eq $false } | ForEach-Object {
# Process each filtered record...
}
내보내기/가져오기 사이클은 디스크 I/O를 사용하고 RAM을 사용하지 않으므로, 전체 데이터를 메모리에 올리지 않고도 데이터를 재처리할 수 있습니다.
요약
- 절대 스트리밍이 가능한 경우 대량 명령 출력물을 변수에 저장하지 마세요.
- 가능하면 모든 데이터‑처리 작업에 파이프라인(
|)을 사용하세요. - 가능한 빨리 기본
-Filter매개변수를 사용해 필터링하세요. - 대용량 데이터 세트를 재처리해야 할 경우 디스크에 스풀하세요.
이러한 패턴을 채택하면 스크립트가 부드럽게 확장되어 운영 서버를 다운시키지 않습니다. 🚀
PowerShell로 대용량 데이터셋 스트리밍
데이터를 디스크의 임시 파일에 저장합니다.
이것은 “해킹적인” 방법이지만 매우 효과적입니다. 모든 데이터를 파일(CSV 또는 JSONL 등)로 한 번 쓰는 성능 비용을 감수한 뒤, 그 파일을 한 줄씩 읽어 들이면 메모리 사용량이 거의 없습니다.
과정
단계 1 – 대용량 데이터셋을 파일로 내보내기
# Export‑Csv is great because it handles objects cleanly.
Get-VeryLargeDataset -Server prod-db-01 |
Export-Csv -Path C:\temp\dataset.csv -NoTypeInformation
단계 2 – 파일에서 스트리밍하여 데이터 처리
# Import‑Csv streams the records one by one if piped.
Import-Csv -Path C:\temp\dataset.csv |
ForEach-Object {
# Work with each row ($_), one at a time.
# The entire file is NOT loaded into memory.
if ($_.Status -eq 'Failed') {
Invoke-MyRetryLogic -ID $_.TransactionID
}
}
단계 3 – 작업이 끝난 후 정리하기!
Remove-Item -Path C:\temp\dataset.csv
방법, 장점 및 단점
| 방법 | 장점 | 단점 |
|---|---|---|
| 1. 파이프라인 | • 매우 낮은 메모리 사용량 • 관용적인 PowerShell • 대부분의 작업에 빠름 | • 데이터가 일시적이며 초기 명령을 다시 실행하지 않으면 재처리하기 어려움 |
| 2. 왼쪽 필터링 | • 가장 효율적인 방법 • 메모리, CPU, 네트워크 부하 감소 | • 소스 명령이 강력한 서버‑측 필터링 기능을 가지고 있어야 함 |
| 3. 디스크에 스풀링 | • 사실상 무한에 가까운 데이터 크기 처리 가능 • 재처리를 위해 데이터가 영구 저장됨 | • 디스크 I/O 때문에 가장 느린 방법 • 임시 디스크 공간 필요 • 코드가 더 복잡함 |
요약
다음에 $results = … 를 입력하려 할 때, 잠시 멈추고 스스로에게 물어보세요:
“이 명령이 반환할 수 있는 항목이 몇 개일까요?”
답이 “모르겠다” 혹은 “많다” 라면, 자신과 서버를 위해 변수를 버리고 파이프라인을 활용하세요. 금요일 오후 3시에 호출되지 않을 당신의 미래의 자신이 고마워할 것입니다.
👉 [TechResolve.blog]에서 원본 기사 읽기
☕ 내 작업을 지원해 주세요
이 글이 도움이 되었다면, 커피 한 잔 사주세요:
👉