親愛なるジョンからの手紙

雑記です。ゲームとか漫画とかプログラミングとか。

PostGISでST_Intersectsした際にIndexがうまく使われていなかったのをダーティに解消した話

結論

ST_Intersectsでデータベース内にないパラメータを使うときは、先に&&演算子でBounding Box同士での比較を行っておくと、インデックスがうまいこと使われて早い。

postgresql - PostGIS doesn't use spatial index with ST_Intersects - Geographic Information Systems Stack Exchange

環境

PostgreSQL 11.1系 PostGIS 2.4系 DBサーバ用の仮想環境:CentOS 7系

詳細な内容

PostGIS使って、データベース内にある地理情報から、ある二次元円内のGeometry型地理情報をぶっこぬいてくるとき、以下のような感じで行ってます。

SELECT
  geometry
FROM
  geometry_table
WHERE
  ST_Intersects(geometry, ST_Buffer(ST_SetSRID( ST_POINT( 139.76719 , 35.6811 ) , 4326 )::geometry, {radius}));

ST_Bufferで中心点と半径を指定した円を作り、作成した円とデータベース内にあるジオメトリの接触判定をし、これを以て範囲とする、というやり方です。

ところがどっこい、プロダクトコードで検証してみたところこのSQL(実際に試したクエリは業務上明かせないためぼかしてあります。上記のSQLをサブクエリ内・別のWhere条件付き、Inner Join利用で行ったものとして考えてください)が遅く、EXPLAINしてみると「これgeometryに張ったgistインデックス使われてなくね……?」という結果に。

これを解消するためにいろいろと試してみてEXPLAINしてみたのですが、内部的にインデックス利用していなさそうというか、そもそも何しているかまったくわからん、という感じな実行計画しか出てきませんでした。

Merge Join  (cost=21135.61..30887.07 rows=917 width=87) (actual time=374.015..1207.340 rows=11 loops=3)
                 Merge Cond: ((geometry_table.primary_key)::text = (other_table.foreign_key)::text)
                 ->  Parallel Index Scan using geometry_table_pkey on   (cost=0.42..71274.59 rows=12823 width=2441) (actual time=335.955..1160.354 rows=11 loops=3)
                       Filter: (((primary_key)::text = ($1)::text) AND st_intersects(geometry, $2))
                       Rows Removed by Filter: 61382
~~~~~~
~~~~~~
 Planning Time: 1.213 ms
 Execution Time: 1273.504 ms

というわけで、この記事は上記現象を解消するためにやったこととか考えてみた仮説とかを並べていく記事となります。

結局どういう結果になったかは一番上を見てください。

仮説1: サブクエリ内で関数使うと実行計画がいい感じに読んでくれない

最初はコレを考えていたんですが、SQLを変えてゴリゴリと実行計画読んでみた感じ、特に表示や速度に代わりはありませんでした。

プランナがキャッシュをもとに計画を立てている等々も考え、VACUUMしたりしながら試してみたのですが、ほぼほぼ誤差な感じ。

仮説2:PostgreSQLPostGISのバージョンの相性が悪い

PostGISPostgreSQLのエクステンションとなります。そのため、バージョン相性がよろしくないと、プランナに正しくインデックスの優先順位が伝わらなくなるのでは?という仮説を立てました。

なお、現状本番環境で動いているプロダクトがあるので、正しくても修正が大規模になります。検証するにしても他の手段試してからの後回し。

仮説3:別インデックスがあるとそちらを優先するようになっている

www.finds.jp

3.3. 空間クエリを作成するにはどうするのですか? 他のデータベースクエリを作るのと同じで、返り値、関数、テストのSQLの組み合わせです。

空間クエリでは、クエリを作成する際に心を平静に保つための重要な二つの問題があります。 一つは、使用することができる空間インデックスがあるか、です。もう一つは、多数のジオメトリを相手に計算量の多い計算を行っているか、です。

一般的に、フィーチャーのバウンディングボックスがインタセクト (交差)しているかをテストするインタセクト演算子 (&&)を使います。&&演算子が便利な理由は、速度向上のために空間インデックスが付けられているなら、&&演算子は空間インデックスを使うからです。これによって、クエリの速度はとてもとても速くなります。

また、検索結果をより狭めるために、Distance(), ST_Intersects(), ST_Contains(), ST_Within() などといった空間関数を使うことでしょう。ほとんどの空間クエリは、インデクスのテストと空間関数のテストを含みます。インデクスのテストで返ってくるタプルを、求める条件に合致するかもしれないタプルのみとして、タプルの数を制限します。それから、空間関数で確実な条件のテストを行います

ST_Intersectsの中身見た感じその通りになってはいるんですが、実際の動作を見る限りだとそうは見えませんでした。

で、原因として思いついたのが、「別のインデックスでスキャンしたら、gistのスキャンはスキップするように実行計画がプランニングされるんじゃね……?」という仮説。

これは別に指定しているAND条件を外すと、そもそもの要件を満たさなくなるので、検証してもそれをプロダクトに反映させるためのSQL再設計が必要になります。というわけで同じく手段としてはあとまわし。

仮説4: そもそもクエリが全部クソ

うるせ~~~~~~~~~!!!

知らね~~~~~~~~~~~!!!

PostgreS

QL

結局どうしたか

方法考えつつ空間インデックス周りの資料読んでて目に留まったのが以下の文です。

postgis.net

15.2. Index-Only Queries Most of the commonly used functions in PostGIS (ST_Contains, ST_Intersects, ST_DWithin, etc) include an index filter automatically. But some functions (e.g., ST_Relate) do not include and index filter. To do a bounding-box search using the index (and no filtering), make use of the && operator. For geometries, the && operator means “bounding boxes overlap or touch” in the same way that for number the = operator means “values are the same”.

Bounding Boxはざっくりいうと対象地理上図形に外接する四角形なのですが、&&演算子を使うとこのBounding Boxで地理上図形の重なり判定を行った上、インデックスも使ってくれる模様。 なので、脳死で対応するため、&&で重なり判定をしてざっくり削ってみた後、ST_Intersectsを利用してもっと正確な重なり判定をする、という手法をとってみました。

これは仮説3でも同様のことを行っているのですが、どうも関数内部に指定された&&では、プランナがうまいこととってくれないパターンもあるのかもしれません。 ですが、今回はもうできればいいやくらいの対応でおまじない的にやってしまう方がよいと判断しました。

というわけで、実際にやったものとは違いますが、以下のような形に変更し、明示的に&&で絞った後にST_Intersectsする形に変更しました。

SELECT
  geometry
FROM
  geometry_table
WHERE
  geometry && ST_Buffer(ST_SetSRID( ST_POINT( 139.76719 , 35.6811 ) , {srid} ), {radius}) AND
  ST_Intersects(geometry, ST_Buffer(ST_SetSRID( ST_POINT( 139.76719 , 35.6811 ) , {srid} )::geometry, {radius}));

結果として以下のように、うまいことgist使ってくれている形になりました。

   ->  Hash  (cost=109.16..109.16 rows=8 width=2377) (actual time=14.110..14.110 rows=36 loops=1) 
         Buckets: 1024  Batches: 1  Memory Usage: 42kB
         ->  Bitmap Index Scan on idx_geometry_table_geometry  (cost=0.00..4.47 rows=25 width=0) (actual time=0.089..0.089 rows=46 loops=1) 
               Index Cond: (geometry && '{buffer}'::geometry) 

~~~~~~
~~~~~~
 Planning Time: 1.732 ms
 Execution Time: 33.898 ms

まとめ

仮説検証せずにとりあえず採用するのはよろしくないが、時間がない場合は&&利用した絞り込み安定なので脳死でやっておけ。

なお調べなおした結果、もう語りつくされたテクニックらしい。車輪の再開発~。 blog.chizuburari.jp

安定です。10年近く?変わらないこの安定感。いぶし銀です。

まあ、その10年近く前にST_指定があったかどうかは覚えてないですが…。

簡単に説明すると、前半の&&は、thegeomカラム内の各ジオメトリの外接矩形で、インデクスによる篩をかけています。

その後のST_Intersectsで、正確な交差判定を行ってます。

この辺はもう定石なので、いちいち&&しなくてもST_Intersectsをする際には自動で&&処理をするようにするとかしないとかいう話があった気がしますが、実際に対応したかは未確認です。

もし最新版でしていたとしても、PostGISのバージョン依存しないようにするなら、やっておいて損はないかなと思います。

Fukabori.fmをいろいろ聞いたので自分なりにアジャイル云々の文脈についてまとめてみた

5個ほど聞いたので自分なりにまとめてみる。 大体アジャイルとかリーンとかスクラムとかその辺の文脈。

そもそもFukabori.fmってなんぞや

Fukabori.fm は、何らかの技術に詳しいゲストを読んで、その技術の入門から応用的な内容まで深掘りしていくPodcastです。 fukabori.fm

聞いたのはこのへん fukabori.fm fukabori.fm fukabori.fm fukabori.fm fukabori.fm

どんな文脈でまとめるのか

最近お仕事してて、結局「何がやりたかったんだっけ」「どういう課題を解決したかったんだっけ」というのを置いておいて、とりあえず仕様書の通りに作るのがベター、みたいな決に落ち着くことが多く、「このままじゃいかんなー」となんとなく思い始めたため、脱現状の足掛かりとするために聞き始めてみました。

そのため、「なんでこんな働きづらいのか、無意味っぽいもの作ってる感じになってるのか」→「働きやすくするためにどうすればいいのか、エモくやってくためにはどうすればいいか」みたいな文脈でつなげていきます。

意識高い系エンジニアっぽい文脈を自分なりに噛み砕いてみる、とも言う。

開発現場を取り巻く現状

部品としてのソフトウェアという幻想

  • 日本のソフトウェア業界は古くから上流による設計工程を行い、作成された仕様書や設計書を下流にある下請けに流して実装し、それを組み合わせることによりシステムを構築してきた。
    • hiranabe氏はこれに対し、鉄鋼業における鉄骨のメタファーを用いてなぜこのような構造になっているかと、そこで生じる問題について述べている。
    • 鉄骨は「産業の米」とも呼ばれる水平ドメイン。部品として製造すれば、鉄骨部品を利用できる現場・ビジネスはどこに対してでも流用できる。元来、ソフトウェアはこのような共有化できる部品としての流用を想定されてきたため、その思想が定着している。
  • ところがソフトウェア(特に業務システム)は水平ドメインではなく、ビジネスや使用法、経営判断にかなり依存する垂直なドメインがしばしば含まれている。

ビジネスを取り巻く社会の高速化

  • ビジネスが構想から展開されるまでの速度が上がってきており、上記のような設計と実装が分断された構造では間に合わなくなってきた。
  • 水平構造で仕様書を現場に渡し、その通りに実装するという流れでは追い付かなくなってきた。
    • 現場のミッションが仕様書通りにやり遂げる、納期通りに収めるというミッションではドメインを理解している現場の実感を計画にフィードバックすることができなくなってきた。

計画(意思決定層)と実装の距離が遠い

  • 見積もりの段階では情報に乏しく、本来は実装に応じてフィードバックしていく必要があるが、納期、コスト優先だと、上層に情報をエスカレーションしていくには遠すぎる。
  • 仕様書・チェックリスト等々のドキュメントでのやり取りでは、ドキュメント自体の時間が停止しており、また、暗黙知とされる「ここで引っかかった時にどうするか」等々の情報がカットされるため、現場と計画の持つ情報に差異が出てくる。

    • hiranabe氏の将棋のメタファー「将棋のように、ビジネスは一手一手で止まっていってはくれない」
    • hiranabe氏の現代戦争のメタファー「戦場に入ったらどんな完璧なプランがあったとしても、プランではなく戦場を見なきゃいけない」「情報を得続ける(計画)、行動に移して何かの形にしていく(実装)を同時並行でいかなければ無理」
    • 例: バグ収束曲線
      • テスト開始時期はかなり多くのバグが出て、そこから減っていく、という、時間に応じたバグ量の指標
      • テストコード・コードレビューと同時に開発されているので、軽微なバグはつぶされている。
      • 本当に立ち向かうべき不具合・コミュニケーションロスというのは「思い違い」「インターフェースの不整合」により発生するバグで、収束曲線で発生するものよりずっと少ない。(twada氏)
  • 部署の分断・Sierとユーザー企業との物理的距離などは、リーンにおける「工程の引き渡しの無駄」を生む。

アジャイルリーンスタートアップスクラム

  • 上記の構造的欠陥・コミュニケーション的欠陥に対する解法提唱。
  • スクラムとかもぶっちゃけどうでもいい。みんなが楽しく成果出せればなんでもいい」「いかに顧客が求めるものを最速でデリバリーするか?が重要」「バズワードに振り回されているなら、軌道修正が必要」(ykmc09氏)

アジャイル

  • ウォーターフォールへのアンチテーゼ
  • 価値観を提供する。
    • どこに則って、何を解決しようとしているのかというのでコンセンサスをチーム内でとり、そこに向かっていくという指標。

リーンスタートアップ

スクラム

  • リーンが全体最適を行い、小さな仮説検証を行っていくライフサイクルの方法論を提供するものとすると、スクラムは開発が仮説検証を素早く回し、リーンにおけるリードタイムを減少させるための手法。
  • ビジネス・開発の二重サイクルがあったとして、全体の流れを規定するのがリーン、開発内での流れを規定するのがスクラム、というイメージ。
  • 暗黙知暗黙知のまま伝達する。

組織構造の変革

  • アジャイル・リーン・スクラムは、全体最適を達成するのにフローを全般的に変えるため、組織的な構造改革が必要になることがある。
  • それらを実行するために、意思決定層・管理職・マネージャー層に手法をリーチさせる、知識を共有する必要性がある。

兼務・作業の詳細分担・マルチプロジェクトのデメリット説得

  • 「兼務しただけで効率が二割下がる」「本当に分担した方が効率いいのか説明してっつって説明できる人とかいない」(TAKAKING22氏)
  • 「分業した方が効率いいじゃんはつらい。効率ってなんだよ」「効率と効果どっち取るんですか?」「ビジネスは効果(長期的に強い組織を作る。プロダクトの成功)を出すことが目的」「別に効率(プロダクトを出す速度)は目的じゃない」(ykmc09氏)

失敗を許容する組織の醸成

  • 既存の情報システム部門と別に、イノベーション部隊を建設すること。イノベーション部隊を既存の進捗管理から切り離し、企画と開発を一体化すること(hiranabe氏)
    • 通常企業は年次の予算、売上を月次で追うが、イノベーションはそのとおりに行かない。報告とかできないもんはできない
    • 組織の形態を全体で変えたり、予算や企画の構成を変えるよりは楽。
  • 失敗した際の巻き返しはどうすればいいか
    • 「傷の浅いうちに、ダメなものを見つけてサービスを畳めるのも成功である」(ryuzee氏)
  • 「出島戦略」

部分的・実験的導入

  • 自分の部署にスコープを絞り実践し、徐々に広げていく(ryuzee氏)
  • 「文化を変えるには、実験的に小さく始め、信頼貯金を使いながら段階的に展開させていく。おおよそ2~5年かかる」(twada氏)
  • 「お試し期間」(ykmc09)
    • ものを変えるにはエネルギーがいるが、試してみるというのは変える一歩目として効果的
    • 「失敗したらやめて戻せばいいじゃん」
  • 「説得する必要なんてない。普段仕事上のコミュニケーション取ってるのの延長線上なんで」(TAKAKING22氏)
    • 「お前を説得することでお金が儲かるのかよ。知らないならお前らが学びに来いよ」

まとめ

色々異なる文脈の話を一本のラインでまとめようとしてるため、かなりグダついた流れにはなってますが、おおよそ合ってるかなとは思ってます。 ぶっちゃけアジャイルとかスクラムとかその辺の文脈のことを「人間はテレパシー使えねえんだぞ、だから情報を整備しろ」という手法だと思ってたので、この辺なんとなく頭に入ったかなあと勝手に満足してます。

でも雑理解なので詳しい人からマサカリバシバシ飛んできそう。

Spring MVCのService層と真剣に向き合ってみた話

はじめに

プログラミングポエム記事のため答えはありません。

概要

Spring MVCはその名の通りみんな大好きMVCを採用しており、アプリケーションをいくつかの層に分けて組んでいくのですが、この層の中にはサービス層というものがあります。(エンタープライズ系のMVCによくある気がするけど名前あるの?MVC2?) サービス層は、ドメイン層におけるオブジェクトのロジックを表現するためにどうしても手続き的な処理をせねばならんところを書いていく層(マサカリポイント)なのですが、この辺に何を書くかは結構ざっくりしちゃってる印象があります。

なんでこの辺どうすればいいかちょっくら考えてみました。

現状のSpring MVCについて思うこと

ぶっちゃけTERASOLUNA Frameworkに沿ってやってけばいいんじゃないかなーと思ってます。 といってもそのまま使うんじゃなくて、基本的にはSpring BootのプロジェクトをSpring Initializrで適当に作って、あとは必要そうなところだけ沿っていけば百点満点とは言わずとも悪くはない構成になるとは思います。他にもデータベースアクセス層をJQQQとかDoma2に変えるとか、バッチをSpring Batchでやるとかちょこちょこやりやすいように変更を加えていく感じ。

TERASOLUNA FrameworkにおけるService層

このへん見て

Service層で迷う点

Spring MVCにおいて@Transactionalによるアノテーションベースのトランザクションをかけようとした場合、Controllerから呼び出すServiceクラスのメソッドは一つにしなければならないのですが、悲しいことに処理は往々にして単一でシンプルなものにならず、複数のServiceを呼び出したくなる時があります。

この時、Controllerから直接呼び出すServiceクラスをトランザクションのコンテナとして扱い、その中で他のServiceを呼び出してやるという手法を取ることが多いのですが、これ本当にいいのか?と最近思うことがあります。

例えば、アグリゲーションの中にアグリゲーション入れようみたいなネストしたオブジェクトをデータベースから持ってくるパターン。いや設計クソだから変えろよみたいなのはまあそれはそうなんですが、やらざるをえん時もあるんですよ。辛いことに。

こういう場合、ServiceがServiceを呼び、さらにそのServiceがServiceを呼びと、TERASOLUNA FrameworkにおけるSharedServiceパターンだとどれがSharedServiceでどれがApplication Serviceだよみたいな感じになりかねんなー、と思ってます。

じゃあどうすんの?

Service層にAtomic Designの考え方適用できないかなー、とぼんやり思ってます。

Serviceに粒度のレベルを設け、パッケージ単位で厳密に区切っちゃうことでうまいことService層をまとめ、下位のServiceは上位のServiceからしか呼べない、という、より詳細化したSharedServiceをパターンとしてまとめられないかということを考えてるのですが、じゃあそれどういう単位で切るのとか、アプリケーションの要綱によって必要かどうかわからんとか、そもそも最小粒度のServiceってModelに突っ込むべきなんじゃねーかとか、ドメインモデルの設定が狂ってるからそんなことになるんだよとか、パターンにするにはわりと限定的な状況にしか適用できない以上、うまいこと設計パターンに落とし込むにはいろいろと引っかかりを覚える部分があります。

パターンとは言わないまでも一つの考え方として以下の感じでどうだろう、と考えています。

  • Serviceの基本単位はShared Serviceとする
  • Transaction ContainerとしてServiceのコンテナを置く。
  • Shared Serviceをいくつかまとめて一つの処理とした集約サービスクラスを置く。
  • 基本的にControllerからはTransaction Containerしか呼ばない
  • Transaction Containerには集約サービスとShared Serviceを組み合わせてエンドポイントとか処理単位ごとに呼び出したい処理の流れを構成する

とかどうかなあ。冗長すぎるよなあ。

まとめ

モデリングをしっかりしないとこんなハチャメチャに複雑な構成を作らざるを得ない状況になるので、設計者は肝に銘じようね。

H2GISでPostGISの代理やろうとして面倒だなコレって知見を得た話

概要

ここ数日お仕事でH2DBを使って動的クエリのテストをしようとアホほど頭を悩ませていたのですが、単にデータベース使うだけでなく地理計算系の拡張を突っ込んでいる部分もまとめてテストしようとしたせいでまあ面倒なこと面倒なこと。舐めてかかると地獄なことになるという知見を得ました。

なので本日は何がクソ面倒なのかをまとめていこうと思います。ググればわかるっちゃわかるけど日本語の記事がほとんどなかったのでまとめの一つとして記述。

登場人物

H2DB

H2 Database Engine

コードに組み込みで動くRDB。オンメモリで動かして各種DBMSの方言用ドライバも完備。JVM上で動く。MPL

H2GIS

H2GIS • Spatial H2

H2DBに地理計算系とか幾何計算とかの関数やら型やらを突っ込む拡張。LGPL3.0

PostgreSQL

PostgreSQL: The world's most advanced open source database

説明いる?RDB。知らないならこんなクソブログ読んでないでRDBについて調べなおせ。

PostGIS

PostGIS — Spatial and Geographic Objects for PostgreSQL

PostgreSQLに地理計算系とか幾何計算とかの関数やら型やらを突っ込む拡張。GPL2.0

やろうとしたこと

PostgreSQLに投げる動的クエリ作ってるところがパラメータによってちゃんとSQL通るかどうかを確認したかった

せや!H2GIS使ったろ!地理計算ならだいたい変わらんやろ!(安直)

テストコード走らせてるときはH2DBを利用。そうでないときは接続文字列に従い正しいデータベースにつなぐ。環境変数で切り替えられるだろ多分。

この安直さ極まる思考により後に死ぬことになる。

知見

そもそもH2GISとPostGISで地理計算系の仕様が微妙に違う

H2GIS、PostGISOGCで定義されている「Simple Feature SQL」と呼ばれる、SQLで地理情報を扱うための仕様に準拠しています。

http://www.opengeospatial.org/standards/sfs

ただし、H2GISはほぼ完全に仕様準拠な一方、PostGISは「Simple Feature SQLの拡張仕様」となっており、PostGISで利用可能なもろもろがH2GISで利用できないということが多々あります。

例として挙げると、地理情報の基本的な方となるGEOMETRY型にすら大きな違いがあります。Simple Feature SQLは地理情報のSRID(地理情報の空間参照方法のIDみたいなもの。本筋からは逸れるので詳しくはググれ)が型の情報として埋め込めなかったり、テーブル定義の際にWKT(Well Known Text 詳しくはググれ)形式のような地理情報地理情報の形状を制約として設定できなかったりします。

テスト用のテーブル作ってるときに、この辺の差異のせいでdumpを使ってデータベース構築できずに必死こいて手書きしました。

同じ名前の関数でも引数や結果が異なる関数がある

例としてはST_Areaと呼ばれる、GEOMETRY型を引数にとってその面積を出力する関数が存在します。

PostGISではGEOMETRY型がSRIDを持つため、地理参照系や地球が球状であるということを考慮した計算や、どの空間参照系を利用して面積を出力するかの設定を行うことができるのですが、H2GISでは座標を2次元平面として見立てた幾何学計算のみを行います。

ST_Area

https://postgis.net/docs/ST_Area.html

GEOGRAPHY型が存在しない

GEOGRAPHY型はGEOMETRYよりも計算に特化した地理情報の型なのですが、これがH2GISには存在していません。そのため、SQL内でGEOGRAPHY型を利用しているとH2GISでは例外が投げられます。

ジオグラフィ型というものがある - Qiita

なぜ面倒なことになったのか

上記のことを全部やらかしてしまっていたため。もっとよく調べてから手を出すべきでした。くそう。

知見からの対策

もし僕のような利用目的でH2GISを利用したい場合、選択肢は二択です。

  • H2GISを利用しない。Docker HubとかVagrant CloudとかにPostGISやらマスタデータやら設定済みのイメージを置いて共有する。
  • H2GISを利用する。気合で実際に実行した内容と結果見ながらSQLで調整する。プロダクトコードが通るかつ実値が近いものになるようにする。無理なものは実値を検証しない。

推奨は上です。僕は下でやって世界を呪いました。 もしH2GISとPostGISを併用するケースがあるなら、安易に採択しないことをお勧めします。ていうかそもそもそんなテストする必要がある動的SQL作らないことをお勧めします。

まとめ

もうH2GISなんてコリゴリだよ~~!!(ワイプ)

Spring Bootでデータベースのテストやろうとして面倒臭いことになった備忘録

概要

基本的にデータベースを利用したテストってコードだけで完結しないし、適当な仮想環境をコードベースで用意できないならやるべきじゃないと思ってるんですが、それはそれとしてデータベースを使いたいテストっていうケースもあると思います。

例えばスパゲッティクエリを動的に組み立てなければいけないようなクラス。設計段階でなんとかしろって話なんですが、どうしてもやらざるを得ないところが出てくるし、やったらやったで実際にSQL通るかが不安になってきます。

今回はそんなテストをやってやろうと頑張ったときの備忘録になります。

環境

  • Spring Boot Test Starter 2.0.0
  • H2 Database Engine 1.4.197

状況整理

  • 基本的にはapplication.ymlの値を利用してDataSourceを組み立て。
  • テスト用のDBにはH2DBを利用。
  • マスタデータが膨大なので、各開発者ごとに仮想環境でデータベースを持っており、テストの種類によってはそちらを利用したいこともある(起動時に実行時引数を使ったりJVM引数を使ったりして調整)
    • profileでテスト内容と対象DBを切り替える手もあるけどできれば一気に全部テスト回したい。
  • 前提となるテストの回し方が悪いのは重々承知の上。

実装

やろうとしてダメだった方法

@ClassRule使ってテスト回す前にテーブルを組み立てる

@ClassRule
private static TestRule testRule;

@Autowired
private void setTestRule(CustomTestRule rule) {
  testRule = rule;
}

@Component
class CustomTestRule extends ExternalResource {
  @Autowired
  private DataSource dataSource;

  @Override
  protected void before() throws Throwable {
    // 必要ならデータベース設定したりなんだりする
  }

  @Override
  protected void after() {
    // 必要ならデータベースキレイにしたりなんだりする
  }
}

staticなメンバーだとField Injectionができないので裏技気味に設定。

これだとClassRuleが読まれた後にインジェクションが行われるため、Ruleがnullの状態でコードが走る = 特に何もせずテストが走る模様。 ライフサイクル的にも実装の残念さ加減的にもそらそうよな、といった感じであきらめました。

結局落ち着いた方法

@ClassRuleも@Ruleも使わないで力技で解決した。なのでJUnit5には互換不可能になりました。ふえぇ。

そもそもH2DB使ってるならクリーンアップ処理とかしないでいいし、JUnitならクラスごとに処理が行われるからクラス内の値は独立してるし、H2DB使ってるかどうかの判断処理を入れれば何も考えなくていいわ。ということで全力でクソコードのお出ましです。

private static boolean isSetupFinished;

@Before
public void setup() {
  if(isSetupFinished){
    return;
  }
  // データベースの設定処理
  isSetupFinished = true;
}

@Autowired
private void setTestRule(CustomTestRule rule) {
  testRule = rule;
}

データが入ってるかどうかの判断に関してはSQL内部でやることにした。うーんこの。

なんでこんなクソ記事書いたの?

もっと賢いやり方ありそうだからマサカリが欲しかった。

SQLの基礎知識がクソ雑魚だったので勉強しなおした話

ここまでのあらすじ

SQLが遅い。

いやSQLは遅くない。コッド先生をはじめあらゆる著名な開発者が関わり、各ベンダーが極限まで最適化を施したRDBMSは基本的に遅くはない。 遅いのはこの複雑怪奇なデータベースから無理やりデータを取得しようとしている、怖気が走るほどのスパゲッティクエリだ。

「最適化」の名のもとに意味不明な組み立て方をされた文字列の地獄。乱立する連結文字列。JDBCの実行限界ギリギリのパラメータ量。見ているだけで泣きたくなる。

SQLアンチパターンって本あるよね。あれの八割を網羅しているようなデータベース設計。主にメタデータトリブル。そんなクソの山から生み出されるSQLはやはりクソの山なのである。ようこそここが地獄。助けてくれ。

SQLアンチパターン

SQLアンチパターン

とはいえ僕自身もそこまでSQLに強いわけではない。せいぜい実行計画を眺めながら「あーこの辺コストかかってるからサブクエリで絞らないとねー」「インデックス張ろうねー」程度の知識だ。手元で工夫ができるSQL技術の手札などたかが知れている。このままの知識レベルだとリファクタリングという名の泥仕合を繰り広げる未来が見える。さながらレベル1勇者単体でナジミの塔に突入するかの如くだ。

というわけで、改めて基礎からSQLを学んでみようということで本を一冊買って実践してみた。

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

今回のブログは一人読書会感想ブログとなっております。

どんな本?

SQLの実践入門本でした。(クソ雑紹介)

といっても、SQLそのものに入門するには不向きだと思います。SQLの書き方やら何をしてくれるのかという本であればSQLの絵本のほうが向いてると思う。

SQLの絵本―データベースがみるみるわかる9つの扉

SQLの絵本―データベースがみるみるわかる9つの扉

「ORM?結局定義書くのめんどくさくね?オブジェクトマッピングだけしてくれればええやんけ。Dapperとかsql2oとかそもそもDjangoのModelみたいにしてくれれば楽じゃろ」といった感じにSQLをざっくりとしか書いたことのないアプリケーションエンジニアが、1年目~3年目くらいの期間に見てみるとちょうどいいんじゃないかなー、という印象です。

内容としては大雑把に「DBMSアーキテクチャ」「DBMSで利用されているアルゴリズムの紹介」「実行計画の見方」「モデルケースをもとに遅くなる原因とチューニング方法を紹介」といった感じで、SQLそのものよりも、クエリ実行が遅くなった際に原因を特定する方法や、ハマりやすいケースへの対処集といった感じでした。

その対処の方法も、SQLでベースとされている集合指向的な解決法だけに限ったわけでなく、そもそものデータモデル設計を変える、適切にループを利用する(本書内ではぐるぐる系と称されている)等々、パフォーマンスを出すために様々な方向性からアプローチを行っていました。

そのアプローチ方面も実際に現場で「あるある」というようなパターンが多々。そういう意味では「SQL」実践入門というより、「SQL実践」入門といったような感じ。

実際読んでみて非常に良かったです。今まで適当に読んでた実行計画読むのが楽しくなった。

使えそうだったテクニック

ウィンドウ関数

集約とカットのうち、カットのみを行う手法。使い方全然知らなかった……。 GROUP BYやら集約関数自体はごにょごにょやっていたことはあるのだけれど、基本的に集計する目的やCOUNT以外では利用していなかったので「こんな方法もあるのか!」と素直に感動。

集約関数 OVER (
  PARTITION BY column
  ORDER BY column
)

めっちゃ使いやすいですねコレ……。

ウィンドウ関数を利用したパフォーマンス向上の方法も例として多々あり、実用の方向性としては多々ありそうな感じ。

スキャン・結合アルゴリズム

一番欲しかった情報。EXPLAINしてみて「なんかよくわからんけど全検索かけてるっぽいところあるからここが遅いやろ……」みたいなことが多かったので、問題点を確定させられるような情報を仕込めたのは最も大きな収穫でした。

再起クエリ

ナイーヴツリーの解決法。SQLアンチパターンにもあったね。

メインで利用しているRDBMSPostgreSQLなのでこの辺はちょいちょい利用してます。

ところでこの辺の解決法で経路列挙モデルと入れ子集合モデルが結構あげられるけど、アレ本当に使えるのか……?って毎回思ってしまう。

こういう人は必要ないかも

がっつり読み込んでみて感じたのは、徹底的に特定のベンダーに依存する実装を排除してるなー、という印象でした。

例えば大量データのテーブルに対してデータマートでの分割を提案してるのですが、SQLアンチパターンでは同様のパターンについてパーティショニング・シャーディングを用いて解決しようとしています。(これでデータマートの実装方法がパーティショニングだったら恥ずかしいな)

なので、SQL標準から外れた機能や、特定のDBMS拡張機能についてチューニングしようとしている場合は、専用の本買ったほうがいいかもしれません。実践ハイパフォーマンスMySQLとか。

実践ハイパフォーマンスMySQL 第3版

実践ハイパフォーマンスMySQL 第3版

まとめ

改めてSQLを勉強しなおすのに良い本でした。普段メインでDB触らないアプリケーションエンジニアにとってはさらにポイント倍。

実装は一通り表面だけでも手札そろえられたので、次に読むなら設計系かなー。

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

もっと詳細にSQL実装の方面掘るならプログラマのためのSQLあたりでしょうか。

プログラマのためのSQL 第4版

プログラマのためのSQL 第4版

なんにせよ、SQLないしはRDBMSの知識を得るにあたって指標となる一冊になりました。ミック先生全然知らなかったけどこれを機に覚えておこう。

今更Backbone.jsを使ってみたのでざっくり解説と感想を書いた話

概要

おしごとでBackbone.jsを利用した開発を行い、ある程度慣れというものが出てきました。 実感として感じたものがいくつかあったので、散発的に書いていこうと思います。

でもやっぱり基本的には公式リファレンスが最も参考になります http://backbonejs.org/

Backboneの基本構成

htmlの要素をいくつかの単位に分割し、一つ一つをコンポーネントとして扱うのに適したライブラリ。 View + Model(Collection)で、一つのコンポーネントとして扱う。

Modelはステートを保持し、変更・取得等があった際に独自のイベントを発火する、いわゆるObservableなオブジェクト。 CollectionはObservable Arrayといったところ。 両方ともオマケとしてunderscore.jsのメソッドが自身に組み込まれている。

Viewは見た目の制御というより、MVCにおけるControllerの役割が大きい。 ユーザーのどの操作に対し、どのメソッドを割り当てるかを決定する。 見た目制御の都合上Fat Viewになりやすい。

基本として、Backboneが提供しているオブジェクトはすべてObservableかつUnderscore.jsのメソッドを搭載しただけのものとして考えると使いやすい。 あとはRouterとかあるけどこれでSPAやろうとすると死ぬと思う。

https://qiita.com/gomi_ningen/items/c796c08fe672610beecf

上記ページのpush型Observerがイメージとして近い

よく使うメソッド・値

Backboneオブジェクト共通

ほぼすべてBackbone.Eventsというオブジェクトを継承している。

リファレンスを見る際はEventsが全てにあるということを考えながら見るといいかも。

メソッド 詳細
listenTo 対象のオブジェクトが発するイベントを監視し、イベントが発火されたらコールバックを呼び出すように仕込むメソッド
listenToOnce listenToは監視が永続するが、listenToOnceは一度コールバックしたらイベント呼び出しが解除される
trigger イベントを発火する
extend 第一引数にはインスタンスに持たせる値、第二引数にはprototypeに持たせる値や継承元を持たせて、JavaScriptでの継承を実現する

Backbone.View

メソッド 詳細
initialize コンストラクタ。newした際に行われるメソッド
id Viewのidではなく、document idのこと。指定されたidのDOM要素があれば、その要素を自身の$el, elに格納する
events htmlが書き込まれた際にイベントハンドラが仕込まれるオブジェクト。書き方が独特なので公式リファレンスを参照
render thisを返せば何をやっても自由なメソッド。よくあるのはthis.$el.html('
hoge hoge hoge<\div>')のような直接htmlを書き込むタイプのもの
delegateEvents イベントを対象に仕込む。引数となるイベントオブジェクトの書き方はeventsと同じ。特定のタイミングでイベントを仕込みたいときに使う
undelegateEvents delegateEventsと対となるメソッド。イベントを解除する

Backbone.Model / Collection

メソッド 詳細
get setした値を取得する。取得したい要素のkeyは文字列となる
set Modelに値を登録する。直接設定する方法との違いは、setで設定するとイベントが発火するため、Observableにオブジェクトを利用したいときは必須
toJSON Modelにsetした値を、JavaScriptのプレーンなオブジェクトとして出力する。underscore templateに値を渡すときなど、Backbone Modelを利用したくない時に使う。CollectionはArrayになる

個人的使用感

シンプルな機能のみを提供してるらしいけど大ウソじゃないですかね……。

Observableなオブジェクトを作るというのにもだいぶ大がかりな設計になっているし、使いやすいっちゃ使いやすいけど、やっぱりヘビーな感じは否めない。

初出からかなり時間がたってフレームワークやライブラリが充実してきたというのもあるけど、それでもやっぱり使っていて重い感じがある。 Virtual DOMを実現するなら脅威の1kb Hyperappなんてのもありますし。

あ、でもCollection.findWhereだけはかなり便利でした。linq.js使えって言われたらそれまでだけど。