#keywords 설치,구축,배포판,서버,ZRAM,swap,압축,스왑,mkswap,swapon,oom,kill,LZO,메모리,부족,fadvise,posix_fadvise,cached,free,compress #title ZRAM (압축메모리) 를 이용한 SWAP 확보 방법 [wiki:Home 대문] / [wiki:CategoryInstall 설치 및 구축] / [wiki:swap_space_using_ZRAM ZRAM (압축메모리) 를 이용한 SWAP 확보 방법] ---- == [wiki:swap_space_using_ZRAM ZRAM (압축메모리) 를 이용한 SWAP 확보 방법] == * 작성자 조재혁([mailto:minzkn@minzkn.com]) * 고친과정 2019년 12월 27일 : 처음씀 [[TableOfContents]] === 개요 === 제한적인 물리적 메모리를 응답성 및 가용성을 최대한 해치지 않고 최대의 활용 방법으로 swap을 압축 메모리로 활용하는 방법을 설명합니다. 부족한 메모리는 곧 서비스 중지라는 사태(oom kill)를 야기하므로 적어도 이 방법은 그에 대한 방어책을 구사할 수 있습니다. ZRAM 이란? * 일종의 swap 이지만 Disk I/O 를 일으키지 않고 일정 조건에 의해서 swap 할 메모리를 우선 압축하도록 하는 기능. * 메모리상에 존재하는 데이터들은 상당히 압축률이 높은 특성을 갖는다는 전재를 깔고 도입되는 사항이며 * 압축 알고리즘은 빠르다고 알려진 LZO (zip 에서 사용하는) 등을 많이 사용. * 이게 생각보다 메모리를 많이 아낄 수 있고 성능손실이 크지 않다고 알려져 있어서 대부분의 임베디드 시스템에는 (특히 NAS, 안드로이드) 많이 도입되는 기능입니다. * 메모리가 작은 모델의 NAS 또는 안드로이드 스마트폰 H/W spec 을 갖는다 하여도 유명 벤더사의 제품들이 더 빠릿한 느낌을 주는 것은 이 기능이 큰 영향을 갖기 때문입니다. === ZRAM 활성화 방법 === 1. Kernel 의 "make menuconfig" 에서 다음의 항목을 활성화(y) 해주고 빌드하여 커널 이미지를 반영 * "Device Drivers" -> "Staging drivers" -> "Compressed RAM block device support" * "Device Drivers" -> "Staging drivers" -> "Dynamic compression of swap pages and clean pagecache pages" * "Device Drivers" -> "Staging drivers" -> "Memory allocator for compressed pages" 1. 부팅 스크립트 초기에 다음과 같은 맥락의 swap 활성화 명령이 실행되도록 합니다. {{{#!enscript sh # ZRAM SWAP ON - 2GBytes mknod -m 600 /dev/zram0 b 251 0 echo 2048M > /sys/block/zram0/disksize mkswap /dev/zram0 swapon -p 100 /dev/zram0 ## DISK SWAP ON - 1GBytes #dd if=/dev/zero of=/swap.bin bs=1024M count=1 #mkswap /swap.bin #swapon -p 0 /swap.bin }}} * => 주1) mknod 에서 major 번호는 커널에 따라 다를 수 있음. 때문에 "cat /proc/devices" 에서 zram 으로 표기되는 block device 의 major 번호를 확인하는게 필수입니다. * 위 예제 명령에서 major 번호를 251로 하였으나 이는 시스템에 따라서 다를 수 있음에 유의하시고 그에 맞도록 major 번호를 사용하세요. * => 주2) zram0 의 크기 결정은 통상 전체 물리 메모리의 20%~80% 정도 잡는것을 기준으로 자신의 시스템에 따른 특성을 고려하여 잡으세요. * => 주3) swapon 의 -p 옵션으로 지정하는 우선순위는 큰 값이 우선 사용됩니다. * => 주4) /dev/zram0 는 ramdisk가 사용하는 /dev/ram0 와 흡사한 block device 의 일종으로 압축유지상태라는 것뿐이며 ramdisk영역과 매우 흡사한 동작특성을 갖습니다. 1. 재부팅하면 다음 명령으로 ZRAM 이 swap 으로 잡혀있는지 확인 가능합니다. {{{#!enscript sh # cat /proc/swaps Filename Type Size Used Priority /dev/zram0 partition 2097148 0 100 <= ZRAM swap /swap.bin file 1048572 0 0 <= Disk swap # cat /proc/meminfo ... SwapTotal: 3145720 kB SwapFree: 3145720 kB ... }}} 1. 그 밖에... * '''Ubuntu 배포판 사용자의 경우 (하기와 같이 설치 및 활성화만하면 적당한 ZRAM이 기본적으로 활성화 됩니다. 추가적인 조치 필요 없음.)''' {{{#!plain $ sudo apt install zram-config ... $ sudo systemctl enable zram-config.service ... $ sudo systemctl start zram-config.service ... => 라즈베리파이에서 Ubuntu 사용중인 경우 다음과 같이 추가 모듈을 설치해야 합니다. $ sudo apt install linux-modules-extra-raspi }}} * sysctl 의 "vm.swappiness" 값을 조정하여 swap 비율을 결정할 수 있습니다. * 기본값은 60 * 이 경우 메모리 사용률이 80% 를 넘어섰을 때부터 swap되는 조건이 됨. {{{#!plain swap_tendency = mapped_ratio / 2 + distress + swappiness = mapped_ratio / 2 + (100 / (2 ^ priority)) + swappiness = 80% / 2 + (100 / 2 ^ (12 ~ 0)) + 60 = 80% / 2 + (0 ~ 100) + 60 = 40 + (0 ~ 100) + 60 = 100 ~ 200 }}} * 80 으로 설정하는 경우 * 이 경우 메모리 사용률이 40% 를 넘어섰을 때부터 swap되는 조건이 됨. {{{#!plain swap_tendency = mapped_ratio / 2 + distress + swappiness = mapped_ratio / 2 + (100 / (2 ^ priority)) + swappiness = 40% / 2 + (100 / 2 ^ (12 ~ 0)) + 80 = 40% / 2 + (0 ~ 100) + 80 = 20 + (0 ~ 100) + 80 = 100 ~ 200 }}} * 90 으로 설정하는 경우 * 이 경우 메모리 사용률이 20% 를 넘어섰을 때부터 swap되는 조건이 됨. {{{#!plain swap_tendency = mapped_ratio / 2 + distress + swappiness = mapped_ratio / 2 + (100 / (2 ^ priority)) + swappiness = 20% / 2 + (100 / 2 ^ (12 ~ 0)) + 90 = 20% / 2 + (0 ~ 100) + 90 = 10 + (0 ~ 100) + 90 = 100 ~ 200 }}} * 일부 검색정보에서 10~20 정도가 적당하다는 의견 있음. (이것은 상황에 따라서 판단해야 하므로 참고만. 사실상 50 이하는 무의미하다고 보임. 50~100 사이에서 결정하는게 맞다고 보임.) * 참고로 필자는 swap을 사용하지 않을 때 평시 메모리 사용률이 80%를 넘는 장비들은 55를 사용하고 그 외에는 60 기본 값을 그대로 사용 중. * Kernel 에서 Critical 한 할당 (GFP_ATOMIC 등) 을 대량으로 수행하는 환경인 경우 50 이하로 설정하는 것은 바람직하지 못함. 하지만 GFP_ATOMIC 등의 커널 할당이 많지 않는 경우는 최대한 swap을 보류하는 차원에서 의미 있을 수 있음. * 50 이하로 설정한다는 것은 priority 가 12에서 0으로 줄어드는 단계에서 swap 조건이 발생하게 됨. * distress 는 "distress = 100 / (2 ^ priority)"와 같이 계산되며 swap_tendency 가 100이하로 떨어질때까지 priority 가 12에서 0으로 줄어들면서 distress가 0부터 100까지 단계적으로 재시도하게 됨. * 적은 값으로 설정할 수록 swap의 기회가 줄어들고 큰 값으로 설정할 수록 swap의 기회가 늘어남. * [^http://blog.naver.com/seuis398/70128624124 참고링크 - 리눅스 스왑 메모리 사용 제어] * 다음과 같이 script 를 만들어 swap 을 활성/비활성하도록 구현할 수 있습니다. {{{#!enscript sh #!/bin/sh ### ### Copyright (C) MINZKN.COM ### All rights reserved. ### Author: JAEHYUK CHO ### def_path_proc_devices="/proc/devices" def_path_dev_zram_name="zram0" def_path_dev_zram="/dev/${def_path_dev_zram_name}" def_path_sys_block_zram_size="/sys/block/${def_path_dev_zram_name}/disksize" def_path_sys_block_zram_reset="/sys/block/${def_path_dev_zram_name}/reset" def_zram_size="2048M" def_zram_swap_priority=100 def_zram_major=$(awk '$2 == "zram" {print $1}' "${def_path_proc_devices}") if [ -z "${def_zram_major}" ] then echo "not supported zram." exit 1 fi hwport_zram_usage() { echo "usage: ${0} " } hwport_zram_enabled_check() { if grep "^${def_path_dev_zram}\s.*$" "/proc/swaps" > /dev/null then return 0 else return 1 fi } hwport_zram_make_block_node() { if [ ! -b "${def_path_dev_zram}" ] then echo "creating node : ${def_path_dev_zram}" rm -f "${def_path_dev_zram}" > /dev/null 2>&1 mknod -m 0600 "${def_path_dev_zram}" b "${def_zram_major}" 0 return ${?} fi return 0 } hwport_zram_enable() { hwport_zram_make_block_node if [ "${?}" != "0" ] then echo "make zram block node failed !" return 1 fi echo 1 > "${def_path_sys_block_zram_reset}" if [ "${?}" != "0" ] then echo "reset zram size failed ! (try zram_size is ${def_zram_size})" return 1 fi echo "${def_zram_size}" > "${def_path_sys_block_zram_size}" if [ "${?}" != "0" ] then echo "setup zram size failed ! (try zram_size is ${def_zram_size})" return 1 fi mkswap "${def_path_dev_zram}" > /dev/null if [ "${?}" != "0" ] then echo "mkswap failed !" return 1 fi swapon -p "${def_zram_swap_priority}" "${def_path_dev_zram}" > /dev/null if [ "${?}" != "0" ] then echo "swapon failed ! (try priority is ${def_zram_swap_priority})" return 1 fi echo "enabled (size=${def_zram_size}, priority=${def_zram_swap_priority}, device=${def_path_dev_zram})" return 0 } hwport_zram_disable() { hwport_zram_make_block_node if [ "${?}" != "0" ] then echo "make zram block node failed !" return 1 fi swapoff "${def_path_dev_zram}" > /dev/null if [ "${?}" != "0" ] then echo "swapoff failed !" return 1 fi echo 1 > "${def_path_sys_block_zram_reset}" if [ "${?}" != "0" ] then echo "reset zram size failed ! (try zram_size is 0)" fi echo "4K" > "${def_path_sys_block_zram_size}" echo "disabled (device=${def_path_dev_zram})" return 0 } case "${1}" in "enable"|"start") hwport_zram_enabled_check if [ "${?}" == "0" ] then echo "zram swap already enabled" exit 1 fi hwport_zram_enable if [ "${?}" != "0" ] then echo "zram enable failed !" exit 1 fi ;; "disable"|"stop") hwport_zram_enabled_check if [ "${?}" != "0" ] then echo "zram swap already disabled" exit 1 fi hwport_zram_disable if [ "${?}" != "0" ] then echo "zram disable failed !" exit 1 fi ;; "status"|"state"|"stat") hwport_zram_enabled_check if [ "${?}" != "0" ] then echo "zram swap disabled" else echo "zram swap enabled" fi ;; *) hwport_zram_usage ;; esac # End of hwport_zram_ctrl.sh }}} === 참고자료 === * [^https://www.kernel.org/doc/Documentation/blockdev/zram.txt] * [^https://os.korea.ac.kr/publication_papers/domestic_confer/2013_spring_kcc_im.pdf] * [^http://bahndal.egloos.com/532783] * [^https://muritzy.tistory.com/1653https://github.com/StuartIanNaylor/zram-config] * [^https://github.com/StuartIanNaylor/zram-config] * swap/zram 관련 sysctrl 제어 값은 다음의 값에 의해서 동작조건에 영향을 받습니다. * vm.swappiness = 60 * [^http://blog.naver.com/seuis398/70128624124] * 요점 {{{#!plain 리눅스 커널에서는 아래와 같은 공식으로 swap_tendency를 계산하는데, 이 swap_tendency 값이 100을 넘어서는 시점부터 스왑을 시작 swap_tendency = mapped_ratio / 2 + distress + swappiness distress = 100 / (2 ^ priority) priority = priority 값은 메모리 회수단계를 의미하며 12 -> 0 으로 떨어지면서 회수비중을 높이는 기준 레벨 (즉, 초기값은 12부터 시작) mapped_ratio = 전체 메모리 사용률을 의미 (만약 전체 16G 중에 12G의 메모리가 사용이 되고 있다면 mapped_ratio 값은 75) swappiness = 기본값은 60으로 약 80% 사용률 메모리 시점부터 swap 이 발생되는 것이 기본이며 50으로 설정하는 경우 100%되기전까지 스왑을 사용하지 않음. }}} * cached memory 를 free 로 환원하기 위해서는 다음을 활용할 수 있습니다. {{{#!plain drop_caches Writing to this will cause the kernel to drop clean caches, as well as reclaimable slab objects like dentries and inodes. Once dropped, their memory becomes free. To free pagecache: echo 1 > /proc/sys/vm/drop_caches To free reclaimable slab objects (includes dentries and inodes): echo 2 > /proc/sys/vm/drop_caches To free slab objects and pagecache: echo 3 > /proc/sys/vm/drop_caches This is a non-destructive operation and will not free any dirty objects. To increase the number of objects freed by this operation, the user may run `sync' prior to writing to /proc/sys/vm/drop_caches. This will minimize the number of dirty objects on the system and create more candidates to be dropped. This file is not a means to control the growth of the various kernel caches (inodes, dentries, pagecache, etc...) These objects are automatically reclaimed by the kernel when memory is needed elsewhere on the system. Use of this file can cause performance problems. Since it discards cached objects, it may cost a significant amount of I/O and CPU to recreate the dropped objects, especially if they were under heavy use. Because of this, use outside of a testing or debugging environment is not recommended. You may see informational messages in your kernel log when this file is used: cat (1234): drop_caches: 3 These are informational only. They do not mean that anything is wrong with your system. To disable them, echo 4 (bit 2) into drop_caches. }}} * posix_fadvise : 지정한 file descriptor 를 통하여 cached memory 를 해제할 수 있음. * python v3.3 미만인 경우 os 모듈에 posix_fadvise 함수가 미구현이므로 다음과 같이 구현해야 함 {{{#!enscript python #!/usr/bin/env python # -*- coding: UTF-8 -*- # vim: set fileencoding=UTF-8 : import os import sys import ctypes if __name__ == "__main__": if sys.version_info[:2] < (3, 3): libc = ctypes.CDLL(u"libc.so.6") os.posix_fadvise = libc.posix_fadvise os.posix_fadvise64 = libc.posix_fadvise64 os.POSIX_FADV_NORMAL = 0 os.POSIX_FADV_RANDOM = 1 os.POSIX_FADV_SEQUENTIAL = 2 os.POSIX_FADV_WILLNEED = 3 os.POSIX_FADV_DONTNEED = 4 os.POSIX_FADV_NOREUSE = 5 with open(sys.argv[1], u"rb") as s_fd: os.posix_fadvise(s_fd.fileno(), 0, 0, os.POSIX_FADV_DONTNEED) }}} * http://man7.org/linux/man-pages/man2/posix_fadvise.2.html {{{#!enscript c #include int posix_fadvise(int fd, off_t offset, off_t len, int advice); int posix_fadvise64(int fd, off64_t offset, off64_t len, int advice); }}} * https://python.hotexamples.com/examples/os/-/posix_fadvise/python-posix_fadvise-function-examples.html * http://small-dbtalk.blogspot.com/2014/03/ * https://code.google.com/archive/p/linux-ftools/ * https://code.google.com/archive/p/linux-ftools/source/default/source * [^https://doc.ubuntu-fr.org/zram]