ふんばりフロントエンジニアのブログ

新米フロントエンジニアの備忘録です。ふんばり温泉タオル欲しい...

LaravelでPostgreSQLでもコネクションプーリングする方法

最近めっちゃLaravel使ってます。

で、Laravelのコネクションプーリングの話です。

コネクションプーリングとは、DBとの接続を永続化させること。

フレームワークによっては、これがデフォルトで永続化されているものもあるようですが、Laravelの場合はデフォルトで永続化がオフになっています。

DBとの接続の永続化、というと語弊があるかもしれないので補足しておくとPDOオブジェクトのインスタンス化時の接続をキャッシュしておく、といったほうが良いでしょうか。

これによって、毎回クエリ実行時に接続をする必要がなくなるのでオーバーヘッドが短縮され、高速化される、というわけですね。

あまり必要に感じる場面には出くわさないかもしれませんが...笑

で、Laravelでコネクションプーリングを有効にする設定が下記です。

 'pgsql' => [
            'driver' => 'pgsql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '5432'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'charset' => 'utf8',
            'prefix' => '',
            'prefix_indexes' => true,
            'schema' => 'public',
            'sslmode' => 'prefer',
            'options' => [
                PDO::ATTR_PERSISTENT => true,
            ],
        ],

デフォルトとの変更点は、optionsの部分です。

「PDO::ATTR_PERSISTENT」を追加することで、接続を永続化することができます。

mysqlの例しかなかったので、PostgreSQLではできないのかと思っていましたが、そんなことはなかったですね。

永続化できているかどうかの確認は、下記手順で確認できます。

  1. php artisan tinker
  2. DB::connection()->getPdo();

出力結果の「PERSISTENT」がtrueになっていれば永続化できています。

余談

コードを追っていただくとわかるのですが、「src/Illuminate/Database/Connectors/Connector.php」がコネクションを作成しているクラスになっています。

その中に、下記のような記述があります。

/**
     * Create a new PDO connection instance.
     *
     * @param  string  $dsn
     * @param  string  $username
     * @param  string  $password
     * @param  array  $options
     * @return \PDO
     */
    protected function createPdoConnection($dsn, $username, $password, $options)
    {
        if (class_exists(PDOConnection::class) && ! $this->isPersistentConnection($options)) {
            return new PDOConnection($dsn, $username, $password, $options);
        }

        return new PDO($dsn, $username, $password, $options);
    }

    /**
     * Determine if the connection is persistent.
     *
     * @param  array  $options
     * @return bool
     */
    protected function isPersistentConnection($options)
    {
        return isset($options[PDO::ATTR_PERSISTENT]) &&
               $options[PDO::ATTR_PERSISTENT];
    }


まあ、見ての通りなのですがoptionsのkeyにPDO::ATTR_PERSISTENTがセットされていれば、永続化されるようになっています。

公式ドキュメントには記載されていない部分ですが、同じ場面に出くわした時助けになれば嬉しいです。

LaravelのJoinをクロージャで使った時に「Invalid text representation: 7 ERROR」が出た時

$query = $this
            ->from('tbl_reservation as tr')
            ->join('tbl_course as tc','tr.f_course_id', '=', 'tc.f_course_id')
            ->join('tbl_slot_course as tsc', function ($join) {
                $join
                    ->on('tsc.f_reservation_slot_id', '=', 'trs.f_reservation_slot_id')
                    ->where('tsc.f_course_id', '=', 'tc.f_course_id');
            })
          


↑は「f_course_id」の部分で「Invalid text representation: 7 ERROR: invalid input syntax for integer」が出る

あ、whereの指定かな?と思って「where('tsc.f_course_id', 'tc.f_course_id')」に変更

$query = $this
            ->from('tbl_reservation as tr')
            ->join('tbl_course as tc','tr.f_course_id', '=', 'tc.f_course_id')
            ->join('tbl_slot_course as tsc', function ($join) {
                $join
                    ->on('tsc.f_reservation_slot_id', '=', 'trs.f_reservation_slot_id')
                    ->where('tsc.f_course_id', 'tc.f_course_id');
            })


↑も「Invalid text representation: 7 ERROR: invalid input syntax for integer」が出る

なので、whereColumnを使ってみると...

$query = $this
            ->from('tbl_reservation as tr')
            ->join('tbl_course as tc','tr.f_course_id', '=', 'tc.f_course_id')
            ->join('tbl_slot_course as tsc', function ($join) {
                $join
                    ->on('tsc.f_reservation_slot_id', '=', 'trs.f_reservation_slot_id')
                    ->whereColumn('tsc.f_course_id', 'tc.f_course_id');
            })


いけた。

なんとなく理解した記事。
stackoverflow.com


joinのクロージャの引数$joinの型が「Illumintate\Database\Query\JoinClause」であるため、このような現象が起きたようです。
使えるメソッドが違うっぽいです。

色々あるのね。。。。。

iPadのブラウザデバッグはXcodeが便利だった話

久しぶりに最近、ブラウザデバッグをする機会がありました。

で、問題となったのは「viewport」の部分。

window幅がタブレットの領域(768-1024px)の場合は、viewportのwidthを固定して、PC版を表示させる処理を書いておりました。

しかし、Safariの開発者ツールでデバッグしているとなんか効いていない感じのご様子..chromeではしっかり効いているようなんですけどね。

ということで「iPad実機でデバッグしよう!」となったのですが、絶賛リモート中なので残念ながら実機が手元にありませんでした..

で、聞くところによるとXcodeのシミュレータがかなり優秀らしく、実機レベルの再現度 + 立ち上げたシミュレータをSafariデバッグできるとのこと。

では、手順紹介です。

1. Xcode開く

2. 下記からシミュレータ開く
f:id:ma1129nm:20201125185622p:plain

3. シミュレータにカーソル合わせてデバイスを選ぶ
f:id:ma1129nm:20201125185758p:plain

4. Safari開く
Safariの開発者ツールが最近調子悪いので、Safari Technology Previewの方がおすすめです
developer.apple.com

5. Safariで開発者ツール開く
f:id:ma1129nm:20201125190516p:plain

6. デバッグ開始

的な感じでかなり手軽に実機に近い形でデバッグすることができます。

開発段階のブラウザデバッグはこれでよさげ。

Dockerでwebサーバ立てて「アクセスできません」となったときに確認すること

Dockerを初めて触ったとき、よくChromeの「アクセスできません」的なエラーページ表示されますよね。

もちろん「Docker apache アクセスできません」とかで検索しても出てこず、どこから調査したらいいのかわからない、という方に向けて書いていきます。

webサーバーはapacheとして話を進めていきます。

コンテナが正常に構築されているか

つまり、コンテナが立ち上がっているかどうかの確認です。

「docker ps」とかやってもwebサーバーのコンテナが見えないときは正常にビルドされていません。

こういう場合、「docker ps -a」とオプションを足すことで、今までビルドして削除されていないコンテナの一覧を見ることができます。

「ビルドするコマンド打ったのに、コンテナが出てこない...」という方はまず「docker ps -a」でコンテナがあるか確認しましょう。

で、該当のコンテナが「exit」とかになっている場合はビルド中に何かしらのエラーが出て終了している場合が多いので「docker logs [コンテナID]」でログを確認します。

configファイルの設定が間違っているとか、ちゃんとログが出ているはずなのでそれを元に修正しましょう。

webサーバは動いているか

「コンテナはビルドされているけど動かない」という場合ですね。

この場合、まずコンテナに入りましょう。

dockerコマンドであれば「docker exec -it [コンテナID] bash」ですかね。

docker-composeであれば、docker-compose.ymlがあるディレクトリで「docker-composer exec [コンテナ名] bash」でいけます。

で、ちゃんとwebサーバーが動いているか確認したいので「service httpd status」(もしくはsystemctl status httpd)を打ちます。

ここで「stopped」とか出てたらログを確認しましょう。

configの設定によりますが、大体は「/var/logs/httpd/error.log」とかにあるはずです。

どうしても見つからないというときは「sudo find / -name error.log」とかで検索しましょう。

あとは、ログの内容を修正して終わりです。

apacheのconfigを修正した場合は「service httpd restart」しないと反映されないので忘れずに打ちましょう。

しかるべきポートは開いているか

普通の80番ポートを開くのは大体テンプレートに書いてあるので問題ないと思いますが、ssl通信したいときに443番ポートを開いておかないと設定自体はうまくいってても一生ssl通信できないです。

めちゃくちゃ初歩ですが、「https化しよー」と思ったときに意外と最後まで忘れてたりするので確認しましょう。

DynamoDBから取得したデータを正常にマッピングして整形する

最近AWSよく使っているのですが、データベースとしてDynamoDBを使うことが多いです。

全てJavaScriptで完結できるのでありがたい面もあるのですが、若干めんどくさいことも多いです。

その中の一つがDynamoDBの中にMapとして保存している場合です。

例としてはこんな感じですかね。

const testDDB = {
  key1: {
    'S': 'value'
  },
  key2: {
    'S': 'value'
  }
}

取得した際にStringなら「S」といったように余計なオブジェクトが作られてしまっています。

そのようなデータをマッピングするのがこちらの関数。

const mapper = (data) => {
    let S = "S";
    let SS = "SS";
    let NN = "NN";
    let NS = "NS";
    let BS = "BS";
    let BB = "BB";
    let N = "N";
    let BOOL = "BOOL";
    let NULL = "NULL";
    let M = "M";
    let L = "L";

    const isObject = (value) => typeof value === "object" && value !== null;

    if (isObject(data)) {
      let keys = Object.keys(data);
      while (keys.length) {
        let key = keys.shift();
        let types = data[key];

        if (isObject(types) && types.hasOwnProperty(S)) {
          data[key] = types[S];
        } else if (isObject(types) && types.hasOwnProperty(N)) {
          data[key] = parseFloat(types[N]);
        } else if (isObject(types) && types.hasOwnProperty(BOOL)) {
          data[key] = types[BOOL];
        } else if (isObject(types) && types.hasOwnProperty(NULL)) {
          data[key] = null;
        } else if (isObject(types) && types.hasOwnProperty(M)) {
          data[key] = mapper(types[M]);
        } else if (isObject(types) && types.hasOwnProperty(L)) {
          data[key] = mapper(types[L]);
        } else if (isObject(types) && types.hasOwnProperty(SS)) {
          data[key] = types[SS];
        } else if (isObject(types) && types.hasOwnProperty(NN)) {
          data[key] = types[NN];
        } else if (isObject(types) && types.hasOwnProperty(BB)) {
          data[key] = types[BB];
        } else if (isObject(types) && types.hasOwnProperty(NS)) {
          data[key] = types[NS];
        } else if (isObject(types) && types.hasOwnProperty(BS)) {
          data[key] = types[BS];
        }
      }
    }
    
    return data;
    
  }

これを実行すると..

console.log(mapper(testDDB)) // Object {key1: "value",key2: "value"}


きれいに一次元になりました。

DynamoDB使うときはこれでいいかも

参考:
Dynamo db json re mapper · GitHub

【Sourcetree】Basic認証でミスって間違ったユーザー名入れて永遠に認証できない時の対処法


Sourcetreeめっちゃ便利で使っているのですが、二段階認証の時のユーザー名の記憶制御によってちょっとハマりました。

Basic認証とか二段階認証を求められるレポジトリを相手にするときにユーザー名とパスワードを求められると思います。

この時大抵の方は「ユーザーとパス覚えとけよー」というオプションにチェックを入れていると思うのですが、間違って先頭に空白スペースとか入った状態でコピペするとそのまま永遠にその記憶が残ってしまいます。

調べていると「キーチェーン(当方Macです)でSourcetreeで引っ掛けて消せばOK」って出てきたのでやってみたのですが依然として残ったまま..

で、さらに調べると下記の記事がヒット!

https://community.atlassian.com/t5/Sourcetree-questions/SourceTree-does-not-retrieve-user-name-and-password-from/qaq-p/386585

まあ「~/Library/Application Support/SourceTree」の中の「hostusernamemap」がhostとusernameのマッピングをしているようです。

てことでこれ削除して再起動したら初期状態に戻ってユーザー名打てました。

ただすべてのマッピングをリセットしちゃうので奥の手ですよねきっと。

踏み台サーバー経由してssh接続する

普通接続先は一つだけのことが多いですよね。

ただIP制限かけられてたりするとサーバーを経由してssh接続しないといけません。

丁寧にやると「sshで踏み台サーバーログイン→踏み台サーバーからssh接続」となるわけですが、ローカルマシンからコマンド一発で踏み台サーバー経由して接続できる方法があるよう。

今回私が行なったのはsftp接続でしたがコマンド的にはこんな感じ。

sftp -o ProxyJump=[踏み台サーバー:User]@[踏み台サーバー:HostName] [最終接続サーバー:User]@[最終接続サーバー:HostName]


これ打ち込むとパスワードが二回聞かれ、打ち込めば接続できます。

「-o」オプションはsshのオプションを指定する、というもので他にも色々なオプションが指定できます。

パスワード接続許可指定ない場合はどちらもconfigのhostname使ってもアクセスできました。

sftp -o ProxyJump=[踏み台サーバー:Host] [最終接続サーバー:Host]

この方法だとローカルディレクトリが自分のマシンを指してくれるので、パッとファイル確認したい時とかは便利です。(ログイン→ssh接続だとローカルディレクトリが踏み台サーバー指すので...)