raspberrypi tempfs化備忘録
rasppiはhddがなく、sdカードに書き込む。 SDカードの寿命を延ばすためにtempfs化は必須。 また、デフォルトでswapが有効化されている。 swap も無効化しないと、sdカードへガリガリ書き込むため、swap無効化も合わせて行なったほうが良い。
swap
swapの確認
free を行うと、swapのfreeが0でないことがわかる
$ free total used free shared buff/cache available Mem: 3885496 94256 3411824 936 379416 3719992 Swap: 102396 0 102396
swap無効化
起動している状態でswapを無効化する
sudo swapoff --all
freeで確認するとfreefが0になることがわかる。
free total used free shared buff/cache available Mem: 3885496 93344 3411704 936 380448 3720772 Swap: 0 0 0
再起動後もswapを無効化する
swapサービスは dphys-swapfile
。
sudo systemctl stop dphys-swapfile.service sudo systemctl disable dphys-swapfile.service
確認
sudo systemctl status dphys-swapfile
tempfs
tempfs化の対象ディレクトリは?
- 常時書き込むディレクトリ
- /tmp
- /var ?
/var 配下の検討
- /var/tmpは再起動後に残るディレクトリ。そのため通常は削除してはいけない。ただ今回はSDカードの寿命が短くなるのは許容できないためtmpfsにする。-> 更新日時が古く、そこまで問題ないと判断しtmpfs化は行わない。
$ ls -l /var 合計 102436 drwxr-xr-x 2 root root 4096 8月 14 06:25 backups drwxr-xr-x 7 root root 4096 1月 28 2022 cache drwxr-xr-x 24 root root 4096 1月 28 2022 lib -> drwxrwsr-x 2 root staff 4096 12月 12 2021 local lrwxrwxrwx 1 root root 9 1月 28 2022 lock -> /run/lock drwxr-xr-x 6 root root 4096 8月 14 00:00 log drwxrwsr-x 2 root mail 4096 1月 28 2022 mail drwxr-xr-x 2 root root 4096 1月 28 2022 opt lrwxrwxrwx 1 root root 4 1月 28 2022 run -> /run drwxr-xr-x 4 root root 4096 1月 28 2022 spool ->crontab=設定ファイルが書き込まれるためtmpfsにしない -rw------- 1 root root 104857600 1月 28 2022 swap drwxrwxrwt 4 root root 80 8月 14 00:00 tmp
/varを全てtmpfsにすると不足する。
kawawa@rpi4-2:/var $ sudo du -hs backups 584K backups kawawa@rpi4-2:/var $ sudo du -hs cache 105M cache kawawa@rpi4-2:/var $ sudo du -hs lib 145M lib kawawa@rpi4-2:/var $ sudo du -hs local 4.0K local kawawa@rpi4-2:/var $ sudo du -hs log 98M log kawawa@rpi4-2:/var $ sudo du -hs mail 4.0K mail kawawa@rpi4-2:/var $ sudo du -hs opt 4.0K opt kawawa@rpi4-2:/var $ sudo du -hs spool 16K spool
- /var/cache
rpi4-2:/var/cache $ find . -maxdepth 1 -type d | grep -v "\.$" | xargs -I{} sudo du -hs {} 100M ./apt 4.0K ./private 1.4M ./man 28K ./ldconfig 3.9M ./debconf
- aptのcacheは入りきらない。
- manも無理。
debconfは用途が不明だが、そもそも/var/cacheのtmpfs化は諦めた方が良さそう.
/var/lib
rpi4-2:/var/lib $ find . -maxdepth 1 -type d | grep -v "\.$" | xargs -I{} sudo du -hs {} 20K ./dhcpcd 8.0K ./logrotate 4.0K ./dhcp 125M ./apt 28K ./pam 8.0K ./vim 4.0K ./private 4.0K ./usb_modeswitch 20M ./dpkg 12K ./nfs 8.0K ./bluetooth 4.0K ./misc 4.0K ./udisks2 4.0K ./dbus 12K ./raspberrypi 8.0K ./alsa 8.0K ./sudo 4.0K ./python 4.0K ./man-db 48K ./ucf 428K ./systemd 36K ./polkit-1
- 支配的なものはapt. PGP
/var/lib $ ls -l 合計 88 drwxr-xr-x 2 root root 4096 8月 15 08:08 alsa drwxr-xr-x 5 root root 4096 8月 13 19:33 apt drwx------ 3 root root 4096 1月 28 2022 bluetooth drwxr-xr-x 2 root root 4096 1月 28 2022 dbus drwxr-xr-x 2 root root 4096 5月 27 2021 dhcp drwxr-xr-x 2 root root 4096 8月 14 08:44 dhcpcd drwxr-xr-x 7 root root 4096 8月 13 19:33 dpkg drwxr-xr-x 2 root root 4096 8月 16 00:00 logrotate drwxr-xr-x 2 root root 4096 1月 28 2022 man-db drwxr-xr-x 2 root root 4096 12月 12 2021 misc drwxr-xr-x 4 root root 4096 1月 28 2022 nfs drwxr-xr-x 2 root root 4096 1月 28 2022 pam drwx------ 3 root root 4096 1月 28 2022 polkit-1 drwx------ 2 root root 4096 1月 28 2022 private drwxr-xr-x 2 root root 4096 1月 28 2022 python drwxr-xr-x 3 root root 4096 1月 28 2022 raspberrypi drwxr-xr-x 3 root root 4096 1月 28 2022 sudo drwxr-xr-x 11 root root 4096 1月 28 2022 systemd drwxr-xr-x 3 root root 4096 1月 28 2022 ucf drwx------ 2 root root 4096 1月 28 2022 udisks2 drwxr-xr-x 2 root root 4096 7月 11 2020 usb_modeswitch drwxr-xr-x 3 root root 4096 1月 28 2022 vim rpi4-2:/var/lib $ date 2022年 8月 16日 火曜日 06:23:39 JST
更新日時を確認しても1回/日程度のため許容範囲と判断。
tmpfsにするのは /var/log だけで良さそう
- /var/logの更新日時が古い?
- journalctlの保存先がSDカードだと対策不十分
- journalctlのデフォルトログ保存先は/run配下。
- /run はtmpfs化既にされているため問題なし.
- 容量はどうする?
Raspberry Pi OSにtmpfsを活用しSDカードに優しいシステムを - Fun Scripting 2.0
ではrpi3にて/tmp,/var それぞれ 16mとしている。 - rpi4 4GB でrpi3よりメモリは増えている。そのため16Mは問題ないように思える。
- tmpfiles.dとfstabの棲み分け
- fstabは起動時にマウントする設定
- tmpfiles.dは一時ファイルのマウント?、アンマウント、初期化、後処理などができる。典型的にはマウント後の初期化処理にてtmpfsにシンボリックリンクを作成するといったことが簡便に実現できる。
- see tmpfiles.d(5)
最終的に以下2行を/etc/fstabに書き込むことで完了
tmpfs /tmp tmpfs defaults,size=16m,noatime,mode=1777 0 0 tmpfs /tmp/log tmpfs defaults,size=16m,noatime,mode=1777 0 0
vscodeでC/C++ デバッグの設定(GDB/MIインターフェース)
vscodeの ms-vscode.cpptools はGDB/MIインターフェイスを使用している。 そのためlaunch.jsonを適切に設定するには、GDB/MIインターフェイスの理解が必要となる。
GDBが解釈するインターフェースは2つある。 一つ目がGDBコマンド、もう一つがGDB/MIインターフェイス。
GDBコマンド
gdbをtuiで使って実行している時に使うもの。
ステップ実行を例にすると
next
やその省略形のn
がGDBコマンドに該当する
(next
は本来、下位プログラムの実行を再開し、 次のソース行の先頭に到達したところで停止するものだが理解しやすさを優先しここではステップ実行と説明する)
GDB/MIインターフェイス
そこから拝借すると以下のような説明がある.
GDBコマンドとGDB/MIインターフェイスの対応づけは上記リンクに記載してあるものが多い。
ステップ実行を行うGDBコマンドnext
に対応するGDB/MIインターフェースは-exec-next
となる。
なお全てのGDB/MIインターフェースのソースコードは以下にある。
sourceware.org Git - binutils-gdb.git/blob - gdb/mi/mi-cmds.c
NURO SGP200Wと ASUS BRT-AC828 二重ルーターを回避する
SGP200W だけの状態だと家のはじだと電波が弱いため別のルーターを購入し対策しようとした。 その際選んだのがコスパが良い ASUS BRT-AC828 。 この組み合わせの事例がググっても出てこなかったため記録に残す。
SGP200W はONU+ルーター機能を有する。 しかしルーター機能は止められない。 当初 いわゆる二重ルーター構成にするしかないと考えたが、 SGP200W の DMZ 機能により二重ルーター構成を回避できることがわかったため DMZ 機能を有効にしてネットワークを構築した。
NURO SGP200Wの設定
WANの設定
通常通り
LANの設定
- NURO SGP200Wのアドレス
- 192.168.1.1
- サブネットマスク
- 255.255.255.0
DMZ
ASUS BRT-AC828 のIPAddressを192.168.1.50とするという設定。あとのステップにて設定する
ASUS RT-AC828の設定
WAN
- IPAddress: 192.168.1.50
- サブネットマスク: 255.255.255.0
- デフォルトゲートウェイ: 192.168.1.1
- DNS1: 8.8.8.8
- DNS2: 8.8.4.4
IPv6
paththrough。を選択。ただ/etc/resolv.conf
にDNSサーバーが書き込まれないため一時的にIPv6をオフにした。多分、IPv6のDMZ設定をしていないから?
W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168d-1.fw for module r8169
以下のページを参考に直した blog.treedown.net
環境
- debian11 bullseye
- Linux ryzen5 5.10.0-15-amd64 #1 SMP Debian 5.10.120-1 (2022-06-09) x86_64 GNU/Linux
現象
W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168e-3.fw for module r8169 W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168e-2.fw for module r8169 W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168e-1.fw for module r8169 W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168d-2.fw for module r8169 W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168d-1.fw for module r8169
原因
package不足
non-free が足りてない。
対策
/etc/apt.sources.listにnon-freeを足す
before
$ cat /etc/apt/sources.list # deb cdrom:[Debian GNU/Linux 11.0.0 _Bullseye_ - Official amd64 NETINST 20210814-10:07]/ bullseye main #deb cdrom:[Debian GNU/Linux 11.0.0 _Bullseye_ - Official amd64 NETINST 20210814-10:07]/ bullseye main deb http://deb.debian.org/debian/ bullseye main deb-src http://deb.debian.org/debian/ bullseye main deb http://security.debian.org/debian-security bullseye-security main deb-src http://security.debian.org/debian-security bullseye-security main # bullseye-updates, to get updates before a point release is made; # see https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_updates_and_backports deb http://deb.debian.org/debian/ bullseye-updates main deb-src http://deb.debian.org/debian/ bullseye-updates main
after
$ cat /etc/apt/sources.list # deb cdrom:[Debian GNU/Linux 11.0.0 _Bullseye_ - Official amd64 NETINST 20210814-10:07]/ bullseye main #deb cdrom:[Debian GNU/Linux 11.0.0 _Bullseye_ - Official amd64 NETINST 20210814-10:07]/ bullseye main deb http://deb.debian.org/debian/ bullseye main non-free deb-src http://deb.debian.org/debian/ bullseye main non-free deb http://security.debian.org/debian-security bullseye-security main non-free deb-src http://security.debian.org/debian-security bullseye-security main non-free # bullseye-updates, to get updates before a point release is made; # see https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_updates_and_backports deb http://deb.debian.org/debian/ bullseye-updates main non-free deb-src http://deb.debian.org/debian/ bullseye-updates main non-free
sudo apt-get install firmware-linux-nonfree
gstreamer の調査
調べたかったのはgstreamerにおける再送処理 RR(receiver report)をもとに再送しているのでは?と思いソースを読んだが、以下のドキュメントを読むと良い。 gitlab.freedesktop.org
GStreamer の主要な RTP コンポーネントのほとんどは gst-plugins-good に収められています。
rtpmanager プラグイン にはrtpbin とrtpjitterbuffer
rtpプラグイン には、多くの異なるコーデックとコンテナフォーマットに対する RTP ペイロードとディペイロードの要素が含まれています。 コーデックやコンテナ形式と、gst-plugins-base にあるいくつかの低レベルのライブラリが含まれています。
GStreamer RTP ライブラリ RTP ペイローダ/デペイローダのベースクラス、RTP および RTCP バッファを処理する関数などが含まれます。
- GStreamer MIKEY ライブラリ セキュアな RTP のための MIKEY メッセージを扱うヘルパー関数が含まれています。
- GStreamer RTSP ライブラリ には、gst-rtsp-server や rtspsrc などの高レベルオブジェクトで使用される低レベル RTSP 機能が含まれています。
- GStreamer SDP ライブラリ は、SDP メッセージのパースと生成のためのユーティリティ関数を含んでいます。
主なコンポーネントをいくつか紹介します。
- rtpbin は高レベルのRTPコンポーネントであり、送信と受信をサポートする。 と受信、データの送信のみ、受信のみ、RTCPサポートあり/なしをサポートします。 サポートします。これはすべてを行うbinで、要求されたパッドに基づいてニーズに動的に適応します。 また、rtpjitterbufferも含まれています。
- rtpjitterbuffer は、ネットワークジッタを制御し、パケットを並べ替える RTP バッファです。またパケットの再送信やパケット紛失の通知も行います。パケット通知、送信者-受信者間のクロックドリフトを調整します。
- rtpptdemux は、通常、rtpbin srcパッドに配置され、RTPストリームに到着した新しいペイロードタイ プを検出する。そして、その新しいペイロードのためのパッドを作成し、そのパッドにデペイローダ/デコーダのパイプラインをそのパッドに接続することができます。
- rtpssrcdemux は、通常、rtpbin srcパッドに配置され、RTPストリームに到着した新しいSSRCを検出する。 そして、その新しいSSRCのためのパッドを作成し、そのパッドにデペイロダー/デコーダーパイプラインをそのパッドに接続することができます。
- GstRTPBaseDepayload は、RTPディペイローダのベースクラスである
- GstRTPBasePayload は、RTP ペイローダの基底クラスです。
- GstRTPBaseAudioPayload (オーディオペイロード)は、オーディオRTPペイローダのベースクラスである。
Note: 多くのRTPエレメントは、RTPバッファの受信にGstNetAddressMetaメタデータが設定されたRTPバッファを受信すると仮定している(udpsrcが生成するように)。
メモ
RTSP,RTCP,RTPの関係が怪しい場合、いかのプロトコルスタックの図がわかりやすいため確認しておくと良い https://www.ieice-hbkb.org/files/03/03gun_04hen_05.pdf
- RTSPにて交換されたSDPに基づいた映像・音声設定
- RTSPにて開始・停止制御
- サーバーからのデータ送信はRTP
- RR,SRはRTCP
sourceの場所
gitlab
gstreamer,gst-plugins-good などのリポジトリがあるが、gstreamer配下にsubprojectsがあり、そこにgst-plugins-goodなどが位置するため、リポジトリを個別にcloneする必要はない
gst-plugins-good/gst 下に以下のものが位置する - rtp - rtsp - rtpmanager - udp
gst-plugins-good/rtpmanager/rtpsession.c にRRなどがありそう
rtp_source_get_last_rb
: rtp_source_process_rb()
で設定された最後のRBレポートの値を取得します。
rb: receiverと思われる
// A receiver report structure. typedef struct { gboolean is_valid; guint32 ssrc; /* which source is the report about */ guint8 fractionlost; guint32 packetslost; guint32 exthighestseq; guint32 jitter; guint32 lsr; guint32 dlsr; guint32 round_trip; } RTPReceiverReport;
統計情報を持っていて、そこにRR情報が含まれる.
// Stats about a source. // ここでいうsourceはsincに対するsourceか? typedef struct { guint64 packets_received; guint64 octets_received; guint64 bytes_received; guint32 prev_expected; guint32 prev_received; guint16 max_seq; guint64 cycles; guint32 base_seq; guint32 bad_seq; guint32 transit; guint32 jitter; guint64 packets_sent; guint64 octets_sent; guint sent_pli_count; guint recv_pli_count; guint sent_fir_count; guint recv_fir_count; guint sent_nack_count; guint recv_nack_count; /* when we received stuff */ GstClockTime prev_rtptime; GstClockTime prev_rtcptime; GstClockTime last_rtptime; GstClockTime last_rtcptime; /* sender and receiver reports */ gint curr_rr; RTPReceiverReport rr[2]; gint curr_sr; RTPSenderReport sr[2]; } RTPSourceStats;
rtp_session_process_rb
: 受信したreceiver reportをRTPSourceStatsに保存する
RTPSourceStats をもとに再送する場所は別らしい。
rtp_source_process_rtp
: src に @pinfo に記述された着信 RTP パケットを処理させる。
多分RTP送信をする前にrtpmanagerにおいて統計情報を更新する
統計情報とは、jitter, bitrateなど
あくまで統計情報の更新しか行なっておらず、再送処理などは行なっていない。
似たようなrtp処理が複数ある process_rtp push_rtp send_rtp
source_push_rtp
では source->internalの場合にsend_rtp
, そうでない場合process_rtp
を行う
internalって何のinternal?
obtain_internal_source
でsource->internal = TRUE;を設定している
gstreamerから送信するものと、gstreamerが受信するものがある。送信はgstremer内部から出ていくからそれを内部と言っている?
gstrtpbin RTP binは、#GstRtpSession、#GstRtpSsrcDemux、#GstRtpJitterBuffer、#GstRtpPtDemuxの機能を1つのエレメントにまとめたものである。これにより、RTCP SRパケットを使用して同期される複数のRTPセッションが可能になります。
gstrtpfunnel RTPファンネルは基本的に通常のファンネルと同様であるが、バンドルに対応するため、 機能がいくつか追加されている。
funnel:漏斗、つまり2つのsincを1つのsrcにする。multiplexerと言ってもよい。
rtphdrextmid RTP Bundle Media Identification (MID) RTP Header Extension (RFC8843)
GstRtphdrext-TWCC Helper methods for dealing with RTP header extensions in the Audio/Video RTP Profile for transport-wide-cc
retransmission(rtx) rtxってre-transmissionか。
rtprtxsend
例については#GstRtpRtxReceiveを参照のこと。 送信側RTXオブジェクトの目的は、設定可能な制限値(max-size-timeまたは max-size-packets)までのRTPパケットの履歴を保持することである。 これは、ダウンストリーム(#GstRtpSession)から来るアップストリームカスタム再送信 イベント(GstRTPRetransmissionRequest)をリッスンしている。 要求を受け取ると、要求されたseqnumを保存されたパケットの一覧から探します。 そのパケットが利用可能であれば、RFC4588に従ってRTXパケットを作成し、これを補助ストリームとして送信します。RTXはSSRC多重化されています。
rtprtxreceive
rtprtxreceiveは下流のrtpjitterbufferからの再送信イベントをリッスンし、ストリームのSSRC(ssrc1)と要求されたシーケンス番号を記憶しています。記憶しているものと同じシーケンス番号で、異なるSSRCを持つパケットを受信すると、新しいSSRC(ssrc2)をssrc1の再送ストリームとして識別する。 この時点から、rtpjitterbufferが元のストリームを再構築できるように、ssrc2ストリームのすべてのパケットでssrc2をssrc1に置き換え、それらを再送信であるとフラグを立てます。
このアルゴリズムは、RFC 4588で規定されているように実装されている。
このエレメントは、送信側でrtprtxsendと共に使用されることを意図している。 GstRtpRtxSend を参照してください。
以下に、rtprtxreceive と rtprtxsend が他の rtp エレメントにどのように適合し、内部でどのように動作するかを示す例をいくつか示します。 しかし、通常はこのようなパイプラインの使用は避け、代わりに #GstRtpBin::request-aux-sender と #GstRtpBin::request-aux-receiver のシグナルを持つ rtpbin を使用する必要があります。詳細は #GstRtpBin を参照してください。
pipelineのサンプル
Send audio stream through port 5000 (5001 and 5002 are just the rtcp link with the receiver
gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \ audiotestsrc is-live=true ! opusenc ! rtpopuspay pt=96 ! \ rtprtxsend payload-type-map="application/x-rtp-pt-map,96=(uint)97" ! \ rtpsession.send_rtp_sink \ rtpsession.send_rtp_src ! identity drop-probability=0.01 ! \ udpsink host="127.0.0.1" port=5000 \ udpsrc port=5001 ! rtpsession.recv_rtcp_sink \ rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5002 \ sync=false async=false
Receive audio stream from port 5000 (5001 and 5002 are just the rtcp link with the sender)
gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \ udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)96" ! \ rtpsession.recv_rtp_sink \ rtpsession.recv_rtp_src ! \ rtprtxreceive payload-type-map="application/x-rtp-pt-map,96=(uint)97" ! \ rtpssrcdemux ! rtpjitterbuffer do-retransmission=true ! \ rtpopusdepay ! opusdec ! audioconvert ! audioresample ! autoaudiosink \ rtpsession.send_rtcp_src ! \ udpsink host="127.0.0.1" port=5001 sync=false async=false \ udpsrc port=5002 ! rtpsession.recv_rtcp_sink
この例では、OPUSストリームの単純なストリーミングで、パケットの一部がIDエレメントによって人為的にドロップされる様子を見ることができます。 再送信のおかげで、drop-probabilityを0より大きいものに設定しても、クリアなサウンドを聞くことができるはずです。
内部的には、rtpjitterbufferは、1つのパケットが欠落していることを検出すると、カスタムアップストリームイベントであるGstRTPRetransmissionRequestを生成します。 次に、このリクエストは、rtpsessionによってrtcpリンクのFB NACKに変換される。 最後に、送信側のrtpsessionは、rtprtxsendが処理するGstRTPRetransmissionRequestに再変換する。rtprtxsendはその後、新しいsrrcと異なるペイロードタイプ (ここでは97)で、元のシーケンス番号と同じで、見つからないパケットを再送信する。受信側では、rtprtxreceiveがこの新しいストリームを元のストリームと関連付け、再送パケットを元のssrcとペイロードタイプでrtpjitterbufferに転送します。
Send two audio streams to port 5000.
gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \ audiotestsrc is-live=true ! opusenc ! rtpopuspay pt=97 seqnum-offset=1 ! \ rtprtxsend payload-type-map="application/x-rtp-pt-map,97=(uint)99" ! \ funnel name=f ! rtpsession.send_rtp_sink \ audiotestsrc freq=660.0 is-live=true ! opusenc ! \ rtpopuspay pt=97 seqnum-offset=100 ! \ rtprtxsend payload-type-map="application/x-rtp-pt-map,97=(uint)99" ! \ f. \ rtpsession.send_rtp_src ! identity drop-probability=0.01 ! \ udpsink host="127.0.0.1" port=5000 \ udpsrc port=5001 ! rtpsession.recv_rtcp_sink \ rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5002 \ sync=false async=false
Receive two audio streams from port 5000.
gst-launch-1.0 rtpsession name=rtpsession rtp-profile=avpf \ udpsrc port=5000 caps="application/x-rtp,media=(string)audio,clock-rate=(int)48000,encoding-name=(string)OPUS,payload=(int)97" ! \ rtpsession.recv_rtp_sink \ rtpsession.recv_rtp_src ! \ rtprtxreceive payload-type-map="application/x-rtp-pt-map,97=(uint)99" ! \ rtpssrcdemux name=demux \ demux. ! queue ! rtpjitterbuffer do-retransmission=true ! rtpopusdepay ! \ opusdec ! audioconvert ! autoaudiosink \ demux. ! queue ! rtpjitterbuffer do-retransmission=true ! rtpopusdepay ! \ opusdec ! audioconvert ! autoaudiosink \ udpsrc port=5002 ! rtpsession.recv_rtcp_sink \ rtpsession.send_rtcp_src ! udpsink host="127.0.0.1" port=5001 \ sync=false async=false
ポート5000から2つのオーディオストリームを受信します。
この例では、同じタイプの2つのストリームを同じポートでストリーミングしています。ただし、これらは異なるSSRCを使用しているため(ssrcは各ペイローダ(この例ではrtpopuspay)でランダムに生成)、受信側のrtpssrcdemuxによって識別および多重化解除が可能になっています。これは、SSRC多重化の例です。
これは、rtprtxreceiveが再送ストリームを識別するために最初に使用する唯一の識別手段であるため、異なる開始シーケンス番号(seqnum-offset)を使用することがここで重要である。 RFC4588によると、2つの異なるストリームに属するが同じシーケンス番号を持つパケットに対して2つの再送要求があるのはエラーである。 デフォルトのseqnum-offset値(-1、つまりランダム)でも問題ないが、ここでは説明のためにオーバーライドしていることに注意。
rtpjitter
RTP_JITTER_BUFFER_MODE_NONE: スキュー補正を行わず、送信タイムスタンプはRTPタイムスタンプから直接計算される。このモードは録画には向いているが、リアルタイムアプリケーションには向いていない。 RTP_JITTER_BUFFER_MODE_SLAVE: 送信者と受信者間のスキューを計算し、平滑化された調整済み送信タイムスタンプを生成する。このモードは低遅延の通信に適している。 RTP_JITTER_BUFFER_MODE_BUFFER: 低ウォーターマーク/高ウォーターマーク間のパケットをバッファリングする。このモードはストリーミング通信に向いている。 RTP_JITTER_BUFFER_MODE_SYNCED: 送信側と受信側のクロックは #RTP_JITTER_BUFFER_MODE_SLAVE のように同期しているが、スキューは 0 と仮定する。 送信側と受信側のクロックが同期しており、クロックスキューがない場合の低レイテンシ通信によい。 RTP_JITTER_BUFFER_MODE_LAST: 最後のバッファーモード。
pg_partman ネイティブパーティションの使い方サンプル
このHowToガイドでは、シンプルなシングルレベル・パーティション設定の例をいくつか紹介します。また、既存のデータを持つテーブルからデータをパーティショニングする方法(既存のテーブルのパーティショニングを参照)と、既存のパーティション・セットのパーティショニングを元に戻す方法(ネイティブ・パーティショニングの取り消しを参照)を紹介します。それぞれの関数が何を行うのか、またこの拡張の追加機能の詳細については、pg_partman.mdドキュメントファイルを参照してください。
この文書にある例は、少なくとも4.4.1版のpg_partmanをPostgreSQL 11以降で動作させていることを前提としています。
ここにあるすべての例は、ネイティブなパーティショニングのためのものであることに注意してください。もし、ネイティブではない、トリガーベースのパーティショニングを使用する必要がある場合は、トリガーベースのHowToファイルを参照してください。
Simple Time Based: 1 Partition Per Day
ネイティブ・パーティショニングを行うには、目的のタイプでパーティショニングされるように既に設定されている親テーブルから開始する必要があります。現在、pg_partmanはRANGEタイプのパーティショニングのみをサポートしています(timeとidの両方)。パーティショニングされていないテーブルをパーティショニングされたセットの親テーブルにすることはできませんので、移行が困難になる可能性があります。このドキュメントでは、後でこれを管理するためのいくつかのテクニックを紹介します。とりあえず、この例では、まったく新しいテーブルから始めます。ユニークでないインデックスもPG11+の親テーブルに追加すれば、すべての子テーブルに自動的に作成されます。
CREATE SCHEMA IF NOT EXISTS partman_test; CREATE TABLE partman_test.time_taptest_table (col1 int, col2 text default 'stuff', col3 timestamptz NOT NULL DEFAULT now()) PARTITION BY RANGE (col3); CREATE INDEX ON partman_test.time_taptest_table (col3);
\d+ partman_test.time_taptest_table Partitioned table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition key: RANGE (col3) Indexes: "time_taptest_table_col3_idx" btree (col3) Number of partitions: 0
ネイティブにパーティショニングされた親に対して、パーティション・キーを含まないユニークインデックス(主キーを含む)を作成することはできません。時間ベースのパーティショニングでは、各子テーブルで単一のタイムスタンプ値のみを制限することになるため、一般的にうまくいきません。pg_partmanは、テンプレートテーブルを使用して、現在ネイティブパーティショニングでサポートされていないプロパティを管理することにより、この管理を支援します。これは、制約がパーティションセット全体に渡って強制されないという問題を解決するものではないことに注意してください。PostgreSQLのバージョンによって、どのプロパティがテンプレートで管理されるかは、メインドキュメントを参照してください。
この例では、create_parent()を実行した時に、最初に作成される子テーブルが主キーを持つように、最初にテンプレートテーブルを手動で作成することにしています。pg_partman にテンプレートテーブルを指定しなかった場合、拡張機能をインストールしたスキーマにテンプレートテーブルを作成します。しかし、そのテンプレートに追加したプロパティは、その時点以降に新しく作成された子テーブルにのみ適用されます。既に存在する子テーブルには、手動でそれらのプロパティを遡及して適用する必要があります。
CREATE TABLE partman_test.time_taptest_table_template (LIKE partman_test.time_taptest_table); ALTER TABLE partman_test.time_taptest_table_template ADD PRIMARY KEY (col1);
\d partman_test.time_taptest_table_template Table "partman_test.time_taptest_table_template" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | integer | | not null | col2 | text | | | col3 | timestamp with time zone | | not null | Indexes: "time_taptest_table_template_pkey" PRIMARY KEY, btree (col1)
SELECT partman.create_parent('partman_test.time_taptest_table', 'col3', 'native', 'daily', p_template_table := 'partman_test.time_taptest_table_template'); create_parent --------------- t (1 row)
\d+ partman_test.time_taptest_table Partitioned table "partman_test.time_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition key: RANGE (col3) Indexes: "time_taptest_table_col3_idx" btree (col3) Partitions: partman_test.time_taptest_table_p2020_10_26 FOR VALUES FROM ('2020-10-26 00:00:00-04') TO ('2020-10-27 00:00:00-04'), partman_test.time_taptest_table_p2020_10_27 FOR VALUES FROM ('2020-10-27 00:00:00-04') TO ('2020-10-28 00:00:00-04'), partman_test.time_taptest_table_p2020_10_28 FOR VALUES FROM ('2020-10-28 00:00:00-04') TO ('2020-10-29 00:00:00-04'), partman_test.time_taptest_table_p2020_10_29 FOR VALUES FROM ('2020-10-29 00:00:00-04') TO ('2020-10-30 00:00:00-04'), partman_test.time_taptest_table_p2020_10_30 FOR VALUES FROM ('2020-10-30 00:00:00-04') TO ('2020-10-31 00:00:00-04'), partman_test.time_taptest_table_p2020_10_31 FOR VALUES FROM ('2020-10-31 00:00:00-04') TO ('2020-11-01 00:00:00-04'), partman_test.time_taptest_table_p2020_11_01 FOR VALUES FROM ('2020-11-01 00:00:00-04') TO ('2020-11-02 00:00:00-05'), partman_test.time_taptest_table_p2020_11_02 FOR VALUES FROM ('2020-11-02 00:00:00-05') TO ('2020-11-03 00:00:00-05'), partman_test.time_taptest_table_p2020_11_03 FOR VALUES FROM ('2020-11-03 00:00:00-05') TO ('2020-11-04 00:00:00-05'), partman_test.time_taptest_table_default DEFAULT
\d+ partman_test.time_taptest_table_p2020_10_26 Table "partman_test.time_taptest_table_p2020_10_26" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------------+----------+--------------+------------- col1 | integer | | not null | | plain | | col2 | text | | | 'stuff'::text | extended | | col3 | timestamp with time zone | | not null | now() | plain | | Partition of: partman_test.time_taptest_table FOR VALUES FROM ('2020-10-26 00:00:00-04') TO ('2020-10-27 00:00:00-04') Partition constraint: ((col3 IS NOT NULL) AND (col3 >= '2020-10-26 00:00:00-04'::timestamp with time zone) AND (col3 < '2020-10-27 00:00:00-04'::timestamp with time zone)) Indexes: "time_taptest_table_p2020_10_26_pkey" PRIMARY KEY, btree (col1) "time_taptest_table_p2020_10_26_col3_idx" btree (col3) Access method: heap
Simple Serial ID: 1 Partition Per 10 ID Values
このユースケースでは、create_parent()を呼び出す前に、テンプレート・テーブルを手動で作成することはありません。そのため、後からプライマリキーやユニークキーを追加しても、現在存在する子テーブルには適用されないことを示しています。それは手動で行う必要があります。
CREATE TABLE partman_test.id_taptest_table ( col1 bigint , col2 text not null , col3 timestamptz DEFAULT now() , col4 text) PARTITION BY RANGE (col1); CREATE INDEX ON partman_test.id_taptest_table (col1);
\d+ partman_test.id_taptest_table Partitioned table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "id_taptest_table_col1_idx" btree (col1) Number of partitions: 0 SELECT partman.create_parent('partman_test.id_taptest_table', 'col1', 'native', '10'); create_parent --------------- t (1 row)
\d+ partman_test.id_taptest_table Partitioned table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "id_taptest_table_col1_idx" btree (col1) Partitions: partman_test.id_taptest_table_p0 FOR VALUES FROM ('0') TO ('10'), partman_test.id_taptest_table_p10 FOR VALUES FROM ('10') TO ('20'), partman_test.id_taptest_table_p20 FOR VALUES FROM ('20') TO ('30'), partman_test.id_taptest_table_p30 FOR VALUES FROM ('30') TO ('40'), partman_test.id_taptest_table_p40 FOR VALUES FROM ('40') TO ('50'), partman_test.id_taptest_table_default DEFAULT
テンプレートテーブルの名前は、その親テーブルのpg_partman設定を見ることで確認できます。
select template_table from partman.part_config where parent_table = 'partman_test.id_taptest_table'; template_table ------------------------------------------------ partman.template_partman_test_id_taptest_table
ALTER TABLE partman.template_partman_test_id_taptest_table ADD PRIMARY KEY (col2);
ここで、データを追加して、再度メンテナンスを実行し、新しい子テーブルを作成すると...。
INSERT INTO partman_test.id_taptest_table (col1, col2) VALUES (generate_series(1,20), generate_series(1,20)::text||'stuff'::text); CALL partman.run_maintenance_proc(); \d+ partman_test.id_taptest_table Partitioned table "partman_test.id_taptest_table" Column | Type | Collation | Nullable | Default | Storage | Stats target | Description --------+--------------------------+-----------+----------+---------+----------+--------------+------------- col1 | bigint | | | | plain | | col2 | text | | not null | | extended | | col3 | timestamp with time zone | | | now() | plain | | col4 | text | | | | extended | | Partition key: RANGE (col1) Indexes: "id_taptest_table_col1_idx" btree (col1) Partitions: partman_test.id_taptest_table_p0 FOR VALUES FROM ('0') TO ('10'), partman_test.id_taptest_table_p10 FOR VALUES FROM ('10') TO ('20'), partman_test.id_taptest_table_p20 FOR VALUES FROM ('20') TO ('30'), partman_test.id_taptest_table_p30 FOR VALUES FROM ('30') TO ('40'), partman_test.id_taptest_table_p40 FOR VALUES FROM ('40') TO ('50'), partman_test.id_taptest_table_p50 FOR VALUES FROM ('50') TO ('60'), partman_test.id_taptest_table_p60 FOR VALUES FROM ('60') TO ('70'), partman_test.id_taptest_table_default DEFAULT
...新しい子テーブル(p50とp60)だけがその主キーを持ち、元のテーブル(p40とそれ以前)は持っていないことがわかるでしょう。
\d partman_test.id_taptest_table_p40 Table "partman_test.id_taptest_table_p40" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | bigint | | | col2 | text | | not null | col3 | timestamp with time zone | | | now() col4 | text | | | Partition of: partman_test.id_taptest_table FOR VALUES FROM ('40') TO ('50') Indexes: "id_taptest_table_p40_col1_idx" btree (col1) \d partman_test.id_taptest_table_p50 Table "partman_test.id_taptest_table_p50" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | bigint | | | col2 | text | | not null | col3 | timestamp with time zone | | | now() col4 | text | | | Partition of: partman_test.id_taptest_table FOR VALUES FROM ('50') TO ('60') Indexes: "id_taptest_table_p50_pkey" PRIMARY KEY, btree (col2) "id_taptest_table_p50_col1_idx" btree (col1) \d partman_test.id_taptest_table_p60 Table "partman_test.id_taptest_table_p60" Column | Type | Collation | Nullable | Default --------+--------------------------+-----------+----------+--------- col1 | bigint | | | col2 | text | | not null | col3 | timestamp with time zone | | | now() col4 | text | | | Partition of: partman_test.id_taptest_table FOR VALUES FROM ('60') TO ('70') Indexes: "id_taptest_table_p60_pkey" PRIMARY KEY, btree (col2) "id_taptest_table_p60_col1_idx" btree (col1)
Add them manually:
ALTER TABLE partman_test.id_taptest_table_p0 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p10 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p20 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p30 ADD PRIMARY KEY (col2); ALTER TABLE partman_test.id_taptest_table_p40 ADD PRIMARY KEY (col2);
既存のテーブルのパーティショニング
既存のテーブルをネイティブ・パーティショニングでパーティショニングする方法は、従来のトリガーベースの方法ほど単純ではありません。上記のように、既存のテーブルをネイティブ・パーティション・セットの親テーブルにすることはできません。ネイティブ・パーティション分割されたテーブルの親は、その作成時にパーティション分割を宣言する必要があります。しかし、既存のテーブルを使用してネイティブにパーティショニングする方法はまだあります。そのうちの2つを以下に紹介します。
オフライン・パーティショニング
この方法を「オフライン」と呼んでいるのは、このプロセスのある時点では、1つのオブジェクトから新旧両方のテーブルにデータをアクセスできないからです。データは元のテーブルから新しいテーブルに移動されます。この方法の利点は、ターゲットパーティションサイズよりもずっと小さなバッチでデータを移動できることです。これは、非常に大きなパーティションセットに対して大きな効率上の利点となります(数千のバッチと数百万のバッチでコミットできます)。また、次のオンライン・パーティショニング・メソッドで説明するように、オブジェクトの名前を変更する手順も少なくなります。
外部キーに関する重要な注意事項
パーティション分割されたテーブルをオフラインにすることは、パーティション分割されたテーブルの外部キーがある場合に現実的に有効な唯一の方法です。新しいテーブルを作成しなければならないので、外部キーも再作成しなければならず、FK関係にあるすべてのテーブルを含む停止をしなければなりません。下記のオンライン・メソッドを使えば、より短い停止時間で済むかもしれませんが、どうしても停止しなければならない場合は、このオフライン・メソッドの方が簡単です。
以下は、元のテーブルと生成されたデータである。
virtualbox-6.1.26からvirtualbox-6.1.28 以降に更新すると、nfsマウントができずにタイムアウトする
現象
virtualbox-6.1.26からvirtualbox-6.1.28 以降に更新すると、nfsマウントができずにタイムアウトする
$ vagrant up Bringing machine 'default' up with 'virtualbox' provider... ==> default: Checking if box 'generic/debian11' version '3.5.4' is up to date... ==> default: Clearing any previously set forwarded ports... ==> default: Clearing any previously set network interfaces... ==> default: Preparing network interfaces based on configuration... default: Adapter 1: nat default: Adapter 2: bridged default: Adapter 3: hostonly ==> default: Forwarding ports... default: 22 (guest) => 2222 (host) (adapter 1) ==> default: Running 'pre-boot' VM customizations... ==> default: Booting VM... ==> default: Waiting for machine to boot. This may take a few minutes... default: SSH address: 127.0.0.1:2222 default: SSH username: vagrant default: SSH auth method: private key ==> default: Machine booted and ready! ==> default: setting dns configuration ==> default: Checking for guest additions in VM... ==> default: Setting hostname... ==> default: Configuring and enabling network interfaces... ==> default: Exporting NFS shared folders... ==> default: Preparing to edit /etc/exports. Administrator privileges will be required... ==> default: Mounting NFS shared folders... The following SSH command responded with a non-zero exit status. Vagrant assumes that this means the command failed! mount -o vers=4 192.168.33.1:/home/hoge/server/xxx/data /home/xxx Stdout from the command: Stderr from the command: mount.nfs: Connection timed out
環境
- HostOS: debian11
- vagrant-2.2.14
- 以下のvirtualboxバージョンで試した
- virtualbox-6.1.28
- virtualbox-6.1.30
- virtualbox-6.1.32
原因
virtualbox-6.1.28 にてホストオンリーネットワークの範囲が制限されたため。
vagarntではnfsにホストオンリーネットワーク(private_networkにて指定したもの)を設定する。
Vagrantfileを確認すると、以下のようになっている。
config.vm.network "private_network", ip: "192.168.33.50"
今回の変更にて、ホストオンリーネットワークはデフォルトで192.168.56.0/21
の範囲に制限されている(IPv6 では、リンクローカルアドレスのみに制限)。
そのため動かない。
なお、この現象はvirtualboxのチケットにて発見した。
しかし変更点を確認すると、きちんと書いてある。。。
Network: More administrative control over host-only network ranges to prevent trouble due to misconfiguration, see user manual. Check updated documentation or your VMs may stop working!
きちんと確認しよう
対策
対策1
ホストオンリーネットワークのIPAddressを192.168.56.0/21
から選択する
私の場合は、IPAddressに特にこだわりがあるわけではないため、この対策を採用した
config.vm.network "private_network", ip: "192.168.56.50"
対策2
/etc/vbox/networks.conf
を作成し許可する範囲を記載する
以下は、10.0.0.0/8
と 192.168.0.0/16 、2001::/64 範囲を許可する例
* 10.0.0.0/8 192.168.0.0/16 * 2001::/64
2021/10/27時点では/etc/vbox/networks.conf
に空行を含むことができない点に注意する。
もう一度 Changelog-6.1 – Oracle VM VirtualBox を確認すると、
Host-only networking: Fixed crash parsing /etc/vbox/networks.conf
6.1.30では空行が含まれても大丈夫になったのかもしれない(未確認)