Apache Hadoop — HDFS 와 YARN 을 한 파드에 세우고 운영한다 · 쿼터 · 이론
공간 쿼터는 쓰기 전에 블록 하나만큼을 미리 잡는다
한 줄 요약
HDFS 쿼터는 둘이다. 이름 쿼터는 디렉터리 아래 파일과 디렉터리의 개수를, 공간 쿼터는 복제본까지 센 바이트를 막는다. 공간 쿼터는 이미 쓴 양이 아니라 새 블록 하나를 끝까지 채울 몫을 보고 막기 때문에, 1바이트 파일이 30MB 쿼터에 걸리는 일이 생긴다.
왜 쿼터가 필요한가
공유 클러스터에는 두 종류의 사고가 있다. 한 팀의 잡이 폭주해 디스크를 가득 채우는 사고, 그리고 작은 파일 수백만 개가 쏟아져 NameNode 가 버티지 못하는 사고다. 앞의 것은 바이트의 문제이고 뒤의 것은 개수의 문제다. 파일 하나, 디렉터리 하나가 모두 NameNode 메모리의 객체라는 것을 앞 모듈에서 봤다. 디스크가 아무리 넉넉해도 이름이 너무 많으면 NameNode 부터 쓰러진다.
그래서 [HDFS 쿼터 안내서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/HdfsQuotaAdminGuide.html)는 두 쿼터를 따로 둔다. 둘은 독립적으로 동작하지만 관리 방식과 구현은 나란하다. 쿼터는 디렉터리에 걸리고, 그 디렉터리를 뿌리로 하는 트리 전체에 적용된다. 설정과 해제는 관리자만 할 수 있다.
어떻게 동작하나
먼저 이름 쿼터다. 이름 쿼터는 트리 안의 파일 이름과 디렉터리 이름의 개수에 대한 딱딱한 상한이다. 넘으면 파일이나 디렉터리 만들기가 실패한다. 안내서에서 눈여겨볼 문장이 있다. 쿼터를 1로 걸면 그 디렉터리는 비어 있을 수밖에 없고, 괄호 안에 디렉터리는 자기 자신도 쿼터에 센다고 적어 두었다. 그래서 쿼터 10 인 디렉터리에는 파일을 9개까지만 넣을 수 있다.
몇 가지 성질이 더 있다. 쿼터는 이름을 바꿔도 디렉터리를 따라가고, 이름 바꾸기가 쿼터를 넘게 만들면 그 이름 바꾸기가 실패한다. 이미 넘은 상태의 디렉터리에도 쿼터 설정 자체는 성공한다. 새로 만든 디렉터리에는 쿼터가 없다. 넘었을 때 클라이언트가 받는 예외는 NSQuotaExceededException 이다.
공간 쿼터는 미리 잡는다
공간 쿼터는 트리 안 파일들이 쓰는 바이트 수에 대한 상한이고, 블록의 복제본 하나하나가 쿼터에 들어간다. 안내서의 예대로 복제 계수 3 인 1GB 자료는 쿼터 3GB 를 먹는다. 디렉터리 자체는 공간을 쓰지 않고, 메타데이터가 쓰는 공간도 세지 않는다. 복제 계수를 바꾸면 쿼터가 그만큼 늘고 준다.
핵심은 이 문장이다. 쿼터가 블록 하나를 가득 쓸 여유를 허락하지 않으면 블록 할당이 실패한다. NameNode 는 파일이 실제로 몇 바이트를 쓸지 미리 알 수 없다. 블록을 내준 다음 클라이언트가 그 블록을 끝까지 채울 수도 있다. 그래서 새 블록을 내주는 순간 "블록 크기 × 복제 계수" 만큼이 남아 있는지를 본다.
왜 이렇게 보수적으로 잡을까. 블록은 파이프라인을 따라 흘러가는 동안에는 최종 크기가 정해지지 않는다. 쓰는 도중에 쿼터를 넘었다고 멈추면 반쯤 쓴 블록과 반쯤 만든 파일이 남는다. 블록을 내주기 전에 최악의 경우를 확인해 두면 거절은 언제나 바이트가 흐르기 전, 블록 경계에서 일어난다. 대신 치르는 값이 있다. 작은 파일도 큰 여유를 요구하고, 여러 파일을 동시에 쓰면 열려 있는 블록마다 그 여유가 따로 필요하다. 남은 쿼터가 넉넉해 보여도 동시에 쓰는 작업이 많으면 갑자기 막히는 이유다.
[hdfs-default.xml](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml) 의 dfs.blocksize 는 기본 134217728 바이트(128MB)다. 복제 계수가 1 이어도 30MB 쿼터 디렉터리에는 128MB 짜리 여유가 없으므로, 내용이 1바이트뿐인 파일도 첫 블록을 받지 못하고 DSQuotaExceededException 으로 실패한다. 이 파일만 블록 크기를 4MB 로 줄이면 필요한 여유가 4MB 가 되어 통과한다. 파일을 닫은 뒤 쿼터에 남는 사용량은 실제로 쓴 바이트에 복제 계수를 곱한 값이다. 미리 잡는 것은 쓰는 동안뿐이다.
hdfs dfsadmin -setSpaceQuota 30m /team/etlhdfs dfs -put one-byte.txt /team/etl/ # DSQuotaExceededExceptionhdfs dfs -D dfs.blocksize=4m -put one-byte.txt /team/etl/ # 통과hadoop fs -count -q -v /team/etl블록 크기를 줄이는 데에도 바닥이 있다. dfs.namenode.fs-limits.min-block-size 가 기본 1MB 이므로 그보다 작게는 만들 수 없다. 극단의 경우도 안내서에 있다. 공간 쿼터 0 은 파일 만들기는 허락하지만 그 파일에 블록은 하나도 붙일 수 없다. 빈 파일만 만들 수 있다는 뜻이다.
쿼터를 읽는 법
[파일 시스템 셸](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-common/FileSystemShell.html)의 count -q 는 여덟 열을 낸다. 순서는 QUOTA, REMAINING_QUOTA, SPACE_QUOTA, REMAINING_SPACE_QUOTA, DIR_COUNT, FILE_COUNT, CONTENT_SIZE, PATHNAME 이다. 쿼터가 없으면 쿼터 칸은 none, 남은 칸은 inf 로 나온다. 여기서 CONTENT_SIZE 는 복제 전 크기이고, 남은 공간 쿼터는 복제본까지 센 값에서 줄어든다. 두 숫자를 섞어 계산하면 복제 계수만큼 어긋난다. -h 를 붙이면 사람이 읽기 좋은 단위로, -v 를 붙이면 머리줄과 함께 나온다.
쿼터는 fsimage 에 저장되어 재시작해도 남고, 설정하거나 지울 때마다 편집 로그에 기록된다. 해제는 -clrQuota 와 -clrSpaceQuota 다. 저장 매체 종류(SSD·DISK 등)마다 따로 거는 쿼터도 있지만 저장 정책을 쓰는 디렉터리에서만 뜻이 있다.
현장에서 만나는 모습
첫째, "쿼터가 한참 남았는데 파일 하나를 못 쓴다." 남은 공간 쿼터가 블록 크기 × 복제 계수보다 작으면 그렇다. 128MB 블록에 복제 3 이면 남은 쿼터가 384MB 미만인 순간부터 새 블록을 못 받는다. 쿼터를 정할 때는 이 여유를 셈에 넣는다.
둘째, 작은 파일이 잔뜩 있는 팀에는 이름 쿼터가 더 급하다. 공간 쿼터만 걸면 1KB 파일 백만 개가 문제없이 들어온다. NameNode 를 지키는 것은 이름 쿼터다.
셋째, 휴지통도 쿼터를 먹는다. [설계 문서](https://hadoop.apache.org/docs/r3.5.0/hadoop-project-dist/hadoop-hdfs/HdfsDesign.html)대로 사용자마다 휴지통은 홈 디렉터리 아래 .Trash 에 있으므로, 홈 디렉터리에 쿼터를 걸면 지운 파일이 휴지통에 있는 동안 계속 그 쿼터를 쓴다. 셸 문서가 -skipTrash 를 쿼터를 넘은 디렉터리에서 파일을 지워야 할 때 쓸모 있다고 설명하는 이유다.
넷째, 쿼터는 이름 바꾸기도 막는다. 큰 디렉터리를 쿼터가 걸린 곳으로 옮기려다 실패하는 것은 정상 동작이다.
다섯째, 지웠는데도 숫자가 그대로다. 셸 문서에 따르면 count 는 -x 를 주지 않는 한 그 경로 아래 스냅샷에 남은 항목까지 모두 세어 결과를 낸다. 스냅샷이 붙잡고 있는 파일은 현재 트리에서 지워도 집계에서 바로 빠지지 않는다. 스냅샷은 다음 모듈에서 다룬다.
실무에서 진짜 중요한 것
- 이름 쿼터는 개수, 공간 쿼터는 복제본까지 센 바이트다. 두 쿼터는 따로 동작한다.
- 디렉터리는 자기 자신도 이름 쿼터에 센다. 쿼터 N 이면 자식은 N−1 개다.
- 공간 쿼터는 블록 크기 × 복제 계수만큼의 여유를 요구한다. 쓰는 동안만 미리 잡고, 닫으면 실제 크기로 돌아온다.
count -q의 CONTENT_SIZE 는 복제 전, 남은 공간은 복제 후다.- 작은 파일이 많은 곳에는 이름 쿼터부터 건다. NameNode 를 지키는 것은 개수 제한이다.
다음 실습에서 할 것
디렉터리에 이름 쿼터 10 을 걸고 작은 파일을 하나씩 올려 몇 번째에서 막히는지 세어, 디렉터리 자신이 한 칸을 먹는다는 것을 확인한다. 이어서 공간 쿼터 30MB 를 걸고 1바이트 파일이 기본 블록 크기의 예약 때문에 거절되는 오류를 재현한 뒤, 같은 파일을 블록 크기 1MiB 로 올려 들어가는 것을 본다. count -q 의 열을 읽어 두 디렉터리의 남은 쿼터를 해석하고, 쿼터가 없는 디렉터리의 파일을 이미 가득 찬 디렉터리로 옮기려다 이름 바꾸기까지 거절되는 것을 확인한다. 보고서에는 실제로 들어간 파일 수와 1바이트 파일이 예약하려던 바이트 수를 숫자로 적는다.