【Vue】Vue+vuecli+Vuex+Vuetify+TypeScript+ClassComponent+Golang+ginでもろもろやったときの参照文献

プライベートで、タイトル通りの技術を使ってS3をアプリから登録して簡単に閲覧できるビュワーを作成した。

GitHub - pakuyuya/s3-web-browser

最近のWeb技術は独自のスキームが進んでいて追っかけていなかった身としてはなかなか踏み出せなかったが、 手を出してみれば、洗練されている上チュートリアルも充実、品質の高いものばかりなのですぐに作れた。

同様な構成を考えている人のために、構築例と参考にしたURLをまとめる。

構成

  • サーバーサイド

    • Golang + シンプルなWebフレームワークgin
    • クライアントサイドのコードで生成したhtml、js、cssをpage系のリクエスト処理関数で読み込み表示
    • URL に/api/ プリフィクスがついているものはシンプルなWebAPIを提供。クライアントサイドのリソース取得や操作はこれを介する。
    • バックにPostgresqlあり。
    • S3との接続はAWS CLI
    • コード分割は正直下手だとおもう
  • クライアントサイド

  • ビルドスクリプト

    • Docker-composeで作成

参考リンクまとめ

Vuecli関連

vuecli公式 Vue CLI

  • ツールをインストールして動かすまでのガイド。
  • 起動したら対話形式でほしい機能を選んでいくだけ

TypeScript関連

ライブラリを yarn add / npm install したけど型エラーになる

  • yarn add @types/ライブラリ名 / npm install @types/ライブラリ名 して、インストールできたら tsconfig.jsontypes にライブラリ名を追記する
  • できなければ、↓の方法で回避

ウェブエンジニア珍道中 typescriptでnodeのfsを動かそうとしてハマったこと - ウェブエンジニア珍道中

  • @types/ライブラリ名 がない場合の対処法。

Vue関連

Vue公式ガイド はじめに — Vue.js

  • だいたいの構文は載っている
  • 基本的な機能は読んでいけばなんとなくわかる
  • 「単一ファイルコンポーネント」の章が実質、実装の手引き

vue-class-component TypeScriptではじめるVueコンポーネント(vue-class-component) - Qiita

  • VueをTypeScriptのclassスタイルで無理やり実装するようにするコンポーネント
  • 従来の.vueファイルの書き方を大きく変えるため、ググラビリティをちょっと下げる

環境変数ファイル Modes and Environment Variables | Vue CLI

  • ビルド環境ごとに設定ファイルを変える方法のデファクト
  • 環境変数名に VUE_APP_ を接頭語としてつけないとだめらしい。

Vuex関連

Vuex公式ガイド https://vuex.vuejs.org/ja/guide/

  • 基本的な機能は載っている

vuex-class-component vuex-class-componentを使ってVuexをクラススタイルでタイプセーフに書いてみよう | DevelopersIO

  • VuexをTypeScriptのclassスタイルで無理やり実装するようにするコンポーネント
  • 従来の.vueファイルの書き方を大きく変えるため、ググラビリティをかなり下げる

Qiita https://qiita.com/hikaruna/items/fff8ed49556e659b9a06

  • 実装サンプル記事
  • 大変助かりました。 NewsStore.CreateProxy

Vuetify関連

Vuetify公式Document Material Component Framework — Vuetify.js

  • すべてが載っている
  • サンプルはちょっと貧弱

Qiita 【Vue.js】Vuetify と TypeScript を使用した環境を構築してサンプルプロジェクトを立ち上げるまでの手順 - Qiita

  • type.dがないとかで怒られる事象の解決の参考に。
  • tsconfig.json のtypes にvuetify追加
  • vuecliで自動fix対応まだっぽい

Qiita Vue でダッシュボードの雛形を作る最速の道 - Qiita

  • ダッシュボードなUIの枠組み作成の手順を詳細に公開いただいている記事。
  • 自作ツールの枠組みとして大いに参考にさせていただきました。
  • Vue 2.xベースで記事が作成されており、現行バージョンのVue 3.xでは廃止されたコンポーネントあり。

iconのCSSでないぞ問題

  • public/index.html に追加

Golangまとめ

Golangの文法

Go modules

Postgresqlにdatabase/sqlでつなぐ

gin まとめ

公式兼最強のドキュメント・サンプル

セッションを扱う用の拡張

Cookie based なセッションでstruct設定したら「securecookie: error - caused by: gob: type not registered for interface:」エラーになった

作ってみて

  • Vue
    • 数年前からちょいちょい使っている
    • 楽でサンプル豊富。
    • 色んな機能やツールを導入するのがしんどかったけど、今やvue cli ですべてボイラーテンプレートしてくれる。マジ敷居低い
  • Vuetify
    • Vue版 Bootstrap + jQuery UI。Dialogもサポートしてる。神か
    • 最初は「card・・・?caption・・・?」ってなるけど実はルール単純。 CSSを知っていれば、チュートリアルといくつかのサンプルをほいほいコピペしていってすぐ組める
    • マジ強力なフレームワークだと思う。
  • TypeScript
    • 使い込むほど「お前やっぱJavaScriptだな??」ってなって安易な型安全なぞ焼け石に水と知る(jsonとかリクエストパースするやつ、型普通にすり抜けてきて残念な気持ちになる)
    • 今回ServerSideをGolangにしたため相互のインタフェースが乖離したことを型で検知できなかったが、Isomorphicにすると違ったのかもね。
  • Vue class component
    • TypeScriptを使うなら、これを導入しない手はない
    • JavaScriptベースなVueのチュートリアルとは違った書き方になり、ググラビリティが下がるの注意
  • Vuex
    • 最高
    • db-update→reflesh→commit→$store.stateを見てるVue更新 の流れを強制できる。コードがすっきり。
    • 双方向バインディングがn:mで発生するのを防いでくれる
  • Vuex class component

    • TypeScriptを使うなら、型安全性から導入推奨
    • ただ、CreateProxy とかを組み込まなきゃ動かないとか、使い方がピュアなVuexと異なる。乗り越えればなんてことない。
  • Golang

    • 直近1年でちょくちょく触れるようになった
    • 手癖殺してとりあえずベストプラクティスに従っていけば「アッアッアッ」って言いながらGolang道に吸い込まれる。オススメ。タノシイヨ。理想の世界だよ。
    • test文化はいい文化。意識すると無駄にモジュール化が進んだり面倒くさいと気づいて作り直すループに入ったり。
    • Go modulesでだいぶ楽になった気がする。GOPATHの中に入るやつ、あれちょっとすごかった。
  • gin
    • ミニマリズムとHTTPの極致
    • どんな処理も手で実装したいマンとして非常にマッチ
    • 例えばログイン認証とかフレームワークで用意しといてよ・・・とかまでリッチな機能が欲しい人は、うーん、Golangじゃなくていいかも・・・。

【Amazon Redshift】CREATE VIEW文をCOMMENT文付きで自動生成するSQL

AWS Redshift utilsにビューのDDLを自動生成するSQLが公開されているが、 COMMENT文も欲しかったので過去に自作したものを公開。 ただ、SQL文に改行やインデントがはいらず、なんか微妙な感じ。

WITH v_generate_view_ddl AS
(
  SELECT derived_table4.schemaname AS schemaname,
         derived_table4.tablename AS tablename,
         derived_table4.seq,
         derived_table4.ddl
  FROM (SELECT derived_table3.schemaname,
               derived_table3.tablename,
               derived_table3.seq,
               derived_table3.ddl
        FROM ((((((SELECT n.nspname AS schemaname,
                          c.relname AS tablename,
                          0 AS seq,
                          ('--DROP VIEW "'::VARCHAR(65535) + n.nspname::VARCHAR(65535) + '"."'::VARCHAR(65535) + c.relname::VARCHAR(65535) + '";'::VARCHAR(65535))::VARCHAR(65535) AS ddl
                   FROM pg_namespace n
                     JOIN pg_class c ON n.oid = c.relnamespace
                   WHERE c.relkind = 'v'
                   UNION
                   SELECT n.nspname AS schemaname,
                          c.relname AS tablename,
                          2 AS seq,
                          ('CREATE VIEW "'::VARCHAR(65535) + n.nspname::VARCHAR(65535) + '"."'::VARCHAR(65535) + c.relname::VARCHAR(65535) + '"'::VARCHAR(65535))::VARCHAR(65535) AS ddl
                   FROM pg_namespace n
                     JOIN pg_class c ON n.oid = c.relnamespace
                   WHERE c.relkind = 'v')
                   UNION
                   SELECT n.nspname AS schemaname,
                          c.relname AS tablename,
                          5 AS seq,
                          '('::VARCHAR(65535) AS ddl
                   FROM pg_namespace n
                     JOIN pg_class c ON n.oid = c.relnamespace
                   WHERE c.relkind = 'v')
                   UNION
                   SELECT derived_table1.schemaname,
                          derived_table1.tablename,
                          derived_table1.seq,
                          ('\011'::VARCHAR(65535) + derived_table1.col_delim + derived_table1.col_name + ' '::VARCHAR(65535) /* + derived_table1.col_datatype + ' '::varchar(65535) + derived_table1.col_nullable + ' '::varchar(65535) + derived_table1.col_default + ' '::varchar(65535) + derived_table1.col_encoding*/)::VARCHAR(65535) AS ddl
                   FROM (SELECT n.nspname AS schemaname,
                                c.relname AS tablename,
                                100000000 + a.attnum AS seq,
                                CASE
                                  WHEN a.attnum > 1 THEN ','::VARCHAR(65535)
                                  ELSE ''::VARCHAR(65535)
                                END AS col_delim,
                                '"'::VARCHAR(65535) + a.attname::VARCHAR(65535) + '"'::VARCHAR(65535) AS col_name
                         FROM pg_namespace n
                           JOIN pg_class c ON n.oid = c.relnamespace
                           JOIN pg_attribute a ON c.oid = a.attrelid
                           LEFT JOIN pg_attrdef adef
                                  ON a.attrelid = adef.adrelid
                                 AND a.attnum = adef.adnum
                         WHERE c.relkind = 'v'
                         AND   a.attnum > 0
                         ORDER BY a.attnum) derived_table1)
                   UNION
                   SELECT n.nspname AS schemaname,
                          c.relname AS tablename,
                          299999999 AS seq,
                          ')'::VARCHAR(65535) AS ddl
                   FROM pg_namespace n
                     JOIN pg_class c ON n.oid = c.relnamespace
                   WHERE c.relkind = 'v')
                   UNION
                   SELECT n.nspname AS schemaname,
                          c.relname AS tablename,
                          300000000 AS seq,
                          'AS'::VARCHAR(65535) AS ddl
                   FROM pg_namespace n
                     JOIN pg_class c ON n.oid = c.relnamespace
                   WHERE c.relkind = 'v')
                   UNION
                   SELECT n.schemaname AS schemaname,
                          n.viewname AS tablename,
                          500000000 AS seq,
                          definition::VARCHAR(65535) AS ddl
                   FROM pg_views n
                   --TABLE COMMENT
                   UNION
                   SELECT schemaname,
                          tablename,
                          seq,
                          'COMMENT ON VIEW ' || schemaname || '.' || tablename || ' IS ''' || description || ''';' AS ddl
                   FROM (SELECT n.nspname AS schemaname,
                                c.relname AS tablename,
                                800000000 AS seq,
                                d.description AS description
                         FROM pg_namespace AS n
                           INNER JOIN pg_class AS c ON n.oid = c.relnamespace
                           INNER JOIN pg_attribute AS a ON c.oid = a.attrelid
                           INNER JOIN pg_description d
                                   ON a.attrelid = d.objoid
                                  AND d.objsubid = 0
                         WHERE c.relkind = 'v'
                         AND   a.attnum > 0)
                   -- COLUMN COMMENT
                   UNION
                   SELECT schemaname,
                          tablename,
                          seq,
                          'COMMENT ON COLUMN ' || schemaname || '.' || tablename || '.' || attrname || ' IS ''' || description || ''';' AS ddl
                   FROM (SELECT n.nspname AS schemaname,
                                c.relname AS tablename,
                                800000000 + a.attnum AS seq,
                                a.attname AS attrname,
                                d.description AS description
                         FROM pg_namespace AS n
                           INNER JOIN pg_class AS c ON n.oid = c.relnamespace
                           INNER JOIN pg_attribute AS a ON c.oid = a.attrelid
                           INNER JOIN pg_description d
                                   ON a.attrelid = d.objoid
                                  AND a.attnum = d.objsubid
                         WHERE c.relkind = 'v'
                         AND   a.attnum > 0)) derived_table3
        ORDER BY 1,
                 2,
                 3) derived_table4
)
SELECT *
FROM v_generate_view_ddl
WHERE schemaname NOT IN ('pg_catalog','information_schema','pg_internal','public')
ORDER BY schemaname,
         tablename,
         seq

下記を参考に、ある程度改行付きのものを作成した。でもなんか不格好。

amazon-redshift-utils/v_generate_view_ddl.sql at master · awslabs/amazon-redshift-utils · GitHub

WITH v_generate_view_ddl AS
(
  SELECT derived_table4.schemaname AS schemaname,
         derived_table4.tablename AS tablename,
         derived_table4.seq,
         derived_table4.ddl
  FROM (SELECT derived_table3.schemaname,
               derived_table3.tablename,
               derived_table3.seq,
               derived_table3.ddl
        FROM
        (
            (
                (
                    SELECT n.nspname AS schemaname,
                        c.relname AS tablename,
                        0 AS seq,
                        '--DROP VIEW "' + n.nspname + '"."' + c.relname + '";' AS ddl
                    FROM
                    pg_namespace n
                        JOIN pg_class c ON n.oid = c.relnamespace
                    WHERE c.relkind = 'v'
                )
                UNION
                SELECT n.nspname AS schemaname,
                       c.relname AS viewname,
                       200000000 AS seq,
                       CASE
                         WHEN c.relnatts > 0 THEN 'CREATE OR REPLACE VIEW ' + QUOTE_IDENT(n.nspname) + '.' + QUOTE_IDENT(c.relname) + ' AS\n' +COALESCE(pg_get_viewdef (c.oid,TRUE),'')
                         ELSE COALESCE(pg_get_viewdef (c.oid,TRUE),'')
                       END AS ddl
                FROM pg_catalog.pg_class AS c
                    JOIN pg_catalog.pg_namespace AS n ON c.relnamespace = n.oid
                WHERE relkind = 'v'
            )
            --TABLE COMMENT
            UNION
            SELECT schemaname,
                   tablename,
                   seq,
                   'COMMENT ON VIEW ' || schemaname || '.' || tablename || ' IS ''' || description || ''';' AS ddl
            FROM (
                SELECT n.nspname AS schemaname,
                       c.relname AS tablename,
                       800000000 AS seq,
                       d.description AS description
                  FROM pg_namespace AS n
                    INNER JOIN pg_class AS c ON n.oid = c.relnamespace
                    INNER JOIN pg_attribute AS a ON c.oid = a.attrelid
                    INNER JOIN pg_description d
                            ON a.attrelid = d.objoid
                           AND d.objsubid = 0
                  WHERE c.relkind = 'v'
                  AND   a.attnum > 0
            )
            -- COLUMN COMMENT
            UNION
            SELECT schemaname,
                   tablename,
                   seq,
                   'COMMENT ON COLUMN ' || schemaname || '.' || tablename || '.' || attrname || ' IS ''' || description || ''';' AS ddl
            FROM
            (
                SELECT n.nspname AS schemaname,
                       c.relname AS tablename,
                       800000000 + a.attnum AS seq,
                       a.attname AS attrname,
                       d.description AS description
                  FROM pg_namespace AS n
                    INNER JOIN pg_class AS c ON n.oid = c.relnamespace
                    INNER JOIN pg_attribute AS a ON c.oid = a.attrelid
                    INNER JOIN pg_description d
                            ON a.attrelid = d.objoid
                           AND a.attnum = d.objsubid
                  WHERE c.relkind = 'v'
                  AND   a.attnum > 0
            )
        ) derived_table3
        ORDER BY 1,
                 2,
                 3
    ) derived_table4
)
SELECT *
FROM v_generate_view_ddl
WHERE schemaname NOT IN ('pg_catalog','information_schema','pg_internal','public')
ORDER BY schemaname,
         tablename,
         seq

【Ruby】メモリに余裕があるはずなのにCannot allocate memory で落ちたらFork()を疑う

事象

  • Amazon Linux
  • RubyVM起動で、メモリ使用率が単体50%のプロセスを実行中、急遽「Cannot allocate memory」のエラーログが出力されて落ちる

追加調査してわかった事象

  • 過去、バッチ実行中、一瞬(1分弱?)だけRubyのプロセスがそれまで使用していたメモリ使用率2倍をマークしてすぐに落ち着く

Fork()の疑い

  • Ruby には Fork()関数が実装
  • 挙動は、Linuxカーネルのfork()に準じる

    • 今のプロセスを保有しているメモリごと複製して新プロセスを作成する
    • すぐに別処理に移った場合、該当のメモリはすぐに開放
  • OSのベースが 10%ほどメモリ占有 + もとのプロセスが50%占有 のところに、さらにプロセス複製で50%を確保しようとしたら不足で落ちた可能性

  • Railsなどの大御所はもちろん、マルチプロセスの常套手段として利用されている。

対策

  • マシンをスケールインする
  • プロセスのメモリ所要量を低減する

【AWS Redshift】導入前に知っておくべきこと

AWS Redshiftを導入する前に知っておくべき、AWS Redshift の特性、長所、他所を開発・運用してきた中で要所っぽいところをいくつかTips的にまとめた。 字量が非常に多くて申し訳ないが、参考になれば。

RDBに比べて有用なケース/苦手なケース

  • 下記のケースに合致する。

    • SQL文をベースとした、複雑で演算コストの高いETL(分析用途用のデータ加工処理の通称)の実行
    • BIツールのような、3~5列程度の列を利用した参照クエリの実行。
  • 下記のケースは向かない。

    • 短時間で非常に多くのクエリを実行するアプリケーション(1秒に5~10クエリなど)のバックエンド
    • 短時間で非常に多くのCommitを実行するアプリケーション(Webフレームワークが勝手に)のバックエンド
    • 一度に多くの列を取得するクエリを発行するアプリケーション(CSV出力など)のバックエンド

性能について

クエリの性能

  • 1つのSQL実行にあたり、秒単位のオーバーヘッドがかかる。
    • Webサイトのように、不特定多数のユーザーからアクセスするシステムのバックグラウンドとしては致命的。
  • 取得する列数が増えるほど性能劣化。15列を超えたぐらいでもう目に見えるほど遅い。
  • 主キーやインデックスが設定できないため、適切にインデックスを設定したRDBと比較してデータ取得が遅い。(ソートキーで代用可)
  • SQLの解析も他のRDBに比べて多少気になるぐらい遅い。
  • SELECT文発行によるデータ取得が、他RDBに比べて2~3倍程度遅い。(UNLOADは別)

  • 逆に、取得する列が4,5程度であれば、GROUP BY などRDBが苦手とする演算でも非常に高速

  • RDBでインデックスを利用した場合には負けるが、そうでないINSERT-SELECTが高速。(特に、GROUP BYやサブクエリを多用する重量級SQLにて顕著に差異が出る)

チューニングについて

  • RDB経験者がチューニングにあたると、必ずと言っていいほど失敗する。
    • RDBは主キー、インデックスありきでチューニングし、また列指向と行試行では効率的な演算がほぼ真逆。
    • インデックスのようにソートキーを使うとハマる。(別記事参照)
    • 分散キーを主キーのように設定してハマる。
  • このあたりのチューニングにも、インデックス作成感覚ではすまない大きな犠牲が伴う。
  • まずはすべてデフォルト(EVEN分散、ソートキーなし)で作り、後からクエリを測定してチューニングをしていくのが無難。

サポートするSQLについて

  • 基本的にPostgresql 8.02 を基準としているが、一部使えない構文や機能がある。
  • ALTER TABLE による列定義変更は、大きな制約がある。
    • ADD COLUMN、DROP COLUMNは可能
    • MODIFY COLUMN は、VARCHARの列サイズ増加などに限って有効。
  • たいていは、別テーブルを CREATE TABLE して ALTER TABLE RENAME で交換、依存するビューを再作成・・・の流れになる。

COPY (CSVなどテキストファイル取込処理) / UNLOADの制約

  • PostgresqlのCOPYとは違い、CSV出力・取込などはS3に直接実行する。
    • 結構早い
  • COPY やUNLOADには、AWSのIAMからユーザーを作ってのACCESSKEY/SECRETKEY指定か、IAMRoleをクラスタに付与してのarn指定かなどで実行。

クエリ同時実行制限について

  • WLM(WorkLoad Management)により、デフォルトではクラスタあたりのSQL同時実行数が「5」に制限されている。
    • Redshiftは、1クエリあたりのリソース消費が激しく、また排他制御に問題がありSQL同時実行数が増えると メモリ不足を引き起こしてDisk書き込み・クエリ性能低下に至ったり、CPU不足によりスループット低下したりする。
  • 同時実行数はいくつかの枠に分割することができる。デフォルトの全枠共有だと、「ユーザーからのクエリが終わらず、バッチがいつまでたっても実行できない・・・」とかになる。注意。

ディスク容量について

  • 基本的に、インスタンスタイプを上げるとかインスタンス数を増やすとかしてランニングコストを上げるしかない。
  • 別手段として、Redshift Spectrum(中身はAthena)を利用してS3に置いたものをクエリするようにするしかないが、こちらは1TBスキャンごとに$5と馬鹿にならない別料金をとられる。(そのうえ、Redshiftとはクエリ特性が違う)
  • 列圧縮エンコーディングによりちびちび節約する手段もあるが・・・基本的には向かない。

運用について

定期的なVACUUM・ANALYZE必須

  • バッチで週一、または夜間余裕のある時に

毎月最低1回はクラスタ再起動が自動でかかる。

  • AWS公式の縛りで、土曜日の JST 22:30頃にクラスタが再起動する。
  • 通常は月1回だが、毎週のように再起動がかかるときもある。
  • AWS ConsoleでRedhisft>各クラスタに入り、「クラスタのステータス」タブで直近の再起動予定が確認できる。
  • 設定により延長できるが、最大45日まで。しかも手運用2月に1回は覚悟しておくべきだろう。

クラスタ再起動直後、ANALYZEによる統計情報が全て無効になっていることがある。

  • 時々ある事象。
  • オートアナライズでいつもよりクエリが滅茶苦茶遅い・・・ということがざらにある。

VACUUM は同時に 1つしか流せない

  • AWS Redshiftの制約。特にソートキー付きのテーブルはVACUUMが前提となるが、非常に時間がかかる上直列、他のクエリに大きな影響がでるほど負荷が高い。

バックアップについて

  • AWS Consoleよりクラスタ無停止でスナップショットを取得することができる。
  • スナップショットはS3に置かれ、S3の体系で課金

docker-compose+fluentdでバッチ処理をいい感じにログ収集しつつな構成サンプル

眠いのでもう必要なことだけをまとめる。

この記事について

  • Docker-Composeを使った簡易なバッチシステムのテンプレート構築。
  • 今回のサンプルは以下の構成。
    • 1つのLinuxマシンに、Docker-composeを利用してPostgresql と fluentdを立てて、同ホストにて docker-compose up で一回起動するようなバッチを使い各位通信する。
    • 1つのマシンに全サービスを詰め込んだため、networkなどに別マシンに分離したときはいらない設定が含まれる。許されよ

ソースコード

Github: https://github.com/pakuyuya/note-docker/tree/master/03.recipts/compose-batch-with-fulentd/docker-batch-fulentd

ディレクトリ構成

docker-batch-fulentd
    ├─batch     # バッチシステム
    │  ├─sql     # バッチで利用するSQLファイル
    │  ├─src     # Golangのソースコード
    │  ├─Dockerfile                    # バッチバイナリビルド用のDockerfile
    │  └─docker-compose.yml  # バッチ実行の yml
    └─daemons
        ├─db        # Postgresql 初期化・設定ファイル
        │  └─init   # init script
        ├─fluentd # Fulentd 初期化・設定ファイル
        │  └─conf
        └─docker-compose.yml  # 事前起動しておく各サービス用

batchのつくり

batch/docker-compose.yml

version: '3'

services:
  batch:
    build: ./   # Dockerfileをビルド
    env_file:
     - ./local.env   # 環境変数に.envファイルを利用
    command: /app/sample-batch -SqlFile /app/sql/BAT0000_update-to-diable-invalid-users.sql  # 実行コマンド
    logging:
      driver: fluentd  # ログドライバをfluentdに指定。標準出力を下記オプション通り転送するように
      options:
        tag: docker.sample_batch
        fluentd-address: 192.168.10.4:24224   # コンテナ名で名前解決する方法が、ついに見つからなかった。。
                                                                     # サービス起動するたびに docker network inspect daemons_default でIPを確認しIPアドレスを
                                                                     # 同yamlなら/etc/hostsに追記される。または、手コマンドでgateway付のdocker networkを作成
                                                                     # DockerホストのマシンのIPに投げてport fowardingからたどり着くか。(未検証)
# ネットワーク設定。
# 同マシンにて事前起動しているデーモン用のDocker-composeのサービスに接続したかったが
# Dockerのnetwork(docker-composeが自動作成する)が同じでないとpingは通れど
# アプリケーションレベルの通信が疎通しなかった。
# 下記設定により、既存のネットワークに乗っかる形で接続する。
networks:
  default:
    external:
      name: daemons_default

batch/Dockerfile

# Dockerコンテナをビルドするための構文がちがちなスクリプトファイル。
# 各コマンドの仔細は、申し訳ないが他者様のドキュメントをあたってほしい。
# MultiStage buildを利用。FROMを複数回書くと、一番最後のものだけがコンテナに残る。コンテナサイズの低減が目的
# コンテナイメージのベースにAlpineを用いているのは、もちろんコンテナイメージを小さくするため。
FROM golang:1.11.11-alpine as builder
ENV CGO_ENABLED=0   # GOのライブラリ用。GCCを使わないようにする
COPY ./src /app
WORKDIR /app
RUN apk update \
    && apk add --no-cache git \
    && go build

FROM alpine:3.10
COPY --from=builder /app/sample-batch /app/sample-batch
COPY ./sql /app/sql
RUN apk add --no-cache ca-certificates

バッチの実装は、引数で指定されたSQLファイルを読み込みSQL実行するシンプルなもの。

main.go

func run(args []string) error {
    app := cli.NewApp()
 
    app.Flags = []cli.Flag{
        cli.StringFlag{
            Name: "SqlFile",
            Value: "",
        },
    }
    app.Action = func(c *cli.Context) error {
        InfoLog("Batch started")

        // 引数取得
        sqlfile := c.String("SqlFile")
        if sqlfile == "" {
            return errors.New("Parameter 'SqlFile' is required.")
        }
     
        // DB接続
        db, err := sql.Open("postgres", GetConnectionString())
        if err != nil {
            return err
        }
        defer db.Close() // defer: returnなど関数が終わるときに実行
     
        // SQLファイル読み取り
        file, err := os.Open(sqlfile)
        if err != nil {
            return err
        }
        defer file.Close()
        bytes, _ := ioutil.ReadAll(file)
        query := string(bytes)
     
        InfoLog("Read SQL from `%s` and run query...", sqlfile)
        InfoLog("Query: `%s`", query)
     
        // SQL実行
        result, err := db.Exec(query)
        if err != nil {
            return err
        }
        // 影響行数取得
        rowsAffected, _ := result.RowsAffected()
        InfoLog("%d rows affected", rowsAffected)
        InfoLog("Batch finished")
     
        return nil
    }

    return app.Run(args)
}

func InfoLog(message string, args ...interface{}) {
    nowTime := time.Now()
    const timefmt = "2006/01/02 15:04:05"
 
    msg := fmt.Sprintf(message, args...)
 
    fmt.Printf("[INFO] %s: %s\n", nowTime.Format(timefmt), msg)
}

func GetConnectionString() (string) {
    return fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"))
}

事前起動しておくDaemonたち

daemons/docker-compose.yml

version: '3'
services:
  #backendのPostgresql.
  db:
    image: postgres:11-alpine
    container_name: db
    ports:
      - 5432:5432
    volumes:
      - ./db/init:/docker-entrypoint-initdb.d
      - ./dbdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: root
      POSTGRES_PASSWORD: root
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8"
    hostname: db
    restart: always
    user: root
  #不要だがメンテ用にpgadminが欲しかった
  pgadmin4:
    image: dpage/pgadmin4:3.3
    container_name: pgadmin4
    ports:
      - 80:80
    volumes:
      - ./pgadmin:/var/lib/pgadmin/strage
    environment:
      PGADMIN_DEFAULT_EMAIL: root
      PGADMIN_DEFAULT_PASSWORD: root
    hostname: pgadmin4
    restart: always
  #fluentd.
  fluentd:
    image: fluent/fluentd:v1.5-1
    container_name: fluentd
    ports:
     - "24224:24224"
    volumes:
     - ./fluentd/conf/fluentd.conf /fluentd/etc/fluentd.conf
    ports:
     - "24224:24224"
     - "24224:24224/udp"
# network設定。IPアドレスを指定したかったので色々configをいれた。driver: bridgeは必須。
networks:
  default:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.10.0/24

実行する

# 事前に、DockerとDocker-Composeをインストールしておく
# 言い忘れたけどCentOS7

#デーモンを事前起動
cd docker-batch-fulentd/daemons
docker-compose up -d

# バッチ起動(スクリプト修正して実行するときは--buildもいれる)
cd ../batch
docker-compose up --build

# ログ確認のためにコンテナに入る
docker exec -it fluentd /bin/sh

cd /fluentd/logs
ls
cat <lsでみたファイル名>

気づいたこと

  • Fluentdってログ出力に対して障害点増えるのでは?(Pluginに変なのいれたらすぐ不安定になるらしい)
    • 「Docker/Kubernetes 実践コンテナ開発入門」でKubernetesの世界を見たけど あそこまで重厚なことまでして冗長化するか?と運用リスクを考えて、最後まで決定を引き伸ばしそう。
    • ファイル名が完全にコントロールできて実行ホストが1つとかだったら、別に他のバッチと同様ローカルへのファイル出力でもいいよなー。
      • とはいえ、ポータビリティはリソースに余力のある別ホストへの実行も可能にする・・・とはいえ、いまのところそ単一のホストに集中していることは、そんなに困っていない。
  • バッチ実行するだけならdocker-composeじゃなくてもいいよね。
    • 環境変数に全部パラメータバインドしてから実行するならdocker-composeでもいいけど
    • 柔軟にパラメータかシェルで docker コマンドを引数モリモリでやっても別にいいんじゃないかなー、人間がうつんじゃないしとか思ってきた次第

勉強になりました(参考サイト)

【AWS Glue】事前調査

AWS Glue とは

  • サーバーレス(オンデマンド実行・実行時間課金)なETL向けサービス
  • 実態は、フルマネージドなPyhton/ApacheSparkの仮想実行環境。
  • AWS手製のライブラリ/ランタイムがAWS上のリソースの取得やクエリにめっちゃくちゃ強く(例:S3のデータをロード、Redshiftにクエリ、Athenaにクエリ)、SQL感覚で大規模のデータ加工スクリプトを書ける。
  • RedshiftやAthenaにテーブル作ると、自動で収集してカタログという形でメタデータを作る。コードのテンプレート生成に使ったりする。

調べる

2019/06次点では、決定版みたいなドキュメントはなさそう

公式ドキュメント

Future Tech ブログ

ジョブについて

  • [1] S3にPythonScalaスクリプトを置く [2] AWS GlueからJobを作成してキック の流れらしい。

  • 現在、ジョブには2つのタイプがある

    • Sparkタイプ
      • 重量級で従来タイプ
      • 実行してからインスタンスの起動に1分~10分ほどかかるインスタンス立ち上げのため)
        • 開発エンドポイントというずっと立ち上がりっぱなし(+課金しっぱなし)のインスタンスを別途立ちあげることはできる・・・がかなりランニングコストが高い(DPU 1 つにつき0.44$/時。 標準の5つであれば、5 * 0.44 * 24 * = $52.8 / 日。30日で$1584。Redshiftの2TBを1つ立てられる)
      • 機能はフル
      • 多くの「Glue使ってみた」記事は、こちらの機能を使っている
    • Python Shellタイプ
  • 1DPU(CPUみたいなの)が1時間稼働したとき、$0.44課金

  • 1ジョブごと走るごと、どんなに短く終わっても最低10分走った金額が加算。20~30回デバッグ実行するだけで$10に。(たかい!)

開発について

  • AWS Consoles にGlueのジョブエディタがついており簡易に作成ができる
  • また、GUIから入力/出力するカタログ内テーブルを選んで、テンプレートからスクリプト自動生成もできる

  • ジョブ実行も、AWS Consolesから可能。ログはCloudWatchに出力。

【AWSAthena】事前調査

概要

AWS Athenaに関する事前調査まとめ

Amazon Athenaとは

S3に配置したデータを直接クエリするAWSのサービス。 巷ではフルマネージドHIVEとかいわれている。

  • S3 に置いたファイルを直接検索。1TB読み取りにより$5。ファイルが圧縮されていると更に低減
  • SQLベースでクエリできる。構文は Presto 準拠

チュートリアル

※要:ELB利用とアクセスログ

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/getting-started.html

  • SQLライクなクエリを流す
  • TABLEに、S3ファイル名のprefixやpathをもたせるみたいだ。

CREATE TABLEのリファレンスから、機能を読み解く

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/create-table.html

CREATE [EXTERNAL] TABLE [IF NOT EXISTS]
 [db_name.]table_name [(col_name data_type [COMMENT col_comment] [, ...] )]
 [COMMENT table_comment]
 [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
 [ROW FORMAT row_format]
 [STORED AS file_format] 
 [WITH SERDEPROPERTIES (...)] ]
 [LOCATION 's3_loc']
 [TBLPROPERTIES ( ['has_encrypted_data'='true | false',] ['classification'='aws_glue_classification',] property_name=property_value [, ...] ) ]

[PARTITIONED BY (col_name data_type [ COMMENT col_comment ], ... ) ]

[ROW FORMAT row_format]

つかってみよう

S3にCSVを配置して、Athenaのコンソールからクエリを投げる

CREATE EXTERNAL TABLE test (
    num int,
    some string,
    one string
    )
ROW FORMAT DELIMITED
      FIELDS TERMINATED BY ','
      ESCAPED BY '"'
      LINES TERMINATED BY '\n'
LOCATION 's3://paku-test-bucket/athena/test/';

-- Put file to s3://paku-test-bucket/athena/test/test.csv

S3にParquetファイルを置いてS3に配置

事前にS3にParquet形式のファイルを配置する

CREATE EXTERNAL TABLE test_parquet (
    num int,
    some string,
    one string
    )
ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
WITH SERDEPROPERTIES (
  'serialization.format' = '1'
) LOCATION 's3://paku-test-bucket/athena/test_parquet/';
select * from test_parquet;

-- Put file to s3://paku-test-bucket/athena/test_parquet/test.parquet