Skip to content

Instantly share code, notes, and snippets.

@okbm
Last active August 24, 2022 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save okbm/6862a156ead0788391ac5ea5514a1ca4 to your computer and use it in GitHub Desktop.
Save okbm/6862a156ead0788391ac5ea5514a1ca4 to your computer and use it in GitHub Desktop.

とあるプロジェクトへの参加メモ

tool

  • DBeaver MySQLクライアント
  • NoSQL Booster Mongoクライアント
  • postman
    • api管理サービス
    • swaggerとかあれ系だが、ダミーデータじゃなくて実際にRESTFullなApiを投げるサービス
  • https://sequel-ace.com/
    • 使ったことないけど、sequel-proの後継らしいので多分使いやすい
  • vscodeでbreak pointが貼れるようになってそれはかなり助かった。
    • vagrantでやってたが、macだとdockerめちゃくちゃ重たいのでvagrantでよかったような気がする

laravel

laravelというかlumenのベースでいい感じのフォルダ構成でルールもある
transformとかその辺もあるのでどのモデルをいじっても同じデータ構造で返すし、policyで例えばuser_idが一致してないとselect時にerrorにするなど細かい設定もできた、

ただ、l5-repositoryが結構癖が強かった。
Serviceクラスが全てBaseのサービスクラスを継承していて、

  • ServiceクラスからSQLを実行する
  • またはServiceクラスからReposotryクラスのSQLを実行する

というパターンが多いが、User::all()だと普通はUserモデルクラスが返ってくるが、Serviceクラスを経由すると全部Arrayになってた. Arrayにさせないパターンは一応できるが、それだとtransformクラスを通らないためレスポンスの形式がApiレベルで変わってしまい統一しにくかった.
他にもデメリット的にmodelクラスに生やしたメソッドが使えないので配列を似たようなif文でめちゃくちゃ見るみたいになっててそれも辛かった。

例) if isAdmin? -> if model.type === 'admin'

それ以外にもチューニング周りは絶望的だった。
githubに書いてるが、http://prettus.local/users?search=John&searchFields=name:like みたいなリクエストでクライアントから呼べばGraphQL風?な感じでとりたいデータが取れるようになってた.
しかし、indexが効かないクソみたいなクエリが大量に発生かつそもそもServiceクラスのほとんどがいわゆるN+1クエリ大量に発行してたのでそれはめちゃくちゃ直した

まぁそれでもpolicyといわれる認証やフォルダ構成は結構しっかりしておりあとから見た人でもわかりやすかったと思う

その他

# carbon便利だけど日付をvar_dumpとかで表示させたい場合いつも忘れる
Carbon::now()->toDateTimeString()

別プロジェクト

laravel7.3 + php7系

  • コードがシンプルにひどい.
  • serviceクラスがあったが、5000行以上あったのでそれを役割ごとにファイル分割していったがそれでも900行ぐらいは残った
  • repositoryパターンとしてSQLだけ別ファイルにあればよかったんだが、それもないから結構厳しい戦いだった
  • home画面でSQLが300行実行されてるかつ同じSQLも何回も発行されているし、bulkInsert使ってないから3000行を一括でinsertするとかいろいろ辛い作りになっていた
  • N+1をなるべく減らすためにwithを多様していたのはいいが、そもそも作りがめちゃくちゃイケてないので無駄クエリが多い+影響範囲が大きすぎるので修正が大変すぎた(以下サンプルコード参照)
    • 以下のようなコードがあちこちに書いており、呼び出し元も不明という感じでめちゃくちゃ辛い。
  • php8使っていればenumとか使えたのにそれも使ってないから文字列で比較するのも辛い
  • modelに関数がほぼなくて、全部serviceクラスに寄せていたので共通のロジックがバラバラになっていた。大体userクラスにロジック偏るはずだがほぼ素のリレーションしかなかった
  • typoとインデントが揃ってないとか気になった(これはめちゃくちゃ個人的だが
  • デバッグの効率がとにかく悪かったので、1件のバグ再現を手元でするのに5時間ぐらいかかるレベルだったのでそれの効率をとにかくあげた
  • コミットメッセージが適当すぎるので過去の意図が全然わからない
  • indexが微妙だし、全テーブルにactiveってカラム(おそらくsoft_delete的な)があって、それで毎回絞ってるので微妙にindexが効いてない
        $user = User::with(['profile' => function ($query) {
            $query->with(
                [
                    'profileSample' => function ($query) {
                        $query->where('active', 1)
                              ->orderBy('id', 'asc');
                    },
                ]
            )->where('active', 1);
        }])
            ->where('id', $user_id)
            ->where('active', 1)
            ->first();

cache戦略

マスターファイルがとても大きいかつ毎回SQLで引いてたので、それをやめるべくキャッシュを入れた.
本来ならmemcachedとかredisに入れたかったが、マスターファイルも結構大きいのでネットワーク的に数十MBとかのほうが辛いかと思って、一旦ファイルにした.
(インフラ的な手間もかからないのも考慮したが)

# このコマンドで削除(fileはわざわざ指定しないとダメっぽい)
php artisan cache:clear -- file
php artisan cache:clear -- redis

参考までにそれぞれのパフォーマンスが書いた記事貼っておく.

vagrant + docker-compose で開発

dokcerファイルが20GBぐらいになってたので、以下コマンドで5Gぐらい減った(それでも多いが)

docker system prune --volumes -f

vagrant + docker-composeでファイルが大きくなったので削除 https://scrapbox.io/jiro4989/%2Fvar%2Flib%2Fdocker%2Foverlay2%E3%81%AE%E3%82%B9%E3%83%88%E3%83%AC%E3%83%BC%E3%82%B8%E8%82%A5%E5%A4%A7%E5%8C%96%E5%AF%BE%E5%BF%9C#5f65bac67ba7f9000013efc3

ライブラリ

その他

<?php
namespace App\Util;
use Illuminate\Support\Facades\Cache;
/**
* マスタデータの結果をキャッシュするクラス
* 全く同じ配列のキーを持ったデータをcacheする
*/
class MstCache extends Cache
{
// objectでサイズが大きくなるのでlocalのfileに保存する
static protected $type = 'file';
static function get(string $tableName, array $key) {
return Cache::store(self::$type)->get($tableName . self::genKey($key));
}
static function put(string $tableName, $key , $value) {
return Cache::store(self::$type)->put($tableName . self::genKey($key), $value);
}
/**
* 配列を文字列に変換
*/
private static function genKey($key)
{
return implode(",", $key);
}
}

.vscode/launch.json

{
  "version": "0.2.0",
  "configurations": [
      {
          "name": "Listen for XDebug",
          "type": "php",
          "request": "launch",
          "port": 9000,
          "hostname": "0.0.0.0",
          "pathMappings": {
            "/var/www/html": "${workspaceRoot}/Docks"
          },
          "xdebugSettings": {
            "max_children": 128,
            "max_data": 1024,
            "max_depth": 5
          }
      },
      {
          "name": "Launch currently open script",
          "type": "php",
          "request": "launch",
          "program": "${file}",
          "cwd": "${fileDirname}",
          "port": 9000
      }
  ]
}

docker/php.dockerfile

+RUN apk add autoconf build-base
+RUN pecl install xdebug && docker-php-ext-enable xdebug

docker/php/php.ini

[xdebug]
xdebug.idekey = 'prj'
xdebug.client_port = 9000
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.discover_client_host = 1
xdebug.log_level = 0

xdebug.var_display_max_children = 128
xdebug.var_display_max_data = 512
xdebug.var_display_max_depth = 3
@okbm
Copy link
Author

okbm commented Mar 8, 2022

throwResponse

コントローラーの任意の場所でリダイレクトさせる

    if (!auth()->user()) {
      return redirect('login')->throwResponse();
    }

@okbm
Copy link
Author

okbm commented Mar 17, 2022

seederでDBにデータを流し込む場合にデータが大きすぎてエラーになっていた

<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

/**
 * sqlファイルを1000行ごとにinsertする
 */
class SplitInsertSeeder extends Seeder {

  public function bulkInsert($tableName, $path) {
    DB::statement('SET FOREIGN_KEY_CHECKS=0;');

    DB::beginTransaction();
    try {
      DB::table($tableName)->truncate();
      $fp = fopen($path, 'r');
      $i = 0;
      $insert = [];
      while (!feof($fp)) {
        $row = fgets($fp);
        $i++;
        $insert[] = $row;
        if ($i >= 1000) {
          DB::unprepared(implode($insert));
          $i = 0;
          unset($insert);
        }
      }
      if ($insert) {
        DB::unprepared(implode($insert));
      }
      fclose($fp);
      DB::commit();
    } catch (\Throwable $th) {
      DB::rollBack();
      throw new Exception('failed seeder rolled back ' . $th);
    }
    DB::statement('SET FOREIGN_KEY_CHECKS=1;');
  }
}

https://laracasts.com/discuss/channels/laravel/db-transactions-query-looping-alternative
https://www.reddit.com/r/laravel/comments/i8quhx/how_do_i_use_the_continue_inside_of_the/

トランザクションごとにしたほうがいいかも?

<?php

use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

/**
 * sqlファイルを2000行ごとにinsertする
 */
class SplitInsertSeeder extends Seeder {

  public function bulkInsert($tableName, $path) {
    DB::statement('SET FOREIGN_KEY_CHECKS=0;');
    DB::table($tableName)->truncate();

    $rows = explode('\n', file_get_contents($path));
    $chunkRows = array_chunk($rows, 2000);
    DB::transaction(function() use ($chunkRows) {
      foreach($chunkRows as $chunkRow) {
        $insert = [];
        foreach($chunkRow as $row) {
          $insert[] = $row;
        }
        DB::unprepared(implode($insert));
      }
    });
    DB::statement('SET FOREIGN_KEY_CHECKS=1;');
  }
}

@okbm
Copy link
Author

okbm commented Aug 24, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment