knex.jsを用いてnode.jsでmysqlを使う

knex.jsとは

Node.js向けのSQLクエリービルダーです。

以下のデータベースに対応しています。

  • Postgres
  • MSSQL
  • MySQL
  • MariaDB
  • SQLite3
  • Oracle
  • Amazon Redshift

実際に使ってみる

今回はmysqlとのセットでknex.jsを使ってみます。

まず、今回使用するmysqlをdockerで用意します。以下のコマンドで、mysqlの動いているコンテナが起動します。

docker run -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql:5 --character-set-server=utf8mb4

そして、以下のコマンドで、'db'という名前のデータベースを作成します。

echo 'CREATE DATABASE db' | mysql -h 127.0.0.1 -u root -ppassword

以下、このデータベースをknex.jsから操作することにします。

適当なディレクトリを作り、knexとmysqlをインストールします。

npm i knex mysql

同一ディレクトリ内で作業します。

knexをインポートし、clientを定義します。

const knex = require('knex');

const client = knex({
  client: 'mysql',
  connection: {
    host: '127.0.0.1',
    user: 'root',
    password: 'password',
    database: 'db',
  },  
});

以下で、'tbl'という名前のテーブルが定義できます。

client.schema.createTable('tbl', function (table) {
  table.increments();
  table.string('name');
  table.timestamps();
})
  .then(console.log);

これによって吐き出されたSQL文はこのようなものでした。賢いですね。

making query: create table `tbl` (`id` int unsigned not null auto_increment primary key, `name` varchar(255), `created_at` datetime, `updated_at` datetime)

なおconsoleへの出力は以下のような感じになります。

[ OkPacket {
    fieldCount: 0,
    affectedRows: 0,
    insertId: 0,
    serverStatus: 2,
    warningCount: 0,
    message: '',
    protocol41: true,
    changedRows: 0 },
  undefined ]

以下のようなコードで、'tbl'テーブルにレコードを挿入できます。2行挿入するためにPromise.allを使っています。

Promise.all([
  client('tbl').insert({
    name: 'hoge',
    created_at: client.fn.now(),
    updated_at: client.fn.now(),
  }),
  client('tbl').insert({
    name: 'foo',
    created_at: client.fn.now(),
    updated_at: client.fn.now(),
  }),
])
  .then(console.log);

consoleへの出力は以下のようになりました。1と2の順番が入れ替わるかもしれません。

[ [ 1 ], [ 2 ] ]

以下のようなコードで'tbl'テーブルからデータを取得できます。

client('tbl')
  .select('name', 'created_at', 'updated_at')
  .then(console.log);

consoleへの出力は以下のようになりました。mysqlモジュールの挙動と同じですね。

[ RowDataPacket {
    id: 1,
    name: 'hoge',
    created_at: 2018-11-06T14:12:45.000Z,
    updated_at: 2018-11-06T14:12:45.000Z },
  RowDataPacket {
    id: 2,
    name: 'foo',
    created_at: 2018-11-06T14:12:45.000Z,
    updated_at: 2018-11-06T14:12:45.000Z } ]

キリがないのでここで紹介する関数はこれくらいにしますが、もちろん他にも多数の関数があります。https://knexjs.org/ を参照してください。

Dockerを用いて一行でmysqlサーバーを動かす

以下のコマンドを実行します。

docker run -e MYSQL_ROOT_PASSWORD=password -d -p 3306:3306 mysql --character-set-server=utf8mb4

これだけで、dockerのmysqlサーバーの動いているコンテナが立ち上がります。なお、以下のように設定しています。

  • rootのパスワードは'password'
  • 文字コードはutf8mb4
  • 3306番ポートをフォワード

以下のコマンドで、シェルから実際に接続できるはずです。

mysql -u root -ppassword

nginxでリバースプロキシを使ってみる

リバースプロキシとは

クライアントからWEBサーバーへのリクエストを経由させるサーバーのことです。

クライアント -> WEBサーバー という状況を、クライアント -> リバースプロキシ -> WEBサーバー のようにします。

この冗長化により、

  • セキュリティの強化
  • 負荷分散
  • SSLの対応
  • キャッシュ

などが便利になります。

実際に使ってみる

言葉だけではわかりにくいので、実際にDockerを用いて実験してみます。

次のように、3つのファイルを作ります。

  • docker-compose.yml
  • app.js
  • nginx.conf

以下、それぞれ見ていきます。

app.js

const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(3000);

簡単なexpressアプリケーションです。

ポート3000にアクセスすると'Hello World!'を返します。

nginx.conf

user  nginx;
worker_processes  1;

pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;

    server {
        listen       80;
        server_name  _;

        location / {
            proxy_pass http://app:3000;
        }
    }
}

nginxの設定ファイルです。

proxy_passの部分でリバースプロキシを設定しています。すべてのリクエストを http://app:3000 に転送しています。appというのはexpressのアプリケーションが動いているコンテナのホスト名です。(docker-compose.ymlで設定しています)

docker-compose.yml

version: '3'
services:
  app:
    image: node:11.1.0
    hostname: app
    volumes:
      - ./app.js:/src/app.js
    working_dir: /src
    command: sh -c "npm i express && node app.js"

  nginx:
    image: nginx:1.15.5
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    environment:
      - NGINX_PORT=80

dockerの設定ファイルです。

appというコンテナでexpressアプリケーションを実行し、nginxというコンテナでnginxサーバーを起動しています。volumesを用いて今回作成したファイルをマウントしています。

hostname: appという部分で、コンテナにappというホスト名を付与しています。これにより、nginxというコンテナからappというホスト名で該当コンテナにアクセスできるようになります。

確認

docker-compose upコマンドで、expressアプリケーション、nginxサーバーが起動します。

http://localhost/にアクセスすると、'Hello World!'と表示されるはずです。

Cの良さげなハッシュテーブルのライブラリ

C++のunordered_mapやPythonのdictなど、ほとんどの言語でハッシュテーブルが使えます。しかし、Cにはハッシュテーブルがありません。

言語仕様が小さいということなのでそれはそれで良いのですが、やはりハッシュテーブルが必要になることが多々あります。

そんな時にこのライブラリがシンプルで(手軽に使いたいときには)良さげです。keyはすべて文字列。 github.com

試してみる(文字列->intのハッシュテーブル)

map_int_t m;
map_init(&m);

map_set(&m, "key", 10);

int *val = map_get(&m, "key");
assert(*val == 10);

map_remove(&m, "key");
assert(val == NULL);

map_deinit(&m);

C++のunordered_set、Pythonのsetみたいなものも簡単に作っておきます

// map.hに追加

#define set_init(m) memset(m, 0, sizeof(*(m)))
#define set_deinit(m) map_deinit_(&(m)->base)
#define set_add(m, key) ( (m)->tmp = (0), map_set_(&(m)->base, key, &(m)->tmp, sizeof((m)->tmp)) )
#define set_contains(m, key) ( ((m)->ref = map_get_(&(m)->base, key)) != NULL )
#define set_remove(m, key) map_remove_(&(m)->base, key)
#define set_iter(m) map_iter_()
#define set_next(m, iter) map_next_(&(m)->base, iter)

keyが文字列だけというのもあれなのでintにもできるようにしておきます(無限に雑です)

// map.hに追加

#define map_i_get(m, key) ( (m)->ref = map_i_get_(&(m)->base, key) )
#define map_i_contains(m, key) ( ((m)->ref = map_i_get_(&(m)->base, key)) != NULL )
#define map_i_set(m, key, value) ( (m)->tmp = (value), map_i_set_(&(m)->base, key, &(m)->tmp, sizeof((m)->tmp)) )
#define map_i_remove(m, key) map_i_remove_(&(m)->base, key)
#define map_i_next(m, iter) map_i_next_(&(m)->base, iter)

void *map_i_get_(map_base_t *m, const int key);
int map_i_set_(map_base_t *m, const int key, void *value, int vsize);
void map_i_remove_(map_base_t *m, const int key);
int map_i_next_(map_base_t *m, map_iter_t *iter);
// map.cに追加

void *map_i_get_(map_base_t *m, const int key) {
  char key_str[20];
  sprintf(key_str, "%d", key);
  return map_get_(m, key_str);
}

int map_i_set_(map_base_t *m, const int key, void *value, int vsize) {
  char key_str[20];
  sprintf(key_str, "%d", key);
  return map_set_(m, key_str, value, vsize);
}

void map_i_remove_(map_base_t *m, const int key) {
  char key_str[20];
  sprintf(key_str, "%d", key);
  return map_remove_(m, key_str);
}

int map_i_next_(map_base_t *m, map_iter_t *iter) {
    if (iter->node) {
        iter->node = iter->node->next;
        if (iter->node == NULL) goto nextBucket;
    } else {
        nextBucket:
        do {
            if (++iter->bucketidx >= m->nbuckets) {
                return NULL;
            }
            iter->node = m->buckets[iter->bucketidx];
        } while (iter->node == NULL);
    }
    return atoi((char*)(iter->node + 1));
}

日本人のいない環境でエンジニアインターンをした記録

この夏休み、IndeedAustinオフィスで2ヶ月弱インターンとして働いていました。

AustinTexasの州都で、最近はIT企業が集積しています。規模は比較にはなりませんがシリコンバレーの用な感じです。

その時の記録をまとめておきます。 

Indeedとは

Indeed.comは、求人の検索エンジンで、ほぼ全世界でサービスを提供しています。最近は日本でもたくさんCMを打ったりしていますね。ユニークユーザーは月間2億くらいです。

応募・選考

最初にIndeed.comを知ったのは、去年に出たCode Festivalというプログラミングコンテストです(リクルートとIndeedが共催しているものです)。そしてその関連でインターンの案内が来ました。

エンジニアとしてレベルが上がりそう、そしてアメリカに行きたいというのがあったので、ダメ元で申し込んでみました。

まずはWeb試験を受け、そして恵比寿にある東京オフィスでコーディング面接を受けました。全部英語で(対策はある程度していったものの)かなり圧倒されました。

コーディング面接は2回ほどあり、特に2回目は3時間ほぼぶっ通しでした。人生で一番頭を使った気がします。

準備

光栄なことに、選考に通過させていただきました。相当嬉しかったです。

J1ビザ取得のための書類の準備・アメリカ大使館での面接などいろいろと想像以上に大変でした。そして一人ビザが下りず、とても悲しかったです...

ちなみに、選考に通ったのが3人で、ビザの関係で実際にオースティンに行くのは2人でした。

オースティン到着・業務開始

オースティンはめちゃくちゃ遠かったです。ミネアポリス乗り換えで、18時間くらいを要しました。

まあなんとか到着し、オースティンでの慣れない生活が始まりました。

一週目になんかしら本番環境にデプロイするのがしきたりらしく、かなり焦りました。社内wikiとチームのコードを無限に読みました。量多すぎて死ぬかと思いましたが、なんとかだんだん理解でき、一週目にも少しですが意味のあるコードを書けたと思います。 

業務中盤

中盤にさしかかってくると、だんだん開発のスピードが上がってきて楽しくなってきました。終わる気がしなかったメインプロジェクトも早めに完成させることができて、その後はチームの実際の開発フローにのっていろいろと開発させていただいていました。

このころになってくると、国際免許取ってこなかったことを後悔し始めました。また、職場が食べ放題飲み放題で太ってきたのでホテルのジムに通うようになったりもしました。 

業務後半

後半になるとなんとチームに新入社員が入ってきて、狭義の先輩になりました。コードレビューも少しだけ書いたりもするようになりました。学ぶこともあり、自分が書いたコードがきちんと全世界で動いたり、とにかく業務はありえないほど楽しんでいました。

そして最後にチームメンバーの前でこの夏やったことをプレゼンし、帰国しました。

やったこと

まず、メインプロジェクトがまず1つ与えられました。

簡単に言うと、仕事の名前を一般的なものに変換したりして(Senior Java Developer -> Software Developerみたいな)、そしてそれをユーザーの行動予測のための機械学習モデルの精度向上に役立てようみたいな感じでした。それにまつわるいろいろな部分をJavaで実装しました。

また、そのメインプロジェクトを完成させた後は、チームメンバーと同様に新機能の開発・既存の機能の改善などをしました。

その中で、アクセス元の位置情報をもとに挙動をいろいろと変えるという既存の処理を高速化する、ということもやりました。一日に数百万回実行される部分で普通に怖かったのですが、不具合もなく、処理の実行時間の中央値がnミリ秒から0ミリ秒になったときにはかなり嬉しかったです。

おわりに

学ぶことも多くあり、そして楽しく、かなり充実した夏だったと思います。

アメリカまでいかせてくれて、そしてこんな経験をさせていただいたこと、本当に感謝しかありません。