/img/avatar.jpg

Flutter 설치

개요

flutter를 설치하는 걸 어려워 하시는 분들이 적잖이 계신 것같아 작성합니다.

OS 별로 설치 방법

Windows

Script를 이용해서

Written by Gemini.

PowerShell 스크립트입니다.
아무 곳이나 get_flutter.ps1같은 파일을 만들어 아래 스크립트를 붙여넣기 하여 저장하고 실행합니다.
그럼 버전을 입력해야하는데, flutter 아카이브에서 확인 후 적절한 버전을 입력하면 됩니다.

# PowerShell 스크립트: Flutter SDK 다운로드

# --- 설정 ---
# Flutter 다운로드 URL의 기본 구조
$flutterBaseUrl = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_"
$flutterUrlSuffix = "-stable.zip"

# --- 사용자 입력 받기 ---
# 사용자에게 다운로드할 Flutter 버전 번호 요청 (예: 3.29.2)
# 사용자가 입력할 때까지 스크립트가 여기서 대기합니다.
$flutterVersionInput = Read-Host -Prompt "다운로드할 Flutter 버전을 입력하세요 (예: 3.29.2). 정확한 버전 번호는 Flutter 웹사이트에서 확인하세요"

# 입력값 유효성 검사 (간단히 비어있는지만 확인)
if ([string]::IsNullOrWhiteSpace($flutterVersionInput)) {
    Write-Error "버전 번호를 입력해야 합니다. 스크립트를 종료합니다."
    # 스크립트 비정상 종료 (오류 코드 1)
    exit 1
}

# 입력받은 버전으로 전체 다운로드 URL 구성
# 사용자가 '3.19.6'을 입력하면, URL은 ".../flutter_windows_3.19.6-stable.zip" 형태가 됩니다.
$flutterZipUrl = "$($flutterBaseUrl)$($flutterVersionInput)$($flutterUrlSuffix)"

# 다운로드 받을 임시 파일 경로 설정
$tempDir = $env:TEMP
# 파일 이름에도 버전을 포함시켜 구분 용이하게 함 (선택 사항)
$downloadFileName = "flutter_sdk_$($flutterVersionInput).zip"
$tempDownloadPath = Join-Path $tempDir $downloadFileName

# 압축 해제 대상 폴더 (사용자 홈 폴더)
$extractDestination = $HOME

# --- 스크립트 실행 ---
Write-Host "Flutter SDK v$($flutterVersionInput) 다운로드 및 설치를 시작합니다..." -ForegroundColor Cyan

# 1. Flutter SDK 다운로드
Write-Host "Flutter SDK 다운로드 중... ($flutterZipUrl)"
Write-Host "임시 저장 경로: $tempDownloadPath"
try {
    # Invoke-WebRequest를 사용하여 파일 다운로드
    Invoke-WebRequest -Uri $flutterZipUrl -OutFile $tempDownloadPath -ErrorAction Stop
    Write-Host "다운로드 완료." -ForegroundColor Green
} catch {
    # 오류 발생 시, 입력한 버전이나 URL이 잘못되었을 가능성을 명시
    Write-Error "Flutter SDK v$($flutterVersionInput) 다운로드 실패: $($_.Exception.Message)"
    Write-Error "입력한 버전 번호('$flutterVersionInput')가 정확한지, 해당 버전이 stable 채널에 존재하는지 확인하세요."
    Write-Error "시도한 URL: $flutterZipUrl"
    # 실패 시 임시 파일 삭제 (존재하는 경우)
    if (Test-Path $tempDownloadPath) {
        Remove-Item -Path $tempDownloadPath -Force -ErrorAction SilentlyContinue
    }
    # 스크립트 중단
    exit 1
}

# 2. Flutter SDK 압축 해제
Write-Host "Flutter SDK 압축 해제 중... ($extractDestination)"
try {
    # Expand-Archive를 사용하여 zip 파일 압축 해제
    Expand-Archive -Path $tempDownloadPath -DestinationPath $extractDestination -Force -ErrorAction Stop
    Write-Host "압축 해제 완료." -ForegroundColor Green

    # 압축 해제 확인
    $expectedFlutterPath = Join-Path $extractDestination "flutter"
    if (Test-Path $expectedFlutterPath) {
        Write-Host "Flutter SDK가 '$expectedFlutterPath' 경로에 성공적으로 압축 해제되었습니다."
    } else {
        Write-Warning "'$expectedFlutterPath' 경로를 찾을 수 없습니다. 압축 해제 결과를 확인하세요."
    }

} catch {
    Write-Error "Flutter SDK 압축 해제 실패: $($_.Exception.Message)"
    # 압축 해제 실패 시에도 임시 파일 삭제
    if (Test-Path $tempDownloadPath) {
        Write-Host "압축 해제 오류 발생. 임시 파일 삭제 중: $tempDownloadPath"
        Remove-Item -Path $tempDownloadPath -Force -ErrorAction SilentlyContinue
    }
    # 스크립트 중단
    exit 1
}

# 3. 임시 다운로드 파일 삭제
Write-Host "임시 다운로드 파일 삭제 중... ($tempDownloadPath)"
try {
    if (Test-Path $tempDownloadPath) {
        Remove-Item -Path $tempDownloadPath -Force -ErrorAction Stop
        Write-Host "임시 파일 삭제 완료." -ForegroundColor Green
    } else {
        Write-Host "임시 파일이 이미 삭제되었거나 존재하지 않습니다."
    }
} catch {
    Write-Warning "임시 파일 '$tempDownloadPath' 삭제 실패: $($_.Exception.Message)"
}

# 추가할 경로 정의
$flutterBinPathToAdd = Join-Path $HOME "flutter\bin"
$variableName = "Path"
$variableScope = "User" # 사용자 범위 지정

# 현재 사용자 PATH 값 가져오기
$currentUserPath = [System.Environment]::GetEnvironmentVariable($variableName, $variableScope)

# 경로가 이미 포함되어 있는지 확인 (중복 추가 방지)
if ($currentUserPath -split ';' -notcontains $flutterBinPathToAdd) {
    # 기존 PATH 끝에 세미콜론(;)과 새 경로 추가
    # 기존 PATH가 비어있거나 세미콜론으로 끝나지 않는 경우 고려
    $newUserPath = ($currentUserPath, $flutterBinPathToAdd) -join ';' -replace ';{2,}', ';' # 여러 개의 세미콜론을 하나로 정리

    # 새 PATH 값 설정
    [System.Environment]::SetEnvironmentVariable($variableName, $newUserPath, $variableScope)

    Write-Host "사용자 환경 변수 '$variableName'에 '$flutterBinPathToAdd' 경로를 추가했습니다." -ForegroundColor Green
    Write-Host "변경 사항을 적용하려면 새 PowerShell 창을 열거나 로그아웃 후 다시 로그인하세요."
} else {
    Write-Host "'$flutterBinPathToAdd' 경로는 이미 사용자 '$variableName' 환경 변수에 존재합니다." -ForegroundColor Yellow
}

# --- 완료 메시지 및 다음 단계 안내 ---
Write-Host "--------------------------------------------------" -ForegroundColor Cyan
Write-Host "Flutter SDK v$($flutterVersionInput) 설치 스크립트가 완료되었습니다." -ForegroundColor Cyan
Write-Host "다음 단계를 진행하세요:"
Write-Host "- 새 PowerShell 창을 열고 'flutter doctor' 명령어를 실행하여 설치 상태를 확인하세요."
Write-Host "--------------------------------------------------" -ForegroundColor Cyan

scoop을 이용해서

Scoop은 windows에서 사용할 수 있는 패키지 매니저입니다. PowerShell에서 편하게 쓸 수 있습니다.

요청 수를 줄이고, 캐시 수용량은 늘리기

valkey?

여전히 valkey보다 redis가 더 익숙한 분들이 계실 겁니다. valkey는 Redis Inc.의 redis에 대한 라이센스 변경 및 독점적 지위 확보 시도에 대한 반발로 redis 7.2.4를 베이스로 추가 개발되고 있는 레디스 대체제입니다. 현재는 레디스보다도 먼저 8.0 버전을 출시하는 등 굉장히 활발하게 개발 및 유지보수 되고 있습니다.

당연한 이야기로 레디스의 포크인 만큼 레디스와 동일하게 다양한 곳에서 캐시로써 활용되고 있습니다. 이 글에서는 그 중 가장 보편적인 흐름 중 하나인 유저가 API 서버에 질의 후, API 서버는 캐시를 조회, 실패 시 RDB에서 조회하는 방식을 사용하는 패턴에서 효과적으로 밸키에 대한 읽기를 감소 시키는 방법을 소개하고자 합니다.

강한 일관성과 최종 일관성

잠재적인 데이터 불일치 문제

MSA는 개발 및 배포의 유연성, 기술 스택의 다양성, 확장성, 장애 격리 등 다양한 장점을 제공하지만, 그로 인해 트레이드 오프되는 단점들도 분명히 존재합니다. 그 대표적인 부분이 비즈니스 로직 오류, 사용자 경험 저하, 심각한 경우 금전적 손실까지 야기할 수 있는 중요한 문제인 잠재적인 데이터 불일치 문제일 겁니다. 이 문제는 다양한 이유로 발생할 수 있지만, 바로 생각나는 이유로는 다음과 같은 것들이 있을 겁니다.

  • 각 서비스 마다 독립적인 데이터베이스를 사용한다거나
  • EDA를 기반으로 요청과 처리를 해서 트랜잭션 처리가 어렵다거나
  • 네트워크 불안정으로 인해 흐름이 순간적으로 끊긴다거나
  • 특정 서비스의 장애로 트랜잭션 처리가 지연되거나 취소된다거나

결국 요지는 기존의 단일 데이터베이스에서 하던 트랜잭션을, 여러 서비스가 서로 다른 목적을 위해 각자의 데이터베이스에서 독립적으로 수행하다보니 연결이 느슨해지고, 그 틈새에서 발생할 수 있는 다양한 이슈로 인해 서비스 간 데이터 상태 동기화에 문제가 생길 수 있다는 것입니다.

로그 스트리밍으로 서비스 구성하기

배경

로그도 남기고, 이벤트도 남겨?

서비스를 쭉 구성하다보니 이런 생각을 했습니다.

  1. 어차피 로그는 남긴다.
  2. 필요한 유저 행동에 대해 이벤트르 발행한다.
  3. 그러면 각 서비스가 로그에서 필요한 걸 가져가서 상태 변화를 기록하면 되는 것 아닐까?
  4. 뭣하러 굳이 이벤트를 별도로 발행하지? 어차피 모두 kafka같은 메시지 브로커를 탈텐데?

왜 굳이 2개를 중복으로 메시지 브로커에게 남겨야 하는가가 매우 큰 궁금증이었습니다.
기본적으로 어떤 메시지 브로커를 선택하든 간에, 아니 가장 대표적인 Kafka를 상정하고 얘기해도, 각 컨슈머 그룹에 따라 메시지를 복제해서 전달하고, 컨슈머 그룹 내에 여러 컨슈머가 있을 경우에 적절히 분배해주는 기능이 존재합니다.
MSA를 구성하든, MMA를 구성하든 간에 사실 필요한 로그를 누락 없이 받아서 처리할 수 있을 것입니다.
처리량이 많다고요? 노드를 늘리고, 스펙을 올리죠!

PGO를 쉽게 하는 방법이 있을까?

개요

왜 사람들은 PGO(Profile Guided Optimization)을 적극적으로 사용하지 않을까?

저는 이 의문을 예전부터 품고 있었습니다. 생각해보니 프로파일을 저장하고, 가져오는 어떠한 표준화된 프로토콜이 없다는 게 이유로 보였습니다. 사실 쓸 사람들은 어떻게든 쓰고 있겠지만, 저도 처음 PGO를 적용할 때에 어떻게 저장하고 가져와야 할지 고민을 좀 했었습니다. 그래서 이번 글에선 해당 내용에 대한 공유를 하겠습니다.

설계 및 구현

전형적인 읽는 사람 따로, 쓰는 사람 따로인 구조

PGO 특성 상, 프로파일을 주기적으로 생성하고 업로드하는 실제 서비스로 올라간 어플리케이션과, 해당 프로파일들을 받아서 하나로 합치고 빌드할 때 적용하는 빌드 어플리케이션이 있습니다. 두 과정이 철저하게 분리되어 있기에 동기적으로 사고할 필요가 없습니다. 그럼 가장 적합한 구조는 중간에 버퍼나 저장소를 두고 계속 데이터를 추가, 필요할 때 다운로드, 주기적으로 데이터를 삭제하는 과정만 있으면 됩니다.

대는 소를 포함한다

?

대는 소를 포함한다는 말은 법률상으로 상위 개념과 하위 개념이 존재할 경우, 상위 개념에 대한 법을 하위 개념에도 적용하는 것입니다. 쉽게 이야기하면, 법률적으로는 자전거가 자동차의 하위 개념이니 자동차에 적용하는 다양한 법을 자전거에도 같이 적용하는 것입니다. 하지만 제가 법률 전문가도 아니고, 법과 관련된 이야기를 하고 싶은 건 아닙니다.

소프트웨어 설계에서

범용은 전용을 포함한다

저희는 때때로 범용으로 무언가를 만들고, 전용으로 사용하는 방법을 취합니다. 대표적인 예로 postgres는 많은 데이터를 저장하고 쿼리할 수 있지만, 저희는 스키마를 한정해서 한정적인 데이터 형태를 저장하고 활용합니다. MongoDB같은 경우엔 그렇지 않다구요? 하지만 높은 확률로 로직이나 환경적으로 들어갈 자료, 해야할 쿼리는 정해져 있을 겁니다. 그게 정녕 비정형 JSON을 저장하기 위한 data lake라 할 지라도요. 그리고 그를 위한 별도 모듈이 존재할 것입니다.

빠르게 메시지를 전파하고 싶어

배경

NATS는 정말 아름다워

NATS는 Go 언어로 작성된 메시지큐입니다. 그리고 제가 개인적으로 좋아하는 오픈소스 프로젝트이기도 합니다. 한때 NATS에서 서브스크립션이 메시지를 받는 방식이 단순 채널이 아닌 독특한 자료구조가 따로 있는 걸 봤었던 적이 있습니다. 이 자료구조는 연결리스트를 통해 특정 서브스크립션에 전달될 메시지를 저장합니다. 그리고 sync.Cond를 이용하여 별도의 대기 중인 서브스크립션을 위한 고루틴에게 알려줍니다.

  1. 서브스크립션은 생성될 때, 별도 고루틴에서 sync.CondWait() 메서드를 통해 메시지가 추가되길 대기합니다.
  2. 커넥션은 외부 연결을 통해 서브스크립션에 대한 메시지를 받아옵니다.
  3. 커넥션은 해당 서브스크립션의 연결리스트에 메시지를 추가하고, sync.CondSignal() 메서드로 해당 고루틴을 깨웁니다.
  4. 서브스크립션은 연결리스트를 순회해서 메시지를 읽어 처리합니다.

쓰는 사람 따로, 읽는 사람 따로

이 구조는 쓰는 주체가 가지는 상태와 읽는 주체가 가지는 상태가 별도로 존재한다는 것에 의의를 느꼈습니다. 물론 channel도 버퍼를 주게 되면 상태를 따로 가질 수 있습니다만, 기본적으로 내부에서 락을 가지는 구조이며 반쯤 동기식으로 동작하도록 코드가 작성됩니다. 그리고 무엇보다 채널은 팬아웃(fan out)에 적합한 구조가 아닙니다.

소..솔직히 유지보수는 아키텍트의 책임이라고 생각해요...

/img/035/goto_hitori.webp

메락아, 그게 무슨 소리니?

아니 일단 들어봐

서비스가 되었든, 소프트웨어가 되었든, 어떤 프로젝트에 대해 보통 기업은 신기술에 밝거나, 손이 빠르거나, 임원의 말을 잘 듣는 사람에게 첫 스타트를 시키는 경우가 많습니다. 하지만 단순히 그렇게 되어서는 서비스나 소프트웨어의 수명이 줄어들어서, 유지보수 비용의 증가 뿐만 아니라 해당 프로젝트를 대체할 새로운 프로젝트를 해야할 수 있어, 큰 지출로 다시 돌아올 수 있습니다.

그래서?

만약 아키텍트가 시작할 때, 어떠한 철학이나 체계 없이 주먹구구로 프로젝트를 진행하면 어떤 일이 벌어질까요?