2.3 Attribute Sets and Configuration Management
핵심 개념
속성집합(Attribute Sets)은 Nix의 핵심 데이터 구조로, 이름-값 쌍으로 구성된 구조화된 설정을 표현합니다. JSON과 달리 변수, 함수, 조건문을 지원하여 복잡한 환경 설정을 재사용 가능하고 유지보수 가능하게 관리할 수 있습니다.
속성집합 기본 구조
속성집합은 중괄호로 둘러싸인 키-값 쌍의 집합입니다. 각 키는 문자열이며, 값은 모든 Nix 타입이 될 수 있습니다.
# 기본 속성집합 구문
{
name = "bioanalysis";
version = "1.0.0";
language = "python";
dependencies = ["numpy" "pandas" "biopython"];
# 중첩 속성집합
config = {
threads = 8;
memory = "16GB";
gpu = false;
};
# 동적 속성명
"python-version" = "3.8";
"blast-db-path" = "/usr/local/db/blast";
}
속성 접근과 조작:
let
project = {
name = "rnaseq-analysis";
samples = ["ctrl1" "ctrl2" "treat1" "treat2"];
pipeline = {
quality_control = true;
alignment = "star";
quantification = "salmon";
};
};
in {
# 점 표기법으로 접근
project_name = project.name; # "rnaseq-analysis"
aligner = project.pipeline.alignment; # "star"
# 동적 접근
first_sample = builtins.elemAt project.samples 0; # "ctrl1"
# 속성 존재 확인
has_gpu = project ? gpu; # false (속성 없음)
has_name = project ? name; # true
}
속성집합 연산과 조합
속성집합은 다양한 연산을 통해 조합하고 변형할 수 있습니다. 이는 설정 상속과 확장에 매우 유용합니다.
let
baseConfig = {
python = "3.8";
memory = "8GB";
timeout = 3600;
packages = ["numpy" "pandas"];
};
# 속성집합 병합 (오른쪽이 우선)
mlConfig = baseConfig // {
memory = "16GB"; # 기존 값 오버라이드
gpu = true; # 새 속성 추가
packages = baseConfig.packages ++ ["tensorflow" "pytorch"]; # 리스트 확장
};
# 조건부 속성 추가
productionConfig = baseConfig // (
if useGPU then { gpu = true; gpu_memory = "8GB"; } else {}
);
# 속성 제거
lightConfig = removeAttrs baseConfig ["timeout"];
in {
inherit baseConfig mlConfig productionConfig lightConfig;
}
고급 속성집합 조작:
let
# 속성 이름 동적 생성
mkSampleConfig = sampleId: condition: {
"sample_${sampleId}" = {
id = sampleId;
condition = condition;
replicate = if condition == "control" then 1 else 2;
};
};
# 여러 속성집합 병합
allSamples =
mkSampleConfig "001" "control" //
mkSampleConfig "002" "treatment" //
mkSampleConfig "003" "control";
# 속성 필터링과 변환
controlSamples = builtins.listToAttrs (
builtins.filter (item: item.value.condition == "control")
(builtins.map (name: {
name = name;
value = allSamples.${name};
}) (builtins.attrNames allSamples))
);
in {
inherit allSamples controlSamples;
}
JSON vs Nix 속성집합 비교
정적 JSON 설정의 한계와 Nix 속성집합의 장점을 구체적으로 살펴보겠습니다.
JSON 설정의 제약사항:
{
"projects": {
"rnaseq_project": {
"python": "3.8",
"packages": ["pandas", "numpy", "deseq2"],
"memory": "16GB",
"samples": 24
},
"chipseq_project": {
"python": "3.8",
"packages": ["pandas", "numpy", "macs2"],
"memory": "32GB",
"samples": 12
},
"methylation_project": {
"python": "3.9",
"packages": ["pandas", "numpy", "methylpy"],
"memory": "64GB",
"samples": 48
}
}
}
문제점들:
- Python 3.8, 공통 패키지 중복 정의
- 총 메모리 요구량, 샘플 수 계산 불가능
- 조건부 로직 (GPU 사용 여부) 구현 어려움
- 설정 템플릿 재사용 불가능
Nix 속성집합 해결책:
let
# 공통 설정 추출
commonConfig = {
basePackages = ["pandas" "numpy"];
defaultPython = "3.8";
standardMemory = "16GB";
};
# 프로젝트 생성 함수
mkProject = { python ? commonConfig.defaultPython
, extraPackages ? []
, memoryMultiplier ? 1
, samples
, useGPU ? false
}: {
inherit python samples;
packages = commonConfig.basePackages ++ extraPackages;
memory = "${toString (16 * memoryMultiplier)}GB";
gpu = useGPU;
estimated_runtime = samples * 2; # 동적 계산
};
in {
projects = {
rnaseq = mkProject {
extraPackages = ["deseq2"];
samples = 24;
};
chipseq = mkProject {
extraPackages = ["macs2"];
memoryMultiplier = 2;
samples = 12;
};
methylation = mkProject {
python = "3.9";
extraPackages = ["methylpy"];
memoryMultiplier = 4;
samples = 48;
useGPU = true;
};
};
# 자동 계산된 통계
totalSamples = builtins.foldl' (acc: proj: acc + proj.samples) 0
(builtins.attrValues projects);
totalMemory = builtins.foldl' (acc: proj: acc + (builtins.fromJSON (builtins.substring 0 2 proj.memory))) 0
(builtins.attrValues projects);
}
환경별 설정 관리
실제 연구 환경에서는 개발, 테스트, 프로덕션 환경에 따라 다른 설정이 필요합니다. Nix 속성집합은 이를 체계적으로 관리할 수 있습니다.
let
# 기본 환경 설정
baseEnvironment = {
python = "3.8";
packages = ["numpy" "pandas" "matplotlib"];
threads = 4;
logging = "info";
};
# 환경별 오버라이드
environments = {
development = baseEnvironment // {
packages = baseEnvironment.packages ++ ["ipython" "jupyter"];
logging = "debug";
debug_mode = true;
};
testing = baseEnvironment // {
packages = baseEnvironment.packages ++ ["pytest" "coverage"];
threads = 2;
timeout = 300;
};
production = baseEnvironment // {
threads = 16;
memory_limit = "64GB";
logging = "warning";
optimization = true;
backup_enabled = true;
};
cluster = baseEnvironment // {
threads = 32;
memory_limit = "256GB";
distributed = true;
scheduler = "slurm";
queue = "gpu";
};
};
# 환경 선택 함수
selectEnvironment = envName:
if environments ? ${envName}
then environments.${envName}
else builtins.throw "Unknown environment: ${envName}";
in {
inherit environments;
# 현재 환경 (환경 변수나 매개변수로 제어)
current = selectEnvironment "development";
# 환경 비교
envComparison = builtins.mapAttrs (name: env: {
inherit (env) threads;
package_count = builtins.length env.packages;
has_debug = env ? debug_mode;
}) environments;
}
복잡한 패키지 의존성 관리
생물정보학 도구들은 복잡한 의존성 관계를 가집니다. 속성집합을 통해 이를 체계적으로 관리할 수 있습니다.
let
# 도구별 요구사항 정의
tools = {
blast = {
version = "2.12.0";
dependencies = ["ncbi-blast+"];
memory_per_thread = "2GB";
database_required = true;
};
bwa = {
version = "0.7.17";
dependencies = ["bwa"];
memory_per_thread = "1GB";
index_required = true;
};
star = {
version = "2.7.10";
dependencies = ["star"];
memory_per_thread = "8GB";
index_required = true;
};
salmon = {
version = "1.9.0";
dependencies = ["salmon"];
memory_per_thread = "4GB";
index_required = true;
};
};
# 분석 파이프라인 정의
pipelines = {
dna_alignment = {
tools = ["bwa" "samtools"];
estimated_memory = 32;
parallel_jobs = 4;
};
rna_quantification = {
tools = ["star" "salmon"];
estimated_memory = 64;
parallel_jobs = 2;
};
sequence_search = {
tools = ["blast"];
estimated_memory = 16;
parallel_jobs = 8;
};
};
# 파이프라인별 리소스 계산
calculateResources = pipelineName:
let
pipeline = pipelines.${pipelineName};
toolConfigs = map (toolName: tools.${toolName}) pipeline.tools;
maxMemoryPerThread = builtins.foldl' builtins.max 0
(map (tool: builtins.fromJSON (builtins.substring 0 1 tool.memory_per_thread)) toolConfigs);
in {
total_memory = pipeline.estimated_memory;
memory_per_job = maxMemoryPerThread * pipeline.parallel_jobs;
dependencies = builtins.concatLists (map (tool: tool.dependencies) toolConfigs);
};
in {
inherit tools pipelines;
# 각 파이프라인의 리소스 요구사항
resource_requirements = builtins.mapAttrs (name: _: calculateResources name) pipelines;
}
Practice Section: 속성집합 실습
실습 1: 기본 속성집합 조작
# basic_attrs.nix
let
project = {
name = "genomics";
samples = 10;
organism = "E.coli";
};
in {
# 속성 접근
project_info = "${project.name}: ${project.organism}";
sample_count = project.samples;
# 속성 확장
extended = project // { version = "1.0"; };
}
nix eval -f basic_attrs.nix --json
실습 2: 설정 병합
# config_merge.nix
let
base = { python = "3.8"; memory = "8GB"; };
gpu_config = { gpu = true; memory = "16GB"; };
in {
cpu_env = base;
gpu_env = base // gpu_config;
# 메모리 비교
memory_diff = gpu_config.memory != base.memory;
}
nix eval -f config_merge.nix
실습 3: 동적 속성 생성
# dynamic_attrs.nix
let
samples = ["ctrl" "treat"];
mkSample = name: { "${name}_config" = { id = name; processed = false; }; };
in
builtins.foldl' (acc: sample: acc // mkSample sample) {} samples
nix eval -f dynamic_attrs.nix --json
실습 4: 조건부 설정
# conditional_config.nix
let
useGPU = true;
baseConfig = { threads = 4; memory = "8GB"; };
in
baseConfig // (if useGPU then {
gpu = true;
gpu_memory = "8GB";
threads = 8;
} else {})
nix eval -f conditional_config.nix
실습 5: 환경 설정 함수
# env_function.nix
let
mkEnv = {name, python ? "3.8", packages ? []}:
{
environment_name = name;
python_version = python;
package_list = ["numpy"] ++ packages;
package_count = builtins.length (["numpy"] ++ packages);
};
in {
basic = mkEnv { name = "basic"; };
advanced = mkEnv {
name = "advanced";
python = "3.9";
packages = ["tensorflow" "pytorch"];
};
}
nix eval -f env_function.nix --json
핵심 정리
속성집합 필수 연산
# 기본 조작
attrs = { a = 1; b = 2; };
extended = attrs // { c = 3; }; # 병합
value = attrs.a; # 접근
exists = attrs ? c; # 존재 확인
# 고급 조작
filtered = removeAttrs attrs ["a"]; # 속성 제거
names = builtins.attrNames attrs; # 키 목록
values = builtins.attrValues attrs; # 값 목록
실용적 패턴
# 환경 설정 패턴
baseEnv // (if condition then extraConfig else {})
# 다중 병합 패턴
config1 // config2 // config3
# 동적 속성 패턴
{ "${name}_${version}" = value; }