ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

PostgreSQL 10 でパーティションをまるっと切り替えてみる

PostgreSQL の 10 では宣言的パーティショニングが使えるようになったそうですね (しかし僕は 10 以前でパーティショニングしたことがなかったので感想がない)

これを使ってデータの一括削除、一括追加を実現するために、パーティションの切り替えについて練習します。

準備

Docker で PostgreSQL 10 を用意します

$ docker run --name postgres -p 5432:5432 -d postgres:10

テーブルの作成

こんな感じのテーブルを作ります。ユーザーがいつどこに居たかをひたすら集める謎のテーブルです。

user_id latitude longitude created_at
integer decimal(11, 8) decimal(11, 8) timestamp

パーティションcreated_atrange で切って見ます。

create database testgres;

\c testgres

create table locations (
  user_id integer not null,
  latitude decimal(11, 8) not null,
  longitude decimal(11, 8) not null,
  created_at timestamp not null
) partition by range (created_at);

パーティションの作成

パーティションは partitions スキーマ以下に作っていきます。一秒刻みで適当なデータを作成します。 20181010 と 20181011 のパーティションを作成しました。

create schema partitions;

create table partitions.locations_20181011 partition of locations for values from ('2018-10-11') to ('2018-10-12');

insert into partitions.locations_20181011 select
  (random() * 10000)::int,
  130 + (20 * random())::decimal(11, 9),
  20 + (20 * random())::decimal(11, 9),
  generate_series('2018-10-12'::timestamp - '1 sec'::interval, '2018-10-11', -'1 sec'::interval)
;

create table partitions.locations_20181010 partition of locations for values from ('2018-10-10') to ('2018-10-11');

insert into partitions.locations_20181010 select
  (random() * 10000)::int,
  130 + (20 * random())::decimal(11, 9),
  20 + (20 * random())::decimal(11, 9),
  generate_series('2018-10-11'::timestamp - '1 sec'::interval, '2018-10-10', -'1 sec'::interval)
;

クエリしてみると、ちゃんとパーティションを使った検索が行われているのがわかります。

explain select * from locations where created_at = '20181010 12:00:00';

                                    QUERY PLAN
-----------------------------------------------------------------------------------
  Append  (cost=0.00..1716.00 rows=1 width=28)
    ->  Seq Scan on locations_20181010  (cost=0.00..1716.00 rows=1 width=28)
          Filter: (created_at = '2018-10-10 12:00:00'::timestamp without time zone)
(3 rows)

パーティションの切り替え

f:id:ushumpei:20181015011230j:plain

以下が概要になります。

  1. 普通のテーブルとして locations_20181011 を作成します。
  2. 現在のパーティション partitions.locations_20181011locations から Detach します
  3. 新しいパーティションとして locations_20181011locations に Attach します
  4. 必要なくなった partitions.locations_20181011 を削除します
  5. (跡片付け) 新しいパーティションとして Attach した locations_20181011partitions.locations_20181011 にリネームします

手順 3, 4 と、なるべく alter table locations をまとめて行うことで、テーブルロック時間を少なくしていこうと思います。

新しく locations_20181010 を普通のテーブルとして public スキーマに作成します。(すでにあるものと被らなければ他の方法でもいい)

ここでは check 制約を追加して、Attach される際のパーティションの制約チェックを先立って行っています。これがないとテーブルロック時間が attach の際に増えてしまうそうです。

create table locations_20181011 (
  user_id integer not null,
  latitude decimal(11, 8) not null,
  longitude decimal(11, 8) not null,
  created_at timestamp not null,
  check (created_at >= '2018-10-11' and created_at < '2018-10-12')
);
insert into locations_20181011 select
  (random() * 10000)::int,
  130 + (20 * random())::decimal(11, 9),
  20 + (20 * random())::decimal(11, 9),
  generate_series('2018-10-12'::timestamp - '1 sec'::interval, '2018-10-11', -'1 sec'::interval)
;

パーティションの切り替えを行います。現在使用されている partitions.locations_20181011 を切り離し、新しく作った locations_20181011 をくっつけます。

begin;
alter table locations detach partition partitions.locations_20181011;
alter table locations attach partition locations_20181011 for values from ('2018-10-11') to ('2018-10-12');
commit;

いらなくなったパーティションの削除、跡片付けとして作成したパーティションのリネームを行います。

drop table partitions.locations_20181011;
alter table locations_20181011 set schema partitions;

以上です。

感想

  • テーブルロック怖い
  • トリガー怖い
  • インデックスも適切に

iOS 版 Chrome の <input type="date">

解決策とかわかったら書きますが、なんなんでしょうかこれ?

iOSChrome の input type="date" 入力時、何かの条件を満たすと Picker から「消去」が消える


ScreenRecording 07 03 2018 01 51 23

  • iOS 11.4
  • Google Chrome: 67.0.3396.87 (Official Build) stable (64 ビット)

WebAssembly って?

ブラウザからアセンブリ言語を実行できる仕組みが WebAssembly という理解です (雑魚)。

とりあえず、動かしてみます。

Emscripten

C/C++ から WebAssembly で実行可能なアセンブリコンパイルするツールだそうです。C/C++ に特に思い入れはなく、仕事で使ったことはないですが、例えば C/C++ で書かれたライブラリを JavaScript ライブラリに変換するとかできるのかなーと思います。

C/C++からWebAssemblyにコンパイルする を参考に emscripten をインストールします。(すっごい時間かかりますね)

使う

適当な cpp ファイル (main.cpp) を作成します (これ c といってもいいのでは)

#include <stdio.h>
#include <emscripten/emscripten.h>

extern "C" {
  int main()
  {
    puts("Hello, World");
  }

  int myFunction(int x)
  {
    return ++x;
  }
}

em++ main.cpp -s EXTRA_EXPORTED_RUNTIME_METHODS="['ccall']" -s EXPORTED_FUNCTIONS="['_main', '_myFunction']" を実行します。ここでは

  • クライアント側から関数を呼び出す Module.ccall を使用するために EXTRA_EXPORTED_RUNTIME_METHODSccall を指定します。
  • 呼び出せる関数を EXPORTED_FUNCTIONS で指定します。関数名に _ プレフィックスをつけなければいけないそうです。

以下のファイルが生成されました。

  • a.out.js
  • a.out.wasm
  • main.cpp

index.html を作成してそこから a.out.js を読み込みます。

<html>
  <head>
    <script src="a.out.js"></script>
    <script>
      function callMyFunction() {
        var count = document.getElementById('count')
        var nextCount = Module.ccall('myFunction', 'number', ['number'], [count.innerText])
        count.innerText = nextCount
      }
    </script>
  </head>
  <body>
    <p>Count: <span id="count">0</span></p>
    <button onclick="callMyFunction()">Call C++ Function</button>
  </body>
</html>

しかしこれでは動きません。http 経由で配信しなければいけないそうです。express で配信するようにします。yarn init && yarn add express でサーバーを準備します。index.js を以下のように記述しました。

var express = require('express');
var app = express();

app.use(express.static('public'));

app.get('/', function(req, res) {
  res.sendFile(__dirname + '/public/hello.html');
});

app.listen(3000);

またディレクトリ構造を少し変えます。コンパイルしたものは public 以下に放置しています。

.
├── index.js
├── node_modules
├── package.json
├── public
│   ├── a.out.js
│   ├── a.out.wasm
│   ├── index.html
│   └── main.cpp
└── yarn.lock

サーバーを node index.js で起動すると localhost:3000 でアクセスできるようになます。

f:id:ushumpei:20180619104114g:plain

感想

また何かに入門だけしているやつです

Pro Git 2nd Edition 読んでる

最近手が痛くてプログラミング時間を少々減してて、久しぶりに本でも読もうかという気分になっています。

git ちゃんとわかっていなかったので、 Pro Git 2nd Edition を読み始めました。ちょっと面白いことがあったのでメモします。

前提: git は差分ではなくファイルのスナップショットを保持している

git が既存の VCS (Version Control System) と大きく異なった点として、ファイル変更履歴の管理方法が 変更されたファイルの差分ではなく変更されたファイル全体のスナップショット であることだと書かれています。自分の理解だと、ファイル容量などを考えると変更差分を保持していた方がいいと思いますが、ぶっちゃけテキストファイルが主だしまあスナップショットでも大丈夫なんだろうなー、くらいの軽い感じでした。しかしこのことが結構重要で、 git がめちゃくちゃ速い理由につながっているようです。

リポジトリ内のファイル全体のスナップショットが作成されるのは commit 時で、次の要素が .git/objects 以下にファイルとして作成されます。ファイル形式は blob でファイル名はそのデータのハッシュ値です。

  • blob オブジェクト: 変更後のファイル
  • tree オブジェクト: ディレクトリツリー。変更対象のファイルを持っていたディレクトリに対して、ツリーのノードの参照先 (tree, blob オブジェクトのハッシュ値) を書き換えた tree オブジェクトが作成される。(毎回リポジトリルートの tree は更新される)
  • commit オブジェクト: 新しく作成されたリポジトリルートの tree への参照と、parent commit オブジェクトへの参照と作成者情報やコミットメッセージ。

言葉だとややこしいですが実物は以下のようになります(ハッシュ値は適当です)。

blob オブジェクト: .git/objects/f5/83c304ea36b6fa554eb01381e781b04e45477f

# Pro Git

URL: [https://git-scm.com/book/ja/v2](https://git-scm.com/book/ja/v2)

tree オブジェクト: .git/objects/20/4bfe00b89a265e7c16e8688a90dfb86e52c5eb

100644 blob f583c304ea36b6fa554eb01381e781b04e45477f README.md

commit オブジェクト: .git/objects/31/ba323616cc8cbc543882c5a4eef6aa95eef803

tree 204bfe00b89a265e7c16e8688a90dfb86e52c5eb
author ushumpei <mail@example.com> 1528220889 +0900
committer ushumpei <mail@example.com> 1528220889 +0900

Initial commit.

(.git/objects 以下のファイルは git cat-file コマンドに -p オプションをつけてハッシュ値の頭から 6 文字を引数にして実行すると、中身を閲覧することができます: .git/objects/f5/83c304ea36b6fa554eb01381e781b04e45477f なら git cat-file -p f583c3)

要するに?

要するに私が面白いと感じたのは、特定のコミットに入っているファイルを取り出したいときは、commit オブジェクトのハッシュを使って、

commit オブジェクト -> tree オブジェクト -> (ツリーの探索) -> blob オブジェクト

という風に取り出してくれるので、めっちゃ速い、すげー、ということです。これは過去のコミットでも、分岐してある程度進んだブランチのコミットでも同じように行われます。(多分)

感想

「git 速い」って何と比較して、というと svn なのですが、ベンチマークとか取っていないので (どう比較するか謎ですが) 怒られそうですね。「考え方の変化で処理が変わった」ということにテンションが上がっただけです。

React 360 で vnc クライアントを作って Oculus Go のブラウザから自分のPCを見る (未完成)

注意: チラシの裏です

こんにちは

Oculus Go に Mac 用のリモートデスクトップがなかったので (現在: 2018/05/31) なんとかできないかと思って色々やっています。理想的には HMD つけながら無線キーボードでお布団に入りながらコーディングしたいという思いがあります。

でも全然完成までの道のりが見えないので、現状の整理のためのメモを書きます。

(これ以降進展なし)

なんで作ってるんですか?

Oculus Go で使える Mac 用のリモートデスクトップアプリが見つからなかったためです (有料のやつある?)。ブラウザで画面共有できる Web サービスもあるのですが、仕事のコードとか書くことを考えるとローカルネットワークで完結するのが気分的に一番良いんじゃないか?と思ったため作り始めました。(自作すること自体はそれはそれでリスクですが)

Mac にはデフォルトで「vnc」と呼ばれる (?) リモートデスクトップのサーバーが入っているため、それ用にクライアントのコード書いたら良いんじゃないか、ということで vnc 周りを調べつつ作っています。

なんで React 360 なんですか?

  • Android studioAndroid Mobile SDK: C++ 混じってて読めなかった
  • Unity: 入ってるけど使ったことない
  • React 360: 既存の知識使ってできる

という消極的な理由からです。 1 週間くらいで終わらせたかったのでがっつり学習する必要があるものは避けました(終わってないけど)。今考えると Unity は空間すでにあるし知見も多いため一番良い気がしてます。

どんな感じで作りますか?

  • Mac
    • 8080 port: http で React 360 のページを返すサーバー
    • 5900 port: 標準の vnc サーバー
    • 5901 port: websockify というライブラリを使用して WebSocket の 5901 への接続と、 vnc の 5900 への接続をつなぐ

という構成を元に、

  1. 同じローカルネットワーク内のブラウザから WebSocket で Macvnc サーバーと接続
  2. vnc サーバーから送られてくる画面データを React 360 内の canvas に描画
  3. ブラウザのキー入力やポインター移動イベントをサーバーに通知する

していくように作っています。

できたこと

  • React 360 の平面オブジェクト (Plane) に canvas を貼り付ける
  • RFB 3.8 プロトコル (vncプロトコル) を実装してサーバーとの接続を確立する
  • ブラウザでサーバーから画面データを受信する
  • 受信したデータを canvas に描画する

できてないこと

  • ブラウザのイベントをサーバーに通知する
    • ブラウザ -> サーバー への通信は、初めはいらないと思っていたんですがユーザーのサインインが必要なので、ないとログイン画面を延々とみていなければならなくなります
  • 画面が全更新になっているので差分更新で済むようにしたい

どうします?

  • RFB プロトコルのコードを整理する
    • RFB プロトコルが接続を確立するまでに複数回のやりとりが必要になるのですが、全てのメッセージを長い onmessage で受け取っていろんなフラグで if else と長々と書いているので破綻してる
    • ドメインの知識が増えて来たのでちゃんと書いてみたい
    • Redux を React 以外の文脈で使ってみたい
    • オレオレイベント emitter 消す
  • React 360 を一旦やめる
    • canvas を貼り付けた Plane の取り扱いが不安定すぎるので単純な html として書き直します (PC めちゃくちゃ熱くなるし)
      • React でも良い気がするけど
        • ブラウザ vnc クライアントの vnc.js とかあるっぽいけど

一応リポジトリ: GitHub - ushumpei/VncClient: Vnc client for browser

感想

遊んでないで仕事探す