NURO SGP200Wと ASUS BRT-AC828 二重ルーターを回避する

SGP200W だけの状態だと家のはじだと電波が弱いため別のルーターを購入し対策しようとした。 その際選んだのがコスパが良い ASUS BRT-AC828 。 この組み合わせの事例がググっても出てこなかったため記録に残す。

SGP200W はONU+ルーター機能を有する。 しかしルーター機能は止められない。 当初 いわゆる二重ルーター構成にするしかないと考えたが、 SGP200W の DMZ 機能により二重ルーター構成を回避できることがわかったため DMZ 機能を有効にしてネットワークを構築した。

NURO SGP200Wの設定

WANの設定

通常通り

LANの設定

DMZ

ASUS BRT-AC828 のIPAddressを192.168.1.50とするという設定。あとのステップにて設定する

ASUS RT-AC828の設定

WAN

IPv6

paththrough。を選択。ただ/etc/resolv.confDNSサーバーが書き込まれないため一時的にIPv6をオフにした。多分、IPv6DMZ設定をしていないから?

W: Possible missing firmware /lib/firmware/rtl_nic/rtl8168d-1.fw for module r8169

以下のページを参考に直した blog.treedown.net

環境

現象

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 ネイティブパーティションの使い方サンプル

github.com

この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

環境

原因

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のチケットにて発見した。

forums.virtualbox.org

しかし変更点を確認すると、きちんと書いてある。。。

www.virtualbox.org

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では空行が含まれても大丈夫になったのかもしれない(未確認)

parallelとcurlでjsonをPOSTする

APIを複数処理する必要が出てきて、parallelやxargsを使う方法が思いついた. ただcurljsonをpostする場合、-dオプションをつけてbodyを指定する。 そのbody中のjsonを表現する',"の扱いが分からなかったため備忘録として残す.

$ cat id_list
123
456
789
cat id_list | parallel -j 1 curl -X POST -H \'Content-Type: application/json\' -H \'Authorization: Bearer $TOKEN\' -d \'\{\"id\":\"{}\",\"force\":true\}\' \"https://myurl\"

デバッグの仕方

parallelに-vオプションをつけると、parallelがどう解釈したか表示されるため、このオプションを使う。 parallelの-vオプションの他に、curl-vオプションをつけても良い