くずきのblog

技術とか色々

LaravelのMacroを使ってBuilderに機能を追加する方法

どうも、くずき(@kzkohashi)です。
先日、正しいJSON APIのフォーマットにするために以下のライブラリを導入した。

github.com

paginateは正しいクエリーパラメーターを取っていない

恥ずかしながら最近知ったのだが、Eloquentで使用しているpaginateは、完璧なJSON APIのフォーマットに対応していない。
ページ番号で使われるpage=2などのクエリパラメーターは、正しくは、page[number]=2にすべきみたいだ。加えて、ページ番号だけでなく、そのページにどれくらい表示させたいかなどはpage[size]=20で表現する。

なので、導入前と導入後ではこうなる。
導入前
xxx.com/xxx?page=2&size=20 // sizeに関しては独自実装

導入後
xxx.com/xxx?page[number]=2&page[size]=20

さらにきになる方は以下を参照。

Note: JSON API is agnostic about the pagination strategy used by a server. Effective pagination strategies include (but are not limited to): page-based, offset-based, and cursor-based. The page query parameter can be used as a basis for any of these strategies. For example, a page-based strategy might use query parameters such as page[number] and page[size], an offset-based strategy might use page[offset] and page[limit], while a cursor-based strategy might use page[cursor].

JSON API — Latest Specification (v1.0)

ライブラリの実装を見て見る

ここからが本題。
このライブラリを導入し、設定ファイルを書くとjsonPaginate()が使えるようになる。

YourModel::where('my_field', 'myValue')->jsonPaginate();

どうやって実装したんだろうとコードをみて見るとBuilder::macroによって独自の関数を追加できるみたいだ。

<?php

  ...
  
  Builder::macro(config('json-api-paginate.method_name'), function (int $maxResults = null, int $defaultSize = null) {
      $maxResults = $maxResults ?? config('json-api-paginate.max_results');
      $defaultSize = $defaultSize ?? config('json-api-paginate.default_size');
      $numberParameter = config('json-api-paginate.number_parameter');
      $sizeParameter = config('json-api-paginate.size_parameter');
      $size = request()->input('page.'.$sizeParameter, $defaultSize);
      if ($size > $maxResults) {
          $size = $maxResults;
      }
      return $this->paginate($size, ['*'], 'page.'.$numberParameter)
          ->setPageName('page['.$numberParameter.']')
          ->appends(array_except(request()->input(), 'page.'.$numberParameter));
  });

github.com

これはLaravelMacroという機能で、Laravelが提供している機能に独自のメソッドなどを拡張実装できるみたい。
Builder以外には、HTML, Responseなどを拡張できる。

他にどう使えるか?

例えば、Eloquentが返すModelではなく、独自に実装したEntityを返すcollectionを用意するとか

<?php

Builder::macro('toDomainCollection', function () {
    /** @noinspection PhpUndefinedMethodInspection */
    return $this->get()->map(function (Domainable $model) {
        return $model->toDomain();
    });
});

レスポンスである条件の時に決まった内容などを送る時とか

<?php

Response::macro('notPermitted', function ($type = null) {
    if (strtolower($type) === 'success') {
        return $this->json(['data' => []], 200);
    }
    return $this->json(['data' => []], 403);
});

今まで作ってたやり方を見直すだけでも色々ありそーだなーと思います。

キカガクで機械学習を学んだことを振り返る(~機械学習における流派の違い~)

どうも、くずき(@kzkohashi)です。
知り合いのエンジニアが、去年からブログを毎月20くらい書いてて、フォロワーも何倍も増え、毎月のPVも1万くらいあるみたいで継続は力なりだなと思う今日頃ごろ。

2017年の4月ごろ、キカガクがやっている、機械学習の初級者向けのブラックスボックスセミナーというものを受けた。
先に感想いっておくと、すごくよかった。機械学習において、理論を知るためには「数学」というハードルが常に付きまとうのだが、そのハードルをだいぶ下げてくれて、色々な本の数式やらが気持ち読めるようになってきた。
あと、基本紙に書かされるので、2日間で30〜40枚くらいノート使いました。笑

www.kikagaku.co.jp

去年より値段上がってるけど、セミナーの内容も洗練されているだろう。しかも他のセミナーも増えているので興味ある人は是非。
とおもったら、Udemyで初級/中級(こないだ出たばかり)があるっぽくて、目次だけ見た感じこっち受けて見てもいいかも

www.udemy.com

www.udemy.com

本題に戻りますと、今回は受けたセミナーを元に内容を復習していきたいと思う。流れとしてはこんな感じ。

授業の内容全部は覚えてないのとオリジナルっぽいところは言わないようにしてるので興味ある人はセミナーかUdemy受けた方がいいです!

なぜ受けたのか?

僕は一昨年ごろから時代の荒波にのまれ、機械学習を勉強しないとなと思い色々な書籍を読んだ。研究室ではクラスタリングや簡単に機械学習を学んでいたのもあって、概念や実装自体はなんとなくわかってたような気がしてしまっていた。
実際は、ビジネスで活用って話になるとあまり理解できず、そもそもこの手法がなんでいいのか、数学的に解いたことがないため本当になんでこれがこうなるのかがわからずにモヤモヤしてた。そんな時に機械学習を数学から丁寧にといてくれるというセミナーをQiitaらへんでみつけ、即応募した。おこづかい制になったので5万円は手痛いたかったけど、時間は金で買うものだ。(と言い聞かせる)

機械学習における、流派の違い

そもそも、機械学習には求め方において流派の違いがある。
線形代数ベースと確率統計ベースとなるものだ。

f:id:kzkohashi:20180212110237p:plain

線形代数ベースは、ディープラーニングSVMなどの手法がある。比較的線形代数は解きやすいとされていて、初学者はこっちからはいるといいらしい。
確率統計ベースは、MCMCベイズ統計などがある。線形代数と比べ、解き方が難しく初学者にはきついらしい。これは目からウロコで、自分が買った書籍などを読み返すと確かにやめてしまったあたりは確率統計ベースの話らへんになっている。。

内挿と外挿

機械学習では、規則性を見つける必要がある。ということは過去のデータが必要になる。
コンピューター自身が規則性を式に表すことは、候補が多すぎるため、式の定数を求めるのが非常に難しい。
過去のデータの範囲内の値を求めることを内挿といい、範囲外の値を求めることを外挿という。 内挿内のデータなら予測することは可能であるのに対して、外挿(内挿外)のデータの場合、また定数を求めなくてはいけない。

ここで例をあげると、特定の年代の人たちのアンケートを収集し、他の年代の人たちの結果を予測するものがあるとしよう。   そこで最も重要になるのが、アンケートを取る際に最小値と最大値を意識して取ること。

  • 20代(最小)と30代(最大)のアンケートを収集: 40台の予測は外挿になる
  • 20代(最小)と60代(最大)のアンケートを収集: 30台の予測は内挿になる

ここらへんは肌感でなんとなくわかる気がする。
ただ、こういったことを知っておかないとデータ取得がもっとも重要な領域なので意識しておきたいところだ。
株価などの時系列データに関しては、値段などが内挿となる。

補足

そもそも、機械学習統計学の違いってなんだろう?とか他に種類あるのか?って思った時に参考になった記事の紹介。
分析の業界じゃ有名なTJO先生

tjo.hatenablog.com

さらに、機械学習エンジニアやデータサイエンティストの違い、要求されるスキルについて書いた記事も参考になるので是非。 要求スキルのレベル高すぎてやばいけど、目標たてずらかったからこの記事のおかげで目標立てやすくなった。

tjo.hatenablog.com

すでにある程度勉強されている方は、データサイエンティストの知り合いが、このやり方をトレースするとかなり勉強になると思うよといっていたのでこちらも。分析する際に、まずはデータの散らばりや、どういうところに着眼点を置くのかがかなり重要なためそういった話とかもあります。

www.analyze-world.com

終わり

区切り悪いのでイントロはこんなもんで。

WEBエンジニア勉強会#5で「Amazon Rekognition」を使った例についてLTしてきた

どうも、くずきです。
昨日は、WEBエンジニア勉強会#5に参加し、LTさせてもらってきた。
この勉強会は、「初心者(LT者も含む)でも大丈夫なコミュニティづくり」をしており、LT初心者の自分にとっては行きやすい勉強会だった。

自分の発表内容と他の方と発表、感想などを書いておこうと思う。

https://web-engineer-meetup.connpass.com/event/75898/

発表資料

テーマは「Amazon Rekognitionを用いてフォロワーの男女比を出す」にした。
これにした理由としては、Amazon Rekogntionの使ってみた系の例は多いものの、実際に目的があって使ってる例はあまり少ないのでこれにしてみた。

補足

  • 本番運用している(良い感じ)
  • 最終的には500万くらいの画像識別を目指したい
  • 顔認識のAPIと物体のAPIは別

途中のFace APIAmazon Rekognitionの比較の記事

kzkohashi.hatenablog.com

途中のアーキテクチャの説明にあったプロフを設定してるかいなかの判別方法の記事

kzkohashi.hatenablog.com

反省

  • 発表時間が過ぎてしまった
    • ちょっと早口めで練習したんだけど、本番だとゆっくりになってしまった
    • 他の人の発表見てると、ゆっくりで聴きやすかったのでそもそもゆっくりのほうがいい・・?
  • 資料ばかり見過ぎた
    • せっかくコントローラーでプレゼン動かすやつ買ったのに、資料ばかり見過ぎた
    • ちゃんと前見よう
  • あまり参加者の属性を考えてなかった
    • 今回はたまたまた難しい内容じゃない(多分)発表になったけど、参加者考えてそれに合わせて作らないとなーと

HTTPレイヤーで行うパソーマンスチューニング

主催者である、OSCAのお話。
基礎的なコンテンツの圧縮やキャッシュ方法についてだった。最近、こういうのめんどくさがって忘れがちだったので非常にタメになる話だった。  

とにかく分かりづらいTwelve-Factor Appの解説を試みる

日本で「Twelve-Factor App」について解説している、suke_masaのお話。
クラウドで動くアプリケーションが従うべき12個のベストプラクティスについて提唱された「Twelve-Factor App」についての内容。
そもそもこういうものが提唱されているのは知らなかったのでためになった。発表が聞きやすく、プロやった。。

なんか作ったらプレスリリースを出そう

斎藤さんこと、binbin4649のお話。
ちゃんとサービスを作ったら、プレスリリースを出そうと言う内容。
自分の中ではこれが一番好きな内容だった。プレスリリースをだす裏技的なやり方だったり、売り上げが上がるサービスの作り方も発表後に聞かせていただいて、すごくためになった。

Redashの導入とチームをまたいだ変化の話

同じ名前(漢字は違うけど)の、zuckey_17のお話。
ビジネスチームが開発チームによく頼むデータのアウトプットなどを「Redash」で解決しようと言う内容。
すごくためになる話で、自分もRedash使っているのに、ビジネスチームにうまくアウトプットできてないなーと反省。

Dockerを利用したローカル環境から本番環境までの構築設計

kkoudevのお話。
Dockerのメリットだったり、開発から本番構築のやり方の内容。
自分のチームでは開発環境でしか使っておらず、結構耳が痛い内容・・・笑。本番の設計方法などが書いてあるので、近々新規サービスを作る際に参考にさせていただきます。

非機能要件を考えてみよう!

iwanaga0918のお話。
機能要件以外の、非機能要件についてしっかり向き合って、ちゃんと解決していこうと言う内容。
耳が耳が痛いです。IPAが出している非機能要求グレードなどあるみたいで、さっそく参考にしてみようと思う。ただ、少し情報が古いためクラウドに対応した内容については近々更新があるかもとのこと。

www.slideshare.net

感想

初心者レベルの内容だけじゃなくて、少し難しい内容もあったりして面白かった!
ただ、勉強会の趣旨が初心者メインのためもう少し質問の時間を長めに取ったりして、難しい内容については補足があればなぁと思った。
自分が発表すると、フィードバックもらえたり、懇親会でも覚えてもらったりするので発表は重要なんだなと思った。またあれば行きたい。

CircleCI2.0でDocker Composeをキャッシュする

どうも、くずきです。
以前、CircleCI2.0を試しました。

kzkohashi.hatenablog.com

CircleCI2.0を使って見たものの、せっかく色々機能があるのに使えてなかったので、今回はWorkflowsを使ってDocker Composeのキャッシュを利用したいと思います。

目標

  • Workflowsに適応させる
  • Docker Composeをキャッシュする
  • キャッシュしたDocker Composeをテストで利用する

あとはビルドが早くなればいいな〜と思う。

一応環境

  • Laravel
    • 5.5.11
  • PHP
    • 7.1.1
  • Mysql
    • 5.7
  • CircleCI
    • 2.0

CircleCI設定

.circleci/config.ymlの設定

versin: 2
jobs:
  generate_cache:
    machine: true
    steps:
      - checkout
      - restore_cache:
          keys:
            - docker-{{ .Branch }}--{{ checksum ".circleci/config.yml" }}--{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }}
          paths:
            - ~/caches/images.tar
      - run:
          name: Setup docker
          command: |
            if [ ! -f ~/caches/images.tar ]; then
              docker-compose pull db cache
              docker-compose build app
              mkdir -p ~/caches
              docker save $(docker images | awk 'NR>=2 && ! /^<none>/{print $1}') -o ~/caches/images.tar
            fi
      - save_cache:
          key: docker-{{ .Branch }}--{{ checksum ".circleci/config.yml" }}--{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }}
          paths:
            - ~/caches/images.tar
  test:
    machine: true
    steps:
      - checkout
      - restore_cache:
          keys:
            - docker-{{ .Branch }}--{{ checksum ".circleci/config.yml" }}--{{ checksum "docker-compose.yml" }}-{{ checksum "Dockerfile" }}
          paths:
            - ~/caches/images.tar
      - run:
          name: docker load
          command: |
            if [[ -e ~/caches/images.tar ]]; then
              docker load -i ~/caches/images.tar
            fi
      - run:
          name: Setup Laravel
          command: |
            docker-compose run app composer install
            cp .env.circleci .env
            docker-compose run app php artisan key:generate
            docker-compose run app php artisan migrate:refresh --seed
      - run:
          name: Run Test
          command: |
            docker-compose run app vendor/bin/phpunit
workflows:
  version: 2
  build_and_test:
    jobs:
      - generate_cache
      - test:
          requires:
            - generate_cache

各設定箇所を簡単に説明。

workflows

jobsに書かれたジョブの実行順序を決めたり、並列化などできる。
本家の画像をパラパラと見れば理解しやすい(英語読まない)。

circleci.com

今回は、generate_cache -> testというジョブの順番で行うようにしてある。

save_cache

pathsで設定した内容をkeyの名前で保存できる。
keyに関しては{{}}を使うことで色々な名前にできて、今回は以下を設定。

  • .Branch
    • ブランチ名ごとにキャッシュ
  • checksum ファイル名
    • 指定したファイル名が変更されるたびにキャッシュ
    • 今回は、dockerファイル周りとcircleciのファイル

restore_cache

save_cacheで指定したkeyで保存したファイルをダウンロードできる。

[タスク] Setup docker

docker-composepullbuildしたイメージを

docker save イメージ名 -o アウトプット先

という形で保存している。
ここは結構詰まったので、後述。

[タスク] docker load

generate_cacheジョブで作ったファイルを、testジョブでrestore_cacheし、
それをdokcer loadで読み込んでいる。

動作結果

以前のビルド 結果: 3分07秒 f:id:kzkohashi:20180128185802j:plain

新しいビルド 結果: 2分52秒 f:id:kzkohashi:20180128185741p:plain

結果あんまり早くならなかった。
原因としては、ジョブを切り替える時間、restore_cacheなどの時間、composer installした内容をキャッシュしていなかったためである。

まあでもかっこよくなったのと、並列化などしやすくなったためよかった。

詰まったところ

キャッシュ名に.circleci/config.ymlを入れてなかった

すごく恥ずかしいんですけど、キャッシュをする際にkey.circleci/config.ymlをいれなかったせいで、.circleci/config.ymlをいくら変更しても前のキャッシュを使用してて全然更新されてなかった。。

docker saveが上手くされてなかった

最初はこのようにsaveしてた。

docker save -o ~/caches/images.tar

これだと、docker loadはできるものの、実際にdokcer-compose runを行う際にキャッシュを使わずに一からPull&Buildをしてしまう。だいぶここにはまった。
docker loadしても、RpositoryTagの名前が<none>になってしまい、Pullする際などに取れないからである。

yoshinorin.net

解決法としては、docker imagesする際に、Repository名を切り出して保存する。

docker save $(docker images | awk 'NR>=2 && ! /^<none>/{print $1}') -o ~/caches/images.tar

qiita.com

ほんと助かりました(ここのコードちょうだいしました・・)。作者様ありがたです。

もう少し良さげなコマンド無いか探して見まする。

ビルドしたDockerFileに名前をつける

docker-compose.yml

  app:
    #image: php:7.1.1-apache
    build: .
    image: app
    container_name: app
    ports:
      - "8000:80"
    volumes:
      - .:/var/www/html
      - ./000-default.conf:/etc/apache2/sites-available/000-default.conf
    depends_on:
      - db
      - cache

http://docs.docker.jp/compose/compose-file.html#build

buildした際に、imageでタグ名をつけとかないとdocker save時に困ります。

まとめ

  • workflowsでかっこよいテストにできる
  • restore_cache, save_cacheを使いこなそう
  • ハマりどころは細かくあるので注意
  • 次は並列化したい(そもそもテストの量増やさないと・・・)

Laravelで日ごとに変わるランダムなソートをする

どうも、くずきです。
こないだ、セールスの方からリストの表示をランダムでだせないのかというご要望がありまして、実装してみた話です。

DB側でランダムで出す

基本的にソートはSQL側で行なっているため、なるべくDB側でできないかな〜と思ってたらありました。

SELECT * FROM table ORDER BY RAND();

ORDER BY RAND()をつけるだけで、ランダムになるというすごく便利な代物。
このままだと毎回変わってしまうため、引数にseedをつければランダムだけと固定にできる。

SELECT * FROM table ORDER BY RAND('1234556778');

EloquentでORDER BY RAND()を呼ぶ

Eloquentなら絶対実装してくれてる!!!と思ったら流石ありました。

https://stackoverflow.com/questions/13917558/laravel-eloquent-or-fluent-random-row

Model::inRandomOrder()

バージョンによって書き方は変わるみたいだけど、5.2以上はこれでいけるみたい。
ただ、このままだと先ほども書いたとおり、毎回読み込むたびに変わってしまう。

1ページならいいけど、実際には->paginate()など使ってページネーションしてる場合がほとんだと思う。2ページ目をみにいったのに、新しくランダムになってしまうせいで1ページ目に表示されたものが出てしまうなんてこともあり得るだろう。

なので、こちらも引数をつけて固定にする。

Model::inRandomOrder(1234567)

日ごとにソートさせる

ランダムの値を固定のシードで固定しまったら、テーブルのレコードが増減しない限り固定な訳で・・、日ごとにランダムにさせよう!と思いできた最終コード。

$today = Carbon::today()->timestamp;

$query->table
      ->orderBy('status', 'desc') // 別のソートを優先で行なっている
      ->inRandomOrder($today);

Carbonで今日のタイムスタンプを取りそれを固定にするやり方で、日ごとに変わるソートの出来上がり。ただ、問題点があり

  • 日をまたいだときに新しいランダムになってしまう
  • テーブルのレコードが増減するとランダムの順番も変わってしまう

という、どうしもない2点がある。
一つ目の問題点の解決方法は、サーバー側にアクセスした際にシード値をフロント側に返してその値を使いまわすことで解決はできそうだなぁと思う。

二つ目の問題点は、過去のレコードをランダムにし、新しいレコードを1ページ目などに表示する仕組みにすれば良さそう。新鮮なレコード + 過去のレコードを埋もれさせないって意味ではこちらは近いうちにやっときたい。

感想

プログラム側でゴリらなくても、DB側でいい感じにランダムにできて楽だった。
シード値もフロントと連携すれば解決するし、新鮮さがないリストに使うにはもってこいなんではないだろうか。

Pretty Result PrinterでPHPUnitのテストを見やすくする

どうも、くずきです。
今日は、PHPUnitを見やすくるPretty Result Printerを紹介したいと思います。

Pretty Result Printerとは

そもそも今のPHPUnitは結果が見辛かったりします。

PHPUnit 5.7.26 by Sebastian Bergmann and contributors.

.........................................F...........             53 / 53 (100%)

Time: 8.72 seconds, Memory: 32.00MB

There was 1 failure:

1) Tests\Unit\App\Services\HttpClientTest::it_returns_correct_xxxx
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'/api/entries/1'
+'/api/entries/2'

/xxx/tests/Unit/Services/HttpClientTest.php:34

FAILURES!
Tests: 53, Assertions: 125, Failures: 1.

ここらへんを改善するためにできたのがPretty Result Printer

github.com

PHPUnit 5.7.26 by Sebastian Bergmann and contributors.


 ==> ...erFollowerAttributeForDepartmentStoreTest ✓ ✓
 ==> ...ncerFollowerAttributeForDiscountStoreTest ✓ ✓
 ==> ...fluencerFollowerAttributeForDrugstoreTest ✓ ✓
 ==> ...luencerFollowerAttributeForOnlineTest ✓ ✓
 ==> ...uencerFollowerAttributeForVarietyShopTest ✓ ✓
 ==> ...uencerFollowerScoreForDepartmentStoreTest ✓
 ==> ...uencerFollowerScoreForDiscountStoreTest ✓
 ==> ...luencerFollowerScoreForDrugstoreTest ✓
 ==> ...uencerFollowerScoreForOnlineTest ✓
 ==> ...ncerFollowerScoreForVarietyShopTest ✓
 ==> ...ns\Services\FollowerChangeRateServiceTest ✓ ✓ ✓
 ==> ...Domains\Services\FollowerScoreServiceTest ✓ ✓ ✓
 ==> ...it\Domains\ValueObjects\AttributeTypeTest ✓ ✓ ✓ ✓
 ==> ...it\Domains\ValueObjects\FollowerCountTest ✓ ✓ ✓ ✓ ✓
 ==> Tests\Unit\ExampleTest                       ✓
 ==> App\Http\Requests\GuzzleRequestTest          ✓
 ==> Tests\Unit\Models\InstagramInsightTest       ✓ ✓ ✓
 ==> ...s\Unit\App\Services\HttpClientTest ✓ ✖ ✓ ✓ ✓ ✓ ✓ ✓ ✓
 ==> ...Unit\App\Services\ResponseJsonTest ✓ ✓ ✓ ✓

Time: 8.18 seconds, Memory: 32.00MB

There was 1 failure:

1) Tests\Unit\App\Services\HttpClientTest::it_returns_correct_xxx
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'/api/entries/1'
+'/api/entries/2'

/xxxx/tests/Unit/Services/HttpClientTest.php:34

FAILURES!
Tests: 53, Assertions: 125, Failures: 1.

このように、どこのテストがこけたのがざっくりと見やすくなる。

導入と実行

導入は簡単で、Laravelcomposer.jsonに追加

    "require-dev": {
        "barryvdh/laravel-ide-helper": "^2.4",
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~5.7",
        "codedungeon/phpunit-result-printer": "^0.4.4",  // 追加
    },

使用方法は簡単で、phpunit.xmlに書いてもいいけどすぐ試すには

phpunit --printer=Codedungeon\\PHPUnitPrettyResultPrinter\\Printer

でさきほどの結果が得られる。

おまけ

Laravelの開発者であるTaylor Otwellツイッターで次のLaravelのバージョンである5.6に入れるか検討してるみたい。

LaravelでRedshiftにインサートする際にでるSQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "returning"の対処法

どうも、くずきです。
以前、LaravelRedshiftを扱う方法について書きました。

kzkohashi.hatenablog.com

今回は、Redshiftにインサートする際にでる

SQLSTATE[42601]: Syntax error: 7 ERROR:  syntax error at or near "returning"

の対処法について。

EloquentとRedshiftを紐づける

以前の記事ではコードかかなかったけど、Eloquentにはモデルごとにコネクションを変えることができる。

class User extends Model
{

   protected $connection = 'redshift';
   
   protected $table = 'users';
   
}

$connectionに該当する接続先を定義してあげれば良い。

Redshiftにインサートする

冒頭に述べたように、EloquentではRedshiftに接続した際に、そのままインサートするとエラーがでる。

User::create(...)

上記のようにcreateする場合、Eloquentではデフォルトで、最後にインサートしたidを返すクエリーを投げている。

SQL: insert into "user" ("user_id", "updated_at", "created_at") values (335767, 2017-12-28 15:54:04, 2017-12-28 15:54:04) returning "id"

returning idがその部分だ。
これの解消方法は簡単で、

class User extends Model
{
  // 2つ追加
  protected $primaryKey = null;
  public $incrementing = false;

  protected $connection = 'redshift';
   
  protected $table = 'users';
   
}

primaryKeynullにし、incrementingfalseにしてauto incrementの設定をオフにすれば良い。