yasutomogのブログ

Software Engineerの雑記

Lumen(Laravel)のQueue管理クラス(DB)の拡張方法

概要

  • Lumenを使用したWebアプリを運用している中で、Queue(DB)を使用
  • DBは、Azure SQL Databaseを使用
  • Azure SQL Databaseでは一時的に負荷が上がると、強制的に接続を切断しDB側の再構成が走る
    DB接続をキャッシュしたり、コネクションプーリングなどを使用している場合、古いDB接続が利用されるとDB接続エラーが発生する docs.microsoft.com
  • LumenのQueue(DB)では、DB接続をキャッシュして使用しているため、デフォルトのまま使用しているとDB負荷が上がったときにDB接続エラーが連続して発生するようになる yasutomo.hatenablog.com

前提

  • lumen:5.5.7
  • PHP:7.1
  • php artisan queue:work のコマンド実行時

詳細

Illuminate\Queue\DatabaseQueueを継承したクラスを作成

<?php

namespace App\Libs;

use Illuminate\Queue\DatabaseQueue;

class CustomDatabaseQueue extends DatabaseQueue
{

    // DB再接続時のwait秒
    const RETRY_WAIT_SEC = 10;

    // DB再接続用のSQL STATEコード
    const RETRY_SQL_STATE = ["08S01", "HY000"];

    /**
     * DBへの再接続処理を加えたoverride関数
     *
     * @see DatabaseQueue::pop()
     * @param  string  $queue
     * @return \Illuminate\Contracts\Queue\Job|null
     * @throws \Exception|\Throwable
     */
    public function pop($queue = null)
    {
        $queue = $this->getQueue($queue);

        try {

            try {

                // DB負荷が高くなるとAzure SQL Databaseは、強制的に切断しDB再構築する
                // Lumenの標準実装では、DB接続インスタンスを保持して使いまわしているため
                // DB再構築後も、切断されたDB接続インスタンスを使用しようとするため
                // キュー管理テーブルを参照するタイミングでDB接続エラーが発生し続ける
                $this->database->beginTransaction();

            } catch (\Throwable $e) {

                \Log::info($e);

                if (property_exists($e, 'errorInfo')) {

                    $sqlstate = $e->errorInfo[0];
                    $sqlerrcd = $e->errorInfo[1];

                    \Log::info("SQLSTATE::" . $sqlstate);
                    \Log::info("SQLERRCD::" . $sqlerrcd);

                    // DBのコネクションプーリングを使用していない場合でも
                    // 連続して接続エラーが発生するケースがある(Azureサポート)
                    // MSサイトから再接続までには最低でも5秒は間隔を空けることを推奨しているので
                    // 余裕をもって10秒後に再接続する処理
                    // https://docs.microsoft.com/ja-jp/azure/sql-database/sql-database-develop-error-messages

                    if (in_array($sqlstate, self::RETRY_SQL_STATE)) {

                        \Log::info('キュー管理テーブル参照時にDB接続エラーが発生したので、10秒後にDB再接続します。');
                        sleep(10);
                        $this->database->reconnect();
                        $this->database->beginTransaction();
                        \Log::info('キュー管理テーブルの再接続に成功しました。');

                    } else {

                        throw $e;

                    }

                } else {

                    throw $e;

                }

            }

            if ($job = $this->getNextAvailableJob($queue)) {
                return $this->marshalJob($queue, $job);
            }

            $this->database->commit();
        } catch (\Throwable $e) {
            $this->database->rollBack();

            throw $e;
        }
    }
}

Illuminate\Queue\Connectors\DatabaseConnectorを継承したクラスを作成

<?php

namespace App\Libs;

use Illuminate\Queue\Connectors\DatabaseConnector;

/**
 * キュー管理用のDBコネクタクラス
 *
 * Class CustomDatabaseConnector
 * @package App\Libs
 */
class CustomDatabaseConnector extends DatabaseConnector
{
    /**
     * returnするクラスを変更したoverride関数
     *
     * @see DatabaseConnector::connect()
     * @param  array  $config
     * @return \Illuminate\Contracts\Queue\Queue
     */
    public function connect(array $config)
    {
        return new CustomDatabaseQueue(
            $this->connections->connection($config['connection'] ?? null),
            $config['table'],
            $config['queue'],
            $config['retry_after'] ?? 60
        );
    }
}

App\Providers\AppServiceProviderの改修

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Libs\CustomDatabaseConnector;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // キュー管理のDBコネクタ登録
        $queueManager = $this->app['queue'];
        $queueManager->addConnector('customdatabase', function () {
            return new CustomDatabaseConnector($this->app['db']);
        });
    }
}

api/config/queue.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Default Queue Driver
    |--------------------------------------------------------------------------
    |
    | The Laravel queue API supports a variety of back-ends via an unified
    | API, giving you convenient access to each back-end using the same
    | syntax for each one. Here you may set the default queue driver.
    |
    | Supported: "null", "sync", "database", "beanstalkd", "sqs", "redis"
    |
    */

    'default' => env('QUEUE_DRIVER', 'customdatabase'),

    /*
    |--------------------------------------------------------------------------
    | Queue Connections
    |--------------------------------------------------------------------------
    |
    | Here you may configure the connection information for each server that
    | is used by your application. A default configuration has been added
    | for each back-end shipped with Laravel. You are free to add more.
    |
    */

    'connections' => [

        'customdatabase' => [
            'connection' => 'hoge',
            'driver' => 'customdatabase',
            'table' => 'QueueJobs',
            'queue' => 'default',
            'retry_after' => 60,
        ]

    ],

];

artisanコマンドを変更

  • php artisan queue:work customdatabase

Lumen(Laravel)のQueue処理(DB)フロー

概要

  • LumenのQueue(DB)実行のコードを追ったのでメモ

前提

  • lumen:5.5.7
  • PHP:7.1
  • php artisan queue:work のコマンド実行時

フロー

  • Illuminate\Queue\Console\WorkCommandクラスが実行

    github.com

  • Illuminate\Queue\Worker.php#daemonが実行

    github.com

    • 本関数内で無限ループさせてキュー監視処理を実行している
      artisanコマンド実行時にsleepオプションをお渡すの無限ループ内のsleep処理に使用される
    • 本関数内で、Illuminate\Queue\QueueManager#connection関数を呼び出し
  • Illuminate\Queue\QueueManager#connection

    github.com

    • 本関数内で、DB接続用のコネクションを返す
    • 一度コネクションを作成すると、クラス変数として保持するようになっていて、使い回すような仕組みになっている
    • コネクション生成の初回時は、本関数内から本クラスのresolve関数が呼び出され、resolve関数内から「Illuminate\Queue\Connectors\DatabaseConnector#connect」が呼ばれる
  • Illuminate\Queue\Connectors\DatabaseConnector#connect

    github.com

    • config/queue.phpのconnections.databaseの設定を使用し、Illuminate\Queue\DatabaseQueueクラスを生成

シリコンバレー式最強の育て方の読書感想

概要

  • 今期に入ってから1on1を実践開始していた
  • ネット上で転がってる記事を読みつつなんとなくで運用していた
  • 継続して実施していくために、「シリコンバレー式 最強の育て方」を読んだのでメモ

シリコンバレー式 最強の育て方 ― 人材マネジメントの新しい常識 1 on1ミーティング―

シリコンバレー式 最強の育て方 ― 人材マネジメントの新しい常識 1 on1ミーティング―

メモ

1on1について

  • 単純な定例になってしまうのは良くない
    • マンネリしてしまったり、重要な時間ではないというイメージが付くと効果的な時間につながらない
    • 細かい工夫や改善を考えて実施することから、質の高い時間に繋がる(重要なものという位置づけとなる)
    • しらけさせないために工夫をすることが重要(こちらの努力がない惰性で実施している風に伝わると相手のやる気もなくなる)
  • メンバーがそれぞれ多種多様な問題を個別に抱える時代になっているので、個人にフォーカスしたマネジメントが必要
    • 個人にフォーカスをあてるのは時間かけなければできないことなので、この時間を後回しにしていたとしてもいずれ同じ労力を使う必要がある
    • 後回しにしていると、同じ労力を使ったとしても良くない結果につながることが多くなるため、それなら定期的に実施することで良い結果に繋げる方が正しい
  • 1on1でのコミュニケーションが増えることで、仕事でのコミュニケーションに掛かる時間を削減可能(先行投資)
  • 最後に会を振り返りまとめてから、次回までにするアクションを決める(アクションは1つに絞って確実な実行を促す)
  • 実施する側も完璧ではないので、等身大の自分をさらけ出すつもりで。

1on1の内容

プライベート相互理解

  • 自分のことを話すことでオープンな関係を構築する
  • 相手を100%受け入れるスタンスで望む

心身の健康チェック

  • 毎回実施したほうがいいトピック
  • 業務時間や業務量の話から、体調面についても確認する

モチベーションアップ

  • 話を全部聞きだして、アウトプットを多くしてもらう。消化不全を防ぐ。
  • 毎回、褒めるところを探して望む(周りからの評価を聞いて伝えることも効果的)

業務・組織課題の改善

  • 現状、改善案、今後の貢献方法などについて話し合う
  • 必要なのは、相手に対してこちらの意見を押し付けるのではなく、促すように動くこと

目標設定/評価

  • 細かくフィードバックする機会をもつことで、お互い納得して目標に対して向き合えるようになる
  • 目標と評価の関係を理解して説明出来る必要がある

能力開発/キャリア支援

  • 失敗からの振り返りや、本人が気づいていない能力に気づかせるサポート
  • 会社内だけでなく個人としてのキャリアプランも一緒に話をしてみるのがいい

戦略・方針の伝達

  • 報告や連絡は受けるだけでなく、積極的にこちらからも共有していく
  • 結果だけではなく、周りのどういった意思決定のプロセスがあったかなども共有していく

JavaScriptの色々な非同期処理の書き方(callback、promise、async/await)

概要

  • callback、promise、async/awaitの3パターンで非同期処理を書いてみる

内容

callback

function add1000(n) {
  setTimeout(() => {
    console.log(n + 1000);
  }, 2000);
}

function add100(n) {
  setTimeout(() => {
    console.log(n + 100);
    add1000(n + 100)
  }, 2000);
}

function add10(n) {
  setTimeout(() => {
    console.log(n + 10);
    add100(n + 10);
  }, 2000);
}

function proc(n) {
  console.log(n);
  add10(n);
}

proc(1);

promise

function add1000(n) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n + 1000);
    }, 2000);
  });
}

function add100(n) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n + 100);
    }, 2000);
  });
}

function add10(n) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n + 10);
    }, 2000);
  });
}

function proc(n) {
  console.log(n);
  add10(n).then((r) => {
    console.log(r);
    return add100(r);
  }).then((r) => {
    console.log(r);
    return add1000(r);
  }).then((r) => {
    console.log(r);
  });
}

proc(1);

async/await

function add1000(n) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n + 1000);
    }, 2000);
  });
}

function add100(n) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n + 100);
    }, 2000);
  });
}

function add10(n) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(n + 10);
    }, 2000);
  });
}

async function proc(n) {
  console.log(n);
  n = await add10(n);
  console.log(n);
  n = await add100(n);
  console.log(n);
  n = await add1000(n);
  console.log(n);
}

proc(1);

github

github.com

  • それぞれjsファイルを用意しているので、index.htmlで読み込むjsを変えて試してみる
  • 手っ取り早く試すには「Web Server for Chrome」を使ってブラウザで開いて、開発ツールのコンソール立ち上げればOK

コンピテンシー面接マニュアルの感想

概要

  • コンピテンシー面接マニュアルの読書感想
  • これまでは感覚的に面接をしていたため、軸みたいなものが欲しかった

内容(ざっくり気になったところだけ)

  • 学力的な優秀さだけを見るのではなく、成果を繋げるための思考やそれに伴う行動が取れる人かを見極める
  • 面接する側、される側の主観的な話は極力避けるようにし、過去の具体的な行動から客観的に判断する
  • 具体的な行動を深掘りすることで、面接への対策は難しくなり、本質を見ることが可能
  • コンピテンシー能力の高い人は問題に対してPDCAサイクルを回すことが出来る人(自己マネジメントができる人)
  • 行動に対しての成果については特に気にする必要はなく、問題に対してどう考えて行動したかが重要(結果ではなく過程を重視する)
  • 6種類のコンピテンシーの要素と6通りのコンピテンシーパターンがあり、それを元に評価。また、会社にとってどのコンピテンシー要素を重要視するなどモデルを作成したほうがいい
  • コンピテンシー面接を導入していることを前もって共有し、学生時代の取り組みなどをエントリーシートに記載してもらうなどすると進めやすい
  • 行動について抽象的な表現がある場合は、なるべく掘り下げて具体的な行動を聞くようにする。但し、話を引き出すための誘導がすぎると面接者の主観が入るため気をつける
  • 2次面接、3次面接のように1人に対して面接の時間が十分に取れる場合、面接者からのテーマに対して時系列で行動を確認し、工夫した点や苦労した点を確認していく
  • 面接者の取り組み内容を選択するときは、取り組み期間が長いものを優先的に選ぶほうがいい(短過ぎるものは計画的なものではなく突発的なものが多いため)
  • 面接者は気をつけないと、質問の口調が機械的になるので、気持ちを込めて質問したり最初にこちらから率先して自己紹介するなどでリラックス出来る雰囲気作りが重要。但し、途中で評価するのはNGなので、その点は気をつける。
  • 行動を詳細にするために5W1Hを意識して確認していく
  • 1次面接などの1人に対して面接の時間が十分に取れない場合は、苦労した点や工夫した点を先に聞き、そこに焦点を絞って掘り下げる

感想

  • これまで自分が面接したときを振り返ると、主観をいれて誘導してしまっていたことが多々あるので、そこは機械的に行動事実を確認する方法を試してみたい
  • 確認そのものは機械的に実施しつつ、口調や雰囲気などはリラックスさせるというのが、慣れるまで難しそう
    この辺は、「エンジニアリング組織論への招待」記載されていた、話し方なども流用して試していきたい
  • セルフマネジメントというところは、こちら側も自分がちゃんと出来ているか改めて思い返すいい機会になった。近いところで「SOFT SKILLS」という本がありセルフプロヂュースについて記載されているので、それも踏まえて実践していければいいなと。

コンピテンシー面接マニュアル

コンピテンシー面接マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

SOFT SKILLS ソフトウェア開発者の人生マニュアル

Sencha ExtJSでasync/awaitを使う方法

概要

  • Sencha ExtJSを使ったプロジェクトの非同期処理でもECMAScript 2017 (ECMA-262)を使って実装したい。
  • 単純にasync/awaitを使ってコーディング後、「sencha ap b」などでビルドすると、エラーとなった

前提

  • Sencha Cmdを使用して生成したプロジェクト
  • Sencha ExtJSのバージョンは、6.5.2.463
  • Sencha Cmdのバージョンは、6.5.2.15

対応方法

  • ビルド設定を変更
  • プロジェクトのルートディレクトリに「app.json」があるので、そこに以下の記述を追加
"language": {
    "js": {
        "input": "ES8",
        "output": "ES8"
    }
},

応用

  • レガシーなブラウザにも対応させる場合は、outputに「ES5」などを指定することで、Babelなどと同様にトランスパイラとして実行できる

参考リンク

www.sencha.com

cordovaを使ったAndroidのリリースエラー回避

概要

  • cordovaでiOSAndroid開発をしているプロジェクトを引き継いだ
  • 数年前に発足したプロジェクトで諸々のライブラリや設定が古い状態
  • 下手にcordovaなどのバージョンアップしようとすると、まともに動かない
  • 当初作成していた開発者とはほぼ連絡取れず、、、

今回のハマったところ

  • cordovaのビルドまでは完了し、apkファイルまでは作成できた
  • apkファイルの署名については、以下のリンクを参考にさせていただいた

phiary.me

  • 署名なども上記をみて問題なくコマンドが通るところまでは確認OK
  • Google Play Consoleにログインしてapkファイルをアップロードすると以下のエラーが発生。
アップロードできませんでした
アップロードした APK の署名が無効です(署名の詳細)。apksigner のエラー: ERROR (Jar signer アプリ名.RSA): JAR signature META-INF/アプリ名.RSA uses digest algorithm SHA-256 and signature algorithm RSA which is not supported on API Level(s) 17 for which this APK is being verified

解消方法

  • 署名のコマンドを変更(その後のzipalignコマンドについては同様)

解消前

  • jarsigner -verbose -keystore キーファイル名 アプリ名.apk アプリエイリアス

解消後

懸念

  • apkファイルは解消方法のやり方で作ったもので正常にアップロードできるようになった。
  • 但し、API Levelが古いものを指定しているよといった警告はでているので、根本的にはその辺から解決しなければいけない気がする。