|  |  | S | P | ?

ZRAM (압축메모리) 를 이용한 SWAP 확보 방법

1.1. 개요

제한적인 물리적 메모리를 응답성 및 가용성을 최대한 해치지 않고 최대의 활용 방법으로 swap을 압축 메모리로 활용하는 방법을 설명합니다. 부족한 메모리는 곧 서비스 중지라는 사태(oom kill)를 야기하므로 적어도 이 방법은 그에 대한 방어책을 구사할 수 있습니다.

ZRAM 이란?
  • 일종의 swap 이지만 Disk I/O 를 일으키지 않고 일정 조건에 의해서 swap 할 메모리를 우선 압축하도록 하는 기능.
  • 메모리상에 존재하는 데이터들은 상당히 압축률이 높은 특성을 갖는다는 전재를 깔고 도입되는 사항이며
  • 압축 알고리즘은 빠르다고 알려진 LZO (zip 에서 사용하는) 등을 많이 사용.
  • 이게 생각보다 메모리를 많이 아낄 수 있고 성능손실이 크지 않다고 알려져 있어서 대부분의 임베디드 시스템에는 (특히 NAS, 안드로이드) 많이 도입되는 기능입니다.
  • 같은 안드로이드 H/W spec 을 갖는 스마트폰이라도 유명 벤더사(S 및 L사) 의 안드로이드폰이 더 빠릿한 느낌을 주는 것은 이 기능이 큰 영향을 갖기 때문입니다.

1.2. 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"
  2. 부팅 스크립트 초기에 다음과 같은 맥락의 swap 활성화 명령이 실행되도록 합니다.
        # 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영역과 매우 흡사한 동작특성을 갖습니다.
  3. 재부팅하면 다음 명령으로 ZRAM 이 swap 으로 잡혀있는지 확인 가능합니다.
        # cat /proc/swaps
        Filename                                Type            Size    Used    Priority
        /dev/zram0                              partition       2097148 0       100
        /swap.bin                               file            1048572 0       0
    
        # cat /proc/meminfo
        ...
        SwapTotal:       3145720 kB
        SwapFree:        3145720 kB
        ...
    
  4. 그 밖에...
    • Ubuntu 배포판 사용자의 경우 (하기와 같이 설치 및 활성화만하면 적당한 ZRAM이 기본적으로 활성화 됩니다. 추가적인 조치 필요 없음.)
      $ sudo apt install zram-config
      ...
      $ sudo systemctl enable zram-config.service
      ...
      $ sudo systemctl start zram-config.service
      ...
      
    • sysctl 의 "vm.swappiness" 값을 조정하여 swap 비율을 결정할 수 있습니다.
      • 기본값은 60
        • 이 경우 메모리 사용률이 80% 를 넘어섰을 때부터 swap되는 조건이 됨.
          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되는 조건이 됨.
          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되는 조건이 됨.
          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 사이에서 결정하는게 맞다고 보임.)
        • 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]참고링크 - 리눅스 스왑 메모리 사용 제어[]
    • 다음과 같이 script 를 만들어 swap 을 활성/비활성하도록 구현할 수 있습니다.
      #!/bin/sh
      
      ###
      ### Copyright (C) MINZKN.COM
      ### All rights reserved.
      ### Author: JAEHYUK CHO <mailto:minzkn@minzkn.com>
      ###
      
      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} <enable|disable|status>"
      }
      
      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
      

1.3. 참고자료

  • [https]https://www.kernel.org/doc/Documentation/blockdev/zram.txt[]
  • [https]https://os.korea.ac.kr/publication_papers/domestic_confer/2013_spring_kcc_im.pdf[]
  • [http]http://bahndal.egloos.com/532783[]
  • [https]https://muritzy.tistory.com/1653https://github.com/StuartIanNaylor/zram-config[]
  • [https]https://github.com/StuartIanNaylor/zram-config[]
  • swap/zram 관련 sysctrl 제어 값은 다음의 값에 의해서 동작조건에 영향을 받습니다.
    • vm.swappiness = 60
    • [http]http://blog.naver.com/seuis398/70128624124[]
      • 요점
        리눅스 커널에서는 아래와 같은 공식으로 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 로 환원하기 위해서는 다음을 활용할 수 있습니다.
    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 를 해제할 수 있음.
  • [https]https://doc.ubuntu-fr.org/zram[]


Copyright ⓒ MINZKN.COM
All Rights Reserved.