エンジニア

2015.02.23

ハンズラボが採用しているユニケージという謎テクノロジーについて 第2回

謎テクノロジー第2回目です。
諸事情によりだいぶ時間が空いてしまいました。色々と厳しい。

今回は簡単なサンプルコードを用いて、もう少し突っ込んでユニケージの処理例について解説します。
繰り返しになりますが、この連載での記述は全て筆者の見解であります。と、念押し。
# しかしまた長くなってしまった……
# 削ったり分割したかったけど、いい加減アウトプットしたかったのでひとまずこれで。

目次

  • コード例
    • フィールド選択での例
    • ごく簡単な集計での例
    • ログ集計
    • まとめ
  • 第1回に対するネット上の声Q&A的なもの
  • 次回以降の内容

コード例

フィールド選択での例

前述の様に、ユニケージ開発手法では、原則半角スペースをセパレータとするフラットファイルをデータストアとして用います。
セパレータが半角スペースであることについては賛否両論有りますが、

  • シェルスクリプトが半角スペースを引数の区切りとして使っている
  • ユニケージコマンドの補助として多用するawkのデフォルトセパレータも半角スペース

であるので、それなりに合理的ではあると考えます。

簡単な具体例として、あるファイルの特定の列のみ選択する操作について説明します。
以下のファイルをサンプルとします。

# 商品コード 発売日 商品名 販売価格
cat item_basic_info
item_1 20150204 商品1 1944
item_2 20150202 商品2 10800

このファイルから商品コードと販売価格のみを出力する場合は、
self コマンドを用いて以下のように行います。

# 商品コード 販売価格
self 1 4 item_basic_info
item_1 1944
item_2 10800

上記と同じファイルをスクリプト内で読み込み、等価な標準出力を行うPHPのコードを以下に記します。

<?php
foreach (new SplFileObject('item_basic_info') as $line) {
    $fields = explode(" ", $line);
    echo @"{$fields[0]} {$fields[3]}";
}

もうちょっといい感じにできるとは思うのですが、
単にフラットファイル丸ごと開いて数列を選択して出力するだけでも冗長な感じです。
# 強引だ……

ユニケージコマンドセットには、概ねこの程度の粒度のコマンドがたくさんあるのですが、
実務的には概ね数十程度(筆者の感覚では50以下)のコマンドの組み合わせにて、アプリケーションを記述しています。

また、前述の様に、ユニケージコマンドだけでは行いにくい処理を行う場合は、だいたいawkを使う事になります。
前述の self コマンドの例と等価なawkを使用したワンライナーは、書くまでもないかも知れませんが、

awk '{print $1, $4;}' item_basic_info

となります。
フラットファイルの行の集合に対して、各々フィルタ処理を行って目的の出力を得る、という処理が中心のユニケージに対して、
awkは枯れている上に、短いフィルタ処理を書く為には簡潔に書けるので、ユニケージ開発には重宝します。
# ただし、awkはなるべく使うな、というふわっとした規約もあったりします……
# 商用版ユニケージコマンドはほぼ全てpure Cで書かれているので、awkでコマンドの組み合わせに相当する処理を記述すると比較して十分に遅くなってしまうので……

ごく簡単な集計での例

前述の商品マスタを用いて、さらに販売実績に関する簡単な集計の例を示します。
以下の様な販売実績のファイルがあるとします。

# 販売日 商品コード 販売店コード 販売数
cat item_sales
20150201 item_1 001 15
20150201 item_1 002 4
20150201 item_1 003 7
20150201 item_2 001 2
20150201 item_2 002 1
20150201 item_2 003 6
20150202 item_1 001 12
20150202 item_1 002 3
20150202 item_1 003 6
20150202 item_2 001 3
20150202 item_2 002 2
20150202 item_2 003 5

上記の販売実績から、店舗と販売日問わず商品毎の販売合計金額を算出する場合は、以下の様にします。

sort -k2 -s item_sales |
join1 key=2 item_basic_info - |
# 1:販売日 2:商品コード 3:発売日 4:商品名 5:販売価格 6:販売店コード 7:販売数
lcalc '$2, $5 * $7' |
# 1:商品コード 2:商品毎の販売合計金額
sm2 1 1 2 2
# 1:商品コード 2:販売合計金額
item_1 50544
item_2 9720

join1コマンドは、指定したキーにて、2つのファイルまたはファイルと標準入力に対して、内部結合を行うものです。
ユニケージコマンドでのキー指定は、極一部の例外を除き、事前にそのキーにてソートされている必要があります。
lcalcコマンドは、基本的な算術演算をawkのようなシンタックスの式で、
36桁の10進数で加減乗除と四捨五入・丸め操作を行うものです。
sm2コマンドは、第1,2引数で集計範囲キーを指定し、第3,4引数の範囲の値の総和を個別に取るものです。

また、店毎のある日の総販売実績は以下の様にして求められます。

sort -k2 -s item_sales |
join1 key=2 item_basic_info - |
# 1:販売日 2:商品コード 3:発売日 4:商品名 5:販売価格 6:販売店コード 7:販売数
lcalc '$1, $6, $2, $5 * $7'
# 1:販売日 2:販売店コード 3:商品コード 4:商品毎の販売合計金額
sort -k1,2 -s |
sm2 1 2 4 4
# 1:販売日 2:販売店コード 3:ある日のある店の総販売実績
20150221 001 31320
20150221 002 8856
20150221 003 20088
20150222 001 26568
20150222 002 7992
20150222 003 17064

集計したいキーと集計したい値だけに着目し、コマンドを作用させれば求める値が得られる、というのは、単純な算術演算だけ行うようなロジックにおいては、他にケアするべきことが少なくなるのでよいです。

ログ集計

簡易なWebサーバのステータス集計など、ログ集計も可能です。
例えば、以下の様なフォーマットのログのみが /var/log/httpd/access_log に出力されている場合、

time:2015-02-22 00:04:04 +0900  domain:localhost        host:10.5.103.183       server:10.5.32.9        method:GET      path:...         status:200      size:341        response_time:15222830

1時間毎のステータスコードを以下の様に集計出来ます。

cat /var/log/httpd/access_log |
egrep -o 'time:[0-9-]+ [0-9:]+|status:[0-9]+' |
tr " " "_"  |
yarr -2 |
self 1.1.18 2 |
sort |
count 1 2
time:2015-02-21_04 status:200 180
time:2015-02-21_05 status:200 771
time:2015-02-21_06 status:200 908
...
time:2015-02-21_17 status:200 3015
time:2015-02-21_17 status:304 30
time:2015-02-21_17 status:404 1
...
time:2015-02-22_00 status:200 112

yarrコマンドは、指定列数になるようにファイルの行を横に展開するものです。
countコマンドは、指定キーにマッチする行の数をカウントするものです。
ログ集計がシェルスクリプトで、下手すればワンライナーで完結するので、あまりアプリケーションコードを書かない方のインフラエンジニアの方やデータ分析官の方にも重宝するのではないでしょうか。
yarr,count共にOpen版に含まれているので、こういった作業をすることがあるならば導入しておくと便利です。

まとめ

普通のプログラミング言語よりも、手続き的な処理が少なく、データに対する操作が主になっている様子を掴んでもらえたと思います。
例示したように、1つのプログラムで、前述の例の様なパイプライン処理で作成した複数のデータを、一時ファイルに置きつつさらに操作して、最終的に求める出力を得ます。
求める出力は、DBのビューに相当するデータであったり、HTMLテンプレートにはめ込むデータであったり、バッチ処理により更新されたテーブルであったりしますが、基本的な手法はほぼ変わりません。
アプリケーションコードが殆どデータに対する宣言的操作だけでできている、と言えます。
基本的には無名のフィールドに対して操作を連続して行い、操作中にフィールド構成がよく変化するので、変化ある毎にフィールド内容を正しくコメントすることは必須です。
これを正しく行っていないと、人間非可読なコードがあっという間に出来上がります。
しかしながら当然あり得るツッコミとしては、コメントが多すぎて可読性がひどいのでは、というのがあります。
これに関しては、また次回以降……

第1回に対するネット上の声Q&A的なもの

エゴサ力を駆使したところ、第1回公開後のネット上の声が思ったよりあったので、勝手に回答します。
ソースは必要があれば正しくURLを引用しますが、晒す感じになってしまいそうなので、 意図を歪めない範囲で省略した文に対してインライン回答する形式にします。

プログラマ視点で引き続き解説を

そのようにしていきます。
ビジネス中心の話はビジネスおじさんに任せればよいのです。
小耳に挟んだところによると、色々な所から人を集めて推進するようなプロジェクトでもユニケージ案件は一定数存在するようなので、そこに突っ込まれた普通のエンジニアにとっても有益なものになるようにしたいです。
また、公にされている情報がビジネス的な話ばかり目立って存在して、技術寄りの話が少ないテクノロジーというのは、はっきり言ってエンジニア視点からは胡散臭さマシマシとなり、それ自身にとって不幸だと思うので、引き続きこのくらいのスタンスでやっていきます。

考え方が関数型言語っぽい

せやな。
……などというとマサカリが四方八方から飛んできそうです。
その上で言うと、

  • データ構造が2次元配列だけの、シンプルなデータ構造で
  • しかもデータソースでもアプリケーション中でも出力でも構造が殆ど変わらない
    • 所詮はフィールド数が多いだけの、テーブルに出力する一覧画面や帳票リプレースものなアプリケーションが多い。
    • 更新系も、所詮表形式データの編集に過ぎないものが多い。

最も抽象度の低い宣言型プログラミング、とも言えるかも知れません。
そろそろ強いマサカリが怖いのでこの辺で……。

これについては、より知見の深い、シェルスクリプト高速開発手法入門で皆様ご存じの上田先生にもご意見を賜りたく。
シェル芸とHaskellの対応を考える | 上田ブログ

エンドユーザーコンピューティングとしてはmuch better Excel。ファイルシステムというドキュメント型KVSの上に実現するための知見の集合?

確かにExcel/Accessによるエンドユーザたこつぼシステムの代替である側面はあります。

ユニケージコマンドがフラットファイル処理に特化しているので、ファイルシステム上のフラットファイルに、という感じではありますが、
ユニケージにはデータの階層構造の概念があり、それが実現できればファイルシステム上のフラットファイルでなくとも、ユニケージ的なものは出来そうです。
例えば、AWSやAzure,GCPなどのクラウドのマネージドサービスにて、各々のデータ階層のデータストアとしての役割や、データ操作の役割を割り当てる、など考えられます。
単純にファイル置き場をS3にして実現したシステムの事例については、
既に当社の田部井による事例紹介が公開されています。
フラットファイルを用いないユニケージ的な考え方については、
ユニケージのデータの階層構造を説明する時に、考えながらあわせて触れたいと思います。

また、等価なコードはだいたいの言語で書けるはずなので、言語もシェルスクリプトである必要はないと思います。

# 個人的にはシェルスクリプトはあまり好きではないし、追加・削除したコード量的にも、一生のうちに書いてよいシェルスクリプトの閾値を超えた気持ちになっているので、おなかいっぱいです。
# あとシェルスクリプトは他の言語に比べて簡単かというと、そうでもない。
# ユニケージコマンドやそれに類するコマンド群がなければ、アプリケーション記述用言語としては初心者に全くおすすめできません。

RDBMSを使用しない理由が不明確

これは確かにそうです。
私はユニケージガチ勢ではないので、普通の言語や普通のRDBMSや普通の分散環境がはまるケースなら普通にそれでやればいいと思います。
適材適所、ユースケースにはまれば採用すればよい。

その上で、エンドユーザーコンピューティング的な視点だけで言うと、
諸々の実行環境やミドルウェアの設定・依存関係に悩まされず、ユーザが脱Excel|Accessして、
自社のデータを自分の手で素データからガンガン取り回すという経験を最速でやれるのは、ユーザ企業にとって、自社を動かすシステムのブラックボックス度を軽減させるという点で、重要な事と考えています。
システムを作ることから離れすぎた・放棄したユーザ企業が、SIerに上手に仕事を投げられなくなり、
ダメなSIの発生率が上がっていそう(ダメSIの定義が出来ていないので妄想みたいなものですが)という気がしますので。
もっと言えば、SIに限らずWeb制作などの、一定以上の専門性を要するものの外注などにも、発注側の過度の “わかってなさ” が良くない事態を招くこともあると思います。
# 自分が何をわかってないかをわかってない人と仕事をするのはつらいです。

システムの物理的実体がRDBMSを使っているかどうかなどの適材適所とは、また別の軸の話となってしまいました。
とりあえず、バージョンや設定に依存するミドルウェアやコンパイラ・インタプリタが事実上ほぼ無いと言える(一応Apache httpd とbash、ユニケージコマンドに依存するが)というのは、
ユーザ企業出身エンジニアにとっては、余計なことを考えず自社のデータのことだけを考えればよい、という点が良いのではないか、と考えています。
筆者はハンズのプロパーではないので、他のプロパーの人たちを見て、という意味です。
エンジニアとしての練度が上がり、業務だけでなく技術に対する理解力もついてくれば、
なんでもユニケージでなく、ケースによって最適なアーキテクチャをエンジニアが選定して推進していけばよいのではないでしょうか。

あんまビジネスの話したくないけどそれっぽい話をしてしまった。あかん。

シェルスクリプトでJSON操作できるコマンドが必要では

ユニケージコマンドには存在しないのですが、一定の範囲では間に合ってますw
例えば、

key val
key2 val2
arr1 1 array_hash_key1 array_hash_val1
arr1 1 array_hash_key2 array_hash_val2
arr1 2 array_hash_key1 array_hash_val3
arr1 2 array_hash_key2 array_hash_val4

みたいな入力を食わせると、

{
  "key": "val",
  "key2": "val2"
  "arr1": [
    {
      "array_hash_key1": "array_hash_val1",
      "array_hash_key2": "array_hash_val2"
    },
    {
      "array_hash_key1": "array_hash_val3",
      "array_hash_key2": "array_hash_val4"
    }
  ],
}

みたいなJSONを吐くコマンドは2年以上前から社内に存在してます。
上記のような、
キー名 値
が連続したファイルを読みこんでjson_encodeするPHPのスクリプトを外部コマンドとして作成し、使用していました。
# しかしシェルスクリプトでJSONパーサ書くとかぞっとする……。
また、AWSのCLIを使用する際などは、jqコマンドを使用してレスポンスをパースしています。

シェルスクリプトでJSONを操るのは、少なくとも弊チーム(EC・アプリ系)は色々あって卒業しました。
色々、については、書く機会があればそのうち書きます。

次回以降の内容

次回は、ユニケージのデータ階層構造の概念について、を予定しています。
可能であれば、クラウドマネージドサービスとの対応を考えてみるところまでやってみたいと思います。
pros/cons については、次々回以降に、ここまでの記述で書いたこと、察せそうなことをまとめる形でやっていく予定です。

第3回はこちら

一覧に戻る