tech.guitarrapc.cóm

Technical updates

PowerShellでメモリ制限を調整する

PoweShellを利用しているとSystem.OutOfMemoryExceptionの表示にめぐり会う時がきます。 特にGet-ChildItem -Recurse | Select-String hogehogeなどという処理をすると巡り会いやすくなります。

今回は、PowerShellのShellに割り当てられたメモリの調整についてです。

WSMAN: ドライブの調整にあたって前提

Windows PowerShellの諸々調整は、WSMAN:ドライブで行えます。

以下を調整にあたり注意してください。

  • WSMANのパラメータ変更操作には管理者権限が必要なのでご注意ください
  • WinRMサービスが動いていることが必要です (Get-Service -Name WinRM)

マシン全体のチューニング

まずはマシン全体の制限を見ましょう。

WSManドライブのlocalhost\Shellを確認します。

cd WSMan:
ls localhost\Shell

調整すべきはMaxMemoryPerShellMBパラメータです。以下のコードはWSManドライブに移動せずとも確認できます。 デフォルトは1024MBです。

Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB

パラメータを調整するにはSet-Itemコマンドレットを利用します。 例えば2048MBに調整するには、次のようにします。

PS> Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 2048
WARNING: The updated configuration might affect the operation of the plugins having a per plugin quota value greater than 2048. Verify the configuration of all the registered plugins and change the per plugin quota values for the affected plugins.

プラグインのチューニング

先の警告の通り、PowerShell 3.0においては、 プラグインのメモリ調整も必要です。 プラグインは色々ありますが、調整すべきはMicrosoft.powershellです。

PS> ls WSMan:localhost\Plugin
   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Plugin

Type            Keys                                Name
----            ----                                ----
Container       {Name=Event Forwarding Plugin}      Event Forwarding Plugin
Container       {Name=microsoft.powershell}         microsoft.powershell
Container       {Name=microsoft.powershell.workf... microsoft.powershell.workflow
Container       {Name=microsoft.powershell32}       microsoft.powershell32
Container       {Name=WMI Provider}                 WMI Provider

以下のコマンドで現在のQuotaを知ることができます。デフォルトのままなら恐らく1000MBと表示されます。

PS> Get-Item WSMan:localhost\Plugin\microsoft.powershell\Quotas\MaxConcurrentCommandsPerShell
   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Plugin\microsoft.powershell\Quotas

Type            Name                           SourceOfValue   Value
----            ----                           -------------   -----
System.String   MaxConcurrentCommandsPerShell                  1000

これもマシン設定と同様に調整します。今回の場合は、2048MBです。

PS> Set-Item WSMan:localhost\Plugin\microsoft.powershell\Quotas\MaxConcurrentCommandsPerShell 2048
WARNING: The configuration changes you made will only be effective after the WinRM service is restarted.  To restart the WinRM service, run the following command: 'Restart-Service winrm'

WinRMの再起動による変更の適用

変更を適用するには、WinRMを再起動します。

Restart-Service WinRM

最後に現在値が変更されていることを確認します。

# Machine Configuration
Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB

# Endpoint Plugin Configuration
Get-Item WSMan:localhost\Plugin\microsoft.powershell\Quotas\MaxConcurrentCommandsPerShell

ファンクション化

面倒なのでfunctionにしてしまいます。

function Set-PowerShellMemoryTuning{

    param(
        [parameter(
            position = 0,
            mandatory = 1)]
        [ValidateNotNullorEmpty()]
        [ValidateRange(1,2147483647)]
        [int]
        $memory
    )

    # Test Elevated or not
    $TestElevated = {
        $user = [Security.Principal.WindowsIdentity]::GetCurrent()
        (New-Object Security.Principal.WindowsPrincipal $user).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
    }

    if (&$TestElevated)
    {

        # Machine Wide Memory Tuning
        Write-Warning "Current Memory for Machine wide is : $((Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB).value) MB"

        Write-Warning "Change Memory for Machine wide to : $memory MB"
        Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB $memory


        # EndPoing Memory Tuning
        Write-Warning "Current Memory for Plugin is : $((Get-Item WSMan:localhost\Plugin\microsoft.powershell\Quotas\MaxConcurrentCommandsPerShell).value) MB"

        Write-Warning "Change Memory for Plugin to : $memory MB"
        Set-Item WSMan:localhost\Plugin\microsoft.powershell\Quotas\MaxConcurrentCommandsPerShell $memory


        # Restart WinRM
        Write-Warning "Restarting WinRM"
        Restart-Service WinRM -Force -PassThru


        # Show Current parameters
        Write-Warning "Current Memory for Machine wide is : $((Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB).value) MB"
        Write-Warning "Current Memory for Plugin is : $((Get-Item WSMan:localhost\Plugin\microsoft.powershell\Quotas\MaxConcurrentCommandsPerShell).value) MB"
    }
    else
    {
        Write-Error "This Cmdlet requires Admin right. Please Elevate and try again."
    }

}

利用するときはこのようにします。 (例 : 2048 MBにするとき)

Set-PowerShellMemoryTuning -memory 2048

GitHub

https://github.com/guitarrapc/PowerShellUtil/blob/master/Set-PowerShellMemoryTuning/Set-PowerShellMemoryTuning.ps1

EC2など高負荷クラウド環境におけるRedisチューニングについて

たまにはPowerShell以外の記事を。

某記事でもRedis (REmote DIctionary Server)がmemcachedに代わり得る利点がBookSleeveを交えて丁寧に説明されました。

Redisの運用が一定の目途を見せていることから、その初期設定に欠かせないチューニングについて記事です。 全部明かすわけではありませんが、なかなかRedisに関する記事は少ないので、少し参考になれば幸いです。

経験上、高負荷環境ではRedisはチューニングで安定性が変わります。インストールは沢山記事ありますし、簡単なのでここでは省きます。

Redis Quick Start

対象バージョン

2.6系とします。2.4系でも大方一緒ですが、2.6系に特有な部分があるので、注意です。

対象OS

RedisはLinuxベースでの運用を主眼に置かれています。Win32/64は直接サポートではありません。 一応、Microsoft Open Tech groupが実験版の開発とメンテを行っているようです。

Download

今回のチューニングはLinux、特にAWS EC2インスタンスです。

負荷と環境

ざっくりと注意点を並べておきます。

項目 目標値 備考
Call 20000 call /sec~が常時かかっている状況 ベンチでは40000 call /sec超えますが平均
Connection 5000 conn/every Redisの負荷はConnectionよりCall
CPU 1Core 80% PipeLiningは必須
Persistence RDB
Virtual Environment 無効
動作環境 AWS EC2 物理環境より性能が落ちる

Redisはシングルスレッドで動作するためCPUは注意です。Callに依存する部分が大きいですが、BookSleeveを活用したPipeLiningは必須です。 また、AOFでのディスクI/O、 RDBのDir指定ミス、RDBでbgsaveに伴うフォークrdb処理時などにCPUが膨らみます。

永続化ですが、今回はApend Only FileではなくRDBを採用した場合とします。 RDBとAOFに関しては、 Persistenceの程度はもちろんの事、Letencyにも大きくかかわるため、良く考慮してください。

Redis Persistence Redis latency problems troubleshooting*

Virtual Memoryは、サルバトーレ御大も失敗と認め2.4で廃止と明言していますので、くれぐれも使われないように。

Virtual Memory

物理環境の同一性能マシンに比較して仮想環境 (Amazon EC2など)は性能を落とすことが公式に認められています。

Redis latency problems troubleshooting

Fork time in different systems Modern hardware is pretty fast to copy the page table, but Xen is not. The problem with Xen is not virtualization-specific, but Xen-specific. For instance using VMware or Virutal Box does not result into slow fork time. The following is a table that compares fork time for different Redis instance size. Data is obtained performing a BGSAVE and looking at the latest_fork_usec filed in the INFO command output.

- Linux beefy VM on VMware 6.0GB RSS forked in 77 milliseconds (12.8 milliseconds per GB)
- Linux running on physical machine (Unknown HW) 6.1GB RSS forked in 80 milliseconds (13.1 milliseconds per GB)
- Linux running on physical machine (Xeon @ 2.27Ghz) 6.9GB RSS forked into 62 millisecodns (9 milliseconds per GB)
- Linux VM on 6sync (KVM) 360 MB RSS forked in 8.2 milliseconds (23.3 millisecond per GB)
- Linux VM on EC2 (Xen) 6.1GB RSS forked in 1460 milliseconds (239.3 milliseconds per GB)
- Linux VM on Linode (Xen) 0.9GBRSS forked into 382 millisecodns (424 milliseconds per GB)

Redisの構築

インストールとベース設定は【EC2のSnapshot運用】 、個別のチューニングが 【capistranoを利用したチューニング適用】でほぼ自動化しています。 例えn台あったとしても、 実行時と実行後の設定周り確認も含めて、新規インスタンスを起動から運用にいれるまでは30分もあれば終わります。 (大半がEC2のSnapshot起動なのですが) それだけRedisは安定しており、チューニングも一定の効果をキッチリ出せる極めて優れた品質と言えます。

PV か HVM インスタンスの選択

Amazon Linuxといえば、Xenですね。2013年まではPVが主流でしたが、2014年以降はHVMが主流です。 HVMでないと、r3インスタンスも使えず、またネットワーク周りの高速化、CPUのIvyBridge系利用もできずシングルスレッド処理が伸びません。 間違いなくHVMインスタンスを使わない理由はないので、ぜひPVは捨ててください。

Linux Tuning

ようやく主題となるチューニングです。 まずLinux Tuningから見てみましょう。

IPv6周り

IPv6を使わずIPv4でそろえた方が安定するのは事実です。 残念ですしこれが最善ともおもいませんが、 LAN内部においてはまだまだIPv4が安定して利用できるのは事実です。

resolv.conf

IPv4なら当然この要素は外せません。 EC2の場合は、実は少しトリックが要りますが省きます。

options single-request-reopen

他のIPv6関連もげもげは、まぁ大丈夫ですよね。 ただ、2014年9月以降のHVMインスタンスではresolve.confをいじらずともv4通信のみがRedisで動いているので余り気にしなくても大丈夫です。

sysctl周り

負荷具合にもよりますが大事です。

/etc/sysctl.confに記述することで、再起動後も適用されています。永続化がいやならsysctlとか/proc書き換えでどうぞ。

port_range

linuxのデフォルトポートレンジでは足りないほどの高負荷環境。 それならば設定は必要ですね。 最大域にするならこうです。 むしろそこまで高負荷環境ではこの程度は焼け石に水ですが、まぁ後述の設定が出来るなら此処はあまり問題になりません。

net.ipv4.ip_local_port_range = 1024 65000

somaxconn

memcachedでもbacklogと並んで重要なパラメータですが、Redisでも当然重要です。 状況によりますが例えば1024にするなら以下です。

net.core.somaxconn = 1024

tcp_fin_timeoutと tcp_tw_recycle

tcp_tw_recycleは、 NAT環境でパケットのtimeoutが頻発してsyn_packet祭り、という問題を良く理解した上で設定を施すか考慮、検証しなくてはいけません。 繰り返しますが、気安くやるとネットワークが意図せず不通/パケットロストになるなど「痛い目にあう可能性がある」ので気を付けてください。 また、tcp_fin_timeout設定はtcp_tw_recycleを有効にした上でないとデフォルト値の60secから変わりませんのでご注意ください。あるいはLinuxソースコードをいじってビルドするかです。

※ もしこの設定が適用できる場合、 port_rangeに関しては不要になる可能性が高いです。 例えば、tcp_fin_timeoutを30秒にするなら以下です。

net.ipv4.tcp_fin_timeout = 30

例えば、tcp_tw_recycleを有効にするなら以下です。

net.ipv4.tcp_tw_recycle = 1

FileDescriptor周り

Redisに限らずネットワークのやり取りが多い場合は当然必要です。 limits.confやpam.dのチューニングは必ず行いましょう。

その他Kernelチューニング

最も大きく関わる足回りを中心に紹介しました。 その他は環境に応じて、各自の設定があるでしょう。

Redis Config

デフォルトでは、 /etc/Redis/redis.confです。 redis.confのチューニングに関しては、それほど複雑でもありません。 また、AOFではなくRDBのパターンなので、 latencyにも気を使う必要が小さく簡単です。

daemonize

daemon化するとRedisのログは出力されません。 ご自由にどうぞ。

daemonize yes

pidfile

敢えて理由がない限りは変更の必要はないですね。 ご自由にどうぞ。

pidfile /var/run/redis.pid

port

デフォルトはTCP 6379です。 iptableにSELinuxなど各自の事情に応じてどうぞ。

port 6379

bind

バインドするnetworkインタフェースです。 特に理由がなければremarkしても問題ありません。ご自由にどうぞ。

# bind 127.0.0.1

unixsocket

unix domain socket利用する場合です。 Appと同一サーバーにするなどでしか使いません。TCP/IPを利用するならコメントアウトです。ご自由にどうぞ。

# unixsocket /tmp/redis.sock
# unixsocketperm 755

timeout

クライアントからn秒間の通信がない場合のタイムアウトです。 0でtimeout無しです。 port使用状況やconnectionの張り方に応じてどうぞ。

timeout 600

TCP keepalive

もし0出ない場合、クライアントとの無通信時にTCP ACKsを送るのにSO_KEEPALIVEを利用します。 ダメになったpeerの検出や、ネットワーク維持の明示に役立ちます。 推奨値は60 (秒)です。

tcp-keepalive 60

loglevel

logの程度です。 出力はdaemon次第ですので悪しからず。 debug、verbose、notice、warningとありますが、本番ではnoticeが推奨です。

loglevel notice

logfile

logfileの出力先です。 daemonにしてると設定値がどうあれ、/dev/nullに破棄されますのでご注意を。

logfile /var/log/redis/redis.log

syslog-enabled

syslogを有効にするかです。 お好きにどうぞ。 もちろんコメントアウトも可です。

# syslog-enabled no

syslog-facility

syslogを実行する際のアレです。 USERか、LOCAL0-LOCAL7である必要あります。

# syslog-facility local0

database

利用するdatabaseの数です。 デフォルトはDB 0を利用し、0 ~ databses-1まで利用可能です。

databases 16

Snapshot

RDBに関することだけにしますね。

save

RDBを取る頻度です。 save n xは、n秒にx回の変更に付きbgsaveを行うという記法です。 saveしない(=In-Memoryとしての利用) 場合、 save ""となります。

save 900 1
save 300 10
save 60 100000

stop-writes-on-bgsave-error

Redisは、 RDBが有効 & 前回のbgsaveが失敗した場合、書き込みを受け付けなくなります。 こうすることで、 redis-infoなどでpersistenceが失われている異常事態が分かるようにしています。 通常は無効にしないでしょうが、モニタの都合なのでディスク異常でも働かせたいなら無効です。

stop-writes-on-bgsave-error yes

rdbcompression

RDBはメモリのダンプですが、保存するときに圧縮するかどうかです。圧縮するとCPUは喰うものの容量が小さくなります。

rdbcompression yes

rdbchecksum

CRC64でのRDBのchecksum検査をお熟します。 破損の検知は永続性で最重要なので、行わない理由はないかと。 ただし、RDBのsaveとloadで10%のパフォーマンス低下が起こります。

rdbchecksum yes

dbfilename

RDBファイル名です。

dbfilename dump.rdb

dir

RDBを保存する、パスです。 先のdbfilenameとセットになって、保存フルパスが定まります。 このパスを間違えると、RDBのbgsaveに失敗し、RedisのCPUが常に高い状況になります。RedisでCPUが常に100%なのはまずありません。異常事態なので必ず存在するパスである事、権限などRDBが生成できている事などを確認しましょう。

dir /var/lib/redis/

REPLICATION

スレーブ今回省きます。

SECURITY

Securityは今回省きます。

LIMITS

2.6.12あたりでFileDescriptor周りが変わりました。

maxclients

最大接続クライアントです。 デフォルトでは10000となります。 ここはfile descriptorに上限が左右されます。(File Descriptor -32) 上限に達すると、'max number of clients reached' とエラーを返します。 無効にする場合は、 #maxclientsとコメントアウトします。

maxclients 10000

maxmemory

Redisが利用する最大メモリ(bytes)です。 そのままですね。 無効にするとシステムの最大まで利用を試みます。 無効にする場合は、 # maxmemoryとコメントアウトします。

# maxmemory <bytes>

MAXMEMORY POLICY

基本的には変更に必要がないです。 maxmemoryへ到達した時にRedisがどのようにmemoryを開放するかの処理です。

# maxmemory-policy volatile-lru

APPEND ONLY MODE

AOFは今回省きます。 基本的には無効にするように設定しておきます。

appendonly no
appendfsync no
no-appendfsync-on-rewrite no

LUA SCRIPTING

LUA SCRIPTINGを利用する場合に使用します。 最大実行時間の設定のみで、デフォルトでも十分実用値です。

lua-time-limit 5000

SLOW LOG

処理に時間がかかったのモノをSLOW LOGに残す際の閾値です。 latencyの計測には欠かせません。

slowlog-log-slower-than 10000
slowlog-max-len 128

ADVANCED CONFIG

基本的には初期値のままでとサルバトーレ御大も行っていますが、まぁしたい人はどうぞ。 ここでは省きます。

まとめ

Linux / Redis両面での推奨設定値を挙げるという事はしませんでしたが、日本語での説明も含めて目安となれば幸いです。

Redisはチューニングされていないlinux & Redisでもある程度快適に動きます。 が、本気でチューニングした時との安定性は格段に上昇し、速度もキッチリ出ます。 EC2の同一VPC / 同一Zone内部であればほぼ遅延はなく、実質network latencyのみとなる (つまり0.0x msの処理) が実現できます。 是非、memcachedなどからRedisにメリットを感じる (C# なら特に! ) 状況の場合は、採用されるといいでしょう。