Golangハマり備忘録 - structの代入、structのポインタ、ポインタの配列/スライス

値渡しの代入

golangの値のコピーはClangの代入演算子とほぼ同じ。 structの代入はstruct全内容のコピーをとる。(値渡し)

type Sample struct {
  Hoge int
  Huga string
}

var val, val2 Sample     // val == 0x4000、val2 == 0x5000のアドレスがそれぞれ割り当てられたとする
val = Sample{10, "test"} // 0x4000 == {10, "test"}
val2 = val               // 0x4000 == {10, "test"}、0x5000 == {10, "test"}
val2.Hoge = 20           // 0x4000 == {10, "test"}、0x5000 == {20, "test"}

ポインタ

ポインタとは、変数のショートカットを作る方法です。 - 苦しんで覚えるC言語 15章:ポインタ変数の仕組み

  • 変数はメモリに物理配置
  • そのメモリの番地を変数にもたせて間接参照

ハマり:ポインタの配列にローカル変数から何かしらつっこむとき

ローカル変数の&を単純にとると、単純にローカル変数に割り当てられたアドレスをコピって値にする。

type SomeType struct {
  X int
}

func main() {
type SomeType struct {
  X int
}

func main() {
  pary := []*SomeType

  var v SomeType           // メモリアドレス 0x40000000で確保されたとする

  v = SomeType{1}          // 0x40000000 == {1}
  pary = append(pary, &v); // pary == [0x40000000]

  v = SomeType{2}          // 0x40000000 == {2} に上書き 
  pary = append(pary, &v); // pary == [0x40000000, 0x40000000]

  for _, pval := range pary {
    fmt.Println(*pval);
  }
  // {2}
  // {2}
  // と表示。
}

paryに別個にallocateされた領域を食わせる場合、newって明示的に領域をアロケートする。

type SomeType struct {
  X int
}

func main() {
  pary := []*SomeType

  var v *SomeType         // メモリアドレス 0x40000000で確保されたとする
  v = new SomeType{}      // 0x40000000 == 0x50000000
  v.X = 1
  pary = append(pary, v); //  pary == [0x50000000]

  v = &SomeType{2}        // 0x40000000 == 0x50000008、↑と同様のシンタクスシュガー 
  pary = append(pary, v); //  pary == [0x50000000, 0x50000008]

  for _, pval := range pary {
    fmt.Println(*pval);
  }
  // {1}
  // {2}
  // と表示。
}

Javaの参照型・string型の代入は全部アロケート相当なのでそっち慣れてるとハマる。

ローカル変数のポインタを関数でreturnしてもつかえるのも理解するうえでいやらしい。。CやC++とは違い、ローカル変数のアドレスをスタックから分離して全てガベコレできるようにしている。「コールスタック解放≠コールスタック内の局所変数の領域解放」 Javaの実装と同じ要領で、golangの使用感はCテイストだがコールスタックの処理はCでいうポインタ渡しでうまいことしているんだろうか

【Postgresql】【Redshift】SELECTの取得値にNULLを定数で指定するときは型指定する

やらかした。

やらかし

SELECT 
   order.orderno,
   orderdtl.orderidx,
   order.orderdate,
   NULL AS customer_id
FROM
   order
;

上記のように、SELECTで型ヒントなしに NULL を指定すると、型が "unknown" となる。

CREATE VIEW view_orderlist AS SELECT 
   order.orderno::numeric,
   orderdtl.orderidx::int,
   order.orderdate::date,
   NULL::unknown AS customer_id
FROM
   order
;

通常の選択操作では困らないが、特定のSQLでエラーとなる。

SELECT COUNT(DISTINCT customer_id) FROM view_orderlist;
-- postgresql 9.4 :  ERROR:  failed to find conversion function from unknown to text
-- Redshift:[Amazon](500310) Invalid operation: could not identify an equality operator for type "unknown";

エラー内容を読むに、unknown 型から別の型へ変換しようとしたが、暗黙の変換方法が見つからなかった旨のエラーが出力される。

これは、型推論ができない場合の事象らしく、 CASEcoalesce() などで具体的な型になる値が与えられているなら問題がない。

ひとまずの解消法

ビューの定義や列の選択時に型を指定することで解消する。

CREATE VIEW view_orderlist AS SELECT 
   order.orderno,
   orderdtl.orderidx,
   order.orderdate,
   NULL::numeric(14) AS customer_id
FROM
   order
;

何故わかったのか?

手製のアプリケーションでも count(distinct 列) なんて書かないから大丈夫だろうと思いきや Tableauという製品がなにやらデータ取得前に上記相当のSQLを実行するらしく、この項目を使うと画面がバグって使えなくなってしまった。

超悔しい。

Redshiftチューニングメモ(WIP)

仕事でRedshiftのチューニングをすることになりそうなのでメモ

※適宜更新

  • 2018/04/04 更新
  • 2018/04/11 更新

Redshiftのチューニングの前に

色々な概念の紹介

クラスタ

Amazon Redshift クラスター - Amazon Redshift

Amazon Redshift データウェアハウスは、ノードと呼ばれるコンピューティングリソースのコレクションであり、これらはクラスターと呼ばれるグループを構成します。各クラスターは、1 つの Amazon Redshift エンジンを実行し、1 つ以上のデータベースを含みます。

つまり、1機の仮想DBサーバーとみてよい。 クラスタ一つサーバーとして落としたり上げたりしたり、稼働時間=課金額になったりする。 クエリを投げるのもクラスタに対して投げるような感覚。

リーダーノード

https://doc.aws.amazon.com/ja_jp/redshift/latest/dg/c_high_level_system_architecture.html

クラスタの先頭に備えている、おもてっつらのコンピューティングリソース。 SQL構文解析SQL結果キャッシュ、WMLの機能(コンピューティングノード処理の同時処理数制限など)を担当。

ノード、コンピューティングノード

Amazon Redshift クラスター - Amazon Redshift

Amazon Redshift クラスターは、ノードで構成されています。クラスターごとに、リーダーノードと 1 つまたは複数のコンピューティングノードがあります。リーダーノードは、クライアントアプリケーションからクエリを受け取ってクエリを解析し、クエリ実行プランを作成します。次に、これらのプランの並列実行をコンピューティングノードと調整し、コンピューティングノードからの中間結果を集計します。最終的にクライアントアプリケーションに結果を返します。

SQLコンパイルして意味解析して、さあデータとってくんぞ演算するぞのコンピューティングリソース消費担当。 料金表を眺めたことがある方は、以下のインスタンスタイプに見覚えがあるかもしれない。

現在の名前 以前の名前
ds2.xlarge ds1.xlarge、dw.hs1.xlarge、dw1.xlarge
ds2.8xlarge ds1.8xlarge、dw.hs1.8xlarge、dw1.8xlarge
dc1.large dw2.large
dc1.8xlarge dw2.8xlarge

スライス

ノード内の一番最小のリソース割当単位。CPUや記憶領域(HDD・SSDやメモリ)のリソースセット。 1 ノードにつき2スライスある。テーブルの実データは、どれかのスライスに分散される。

料金表には出てこないが、パフォーマンスチューニングでは重要な概念。(詳しくは後述)

システムテーブル STV_TBL_PERM を用いれば、テーブルごとの各スライスの分散状況が見れる。

STV_TBL_PERM - Amazon Redshift

データブロック と ゾーンマップ

Redshiftは、列ごとに、1 MB 区切りのブロックにデータを格納する。(データブロック) ゾーンマップは、各データブロックごとの値の最大値・最小値を保持する。

データブロックは、システムテーブル STV_BLOCKLIST から一覧が見れるらしい。(みたくない)

STV_BLOCKLIST - Amazon Redshift


チューニング:スライスと分散

  • スライスは、コンピューティング資源の最小単位。

スライスへの分散

レコードが挿入された時、テーブルごとに設定された分散スタイルに従っていずれかのノードに分散される。

分散スタイルについて

  • EVEN分散 ・・・ ラウンドロビンで均等に分散される。何も指定しないときはこれ
  • KEY分散・・・ 列を1つ指定し、その値によってどのノードに配分するか決定する。
  • ALL分散 ・・・ すべてのノードに分散する。(データは全ノードで重複して持つ)。INSERTやCOPYのときのトラフィック総量がスライス数分倍増。また、並列演算のリソースも食う。

スライスごとに並列で計算するので、均等に分散ができているほど、特定のスライスに負荷がかかりすぎるなどが発生せず高速に演算できる。

再分散

2つのテーブルを結合するとき、各結合するデータ同士が同じスライスに分散されている場合は、そのまま計算するが、違うスライスにあった場合は、全ノードにばらまきなおす「再分散」が起きる。 explainでSQLの実行計画を見ると、「DS_BCAST_INNER」など「BCAST」付きの結合をしていると出力される場合がある。これは再分散が発生している。 再分散を避ける設計は2つ。

  1. 分散キーを複数テーブル間で同一に設定する
  2. inner 側の結合テーブル(OUTER JOINで NULL にならないほう)をALL分散に設定する。

分散キー

分散スタイルで「キー分散」を選ぶときは、テーブルのうち1つの項目を指定する。 一般的に、レコード数の偏りがないものを選ぶとよい。(受注番号やidなど連番で重複のないもの) 上記の再分散を避けるため、「ヘッダー」-「明細」のようによく結合すうるテーブル間で共通のキーを設定するとよい。

分散キーの効果とサマりかた

分散キーは、テーブルのJOINで結合キーに指定していると効く。 実際に効果があるかどうかは、explainで実行計画をみてテーブル同士の結合時の分散のしかたを見るとよい。

クエリプランの評価 - Amazon Redshift

上記にある通り、 DS_BCAST_INNERDS_BCAST_BOTH が出ると結合のパフォーマンスが著しく悪くなる。

いくつかSQLを作って実行計画を見た感じ、2のテーブルをJOINする程度の簡易なクエリであれば DS_BCAST_INNER は発生せず、 少し複雑になって tableX <-> tableY <-> tableZ <-> tableV で結合するSQLが分散キーで不一致になると割と出るようになる。

※最適化のしかたは模索中


チューニング:ソートキー

  • WIP

チューニング:列圧縮タイプ

列圧縮タイプの選択 - Amazon Redshift

データの物理的な保存形式を指定する。単純な圧縮ではなく、もうデータ配列の方法自体をダイナミックに設定してくれる。(詳しくは公式ドキュメント参照) 最適なものを選べば、SQLの初回実行時のパフォーマンスが大きく向上する。

  • ※2018/04/04に試した見たところ、連続で実行した場合に最適化有り/無しで結果が大差なかった。もしかしたら影響するのはディスクへの保存のされかただけで、コンピューティングノードにオンメモリになってると影響しない?

  • CREATE TABLE したとき、ソートキーを設定したものは一律 raw(圧縮なし)、それ以外のものはlzo(汎用圧縮)になる。

  • 公式ドキュメントでいう「データサイズ」≠「データブロック数」。

    • テーブルごとのディスク使用率を図りたいなら、Redshiftのデータブロック数(1個 1MB)を数えるとよい、とよく記事で見るが、恐らくこの通りではない。
    • 実際、lzoだらけのテーブルを調整した結果データブロック数が1.7倍になったのに、速度が2倍改善した。
    • きちんと裏をとってないので憶測にすぎないが、単純に圧縮してデータブロック内にしきつめるlzoのほうが各値ごとに1データブロック専有するrunlengthのよりデータブロック数の消費が軽い、しかし実際に格納している実データはrunlengthのほうが小さいのでトラフィックが軽い、などが起きたのかもしれない。
  • こちらに関して、素晴らしい記事を書かれた先人がいらっしゃいます。「redshift 列圧縮タイプ」でぐぐってくださいな。


WLM(Work Load Manager)

SQL実行したときのリソース割り当て管理をしてくれる。

Amazon Redshiftのワークロード管理(WLM)を使ってミックスワークロードを実行する | Amazon Web Services ブログ

  • 特定ユーザーグループが同時にSQLを実行できる数の上限
  • 特定ユーザーグループが利用できるメモリの上限

CPU使用率低いのにSQLふんずまってる・・・っていうボトルネックがある場合こいつを見直すとよいかもしれない。 というか、まずこいつを設定して ①アプリ用グループ ②バッチ用グループ ③保守用グループ ④特権ユーザー用グループ とか用意してもらうと下手な事故を避けれるかもしれない。

デフォルトは同時実行数 5 。理論上は500まで設定可能だが、ちょっと大きくするとリソースを使いまくってえらいことになるらしい。 こちらに関して、素晴らしい記事を書かれた先人がいらっしゃいます。「Redshift 同時実行数」でぐぐってください。

クエリがキュー待ちになっているか確認する

システムテーブル STL_WLM_QUERY から、スーパーユーザーなら全ユーザーの実行履歴、通常ユーザーならばそのユーザーの分のSQL実行履歴が見れる。

docs.aws.amazon.com

その中で total_queue_time が 0 でなければ、そのクエリは同時実行数の制限によりキュー待ちとなっている。(単位はマイクロ秒)


VACUUM と ANALYZE

Redshift公式でも、定期実行が勧められている。

VACUUM

DELETEやUPDATEによりできた不使用空間のコレクト、ソートキーに基づいた再ソートを実施する。

VACUUM - Amazon Redshift テーブルのバキューム処理 - Amazon Redshift https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/vacuum-managing-volume-of-unmerged-rows.html

  • 削除フラグがついた無駄領域を削減することで、スキャンのミス率を低減する。
  • ソートキーを設定しても更新をした時点では、新しいデータはソートキー順に並んでいない。ソートキー順に並べなおす。
  • なお、デフォルトだと、ソート率95%以上のテーブルはVACUUMをスキップする

ANALYZE

テーブルの統計情報を更新する。

マージ済みの行のボリューム管理 - Amazon Redshift

  • 統計情報は、各列ごとに作られる。
  • 統計情報はクエリの実行プランに反映。
  • テーブルの全体行数に対する変更された行の割合が、パラメータanalyze_threshold_percent以下であればスキップする。

SQLパーサー作成メモ

備忘録メモ

Postgresql内部実装のSQLパーサー

https://wiki.postgresql.org/wiki/Query_Parsing

raw_parserという関数があるらしい。

postgres/parser.c at 9d4649ca49416111aee2c84b7e4441a0b7aa2fac · postgres/postgres · GitHub

構文別にノードツリーレベルまで展開してくれるが、おそらく意味解析は行われない。 自分は意味解析までほしいのでもうちょっと実装しないといけないが、いい感じにparseしてくれるなら使うかもしれない。

識別子の判別タイミング

Postgresqlは、date など普通はキーワードだろ?という単語も列名にできる。以下は、テーブル定義によってはエラーとならない。

SELECT date FROM ordertransaction;

構文の評価

例えばSQLにおいて、 ( ) は様々な意味合いを持つ。

  • 関数の引数部
  • サブクエリの始点終点
  • 式の結合優先度を決めるだけの演算子
SELECT * FROM (
  SELECT clientid, orderno, orderym, RANK() OVER(PARTITION BY clientid, orderym ORDER BY orderno) AS idx FROM ordertransaction
) Q ORDER BY clientid, idx;

パーサーは前から順に読み込み構文を判断する。 しかし ( ) のように一節を読み切った後に前まで読んでいた式をどう評価すべきか不確定なときがある。

RDBの実装を見たことはないが、おそらくメタメタなパターンマッチングを行わなければいけない気がする。

SELECT count(*) FROM master;
  1. SELECT まで読む → 構文のはじまりはSELECT、INSERT、WITHなど固定のキーワードから始まるため、これはSELECT句で確定
  2. count まで読む → SELECTのあとは、DISTINCTなどのキーワードか、各抽出列の指定(サブクエリ、関数、項目名、定数など)をとりえる。この時点では、count が列名なのか、関数名なのか不明(謎の識別子状態)
  3. ( まで読む → ここで count が関数名だと確定する?
  4. * まで読む → 各抽出列の指定(サブクエリ、関数、項目名、定数など)か、全列を指定するキーワード * をとりえる。パターンマッチングで唯一である * が確定 ...

単なる列指定するところでもサブクエリで複数階層化できるところがメタメタに難しそう。 いい感じの関数のインタフェース考えながら、EBNFとにらめっこして作ることになりそう

Javaおじさん初めてのGolangのinterface所感

延々とだらだらしていてたらいつか死ぬ気がしてきたので、いい加減アウトプットする。 明日続かないとしても今日書けばよいのだ。

Golangさわってみた

Javaを中心にOOPな言語をずっと触ってきて、ちょっと回帰的だがGolangSQLパーサーを描いている。

Golangは色々いわれているように、

  • package指向
  • 構文機能は手続き型レベルのしかない
  • 定番の書き方がググれば出てきてそのとおり実装するのがベストプラクティス

という、スクラッチコンポーネント作るとかどう考えても向いてねえな?な言語。 まあ異文化を痛感するのにちょうどいいだろうと思って、ちょっとリッチなSQLパーサを書いている。

Golangのintafaceに対するJavaの冗長さ

interface いいね。 Javaで例えるなら「にゃーん」と鳴くのを識別するのにオブジェクトを「猫」か「動物」か「発生源」かとか文脈によって違う名前でクラスを付けなければならなかった。 でも、実際コードに起こしてクラスを指定する際、どの文脈使うか決まっている。DIするならともかく、クラスのインスタンス化とか煩わしいことせずともサクッと関数呼び出すだけで直感的に使える。 奇跡的にも、オブジェクト指向原理主義が言うような定義の曖昧さによる述語の揺れは杞憂に過ぎず、熱心は学者先生さんは理論先行頭でっかちだとGopher君が目で訴えている。

実装がんばります

とはいえ、書いているコードがどうもJavaっぽい。 多相の表現が、どうもintafaceを構造体で実装しているだけだ。 しかもどのコードもスクラッチ気味。ググりながら、楽な書き方を覚えるしかないかな。

ちなみに、SQLパーサーが目指すものは例えば集計バッチで流れる一連のSELECT文を4つぐらい食わせて、「このSELECT文のこの項目は、このテーブルのこの項目をこの式で評価したものです。で、このテーブルの結果はまたこのSELECTで・・・」と複数再帰的に辿れるようにすること。 SQLパーサーを探していたが、なんか期待できるレベルまで分解するものがなかった。たぶんPostgreSQLとかCソースをチョチョイってやったほうが早い気がするけど、それはそれで沼っぽいしCあんまわかんないので避けた。

引き続き頑張っていこう。

【.NET】【WPF】Bindingを強制反映するやりかた

遊びでC#を触ったら、まんまと有給を潰してしまった。

毒舌に.NETのWPFのBinding

  • .NET Framework
    • Microsoftが提供している、ウィンドウアプリケーションのランタイム環境。 *ウィンドウ付きのアプリが(裏方にある膨大な概念を意識するのに比べて)いとも簡単に(小さいアプリなら)できてしまう。
  • WPF

  • Binding

    • 従来だと、テキストボックスの文字や有効/無効などのプロパティをコード上の変数とで代入したりこねたりする必要があったのを、XAMLに「{Binding プロパティ名}」って書いたら自動で関連付けてくれるようにする機能。
    • 今回一番悪口を言いたいところ

事故

やりたかったこと

  • テキストボックスのフォーカスが離れた時点で、テキストボックスの値を検証しフォーム上にエラーメッセージを出したい
public partial class ReplaceSetEntryWnd : Window

   public ReplaceSet Value { get; set; } // ビューモデル。定義は割愛

   // フォーカスを失った時に実行するイベントハンドラ
   private void Controls_LostFocus(object sender, RoutedEventArgs e)
   {
      _validateAndShowError();
   }
<!-- Valueのプロパティと連携 -->
<TextBox Name="txName" Text="{Binding Value.Name}" LostFocus="Controls_LostFocus"/>
<TextBox Name="txPattern" Text="{Binding Value.Pattern}" LostFocus="Controls_LostFocus"/>

起きたこと

  • Value.xxxに反映するのは、LostFocusイベントが終わった後。つまり、↑のコードは、テキストボックスからフォーカスが離れた時点では、編集直前のValueの状態が残ったままで _validateAndShowError() を実行してしまう。

対策

  • コントロールから、Binding用のクラスっぽいものを直接取得してコントロール → プロパティに強制送信する。
    private void UpdateSources()
    {
        txName.GetBindingExpression(TextBox.TextProperty).UpdateSource();
        txPattern.GetBindingExpression(TextBox.TextProperty).UpdateSource();
    }
  • ちなみに逆の プロパティ → コントロール強制送信はこれ。
    private void UpdateSources()
    {
        txName.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
        txPattern.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
    }
  • これがマジでイケてない。txNameとかを直接指定したコード組んで、ビューモデルとの連携を疎にする目的で導入したいのに、これができるんならじゃあと欲張ると、結局依存性を増やし記述がごっちゃになるハメに。
  • ちなみに、以下のようにマルチスレッドしながら遅延実行するコードも書いてみたが、.NETコントロールいじるコードで別スレッドからのアクセス違反になった。
new Task(() => {
   Task.Delay(20).Wait();
   _validateAndShowError();
}).Wait();
  • 下手にマルチスレッドするとこうなるの。。非同期でUIのロックを回避したいのに、結局シングルスレッドにせざるをえないってこれもうわかんねえな
  • バックグラウンドプロセス⇔UIの分業と同期をより細かい単位にするために、外スレッドからUI変更をキューイングしていい感じに更新するような仕組みかもしくはFLUXにインスパイヤされた新しいフレームワークがほしい
    • え?なに?Electorn?

2017/09/23 タイトル変えました

分析系システムの業務的なメモ②

ちょっと実務的な部分に絞って2枚目

データ的な特徴

ログデータ

アクセス数やPV数、UserAgentの解析をしたい。とすれば、ApacheとかのWebサーバーのアクセスログから拾って集めて、分析用のDBに片っ端から突っ込むことになる。

Google Analytics などの外部ツールでもこの系のアクセスログは蓄積できるが、内部で集計するためのデータソースにするには情報量の不足や取得までの仕組みの関係で厳しい。

想像のとおり、非常にデータ量が膨大であるため、どういう単位でデータを残すかが非常に悩ましい部分。しかもApacheのログなんかは、何か問題がった時の調査用にログ出力フォーマットを変えたりする。(ヘッダーサイズ出力とか) 柔軟な設計が試される部分。

保守業務で辛いところ

完全に愚痴

業務領域のデータにまつわる変更が全部分析DBに落ちてくる

インプットデータとして、全領域全システムの全テーブルの面倒を見ることになる。

例えば、業務領域の改修でとあるコード値が入力できなくなったとする。(もう使わないしいらないよね?)とすると、そのコード値がリリース時から分析に落ちてこないようになり、後日集計してから「〇〇コードの最新タイムスタンプがこの日で止まってるけどなんなの?この日以降の実績消えたの?」とユーザー問い合わせがくる。

集計処理の変更が滅茶苦茶怖い

5年も続けて保守してきた分析システムの集計はどうなっているだろうか? 一つの答えが、「300行SQLの集計が3個ぐらい連なったバッチ」のような、集計結果のチェーン味噌ダレ。

「とある属性値の選択ルールを、コード最小値から画面上で先頭のものに変えました」なんてのがめっちゃ怖い。レコード数増えたり減ったりしない??集計前後でjoin結果がめっちゃ変わらない?? ここらへんはなるべくポリシー化してできるならポリシーに従った集計を個別に実施して、レイヤ的な集計を実施していきたいが。。

現在、確認範囲を全て確認していっているが、ここらへんうまく自動化できないものかなと考えている。 自動テストしたい。