numeric型の集約関数の性能向上

# はじめに
にゃーん
この記事は、[PostgreSQL 10全部ぬこ Advent Calendar 2017](https://qiita.com/advent-calendar/2017/pg10-nuko-only) の5日目のエントリです。
今日は[PGCONF.Asia 2017のDay-1](http://www.pgconf.asia/JA/2017/day-1/)。私も勉強のためにいろいろ聞いてきます。

で、今日もちょい地味目のネタですまんな。
#numeric型
PostgreSQLでは何種類か数値型がサポートされている。(参照：[Numeric Types](https://www.postgresql.org/docs/10/static/datatype-numeric.html))
ちょっと紛らわしいが、「数値型全般」をNumeric TypesとPostgreSQLでは表記しているんだが、今日のテーマはその数値型のカテゴリの一つ、[Arbitrary Precision Numbers](https://www.postgresql.org/docs/10/static/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL)（日本語PostgreSQL文書では「任意の精度を持つ数」のデータ型```numeric```に関するもの。

このデータ型はbigintを超える範囲の数を格納でき、さらに小数点以下の値も格納可能。そして浮動小数点データ型と異なり演算の誤差がでない、という機能的には優れもの。そんかし、その代償として、他の数値型と比較すると大きな格納領域を必要とし、演算性能もよろしくないという特徴がある。
OracleのNUMBER型に相当するもの（でいいのかな？）。

# PostgreSQL 10の性能改善内容
numeric型の演算性能については、以前から課題としてPostgreSQL開発コミュニティでも認識されていて、これまでにも幾つかのバージョンでnumeric演算に関する性能改善が実施されている。
そして、PostgreSQL 10でも集約関数の性能改善がされている。対象となる関数は以下の3つ。

* SUM()
* AVG()
* STDDEV()

## どうやってやってるの
ここ数年のPostgreSQL開発ではCommitfestという枠組みを導入して、パッチレビューとパッチの組み込みを実施しており、パッチ投稿やレビューの様子を、自分のような非開発者・英語やC言語を読むのが苦手な人であっても追跡することができる。
今回のテーマになっている集約関数の高速化は、たぶん、このへんの議論の結果だと思われる。

* [Optimizing numeric SUM() aggregate](https://commitfest.postgresql.org/10/684/)

で、そこにパッチのソースがついている。大抵の場合、ソースには詳しいコメントがついており、それを（俺みたいに英語が読めない情弱はGoogle先生のパワーを借りて）読むことである程度、どんな改善をしているか推測ができる。

この改善の場合、コストの高い桁繰り上がりの処理をint32の変数をうまいこと使って、10000単位で遅延させることで性能を改善させているように思える。
SUM(), AVG(), STDDEV()では大量の加算処理が発生するため、桁繰り上がりの処理を毎桁行わずに遅延させることで性能を向上しているのだろうと推測してみた。
（きちんと英語やCコードが読める人がいたら指摘よろしくです）

# 実測
とりあえず手元の環境で、本当に性能が向上しているのか見てみることにする。
## 環境

* Let's note SX4/SSD モデル/メモリ8GB
* VMWare/CentOS 7
* PostgreSQL 9.6/PostgreSQL 10ともにビルドインストール。

お金がないので手元で使えるノートPC上で測定します。

## 検証用データ
こんな感じのクエリで100万件のランダムなnumericデータを生成しておく。

```postgres
COPY 
  (SELECT t.id, (random() * 1000000000000000)::numeric + random()::numeric 
    FROM (SELECT generate_series(1,1000000) as id) AS t) 
  TO '/tmp/numeric.dat';
```

データ内容はこんな感じ

```
1       622149858158082.795957470312715
2       483273191843182.15136305661872
3       206579154357314.880250687710941
4       551065794192255.122989198192954
5       473818496335298.539346074219793
6       910536431707442.00904520973563194
（中略）
999996	721897167619318.856217079795897
999997	125256475992501.740928886458278
999998	5043825134635.436023179702461
999999	260133428964764.634972156491131
1000000	472933328710496.334050448611379
```

これを以下のようなテーブルに突っ込んでおく。

```postgres-console
numeric=# \d numeric_test
Unlogged table "public.numeric_test"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | not null
 data   | numeric | 
Indexes:
    "numeric_test_pkey" PRIMARY KEY, btree (id)
```
## 検証用のクエリと設定
今回は以下の3つのクエリをEXPLAIN ANALYZEを付けて実行し、そのExecution Timeを測定する。

```postgres
SELECT SUM(data) FROM numeric_test ;
```

```postgres
SELECT AVG(data) FROM numeric_test ;
```

```postgres
SELECT STDDEV(data) FROM numeric_test ;
```

なお、このクエリはPostgreSQL 9.6以降ではパラレルクエリの対象になるので、PostgreSQL 10の場合は

```postgres
SET max_parallel_workers_per_gather TO 0;
```
でパラレルクエリを無効化しておく(9.6はデフォルトが0なのでそのまま)。
また、シンプルに集約関数の処理時間の差を測定することになるので、他のPostgreSQLパラメータは9.6も10もデフォルトのままにしてある。たぶん、それで大丈夫。

測定前にcount(*) を使って事前に共有バッファ上にキャッシュしておく。
3回測定し、その平均と分散を取得。

## 検証結果
数値の単位はms。

|バージョン|SUM	|AVG	|STDDEV|
|:--|:--|:--|:--|
|PG9.6	|354.039|357.863|657.945|
|PG10	|263.492|265.170|457.593|

![pg10-nuko-only-05-01.png](https://qiita-image-store.s3.amazonaws.com/0/32626/da9c1217-9482-ae80-20da-f0ac876e231d.png)

100万件程度でも有意な性能差が確認できた。

# おわりに
numeric型の改善はこれからも続くのかな。
なので、以前ほどnumeric型を嫌うこともなくなってくるかもしれない。
とはいえ、格納するデータの値域が、int8で格納できる範囲であったり、誤差を許容できるデータであるならnumericよりはint8やdouble precisionを使うべき、というセオリーは変わらないとは思うけど。

# 参考：該当するリリースノート
本エントリに関連するPostgreSQL 10リリースノートの記載です。

E.2.3.1.5. General Performance

> * Speed up aggregate functions that calculate a running sum using numeric-type arithmetic, including some variants of SUM(), AVG(), and STDDEV() (Heikki Linnakangas)