非同期処理の悩みどころ

「UI向上のために非同期をしなければいけないんだ!!!!!!!」が界隈世間一般の語彙になって10年ぐらいだろうか、今初めてUIで簡単な非同期処理を実装してみてマルチスレッドは難しい問題を身に染みている。

タスクの取り消しは正常系

検索をかけた、でも結果が返ってこない。おそらくユーザーはキャンセルするだろう。 その挙動は、キャンセル可能であれば望ましい。

夜間に更新して日中は一切更新しないデータベースを参照するだけのアプリであれば何も考えずThreadにabort要求を投げて、また新しく検索しなおせば同じ結果になるが、 更新と参照が入り乱れる業務アプリや、動的にキャッシュを作成してやりくりする涙ぐましいリソースの切り盛りをしている基盤は、abortできる処理とできない処理を切り分けないといけないし、恐らくそれは、心地の良いUIを作るうえで障害となる。

ぱっと考えた対策

1. トランザクションのACID特性の強化

更新処理は、abortできるものにする。 例えば「法人を消したら、それに紐づく顧客情報すべても無効とする」という少なくも2テーブルを更新する処理をする際、 処理が中途半端にabortしたら「法人はつぶしたけど、顧客は生きている」などの更新不整合が発生しないようにする。

HTTP <-> client なアプリでは導入しやすいが、WindowsアプリやJavaScriptなメモリを書き換える環境では、グローバルな参照オブジェクトへのCommitタイミングを意図的に調整する工夫がいる。

2. 関連する処理を直列キューにする

顧客情報を更新・参照する処理は顧客情報関連の直列キューなど、処理の順序を保証する。 サバクラだと性能が死ぬが、クライアント内で実装する分には、GUIのロックを下げるだけの効果はある。(どうせ処理待ちは発生するので、ユーザーの自由度を少しでも上げる)

Redshiftでクエリの処理時間を計測する

SQL

-- セッション中、結果キャッシュを切る
-- コンマ数秒で結果が返ってくることはなくなるが、
-- 同じようなレコードを取得するクエリを連続で実行すると、
-- コンピューティングノードで直近ディスクロードした内容が
-- オンメモリになってキャッシュされているか何かで
-- 読み込みが倍以上に早くなり正確に計測できなくなる。
enable_result_cache_for_session TO off;

-- トランザクション
begin;

-- 何かクエリ
select * from pg_users;

-- クエリID指定で、直前のクエリのWLMのクエリ処理ログ確認。トランザクション切っていると、新しく出たのは自トランザクションで切ったものしか見えない。
SELECT userid, xid, query, total_queue_time, exec_end_time FROM stl_wlm_query WHERE query = PG_LAST_QUERY_ID();

Linuxディストリビューションでタイムゾーンの設定を確認する

共通

$ date
Thu May 31 16:25:06 UTC 2012

Ubuntu 16.04.1

$ cat /etc/timezone
Asia/Tokyo

RHEL 6.x / CentOS 7

$ cat /etc/localtime
TZif2
// 中略
JST-9

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を実行するらしく、この項目を使うと画面がバグって使えなくなってしまった。

超悔しい。